未验证 提交 5b952f28 编写于 作者: M Matt Carroll 提交者: GitHub

Forwards Flutter View to platform views and detaches when needed. (#12359)

上级 d8d0d3f1
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
package io.flutter.plugin.platform; package io.flutter.plugin.platform;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.support.annotation.NonNull;
import android.view.View; import android.view.View;
/** /**
...@@ -16,6 +17,39 @@ public interface PlatformView { ...@@ -16,6 +17,39 @@ public interface PlatformView {
*/ */
View getView(); View getView();
/**
* Called by the {@link FlutterEngine} that owns this {@code PlatformView} when
* the Android {@link View} responsible for rendering a Flutter UI is associated
* with the {@link FlutterEngine}.
*
* <p>This means that our associated {@link FlutterEngine} can now render a UI and
* interact with the user.
*
* <p>Some platform views may have unusual dependencies on the {@link View} that
* renders Flutter UIs, such as unique keyboard interactions. That {@link View}
* is provided here for those purposes. Use of this {@link View} should be avoided
* if it is not absolutely necessary, because depending on this {@link View} will
* tend to make platform view code more brittle to future changes.
*/
// Default interface methods are supported on all min SDK versions of Android.
@SuppressLint("NewApi")
default void onFlutterViewAttached(@NonNull View flutterView) {}
/**
* Called by the {@link FlutterEngine} that owns this {@code PlatformView} when
* the Android {@link View} responsible for rendering a Flutter UI is detached
* and disassociated from the {@link FlutterEngine}.
*
* <p>This means that our associated {@link FlutterEngine} no longer has a rendering
* surface, or a user interaction surface of any kind.
*
* <p>This platform view must release any references related to the Android {@link View}
* that was provided in {@link #onFlutterViewAttached(View)}.
*/
// Default interface methods are supported on all min SDK versions of Android.
@SuppressLint("NewApi")
default void onFlutterViewDetached() {}
/** /**
* Dispose this platform view. * Dispose this platform view.
* *
...@@ -23,6 +57,10 @@ public interface PlatformView { ...@@ -23,6 +57,10 @@ public interface PlatformView {
* *
* <p>Plugins implementing {@link PlatformView} must clear all references to the View object and the PlatformView * <p>Plugins implementing {@link PlatformView} must clear all references to the View object and the PlatformView
* after this method is called. Failing to do so will result in a memory leak. * after this method is called. Failing to do so will result in a memory leak.
*
* <p>References related to the Android {@link View} attached in
* {@link #onFlutterViewAttached(View)} must be released in {@code dispose()} to avoid memory
* leaks.
*/ */
void dispose(); void dispose();
......
...@@ -11,6 +11,7 @@ import android.annotation.TargetApi; ...@@ -11,6 +11,7 @@ import android.annotation.TargetApi;
import android.content.Context; import android.content.Context;
import android.os.Build; import android.os.Build;
import android.support.annotation.UiThread; import android.support.annotation.UiThread;
import android.support.annotation.VisibleForTesting;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.util.Log; import android.util.Log;
...@@ -44,6 +45,9 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega ...@@ -44,6 +45,9 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega
// The context of the Activity or Fragment hosting the render target for the Flutter engine. // The context of the Activity or Fragment hosting the render target for the Flutter engine.
private Context context; private Context context;
// The View currently rendering the Flutter UI associated with these platform views.
private View flutterView;
// The texture registry maintaining the textures into which the embedded views will be rendered. // The texture registry maintaining the textures into which the embedded views will be rendered.
private TextureRegistry textureRegistry; private TextureRegistry textureRegistry;
...@@ -55,7 +59,11 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega ...@@ -55,7 +59,11 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega
// The accessibility bridge to which accessibility events form the platform views will be dispatched. // The accessibility bridge to which accessibility events form the platform views will be dispatched.
private final AccessibilityEventsDelegate accessibilityEventsDelegate; private final AccessibilityEventsDelegate accessibilityEventsDelegate;
private final HashMap<Integer, VirtualDisplayController> vdControllers; // TODO(mattcarroll): Refactor overall platform views to facilitate testing and then make
// this private. This is visible as a hack to facilitate testing. This was deemed the least
// bad option at the time of writing.
@VisibleForTesting
/* package */ final HashMap<Integer, VirtualDisplayController> vdControllers;
// Maps a virtual display's context to the platform view hosted in this virtual display. // Maps a virtual display's context to the platform view hosted in this virtual display.
// Since each virtual display has it's unique context this allows associating any view with the platform view that // Since each virtual display has it's unique context this allows associating any view with the platform view that
...@@ -115,6 +123,12 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega ...@@ -115,6 +123,12 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega
+ request.viewType + " with id: " + request.viewId); + request.viewType + " with id: " + request.viewId);
} }
// If our FlutterEngine is already attached to a Flutter UI, provide that Android
// View to this new platform view.
if (flutterView != null) {
vdController.onFlutterViewAttached(flutterView);
}
vdControllers.put(request.viewId, vdController); vdControllers.put(request.viewId, vdController);
View platformView = vdController.getView(); View platformView = vdController.getView();
platformView.setLayoutDirection(request.direction); platformView.setLayoutDirection(request.direction);
...@@ -290,6 +304,37 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega ...@@ -290,6 +304,37 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega
textureRegistry = null; textureRegistry = null;
} }
/**
* This {@code PlatformViewsController} and its {@code FlutterEngine} is now attached to
* an Android {@code View} that renders a Flutter UI.
*/
public void attachToView(@NonNull View flutterView) {
this.flutterView = flutterView;
// Inform all existing platform views that they are now associated with
// a Flutter View.
for (VirtualDisplayController controller : vdControllers.values()) {
controller.onFlutterViewAttached(flutterView);
}
}
/**
* This {@code PlatformViewController} and its {@code FlutterEngine} are no longer attached
* to an Android {@code View} that renders a Flutter UI.
* <p>
* All platform views controlled by this {@code PlatformViewController} will be detached
* from the previously attached {@code View}.
*/
public void detachFromView() {
this.flutterView = null;
// Inform all existing platform views that they are no longer associated with
// a Flutter View.
for (VirtualDisplayController controller : vdControllers.values()) {
controller.onFlutterViewDetached();
}
}
@Override @Override
public void attachAccessibilityBridge(AccessibilityBridge accessibilityBridge) { public void attachAccessibilityBridge(AccessibilityBridge accessibilityBridge) {
accessibilityEventsDelegate.setAccessibilityBridge(accessibilityBridge); accessibilityEventsDelegate.setAccessibilityBridge(accessibilityBridge);
......
...@@ -9,6 +9,7 @@ import android.content.Context; ...@@ -9,6 +9,7 @@ import android.content.Context;
import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay; import android.hardware.display.VirtualDisplay;
import android.os.Build; import android.os.Build;
import android.support.annotation.NonNull;
import android.util.Log; import android.util.Log;
import android.view.Surface; import android.view.Surface;
import android.view.View; import android.view.View;
...@@ -159,6 +160,26 @@ class VirtualDisplayController { ...@@ -159,6 +160,26 @@ class VirtualDisplayController {
textureEntry.release(); textureEntry.release();
} }
/**
* See {@link PlatformView#onFlutterViewAttached(View)}
*/
/*package*/ void onFlutterViewAttached(@NonNull View flutterView) {
if (presentation == null || presentation.getView() == null) {
return;
}
presentation.getView().onFlutterViewAttached(flutterView);
}
/**
* See {@link PlatformView#onFlutterViewDetached()}
*/
/*package*/ void onFlutterViewDetached() {
if (presentation == null || presentation.getView() == null) {
return;
}
presentation.getView().onFlutterViewDetached();
}
/*package*/ void onInputConnectionLocked() { /*package*/ void onInputConnectionLocked() {
if (presentation == null || presentation.getView() == null) { if (presentation == null || presentation.getView() == null) {
return; return;
......
package io.flutter.plugin.platform;
import android.view.View;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@Config(manifest=Config.NONE)
@RunWith(RobolectricTestRunner.class)
public class PlatformViewsControllerTest {
@Test
public void itNotifiesVirtualDisplayControllersOfViewAttachmentAndDetachment() {
// Setup test structure.
// Create a fake View that represents the View that renders a Flutter UI.
View fakeFlutterView = new View(RuntimeEnvironment.systemContext);
// Create fake VirtualDisplayControllers. This requires internal knowledge of
// PlatformViewsController. We know that all PlatformViewsController does is
// forward view attachment/detachment calls to it's VirtualDisplayControllers.
//
// TODO(mattcarroll): once PlatformViewsController is refactored into testable
// pieces, remove this test and avoid verifying private behavior.
VirtualDisplayController fakeVdController1 = mock(VirtualDisplayController.class);
VirtualDisplayController fakeVdController2 = mock(VirtualDisplayController.class);
// Create the PlatformViewsController that is under test.
PlatformViewsController platformViewsController = new PlatformViewsController();
// Manually inject fake VirtualDisplayControllers into the PlatformViewsController.
platformViewsController.vdControllers.put(0, fakeVdController1);
platformViewsController.vdControllers.put(1, fakeVdController1);
// Execute test & verify results.
// Attach PlatformViewsController to the fake Flutter View.
platformViewsController.attachToView(fakeFlutterView);
// Verify that all virtual display controllers were notified of View attachment.
verify(fakeVdController1, times(1)).onFlutterViewAttached(eq(fakeFlutterView));
verify(fakeVdController1, never()).onFlutterViewDetached();
verify(fakeVdController2, times(1)).onFlutterViewAttached(eq(fakeFlutterView));
verify(fakeVdController2, never()).onFlutterViewDetached();
// Detach PlatformViewsController from the fake Flutter View.
platformViewsController.detachFromView();
// Verify that all virtual display controllers were notified of the View detachment.
verify(fakeVdController1, times(1)).onFlutterViewAttached(eq(fakeFlutterView));
verify(fakeVdController1, times(1)).onFlutterViewDetached();
verify(fakeVdController2, times(1)).onFlutterViewAttached(eq(fakeFlutterView));
verify(fakeVdController2, times(1)).onFlutterViewDetached();
}
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册