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

Rename first frame method and notify FlutterActivity when full drawn (#38714 #36796). (#11357)

上级 ff4cf8ee
...@@ -603,7 +603,8 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/plugin ...@@ -603,7 +603,8 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/plugin
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/plugins/shim/ShimPluginRegistry.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/plugins/shim/ShimPluginRegistry.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/plugins/shim/ShimRegistrar.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/plugins/shim/ShimRegistrar.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/renderer/OnFirstFrameRenderedListener.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterUiDisplayListener.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/renderer/RenderSurface.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/AccessibilityChannel.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/AccessibilityChannel.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/KeyEventChannel.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/KeyEventChannel.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/LifecycleChannel.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/LifecycleChannel.java
......
...@@ -170,7 +170,8 @@ action("flutter_shell_java") { ...@@ -170,7 +170,8 @@ action("flutter_shell_java") {
"io/flutter/embedding/engine/plugins/shim/ShimPluginRegistry.java", "io/flutter/embedding/engine/plugins/shim/ShimPluginRegistry.java",
"io/flutter/embedding/engine/plugins/shim/ShimRegistrar.java", "io/flutter/embedding/engine/plugins/shim/ShimRegistrar.java",
"io/flutter/embedding/engine/renderer/FlutterRenderer.java", "io/flutter/embedding/engine/renderer/FlutterRenderer.java",
"io/flutter/embedding/engine/renderer/OnFirstFrameRenderedListener.java", "io/flutter/embedding/engine/renderer/FlutterUiDisplayListener.java",
"io/flutter/embedding/engine/renderer/RenderSurface.java",
"io/flutter/embedding/engine/systemchannels/AccessibilityChannel.java", "io/flutter/embedding/engine/systemchannels/AccessibilityChannel.java",
"io/flutter/embedding/engine/systemchannels/KeyEventChannel.java", "io/flutter/embedding/engine/systemchannels/KeyEventChannel.java",
"io/flutter/embedding/engine/systemchannels/LifecycleChannel.java", "io/flutter/embedding/engine/systemchannels/LifecycleChannel.java",
...@@ -412,6 +413,9 @@ action("robolectric_tests") { ...@@ -412,6 +413,9 @@ action("robolectric_tests") {
"test/io/flutter/embedding/android/FlutterActivityTest.java", "test/io/flutter/embedding/android/FlutterActivityTest.java",
"test/io/flutter/embedding/android/FlutterFragmentTest.java", "test/io/flutter/embedding/android/FlutterFragmentTest.java",
"test/io/flutter/embedding/engine/FlutterEngineCacheTest.java", "test/io/flutter/embedding/engine/FlutterEngineCacheTest.java",
"test/io/flutter/embedding/engine/FlutterJNITest.java",
"test/io/flutter/embedding/engine/RenderingComponentTest.java",
"test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java",
"test/io/flutter/embedding/engine/systemchannels/PlatformChannelTest.java", "test/io/flutter/embedding/engine/systemchannels/PlatformChannelTest.java",
"test/io/flutter/util/PreconditionsTest.java", "test/io/flutter/util/PreconditionsTest.java",
] ]
......
...@@ -886,7 +886,18 @@ public class FlutterActivity extends Activity ...@@ -886,7 +886,18 @@ public class FlutterActivity extends Activity
} }
@Override @Override
public void onFirstFrameRendered() {} public void onFlutterUiDisplayed() {
// Notifies Android that we're fully drawn so that performance metrics can be collected by
// Flutter performance tests.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
reportFullyDrawn();
}
}
@Override
public void onFlutterUiNoLongerDisplayed() {
// no-op
}
/** /**
* The mode of the background of a {@code FlutterActivity}, either opaque or transparent. * The mode of the background of a {@code FlutterActivity}, either opaque or transparent.
......
...@@ -25,7 +25,7 @@ import io.flutter.embedding.engine.FlutterEngine; ...@@ -25,7 +25,7 @@ import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterEngineCache; import io.flutter.embedding.engine.FlutterEngineCache;
import io.flutter.embedding.engine.FlutterShellArgs; import io.flutter.embedding.engine.FlutterShellArgs;
import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener; import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener;
import io.flutter.plugin.platform.PlatformPlugin; import io.flutter.plugin.platform.PlatformPlugin;
import io.flutter.view.FlutterMain; import io.flutter.view.FlutterMain;
...@@ -83,10 +83,15 @@ import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW; ...@@ -83,10 +83,15 @@ import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
private boolean isFlutterEngineFromHost; private boolean isFlutterEngineFromHost;
@NonNull @NonNull
private final OnFirstFrameRenderedListener onFirstFrameRenderedListener = new OnFirstFrameRenderedListener() { private final FlutterUiDisplayListener flutterUiDisplayListener = new FlutterUiDisplayListener() {
@Override @Override
public void onFirstFrameRendered() { public void onFlutterUiDisplayed() {
host.onFirstFrameRendered(); host.onFlutterUiDisplayed();
}
@Override
public void onFlutterUiNoLongerDisplayed() {
host.onFlutterUiNoLongerDisplayed();
} }
}; };
...@@ -228,7 +233,7 @@ import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW; ...@@ -228,7 +233,7 @@ import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
* <p> * <p>
* {@code inflater} and {@code container} may be null when invoked from an {@code Activity}. * {@code inflater} and {@code container} may be null when invoked from an {@code Activity}.
* <p> * <p>
* This method creates a new {@link FlutterView}, adds a {@link OnFirstFrameRenderedListener} to * This method creates a new {@link FlutterView}, adds a {@link FlutterUiDisplayListener} to
* it, and then returns it. * it, and then returns it.
*/ */
@NonNull @NonNull
...@@ -236,7 +241,7 @@ import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW; ...@@ -236,7 +241,7 @@ import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
Log.v(TAG, "Creating FlutterView."); Log.v(TAG, "Creating FlutterView.");
ensureAlive(); ensureAlive();
flutterView = new FlutterView(host.getActivity(), host.getRenderMode(), host.getTransparencyMode()); flutterView = new FlutterView(host.getActivity(), host.getRenderMode(), host.getTransparencyMode());
flutterView.addOnFirstFrameRenderedListener(onFirstFrameRenderedListener); flutterView.addOnFirstFrameRenderedListener(flutterUiDisplayListener);
flutterSplashView = new FlutterSplashView(host.getContext()); flutterSplashView = new FlutterSplashView(host.getContext());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
...@@ -391,12 +396,12 @@ import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW; ...@@ -391,12 +396,12 @@ import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
/** /**
* Invoke this from {@code Activity#onDestroy()} or {@code Fragment#onDestroyView()}. * Invoke this from {@code Activity#onDestroy()} or {@code Fragment#onDestroyView()}.
* <p> * <p>
* This method removes this delegate's {@link FlutterView}'s {@link OnFirstFrameRenderedListener}. * This method removes this delegate's {@link FlutterView}'s {@link FlutterUiDisplayListener}.
*/ */
void onDestroyView() { void onDestroyView() {
Log.v(TAG, "onDestroyView()"); Log.v(TAG, "onDestroyView()");
ensureAlive(); ensureAlive();
flutterView.removeOnFirstFrameRenderedListener(onFirstFrameRenderedListener); flutterView.removeOnFirstFrameRenderedListener(flutterUiDisplayListener);
} }
/** /**
...@@ -695,9 +700,13 @@ import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW; ...@@ -695,9 +700,13 @@ import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
boolean shouldAttachEngineToActivity(); boolean shouldAttachEngineToActivity();
/** /**
* Invoked by this delegate when its {@link FlutterView} has rendered its first Flutter * Invoked by this delegate when its {@link FlutterView} starts painting pixels.
* frame. */
void onFlutterUiDisplayed();
/**
* Invoked by this delegate when its {@link FlutterView} stops painting pixels.
*/ */
void onFirstFrameRendered(); void onFlutterUiNoLongerDisplayed();
} }
} }
...@@ -21,7 +21,7 @@ import android.view.ViewGroup; ...@@ -21,7 +21,7 @@ import android.view.ViewGroup;
import io.flutter.Log; import io.flutter.Log;
import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterShellArgs; import io.flutter.embedding.engine.FlutterShellArgs;
import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener; import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener;
import io.flutter.plugin.platform.PlatformPlugin; import io.flutter.plugin.platform.PlatformPlugin;
import io.flutter.view.FlutterMain; import io.flutter.view.FlutterMain;
...@@ -567,21 +567,6 @@ public class FlutterFragment extends Fragment implements FlutterActivityAndFragm ...@@ -567,21 +567,6 @@ public class FlutterFragment extends Fragment implements FlutterActivityAndFragm
// implementation for details about why it exists. // implementation for details about why it exists.
private FlutterActivityAndFragmentDelegate delegate; private FlutterActivityAndFragmentDelegate delegate;
private final OnFirstFrameRenderedListener onFirstFrameRenderedListener = new OnFirstFrameRenderedListener() {
@Override
public void onFirstFrameRendered() {
// Notify our subclasses that the first frame has been rendered.
FlutterFragment.this.onFirstFrameRendered();
// Notify our owning Activity that the first frame has been rendered.
FragmentActivity fragmentActivity = getActivity();
if (fragmentActivity instanceof OnFirstFrameRenderedListener) {
OnFirstFrameRenderedListener activityAsListener = (OnFirstFrameRenderedListener) fragmentActivity;
activityAsListener.onFirstFrameRendered();
}
}
};
public FlutterFragment() { public FlutterFragment() {
// Ensure that we at least have an empty Bundle of arguments so that we don't // Ensure that we at least have an empty Bundle of arguments so that we don't
// need to continually check for null arguments before grabbing one. // need to continually check for null arguments before grabbing one.
...@@ -951,21 +936,40 @@ public class FlutterFragment extends Fragment implements FlutterActivityAndFragm ...@@ -951,21 +936,40 @@ public class FlutterFragment extends Fragment implements FlutterActivityAndFragm
} }
/** /**
* Invoked after the {@link FlutterView} within this {@code FlutterFragment} renders its first * Invoked after the {@link FlutterView} within this {@code FlutterFragment} starts rendering
* frame. * pixels to the screen.
* <p>
* This method forwards {@code onFlutterUiDisplayed()} to its attached {@code Activity}, if
* the attached {@code Activity} implements {@link FlutterUiDisplayListener}.
* <p>
* Subclasses that override this method must call through to the {@code super} method.
* <p>
* Used by this {@code FlutterFragment}'s {@link FlutterActivityAndFragmentDelegate.Host}
*/
@Override
public void onFlutterUiDisplayed() {
FragmentActivity attachedActivity = getActivity();
if (attachedActivity instanceof FlutterUiDisplayListener) {
((FlutterUiDisplayListener) attachedActivity).onFlutterUiDisplayed();
}
}
/**
* Invoked after the {@link FlutterView} within this {@code FlutterFragment} stops rendering
* pixels to the screen.
* <p> * <p>
* This method forwards {@code onFirstFrameRendered()} to its attached {@code Activity}, if * This method forwards {@code onFlutterUiNoLongerDisplayed()} to its attached {@code Activity},
* the attached {@code Activity} implements {@link OnFirstFrameRenderedListener}. * if the attached {@code Activity} implements {@link FlutterUiDisplayListener}.
* <p> * <p>
* Subclasses that override this method must call through to the {@code super} method. * Subclasses that override this method must call through to the {@code super} method.
* <p> * <p>
* Used by this {@code FlutterFragment}'s {@link FlutterActivityAndFragmentDelegate.Host} * Used by this {@code FlutterFragment}'s {@link FlutterActivityAndFragmentDelegate.Host}
*/ */
@Override @Override
public void onFirstFrameRendered() { public void onFlutterUiNoLongerDisplayed() {
FragmentActivity attachedActivity = getActivity(); FragmentActivity attachedActivity = getActivity();
if (attachedActivity instanceof OnFirstFrameRenderedListener) { if (attachedActivity instanceof FlutterUiDisplayListener) {
((OnFirstFrameRenderedListener) attachedActivity).onFirstFrameRendered(); ((FlutterUiDisplayListener) attachedActivity).onFlutterUiNoLongerDisplayed();
} }
} }
......
...@@ -16,7 +16,7 @@ import android.widget.FrameLayout; ...@@ -16,7 +16,7 @@ import android.widget.FrameLayout;
import io.flutter.Log; import io.flutter.Log;
import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener; import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener;
/** /**
* {@code View} that displays a {@link SplashScreen} until a given {@link FlutterView} * {@code View} that displays a {@link SplashScreen} until a given {@link FlutterView}
...@@ -51,13 +51,18 @@ import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener; ...@@ -51,13 +51,18 @@ import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener;
}; };
@NonNull @NonNull
private final OnFirstFrameRenderedListener onFirstFrameRenderedListener = new OnFirstFrameRenderedListener() { private final FlutterUiDisplayListener flutterUiDisplayListener = new FlutterUiDisplayListener() {
@Override @Override
public void onFirstFrameRendered() { public void onFlutterUiDisplayed() {
if (splashScreen != null) { if (splashScreen != null) {
transitionToFlutter(); transitionToFlutter();
} }
} }
@Override
public void onFlutterUiNoLongerDisplayed() {
// no-op
}
}; };
@NonNull @NonNull
...@@ -114,7 +119,7 @@ import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener; ...@@ -114,7 +119,7 @@ import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener;
) { ) {
// If we were displaying a previous FlutterView, remove it. // If we were displaying a previous FlutterView, remove it.
if (this.flutterView != null) { if (this.flutterView != null) {
this.flutterView.removeOnFirstFrameRenderedListener(onFirstFrameRenderedListener); this.flutterView.removeOnFirstFrameRenderedListener(flutterUiDisplayListener);
removeView(this.flutterView); removeView(this.flutterView);
} }
// If we were displaying a previous splash screen View, remove it. // If we were displaying a previous splash screen View, remove it.
...@@ -136,7 +141,7 @@ import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener; ...@@ -136,7 +141,7 @@ import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener;
// waiting for the first frame to render. Show a splash UI until that happens. // waiting for the first frame to render. Show a splash UI until that happens.
splashScreenView = splashScreen.createSplashView(getContext(), splashScreenState); splashScreenView = splashScreen.createSplashView(getContext(), splashScreenState);
addView(this.splashScreenView); addView(this.splashScreenView);
flutterView.addOnFirstFrameRenderedListener(onFirstFrameRenderedListener); flutterView.addOnFirstFrameRenderedListener(flutterUiDisplayListener);
} else if (isSplashScreenTransitionNeededNow()) { } else if (isSplashScreenTransitionNeededNow()) {
Log.v(TAG, "Showing an immediate splash transition to Flutter due to previously interrupted transition."); Log.v(TAG, "Showing an immediate splash transition to Flutter due to previously interrupted transition.");
splashScreenView = splashScreen.createSplashView(getContext(), splashScreenState); splashScreenView = splashScreen.createSplashView(getContext(), splashScreenState);
......
...@@ -12,12 +12,10 @@ import android.util.AttributeSet; ...@@ -12,12 +12,10 @@ import android.util.AttributeSet;
import android.view.SurfaceHolder; import android.view.SurfaceHolder;
import android.view.SurfaceView; import android.view.SurfaceView;
import java.util.HashSet;
import java.util.Set;
import io.flutter.Log; import io.flutter.Log;
import io.flutter.embedding.engine.renderer.FlutterRenderer; import io.flutter.embedding.engine.renderer.FlutterRenderer;
import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener; import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener;
import io.flutter.embedding.engine.renderer.RenderSurface;
/** /**
* Paints a Flutter UI on a {@link android.view.Surface}. * Paints a Flutter UI on a {@link android.view.Surface}.
...@@ -34,7 +32,7 @@ import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener; ...@@ -34,7 +32,7 @@ import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener;
* desired, consider using a {@link FlutterView} which provides all of these behaviors and * desired, consider using a {@link FlutterView} which provides all of these behaviors and
* utilizes a {@code FlutterSurfaceView} internally. * utilizes a {@code FlutterSurfaceView} internally.
*/ */
public class FlutterSurfaceView extends SurfaceView implements FlutterRenderer.RenderSurface { public class FlutterSurfaceView extends SurfaceView implements RenderSurface {
private static final String TAG = "FlutterSurfaceView"; private static final String TAG = "FlutterSurfaceView";
private final boolean renderTransparently; private final boolean renderTransparently;
...@@ -42,8 +40,6 @@ public class FlutterSurfaceView extends SurfaceView implements FlutterRenderer.R ...@@ -42,8 +40,6 @@ public class FlutterSurfaceView extends SurfaceView implements FlutterRenderer.R
private boolean isAttachedToFlutterRenderer = false; private boolean isAttachedToFlutterRenderer = false;
@Nullable @Nullable
private FlutterRenderer flutterRenderer; private FlutterRenderer flutterRenderer;
@NonNull
private Set<OnFirstFrameRenderedListener> onFirstFrameRenderedListeners = new HashSet<>();
// Connects the {@code Surface} beneath this {@code SurfaceView} with Flutter's native code. // Connects the {@code Surface} beneath this {@code SurfaceView} with Flutter's native code.
// Callbacks are received by this Object and then those messages are forwarded to our // Callbacks are received by this Object and then those messages are forwarded to our
...@@ -51,7 +47,7 @@ public class FlutterSurfaceView extends SurfaceView implements FlutterRenderer.R ...@@ -51,7 +47,7 @@ public class FlutterSurfaceView extends SurfaceView implements FlutterRenderer.R
private final SurfaceHolder.Callback surfaceCallback = new SurfaceHolder.Callback() { private final SurfaceHolder.Callback surfaceCallback = new SurfaceHolder.Callback() {
@Override @Override
public void surfaceCreated(@NonNull SurfaceHolder holder) { public void surfaceCreated(@NonNull SurfaceHolder holder) {
Log.v(TAG, "SurfaceHolder.Callback.surfaceCreated()"); Log.v(TAG, "SurfaceHolder.Callback.startRenderingToSurface()");
isSurfaceAvailableForRendering = true; isSurfaceAvailableForRendering = true;
if (isAttachedToFlutterRenderer) { if (isAttachedToFlutterRenderer) {
...@@ -69,7 +65,7 @@ public class FlutterSurfaceView extends SurfaceView implements FlutterRenderer.R ...@@ -69,7 +65,7 @@ public class FlutterSurfaceView extends SurfaceView implements FlutterRenderer.R
@Override @Override
public void surfaceDestroyed(@NonNull SurfaceHolder holder) { public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
Log.v(TAG, "SurfaceHolder.Callback.surfaceDestroyed()"); Log.v(TAG, "SurfaceHolder.Callback.stopRenderingToSurface()");
isSurfaceAvailableForRendering = false; isSurfaceAvailableForRendering = false;
if (isAttachedToFlutterRenderer) { if (isAttachedToFlutterRenderer) {
...@@ -78,6 +74,24 @@ public class FlutterSurfaceView extends SurfaceView implements FlutterRenderer.R ...@@ -78,6 +74,24 @@ public class FlutterSurfaceView extends SurfaceView implements FlutterRenderer.R
} }
}; };
private final FlutterUiDisplayListener flutterUiDisplayListener = new FlutterUiDisplayListener() {
@Override
public void onFlutterUiDisplayed() {
Log.v(TAG, "onFlutterUiDisplayed()");
// Now that a frame is ready to display, take this SurfaceView from transparent to opaque.
setAlpha(1.0f);
if (flutterRenderer != null) {
flutterRenderer.removeIsDisplayingFlutterUiListener(this);
}
}
@Override
public void onFlutterUiNoLongerDisplayed() {
// no-op
}
};
/** /**
* Constructs a {@code FlutterSurfaceView} programmatically, without any XML attributes. * Constructs a {@code FlutterSurfaceView} programmatically, without any XML attributes.
*/ */
...@@ -123,6 +137,12 @@ public class FlutterSurfaceView extends SurfaceView implements FlutterRenderer.R ...@@ -123,6 +137,12 @@ public class FlutterSurfaceView extends SurfaceView implements FlutterRenderer.R
setAlpha(0.0f); setAlpha(0.0f);
} }
@Nullable
@Override
public FlutterRenderer getAttachedRenderer() {
return flutterRenderer;
}
/** /**
* Invoked by the owner of this {@code FlutterSurfaceView} when it wants to begin rendering * Invoked by the owner of this {@code FlutterSurfaceView} when it wants to begin rendering
* a Flutter UI to this {@code FlutterSurfaceView}. * a Flutter UI to this {@code FlutterSurfaceView}.
...@@ -140,12 +160,15 @@ public class FlutterSurfaceView extends SurfaceView implements FlutterRenderer.R ...@@ -140,12 +160,15 @@ public class FlutterSurfaceView extends SurfaceView implements FlutterRenderer.R
Log.v(TAG, "Attaching to FlutterRenderer."); Log.v(TAG, "Attaching to FlutterRenderer.");
if (this.flutterRenderer != null) { if (this.flutterRenderer != null) {
Log.v(TAG, "Already connected to a FlutterRenderer. Detaching from old one and attaching to new one."); Log.v(TAG, "Already connected to a FlutterRenderer. Detaching from old one and attaching to new one.");
this.flutterRenderer.detachFromRenderSurface(); this.flutterRenderer.stopRenderingToSurface();
this.flutterRenderer.removeIsDisplayingFlutterUiListener(flutterUiDisplayListener);
} }
this.flutterRenderer = flutterRenderer; this.flutterRenderer = flutterRenderer;
isAttachedToFlutterRenderer = true; isAttachedToFlutterRenderer = true;
this.flutterRenderer.addIsDisplayingFlutterUiListener(flutterUiDisplayListener);
// If we're already attached to an Android window then we're now attached to both a renderer // If we're already attached to an Android window then we're now attached to both a renderer
// and the Android window. We can begin rendering now. // and the Android window. We can begin rendering now.
if (isSurfaceAvailableForRendering) { if (isSurfaceAvailableForRendering) {
...@@ -173,6 +196,8 @@ public class FlutterSurfaceView extends SurfaceView implements FlutterRenderer.R ...@@ -173,6 +196,8 @@ public class FlutterSurfaceView extends SurfaceView implements FlutterRenderer.R
// Make the SurfaceView invisible to avoid showing a black rectangle. // Make the SurfaceView invisible to avoid showing a black rectangle.
setAlpha(0.0f); setAlpha(0.0f);
this.flutterRenderer.removeIsDisplayingFlutterUiListener(flutterUiDisplayListener);
flutterRenderer = null; flutterRenderer = null;
isAttachedToFlutterRenderer = false; isAttachedToFlutterRenderer = false;
} else { } else {
...@@ -186,7 +211,7 @@ public class FlutterSurfaceView extends SurfaceView implements FlutterRenderer.R ...@@ -186,7 +211,7 @@ public class FlutterSurfaceView extends SurfaceView implements FlutterRenderer.R
throw new IllegalStateException("connectSurfaceToRenderer() should only be called when flutterRenderer and getHolder() are non-null."); throw new IllegalStateException("connectSurfaceToRenderer() should only be called when flutterRenderer and getHolder() are non-null.");
} }
flutterRenderer.surfaceCreated(getHolder().getSurface()); flutterRenderer.startRenderingToSurface(getHolder().getSurface());
} }
// FlutterRenderer must be non-null. // FlutterRenderer must be non-null.
...@@ -205,36 +230,6 @@ public class FlutterSurfaceView extends SurfaceView implements FlutterRenderer.R ...@@ -205,36 +230,6 @@ public class FlutterSurfaceView extends SurfaceView implements FlutterRenderer.R
throw new IllegalStateException("disconnectSurfaceFromRenderer() should only be called when flutterRenderer is non-null."); throw new IllegalStateException("disconnectSurfaceFromRenderer() should only be called when flutterRenderer is non-null.");
} }
flutterRenderer.surfaceDestroyed(); flutterRenderer.stopRenderingToSurface();
}
/**
* Adds the given {@code listener} to this {@code FlutterSurfaceView}, to be notified upon Flutter's
* first rendered frame.
*/
@Override
public void addOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) {
onFirstFrameRenderedListeners.add(listener);
}
/**
* Removes the given {@code listener}, which was previously added with
* {@link #addOnFirstFrameRenderedListener(OnFirstFrameRenderedListener)}.
*/
@Override
public void removeOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) {
onFirstFrameRenderedListeners.remove(listener);
}
@Override
public void onFirstFrameRendered() {
// TODO(mattcarroll): decide where this method should live and what it needs to do.
Log.v(TAG, "onFirstFrameRendered()");
// Now that a frame is ready to display, take this SurfaceView from transparent to opaque.
setAlpha(1.0f);
for (OnFirstFrameRenderedListener listener : onFirstFrameRenderedListeners) {
listener.onFirstFrameRendered();
}
} }
} }
...@@ -12,12 +12,9 @@ import android.util.AttributeSet; ...@@ -12,12 +12,9 @@ import android.util.AttributeSet;
import android.view.Surface; import android.view.Surface;
import android.view.TextureView; import android.view.TextureView;
import java.util.HashSet;
import java.util.Set;
import io.flutter.Log; import io.flutter.Log;
import io.flutter.embedding.engine.renderer.FlutterRenderer; import io.flutter.embedding.engine.renderer.FlutterRenderer;
import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener; import io.flutter.embedding.engine.renderer.RenderSurface;
/** /**
* Paints a Flutter UI on a {@link SurfaceTexture}. * Paints a Flutter UI on a {@link SurfaceTexture}.
...@@ -34,15 +31,13 @@ import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener; ...@@ -34,15 +31,13 @@ import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener;
* desired, consider using a {@link FlutterView} which provides all of these behaviors and * desired, consider using a {@link FlutterView} which provides all of these behaviors and
* utilizes a {@code FlutterTextureView} internally. * utilizes a {@code FlutterTextureView} internally.
*/ */
public class FlutterTextureView extends TextureView implements FlutterRenderer.RenderSurface { public class FlutterTextureView extends TextureView implements RenderSurface {
private static final String TAG = "FlutterTextureView"; private static final String TAG = "FlutterTextureView";
private boolean isSurfaceAvailableForRendering = false; private boolean isSurfaceAvailableForRendering = false;
private boolean isAttachedToFlutterRenderer = false; private boolean isAttachedToFlutterRenderer = false;
@Nullable @Nullable
private FlutterRenderer flutterRenderer; private FlutterRenderer flutterRenderer;
@NonNull
private Set<OnFirstFrameRenderedListener> onFirstFrameRenderedListeners = new HashSet<>();
// Connects the {@code SurfaceTexture} beneath this {@code TextureView} with Flutter's native code. // Connects the {@code SurfaceTexture} beneath this {@code TextureView} with Flutter's native code.
// Callbacks are received by this Object and then those messages are forwarded to our // Callbacks are received by this Object and then those messages are forwarded to our
...@@ -111,6 +106,12 @@ public class FlutterTextureView extends TextureView implements FlutterRenderer.R ...@@ -111,6 +106,12 @@ public class FlutterTextureView extends TextureView implements FlutterRenderer.R
setSurfaceTextureListener(surfaceTextureListener); setSurfaceTextureListener(surfaceTextureListener);
} }
@Nullable
@Override
public FlutterRenderer getAttachedRenderer() {
return flutterRenderer;
}
/** /**
* Invoked by the owner of this {@code FlutterTextureView} when it wants to begin rendering * Invoked by the owner of this {@code FlutterTextureView} when it wants to begin rendering
* a Flutter UI to this {@code FlutterTextureView}. * a Flutter UI to this {@code FlutterTextureView}.
...@@ -128,7 +129,7 @@ public class FlutterTextureView extends TextureView implements FlutterRenderer.R ...@@ -128,7 +129,7 @@ public class FlutterTextureView extends TextureView implements FlutterRenderer.R
Log.v(TAG, "Attaching to FlutterRenderer."); Log.v(TAG, "Attaching to FlutterRenderer.");
if (this.flutterRenderer != null) { if (this.flutterRenderer != null) {
Log.v(TAG, "Already connected to a FlutterRenderer. Detaching from old one and attaching to new one."); Log.v(TAG, "Already connected to a FlutterRenderer. Detaching from old one and attaching to new one.");
this.flutterRenderer.detachFromRenderSurface(); this.flutterRenderer.stopRenderingToSurface();
} }
this.flutterRenderer = flutterRenderer; this.flutterRenderer = flutterRenderer;
...@@ -171,7 +172,7 @@ public class FlutterTextureView extends TextureView implements FlutterRenderer.R ...@@ -171,7 +172,7 @@ public class FlutterTextureView extends TextureView implements FlutterRenderer.R
throw new IllegalStateException("connectSurfaceToRenderer() should only be called when flutterRenderer and getSurfaceTexture() are non-null."); throw new IllegalStateException("connectSurfaceToRenderer() should only be called when flutterRenderer and getSurfaceTexture() are non-null.");
} }
flutterRenderer.surfaceCreated(new Surface(getSurfaceTexture())); flutterRenderer.startRenderingToSurface(new Surface(getSurfaceTexture()));
} }
// FlutterRenderer must be non-null. // FlutterRenderer must be non-null.
...@@ -190,34 +191,6 @@ public class FlutterTextureView extends TextureView implements FlutterRenderer.R ...@@ -190,34 +191,6 @@ public class FlutterTextureView extends TextureView implements FlutterRenderer.R
throw new IllegalStateException("disconnectSurfaceFromRenderer() should only be called when flutterRenderer is non-null."); throw new IllegalStateException("disconnectSurfaceFromRenderer() should only be called when flutterRenderer is non-null.");
} }
flutterRenderer.surfaceDestroyed(); flutterRenderer.stopRenderingToSurface();
}
/**
* Adds the given {@code listener} to this {@code FlutterTextureView}, to be notified upon Flutter's
* first rendered frame.
*/
@Override
public void addOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) {
onFirstFrameRenderedListeners.add(listener);
}
/**
* Removes the given {@code listener}, which was previously added with
* {@link #addOnFirstFrameRenderedListener(OnFirstFrameRenderedListener)}.
*/
@Override
public void removeOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) {
onFirstFrameRenderedListeners.remove(listener);
}
@Override
public void onFirstFrameRendered() {
// TODO(mattcarroll): decide where this method should live and what it needs to do.
Log.v(TAG, "onFirstFrameRendered()");
for (OnFirstFrameRenderedListener listener : onFirstFrameRenderedListeners) {
listener.onFirstFrameRendered();
}
} }
} }
...@@ -37,7 +37,8 @@ import java.util.Set; ...@@ -37,7 +37,8 @@ import java.util.Set;
import io.flutter.Log; import io.flutter.Log;
import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.renderer.FlutterRenderer; import io.flutter.embedding.engine.renderer.FlutterRenderer;
import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener; import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener;
import io.flutter.embedding.engine.renderer.RenderSurface;
import io.flutter.plugin.editing.TextInputPlugin; import io.flutter.plugin.editing.TextInputPlugin;
import io.flutter.plugin.platform.PlatformViewsController; import io.flutter.plugin.platform.PlatformViewsController;
import io.flutter.view.AccessibilityBridge; import io.flutter.view.AccessibilityBridge;
...@@ -74,9 +75,9 @@ public class FlutterView extends FrameLayout { ...@@ -74,9 +75,9 @@ public class FlutterView extends FrameLayout {
// Internal view hierarchy references. // Internal view hierarchy references.
@Nullable @Nullable
private FlutterRenderer.RenderSurface renderSurface; private RenderSurface renderSurface;
private final Set<OnFirstFrameRenderedListener> onFirstFrameRenderedListeners = new HashSet<>(); private final Set<FlutterUiDisplayListener> flutterUiDisplayListeners = new HashSet<>();
private boolean didRenderFirstFrame; private boolean isFlutterUiDisplayed;
// Connections to a Flutter execution context. // Connections to a Flutter execution context.
@Nullable @Nullable
...@@ -108,13 +109,22 @@ public class FlutterView extends FrameLayout { ...@@ -108,13 +109,22 @@ public class FlutterView extends FrameLayout {
} }
}; };
private final OnFirstFrameRenderedListener onFirstFrameRenderedListener = new OnFirstFrameRenderedListener() { private final FlutterUiDisplayListener flutterUiDisplayListener = new FlutterUiDisplayListener() {
@Override @Override
public void onFirstFrameRendered() { public void onFlutterUiDisplayed() {
didRenderFirstFrame = true; isFlutterUiDisplayed = true;
for (OnFirstFrameRenderedListener listener : onFirstFrameRenderedListeners) { for (FlutterUiDisplayListener listener : flutterUiDisplayListeners) {
listener.onFirstFrameRendered(); listener.onFlutterUiDisplayed();
}
}
@Override
public void onFlutterUiNoLongerDisplayed() {
isFlutterUiDisplayed = false;
for (FlutterUiDisplayListener listener : flutterUiDisplayListeners) {
listener.onFlutterUiNoLongerDisplayed();
} }
} }
}; };
...@@ -228,23 +238,23 @@ public class FlutterView extends FrameLayout { ...@@ -228,23 +238,23 @@ public class FlutterView extends FrameLayout {
* </ol> * </ol>
*/ */
public boolean hasRenderedFirstFrame() { public boolean hasRenderedFirstFrame() {
return didRenderFirstFrame; return isFlutterUiDisplayed;
} }
/** /**
* Adds the given {@code listener} to this {@code FlutterView}, to be notified upon Flutter's * Adds the given {@code listener} to this {@code FlutterView}, to be notified upon Flutter's
* first rendered frame. * first rendered frame.
*/ */
public void addOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) { public void addOnFirstFrameRenderedListener(@NonNull FlutterUiDisplayListener listener) {
onFirstFrameRenderedListeners.add(listener); flutterUiDisplayListeners.add(listener);
} }
/** /**
* Removes the given {@code listener}, which was previously added with * Removes the given {@code listener}, which was previously added with
* {@link #addOnFirstFrameRenderedListener(OnFirstFrameRenderedListener)}. * {@link #addOnFirstFrameRenderedListener(FlutterUiDisplayListener)}.
*/ */
public void removeOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) { public void removeOnFirstFrameRenderedListener(@NonNull FlutterUiDisplayListener listener) {
onFirstFrameRenderedListeners.remove(listener); flutterUiDisplayListeners.remove(listener);
} }
//------- Start: Process View configuration that Flutter cares about. ------ //------- Start: Process View configuration that Flutter cares about. ------
...@@ -580,9 +590,9 @@ public class FlutterView extends FrameLayout { ...@@ -580,9 +590,9 @@ public class FlutterView extends FrameLayout {
// Instruct our FlutterRenderer that we are now its designated RenderSurface. // Instruct our FlutterRenderer that we are now its designated RenderSurface.
FlutterRenderer flutterRenderer = this.flutterEngine.getRenderer(); FlutterRenderer flutterRenderer = this.flutterEngine.getRenderer();
didRenderFirstFrame = flutterRenderer.hasRenderedFirstFrame(); isFlutterUiDisplayed = flutterRenderer.isDisplayingFlutterUi();
flutterRenderer.attachToRenderSurface(renderSurface); renderSurface.attachToRenderer(flutterRenderer);
flutterRenderer.addOnFirstFrameRenderedListener(onFirstFrameRenderedListener); flutterRenderer.addIsDisplayingFlutterUiListener(flutterUiDisplayListener);
// Initialize various components that know how to process Android View I/O // Initialize various components that know how to process Android View I/O
// in a way that Flutter understands. // in a way that Flutter understands.
...@@ -631,8 +641,8 @@ public class FlutterView extends FrameLayout { ...@@ -631,8 +641,8 @@ public class FlutterView extends FrameLayout {
// If the first frame has already been rendered, notify all first frame listeners. // If the first frame has already been rendered, notify all first frame listeners.
// Do this after all other initialization so that listeners don't inadvertently interact // Do this after all other initialization so that listeners don't inadvertently interact
// with a FlutterView that is only partially attached to a FlutterEngine. // with a FlutterView that is only partially attached to a FlutterEngine.
if (didRenderFirstFrame) { if (isFlutterUiDisplayed) {
onFirstFrameRenderedListener.onFirstFrameRendered(); flutterUiDisplayListener.onFlutterUiDisplayed();
} }
} }
...@@ -674,9 +684,9 @@ public class FlutterView extends FrameLayout { ...@@ -674,9 +684,9 @@ public class FlutterView extends FrameLayout {
// Instruct our FlutterRenderer that we are no longer interested in being its RenderSurface. // Instruct our FlutterRenderer that we are no longer interested in being its RenderSurface.
FlutterRenderer flutterRenderer = flutterEngine.getRenderer(); FlutterRenderer flutterRenderer = flutterEngine.getRenderer();
didRenderFirstFrame = false; isFlutterUiDisplayed = false;
flutterRenderer.removeOnFirstFrameRenderedListener(onFirstFrameRenderedListener); flutterRenderer.removeIsDisplayingFlutterUiListener(flutterUiDisplayListener);
flutterRenderer.detachFromRenderSurface(); flutterRenderer.stopRenderingToSurface();
flutterEngine = null; flutterEngine = null;
} }
...@@ -685,7 +695,8 @@ public class FlutterView extends FrameLayout { ...@@ -685,7 +695,8 @@ public class FlutterView extends FrameLayout {
*/ */
@VisibleForTesting @VisibleForTesting
public boolean isAttachedToFlutterEngine() { public boolean isAttachedToFlutterEngine() {
return flutterEngine != null && flutterEngine.getRenderer().isAttachedTo(renderSurface); return flutterEngine != null
&& flutterEngine.getRenderer() == renderSurface.getAttachedRenderer();
} }
/** /**
......
...@@ -20,6 +20,7 @@ import io.flutter.embedding.engine.plugins.broadcastreceiver.BroadcastReceiverCo ...@@ -20,6 +20,7 @@ import io.flutter.embedding.engine.plugins.broadcastreceiver.BroadcastReceiverCo
import io.flutter.embedding.engine.plugins.contentprovider.ContentProviderControlSurface; import io.flutter.embedding.engine.plugins.contentprovider.ContentProviderControlSurface;
import io.flutter.embedding.engine.plugins.service.ServiceControlSurface; import io.flutter.embedding.engine.plugins.service.ServiceControlSurface;
import io.flutter.embedding.engine.renderer.FlutterRenderer; import io.flutter.embedding.engine.renderer.FlutterRenderer;
import io.flutter.embedding.engine.renderer.RenderSurface;
import io.flutter.embedding.engine.systemchannels.AccessibilityChannel; import io.flutter.embedding.engine.systemchannels.AccessibilityChannel;
import io.flutter.embedding.engine.systemchannels.KeyEventChannel; import io.flutter.embedding.engine.systemchannels.KeyEventChannel;
import io.flutter.embedding.engine.systemchannels.LifecycleChannel; import io.flutter.embedding.engine.systemchannels.LifecycleChannel;
...@@ -51,8 +52,8 @@ import io.flutter.plugin.platform.PlatformViewsController; ...@@ -51,8 +52,8 @@ import io.flutter.plugin.platform.PlatformViewsController;
* invoked twice on the same {@code FlutterEngine}. * invoked twice on the same {@code FlutterEngine}.
* *
* To start rendering Flutter content to the screen, use {@link #getRenderer()} to obtain a * To start rendering Flutter content to the screen, use {@link #getRenderer()} to obtain a
* {@link FlutterRenderer} and then attach a {@link FlutterRenderer.RenderSurface}. Consider using * {@link FlutterRenderer} and then attach a {@link RenderSurface}. Consider using
* a {@link io.flutter.embedding.android.FlutterView} as a {@link FlutterRenderer.RenderSurface}. * a {@link io.flutter.embedding.android.FlutterView} as a {@link RenderSurface}.
*/ */
// TODO(mattcarroll): re-evaluate system channel APIs - some are not well named or differentiated // TODO(mattcarroll): re-evaluate system channel APIs - some are not well named or differentiated
public class FlutterEngine implements LifecycleOwner { public class FlutterEngine implements LifecycleOwner {
...@@ -118,8 +119,8 @@ public class FlutterEngine implements LifecycleOwner { ...@@ -118,8 +119,8 @@ public class FlutterEngine implements LifecycleOwner {
* to begin executing Dart code within this {@code FlutterEngine}. * to begin executing Dart code within this {@code FlutterEngine}.
* *
* A new {@code FlutterEngine} will not display any UI until a * A new {@code FlutterEngine} will not display any UI until a
* {@link io.flutter.embedding.engine.renderer.FlutterRenderer.RenderSurface} is registered. See * {@link RenderSurface} is registered. See
* {@link #getRenderer()} and {@link FlutterRenderer#attachToRenderSurface(FlutterRenderer.RenderSurface)}. * {@link #getRenderer()} and {@link FlutterRenderer#startRenderingToSurface(RenderSurface)}.
* *
* A new {@code FlutterEngine} does not come with any Flutter plugins attached. To attach plugins, * A new {@code FlutterEngine} does not come with any Flutter plugins attached. To attach plugins,
* see {@link #getPlugins()}. * see {@link #getPlugins()}.
...@@ -221,7 +222,7 @@ public class FlutterEngine implements LifecycleOwner { ...@@ -221,7 +222,7 @@ public class FlutterEngine implements LifecycleOwner {
* The rendering system associated with this {@code FlutterEngine}. * The rendering system associated with this {@code FlutterEngine}.
* *
* To render a Flutter UI that is produced by this {@code FlutterEngine}'s Dart code, attach * To render a Flutter UI that is produced by this {@code FlutterEngine}'s Dart code, attach
* a {@link io.flutter.embedding.engine.renderer.FlutterRenderer.RenderSurface} to this * a {@link RenderSurface} to this
* {@link FlutterRenderer}. * {@link FlutterRenderer}.
*/ */
@NonNull @NonNull
......
...@@ -12,18 +12,19 @@ import android.os.Looper; ...@@ -12,18 +12,19 @@ import android.os.Looper;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.UiThread; import android.support.annotation.UiThread;
import android.support.annotation.VisibleForTesting;
import android.view.Surface; import android.view.Surface;
import android.view.SurfaceHolder; import android.view.SurfaceHolder;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import io.flutter.Log; import io.flutter.Log;
import io.flutter.embedding.engine.FlutterEngine.EngineLifecycleListener; import io.flutter.embedding.engine.FlutterEngine.EngineLifecycleListener;
import io.flutter.embedding.engine.dart.PlatformMessageHandler; import io.flutter.embedding.engine.dart.PlatformMessageHandler;
import io.flutter.embedding.engine.renderer.FlutterRenderer; import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener;
import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener; import io.flutter.embedding.engine.renderer.RenderSurface;
import io.flutter.plugin.common.StandardMessageCodec; import io.flutter.plugin.common.StandardMessageCodec;
import io.flutter.view.AccessibilityBridge; import io.flutter.view.AccessibilityBridge;
import io.flutter.view.FlutterCallbackInformation; import io.flutter.view.FlutterCallbackInformation;
...@@ -72,13 +73,13 @@ import io.flutter.view.FlutterCallbackInformation; ...@@ -72,13 +73,13 @@ import io.flutter.view.FlutterCallbackInformation;
* } * }
* *
* To provide a visual, interactive surface for Flutter rendering and touch events, register a * To provide a visual, interactive surface for Flutter rendering and touch events, register a
* {@link FlutterRenderer.RenderSurface} with {@link #setRenderSurface(FlutterRenderer.RenderSurface)} * {@link RenderSurface} with {@link #setRenderSurface(RenderSurface)}
* *
* To receive callbacks for certain events that occur on the native side, register listeners: * To receive callbacks for certain events that occur on the native side, register listeners:
* *
* <ol> * <ol>
* <li>{@link #addEngineLifecycleListener(EngineLifecycleListener)}</li> * <li>{@link #addEngineLifecycleListener(EngineLifecycleListener)}</li>
* <li>{@link #addOnFirstFrameRenderedListener(OnFirstFrameRenderedListener)}</li> * <li>{@link #addIsDisplayingFlutterUiListener(FlutterUiDisplayListener)}</li>
* </ol> * </ol>
* *
* To facilitate platform messages between Java and Dart running in Flutter, register a handler: * To facilitate platform messages between Java and Dart running in Flutter, register a handler:
...@@ -155,15 +156,13 @@ public class FlutterJNI { ...@@ -155,15 +156,13 @@ public class FlutterJNI {
@Nullable @Nullable
private Long nativePlatformViewId; private Long nativePlatformViewId;
@Nullable @Nullable
private FlutterRenderer.RenderSurface renderSurface;
@Nullable
private AccessibilityDelegate accessibilityDelegate; private AccessibilityDelegate accessibilityDelegate;
@Nullable @Nullable
private PlatformMessageHandler platformMessageHandler; private PlatformMessageHandler platformMessageHandler;
@NonNull @NonNull
private final Set<EngineLifecycleListener> engineLifecycleListeners = new HashSet<>(); private final Set<EngineLifecycleListener> engineLifecycleListeners = new CopyOnWriteArraySet<>();
@NonNull @NonNull
private final Set<OnFirstFrameRenderedListener> firstFrameListeners = new HashSet<>(); private final Set<FlutterUiDisplayListener> flutterUiDisplayListeners = new CopyOnWriteArraySet<>();
@NonNull @NonNull
private final Looper mainLooper; // cached to avoid synchronization on repeat access. private final Looper mainLooper; // cached to avoid synchronization on repeat access.
...@@ -233,58 +232,46 @@ public class FlutterJNI { ...@@ -233,58 +232,46 @@ public class FlutterJNI {
//----- Start Render Surface Support ----- //----- Start Render Surface Support -----
/** /**
* Sets the {@link FlutterRenderer.RenderSurface} delegate for the attached Flutter context. * Adds a {@link FlutterUiDisplayListener}, which receives a callback when Flutter's
* <p> * engine notifies {@code FlutterJNI} that Flutter is painting pixels to the {@link Surface} that
* Flutter expects a user interface to exist on the platform side (Android), and that interface * was provided to Flutter.
* is expected to offer some capabilities that Flutter depends upon. The {@link FlutterRenderer.RenderSurface}
* interface represents those expectations.
* <p>
* If an app includes a user interface that renders a Flutter UI then a {@link FlutterRenderer.RenderSurface}
* should be set (this is the typical Flutter scenario). If no UI is being rendered, such as a
* Flutter app that is running Dart code in the background, then no registration may be necessary.
* <p>
* If no {@link FlutterRenderer.RenderSurface} is registered, then related messages coming from
* Flutter will be dropped (ignored).
*/ */
@UiThread @UiThread
public void setRenderSurface(@Nullable FlutterRenderer.RenderSurface renderSurface) { public void addIsDisplayingFlutterUiListener(@NonNull FlutterUiDisplayListener listener) {
ensureRunningOnMainThread(); ensureRunningOnMainThread();
this.renderSurface = renderSurface; flutterUiDisplayListeners.add(listener);
} }
/** /**
* Adds a {@link OnFirstFrameRenderedListener}, which receives a callback when Flutter's * Removes a {@link FlutterUiDisplayListener} that was added with
* engine notifies {@code FlutterJNI} that the first frame of a Flutter UI has been rendered * {@link #addIsDisplayingFlutterUiListener(FlutterUiDisplayListener)}.
* to the {@link Surface} that was provided to Flutter.
*/ */
@UiThread @UiThread
public void addOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) { public void removeIsDisplayingFlutterUiListener(@NonNull FlutterUiDisplayListener listener) {
ensureRunningOnMainThread(); ensureRunningOnMainThread();
firstFrameListeners.add(listener); flutterUiDisplayListeners.remove(listener);
} }
/** // Called by native to notify first Flutter frame rendered.
* Removes a {@link OnFirstFrameRenderedListener} that was added with @SuppressWarnings("unused")
* {@link #addOnFirstFrameRenderedListener(OnFirstFrameRenderedListener)}. @VisibleForTesting
*/
@UiThread @UiThread
public void removeOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) { void onFirstFrame() {
ensureRunningOnMainThread(); ensureRunningOnMainThread();
firstFrameListeners.remove(listener);
for (FlutterUiDisplayListener listener : flutterUiDisplayListeners) {
listener.onFlutterUiDisplayed();
}
} }
// Called by native to notify first Flutter frame rendered. // TODO(mattcarroll): get native to call this when rendering stops.
@SuppressWarnings("unused") @VisibleForTesting
@UiThread @UiThread
private void onFirstFrame() { void onRenderingStopped() {
ensureRunningOnMainThread(); ensureRunningOnMainThread();
if (renderSurface != null) {
renderSurface.onFirstFrameRendered();
}
// TODO(mattcarroll): log dropped messages when in debug mode (https://github.com/flutter/flutter/issues/25391)
for (OnFirstFrameRenderedListener listener : firstFrameListeners) { for (FlutterUiDisplayListener listener : flutterUiDisplayListeners) {
listener.onFirstFrameRendered(); listener.onFlutterUiNoLongerDisplayed();
} }
} }
...@@ -331,6 +318,7 @@ public class FlutterJNI { ...@@ -331,6 +318,7 @@ public class FlutterJNI {
public void onSurfaceDestroyed() { public void onSurfaceDestroyed() {
ensureRunningOnMainThread(); ensureRunningOnMainThread();
ensureAttachedToNative(); ensureAttachedToNative();
onRenderingStopped();
nativeSurfaceDestroyed(nativePlatformViewId); nativeSurfaceDestroyed(nativePlatformViewId);
} }
...@@ -431,7 +419,6 @@ public class FlutterJNI { ...@@ -431,7 +419,6 @@ public class FlutterJNI {
* See {@link AccessibilityBridge} for an example of an {@link AccessibilityDelegate} and the * See {@link AccessibilityBridge} for an example of an {@link AccessibilityDelegate} and the
* surrounding responsibilities. * surrounding responsibilities.
*/ */
// TODO(mattcarroll): move AccessibilityDelegate definition into FlutterJNI. FlutterJNI should be the basis of dependencies, not the other way round.
@UiThread @UiThread
public void setAccessibilityDelegate(@Nullable AccessibilityDelegate accessibilityDelegate) { public void setAccessibilityDelegate(@Nullable AccessibilityDelegate accessibilityDelegate) {
ensureRunningOnMainThread(); ensureRunningOnMainThread();
...@@ -772,7 +759,7 @@ public class FlutterJNI { ...@@ -772,7 +759,7 @@ public class FlutterJNI {
/** /**
* Removes the given {@code engineLifecycleListener}, which was previously added using * Removes the given {@code engineLifecycleListener}, which was previously added using
* {@link #addOnFirstFrameRenderedListener(OnFirstFrameRenderedListener)}. * {@link #addIsDisplayingFlutterUiListener(FlutterUiDisplayListener)}.
*/ */
@UiThread @UiThread
public void removeEngineLifecycleListener(@NonNull EngineLifecycleListener engineLifecycleListener) { public void removeEngineLifecycleListener(@NonNull EngineLifecycleListener engineLifecycleListener) {
......
...@@ -17,95 +17,85 @@ import java.nio.ByteBuffer; ...@@ -17,95 +17,85 @@ import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import io.flutter.Log; import io.flutter.Log;
import io.flutter.embedding.android.FlutterView;
import io.flutter.embedding.engine.FlutterJNI; import io.flutter.embedding.engine.FlutterJNI;
import io.flutter.view.TextureRegistry; import io.flutter.view.TextureRegistry;
/** /**
* WARNING: THIS CLASS IS EXPERIMENTAL. DO NOT SHIP A DEPENDENCY ON THIS CODE. * Represents the rendering responsibilities of a {@code FlutterEngine}.
* IF YOU USE IT, WE WILL BREAK YOU. * <p>
* * {@code FlutterRenderer} works in tandem with a provided {@link RenderSurface} to paint Flutter
* {@code FlutterRenderer} works in tandem with a provided {@link RenderSurface} to create an * pixels to an Android {@code View} hierarchy.
* interactive Flutter UI. * <p>
* * {@code FlutterRenderer} manages textures for rendering, and forwards some Java calls to native
* {@code FlutterRenderer} manages textures for rendering, and forwards some Java calls to native Flutter * Flutter code via JNI. The corresponding {@link RenderSurface} provides the Android
* code via JNI. The corresponding {@link RenderSurface} is used as a delegate to carry out * {@link Surface} that this renderer paints.
* certain actions on behalf of this {@code FlutterRenderer} within an Android view hierarchy. * <p>
* * {@link io.flutter.embedding.android.FlutterSurfaceView} and
* {@link FlutterView} is an implementation of a {@link RenderSurface}. * {@link io.flutter.embedding.android.FlutterTextureView} are implementations of
* {@link RenderSurface}.
*/ */
@TargetApi(Build.VERSION_CODES.JELLY_BEAN) @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public class FlutterRenderer implements TextureRegistry { public class FlutterRenderer implements TextureRegistry {
private static final String TAG = "FlutterRenderer"; private static final String TAG = "FlutterRenderer";
@NonNull
private final FlutterJNI flutterJNI; private final FlutterJNI flutterJNI;
@NonNull
private final AtomicLong nextTextureId = new AtomicLong(0L); private final AtomicLong nextTextureId = new AtomicLong(0L);
private RenderSurface renderSurface; @Nullable
private boolean hasRenderedFirstFrame = false; private Surface surface;
private boolean isDisplayingFlutterUi = false;
private final OnFirstFrameRenderedListener onFirstFrameRenderedListener = new OnFirstFrameRenderedListener() { @NonNull
private final FlutterUiDisplayListener flutterUiDisplayListener = new FlutterUiDisplayListener() {
@Override @Override
public void onFirstFrameRendered() { public void onFlutterUiDisplayed() {
hasRenderedFirstFrame = true; isDisplayingFlutterUi = true;
}
@Override
public void onFlutterUiNoLongerDisplayed() {
isDisplayingFlutterUi = false;
} }
}; };
public FlutterRenderer(@NonNull FlutterJNI flutterJNI) { public FlutterRenderer(@NonNull FlutterJNI flutterJNI) {
this.flutterJNI = flutterJNI; this.flutterJNI = flutterJNI;
this.flutterJNI.addOnFirstFrameRenderedListener(onFirstFrameRenderedListener); this.flutterJNI.addIsDisplayingFlutterUiListener(flutterUiDisplayListener);
} }
/** /**
* Returns true if this {@code FlutterRenderer} is attached to the given {@link RenderSurface}, * Returns true if this {@code FlutterRenderer} is painting pixels to an Android {@code View}
* false otherwise. * hierarchy, false otherwise.
*/ */
public boolean isAttachedTo(@NonNull RenderSurface renderSurface) { public boolean isDisplayingFlutterUi() {
return this.renderSurface == renderSurface; return isDisplayingFlutterUi;
}
public void attachToRenderSurface(@NonNull RenderSurface renderSurface) {
Log.v(TAG, "Attaching to RenderSurface.");
// TODO(mattcarroll): determine desired behavior when attaching to an already attached renderer
if (this.renderSurface != null) {
Log.v(TAG, "Already attached to a RenderSurface. Detaching from old one and attaching to new one.");
detachFromRenderSurface();
}
this.renderSurface = renderSurface;
this.renderSurface.attachToRenderer(this);
this.flutterJNI.setRenderSurface(renderSurface);
}
public void detachFromRenderSurface() {
Log.v(TAG, "Detaching from RenderSurface.");
// TODO(mattcarroll): determine desired behavior if we're asked to detach without first being attached
if (this.renderSurface != null) {
this.renderSurface.detachFromRenderer();
this.renderSurface = null;
surfaceDestroyed();
this.flutterJNI.setRenderSurface(null);
}
} }
public boolean hasRenderedFirstFrame() { /**
return hasRenderedFirstFrame; * Adds a listener that is invoked whenever this {@code FlutterRenderer} starts and stops painting
} * pixels to an Android {@code View} hierarchy.
*/
public void addOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) { public void addIsDisplayingFlutterUiListener(@NonNull FlutterUiDisplayListener listener) {
flutterJNI.addOnFirstFrameRenderedListener(listener); flutterJNI.addIsDisplayingFlutterUiListener(listener);
if (hasRenderedFirstFrame) { if (isDisplayingFlutterUi) {
listener.onFirstFrameRendered(); listener.onFlutterUiDisplayed();
} }
} }
public void removeOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) { /**
flutterJNI.removeOnFirstFrameRenderedListener(listener); * Removes a listener added via
* {@link #addIsDisplayingFlutterUiListener(FlutterUiDisplayListener)}.
*/
public void removeIsDisplayingFlutterUiListener(@NonNull FlutterUiDisplayListener listener) {
flutterJNI.removeIsDisplayingFlutterUiListener(listener);
} }
//------ START TextureRegistry IMPLEMENTATION ----- //------ START TextureRegistry IMPLEMENTATION -----
// TODO(mattcarroll): detachFromGLContext requires API 16. Create solution for earlier APIs. /**
@TargetApi(Build.VERSION_CODES.JELLY_BEAN) * Creates and returns a new {@link SurfaceTexture} that is also made available to Flutter code.
*/
@Override @Override
public SurfaceTextureEntry createSurfaceTexture() { public SurfaceTextureEntry createSurfaceTexture() {
Log.v(TAG, "Creating a SurfaceTexture."); Log.v(TAG, "Creating a SurfaceTexture.");
...@@ -180,19 +170,57 @@ public class FlutterRenderer implements TextureRegistry { ...@@ -180,19 +170,57 @@ public class FlutterRenderer implements TextureRegistry {
} }
//------ END TextureRegistry IMPLEMENTATION ---- //------ END TextureRegistry IMPLEMENTATION ----
// TODO(mattcarroll): describe the native behavior that this invokes /**
public void surfaceCreated(@NonNull Surface surface) { * Notifies Flutter that the given {@code surface} was created and is available for Flutter
* rendering.
* <p>
* See {@link android.view.SurfaceHolder.Callback} and
* {@link android.view.TextureView.SurfaceTextureListener}
*/
public void startRenderingToSurface(@NonNull Surface surface) {
if (this.surface != null) {
stopRenderingToSurface();
}
this.surface = surface;
flutterJNI.onSurfaceCreated(surface); flutterJNI.onSurfaceCreated(surface);
} }
// TODO(mattcarroll): describe the native behavior that this invokes /**
* Notifies Flutter that a {@code surface} previously registered with
* {@link #startRenderingToSurface(Surface)} has changed size to the given {@code width} and
* {@code height}.
* <p>
* See {@link android.view.SurfaceHolder.Callback} and
* {@link android.view.TextureView.SurfaceTextureListener}
*/
public void surfaceChanged(int width, int height) { public void surfaceChanged(int width, int height) {
flutterJNI.onSurfaceChanged(width, height); flutterJNI.onSurfaceChanged(width, height);
} }
// TODO(mattcarroll): describe the native behavior that this invokes /**
public void surfaceDestroyed() { * Notifies Flutter that a {@code surface} previously registered with
* {@link #startRenderingToSurface(Surface)} has been destroyed and needs to be released and
* cleaned up on the Flutter side.
* <p>
* See {@link android.view.SurfaceHolder.Callback} and
* {@link android.view.TextureView.SurfaceTextureListener}
*/
public void stopRenderingToSurface() {
flutterJNI.onSurfaceDestroyed(); flutterJNI.onSurfaceDestroyed();
surface = null;
// TODO(mattcarroll): the source of truth for this call should be FlutterJNI, which is where
// the call to onFlutterUiDisplayed() comes from. However, no such native callback exists yet,
// so until the engine and FlutterJNI are configured to call us back when rendering stops,
// we will manually monitor that change here.
if (isDisplayingFlutterUi) {
flutterUiDisplayListener.onFlutterUiNoLongerDisplayed();
}
isDisplayingFlutterUi = false;
} }
// TODO(mattcarroll): describe the native behavior that this invokes // TODO(mattcarroll): describe the native behavior that this invokes
...@@ -279,61 +307,6 @@ public class FlutterRenderer implements TextureRegistry { ...@@ -279,61 +307,6 @@ public class FlutterRenderer implements TextureRegistry {
); );
} }
/**
* Delegate used in conjunction with a {@link FlutterRenderer} to create an interactive Flutter
* UI.
*
* A {@code RenderSurface} is responsible for carrying out behaviors that are needed by a
* corresponding {@link FlutterRenderer}.
*
* A {@code RenderSurface} also receives callbacks for important events, e.g.,
* {@link #onFirstFrameRendered()}.
*/
public interface RenderSurface {
/**
* Invoked by the owner of this {@code RenderSurface} when it wants to begin rendering
* a Flutter UI to this {@code RenderSurface}.
*
* The details of how rendering is handled is an implementation detail.
*/
void attachToRenderer(@NonNull FlutterRenderer renderer);
/**
* Invoked by the owner of this {@code RenderSurface} when it no longer wants to render
* a Flutter UI to this {@code RenderSurface}.
*
* This method will cease any on-going rendering from Flutter to this {@code RenderSurface}.
*/
void detachFromRenderer();
// TODO(mattcarroll): convert old FlutterView to use FlutterEngine instead of individual
// components, then use FlutterEngine's FlutterRenderer to watch for the first frame and
// remove the following methods from this interface.
/**
* The {@link FlutterRenderer} corresponding to this {@code RenderSurface} has painted its
* first frame since being initialized.
*
* "Initialized" refers to Flutter engine initialization, not the first frame after attaching
* to the {@link FlutterRenderer}. Therefore, the first frame may have already rendered by
* the time a {@code RenderSurface} has called {@link #attachToRenderSurface(RenderSurface)}
* on a {@link FlutterRenderer}. In such a situation, {@code #onFirstFrameRendered()} will
* never be called.
*/
void onFirstFrameRendered();
/**
* Adds the given {@code listener} to this {@code FlutterRenderer}, to be notified upon Flutter's
* first rendered frame.
*/
void addOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener);
/**
* Removes the given {@code listener}, which was previously added with
* {@link #addOnFirstFrameRenderedListener(OnFirstFrameRenderedListener)}.
*/
void removeOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener);
}
/** /**
* Mutable data structure that holds all viewport metrics properties that Flutter cares about. * Mutable data structure that holds all viewport metrics properties that Flutter cares about.
* *
......
...@@ -5,16 +5,20 @@ ...@@ -5,16 +5,20 @@
package io.flutter.embedding.engine.renderer; package io.flutter.embedding.engine.renderer;
/** /**
* Listener invoked after Flutter paints its first frame since being initialized. * Listener invoked when Flutter starts and stops rendering pixels to an Android {@code View}
* * hierarchy.
* WARNING: THIS CLASS IS EXPERIMENTAL. DO NOT SHIP A DEPENDENCY ON THIS CODE.
* IF YOU USE IT, WE WILL BREAK YOU.
*/ */
public interface OnFirstFrameRenderedListener { public interface FlutterUiDisplayListener {
/** /**
* A {@link FlutterRenderer} has painted its first frame since being initialized. * Flutter started painting pixels to an Android {@code View} hierarchy.
* * <p>
* This method will not be invoked if this listener is added after the first frame is rendered. * This method will not be invoked if this listener is added after the {@link FlutterRenderer}
* has started painting pixels.
*/ */
void onFirstFrameRendered(); void onFlutterUiDisplayed();
/**
* Flutter stopped painting pixels to an Android {@code View} hierarchy.
*/
void onFlutterUiNoLongerDisplayed();
} }
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package io.flutter.embedding.engine.renderer;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.Surface;
/**
* Owns a {@code Surface} that {@code FlutterRenderer} would like to paint.
* <p>
* {@code RenderSurface} is responsible for providing a {@code Surface} to a given
* {@code FlutterRenderer} when requested, and then notify that {@code FlutterRenderer} when
* the {@code Surface} changes, or is destroyed.
* <p>
* The behavior of providing a {@code Surface} is delegated to this interface because the timing
* of a {@code Surface}'s availability is determined by Android. Therefore, an accessor method
* would not fulfill the requirements. Therefore, a {@code RenderSurface} is given a
* {@code FlutterRenderer}, which the {@code RenderSurface} is expected to notify as a
* {@code Surface} becomes available, changes, or is destroyed.
*/
public interface RenderSurface {
/**
* Returns the {@code FlutterRenderer} that is attached to this {@code RenderSurface}, or
* null if no {@code FlutterRenderer} is currently attached.
*/
@Nullable
FlutterRenderer getAttachedRenderer();
/**
* Instructs this {@code RenderSurface} to give its {@code Surface} to the given
* {@code FlutterRenderer} so that Flutter can paint pixels on it.
* <p>
* After this call, {@code RenderSurface} is expected to invoke the following methods on
* {@link FlutterRenderer} at the appropriate times:
* <ol>
* <li>{@link FlutterRenderer#startRenderingToSurface(Surface)}</li>
* <li>{@link FlutterRenderer#surfaceChanged(int, int)}}</li>
* <li>{@link FlutterRenderer#stopRenderingToSurface()}</li>
* </ol>
*/
void attachToRenderer(@NonNull FlutterRenderer renderer);
/**
* Instructs this {@code RenderSurface} to stop forwarding {@code Surface} notifications to the
* {@code FlutterRenderer} that was previously connected with
* {@link #attachToRenderer(FlutterRenderer)}.
* <p>
* This {@code RenderSurface} should also clean up any references related to the previously
* connected {@code FlutterRenderer}.
*/
void detachFromRenderer();
}
...@@ -13,16 +13,9 @@ import io.flutter.app.FlutterPluginRegistry; ...@@ -13,16 +13,9 @@ import io.flutter.app.FlutterPluginRegistry;
import io.flutter.embedding.engine.FlutterJNI; import io.flutter.embedding.engine.FlutterJNI;
import io.flutter.embedding.engine.FlutterEngine.EngineLifecycleListener; import io.flutter.embedding.engine.FlutterEngine.EngineLifecycleListener;
import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.embedding.engine.renderer.FlutterRenderer; import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener;
import io.flutter.embedding.engine.renderer.FlutterRenderer.RenderSurface;
import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener;
import io.flutter.plugin.common.*; import io.flutter.plugin.common.*;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.HashMap;
import java.util.Map;
import io.flutter.embedding.engine.dart.PlatformMessageHandler;
public class FlutterNativeView implements BinaryMessenger { public class FlutterNativeView implements BinaryMessenger {
private static final String TAG = "FlutterNativeView"; private static final String TAG = "FlutterNativeView";
...@@ -34,6 +27,21 @@ public class FlutterNativeView implements BinaryMessenger { ...@@ -34,6 +27,21 @@ public class FlutterNativeView implements BinaryMessenger {
private final Context mContext; private final Context mContext;
private boolean applicationIsRunning; private boolean applicationIsRunning;
private final FlutterUiDisplayListener flutterUiDisplayListener = new FlutterUiDisplayListener() {
@Override
public void onFlutterUiDisplayed() {
if (mFlutterView == null) {
return;
}
mFlutterView.onFirstFrame();
}
@Override
public void onFlutterUiNoLongerDisplayed() {
// no-op
}
};
public FlutterNativeView(@NonNull Context context) { public FlutterNativeView(@NonNull Context context) {
this(context, false); this(context, false);
} }
...@@ -42,7 +50,7 @@ public class FlutterNativeView implements BinaryMessenger { ...@@ -42,7 +50,7 @@ public class FlutterNativeView implements BinaryMessenger {
mContext = context; mContext = context;
mPluginRegistry = new FlutterPluginRegistry(this, context); mPluginRegistry = new FlutterPluginRegistry(this, context);
mFlutterJNI = new FlutterJNI(); mFlutterJNI = new FlutterJNI();
mFlutterJNI.setRenderSurface(new RenderSurfaceImpl()); mFlutterJNI.addIsDisplayingFlutterUiListener(flutterUiDisplayListener);
this.dartExecutor = new DartExecutor(mFlutterJNI, context.getAssets()); this.dartExecutor = new DartExecutor(mFlutterJNI, context.getAssets());
mFlutterJNI.addEngineLifecycleListener(new EngineLifecycleListenerImpl()); mFlutterJNI.addEngineLifecycleListener(new EngineLifecycleListenerImpl());
attach(this, isBackgroundView); attach(this, isBackgroundView);
...@@ -58,6 +66,7 @@ public class FlutterNativeView implements BinaryMessenger { ...@@ -58,6 +66,7 @@ public class FlutterNativeView implements BinaryMessenger {
mPluginRegistry.destroy(); mPluginRegistry.destroy();
dartExecutor.onDetachedFromJNI(); dartExecutor.onDetachedFromJNI();
mFlutterView = null; mFlutterView = null;
mFlutterJNI.removeIsDisplayingFlutterUiListener(flutterUiDisplayListener);
mFlutterJNI.detachFromNativeAndReleaseResources(); mFlutterJNI.detachFromNativeAndReleaseResources();
applicationIsRunning = false; applicationIsRunning = false;
} }
...@@ -143,48 +152,6 @@ public class FlutterNativeView implements BinaryMessenger { ...@@ -143,48 +152,6 @@ public class FlutterNativeView implements BinaryMessenger {
dartExecutor.onAttachedToJNI(); dartExecutor.onAttachedToJNI();
} }
private final class RenderSurfaceImpl implements RenderSurface {
@Override
public void attachToRenderer(@NonNull FlutterRenderer renderer) {
// Not relevant for v1 embedding.
}
@Override
public void detachFromRenderer() {
// Not relevant for v1 embedding.
}
// Called by native to update the semantics/accessibility tree.
public void updateSemantics(ByteBuffer buffer, String[] strings) {
if (mFlutterView == null) {
return;
}
mFlutterView.updateSemantics(buffer, strings);
}
// Called by native to update the custom accessibility actions.
public void updateCustomAccessibilityActions(ByteBuffer buffer, String[] strings) {
if (mFlutterView == null) {
return;
}
mFlutterView.updateCustomAccessibilityActions(buffer, strings);
}
// Called by native to notify first Flutter frame rendered.
public void onFirstFrameRendered() {
if (mFlutterView == null) {
return;
}
mFlutterView.onFirstFrame();
}
@Override
public void addOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) {}
@Override
public void removeOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) {}
}
private final class EngineLifecycleListenerImpl implements EngineLifecycleListener { private final class EngineLifecycleListenerImpl implements EngineLifecycleListener {
// Called by native to notify when the engine is restarted (cold reload). // Called by native to notify when the engine is restarted (cold reload).
@SuppressWarnings("unused") @SuppressWarnings("unused")
......
...@@ -679,29 +679,6 @@ public class FlutterView extends SurfaceView implements BinaryMessenger, Texture ...@@ -679,29 +679,6 @@ public class FlutterView extends SurfaceView implements BinaryMessenger, Texture
); );
} }
// Called by native to update the semantics/accessibility tree.
public void updateSemantics(ByteBuffer buffer, String[] strings) {
try {
if (mAccessibilityNodeProvider != null) {
buffer.order(ByteOrder.LITTLE_ENDIAN);
mAccessibilityNodeProvider.updateSemantics(buffer, strings);
}
} catch (Exception ex) {
Log.e(TAG, "Uncaught exception while updating semantics", ex);
}
}
public void updateCustomAccessibilityActions(ByteBuffer buffer, String[] strings) {
try {
if (mAccessibilityNodeProvider != null) {
buffer.order(ByteOrder.LITTLE_ENDIAN);
mAccessibilityNodeProvider.updateCustomAccessibilityActions(buffer, strings);
}
} catch (Exception ex) {
Log.e(TAG, "Uncaught exception while updating local context actions", ex);
}
}
// Called by FlutterNativeView to notify first Flutter frame rendered. // Called by FlutterNativeView to notify first Flutter frame rendered.
public void onFirstFrame() { public void onFirstFrame() {
didRenderFirstFrame = true; didRenderFirstFrame = true;
......
...@@ -6,6 +6,7 @@ package io.flutter.view; ...@@ -6,6 +6,7 @@ package io.flutter.view;
import android.graphics.SurfaceTexture; import android.graphics.SurfaceTexture;
// TODO(mattcarroll): re-evalute docs in this class and add nullability annotations.
/** /**
* Registry of backend textures used with a single {@link FlutterView} instance. * Registry of backend textures used with a single {@link FlutterView} instance.
* Entries may be embedded into the Flutter view using the * Entries may be embedded into the Flutter view using the
......
...@@ -486,7 +486,7 @@ static void InvokePlatformMessageEmptyResponseCallback(JNIEnv* env, ...@@ -486,7 +486,7 @@ static void InvokePlatformMessageEmptyResponseCallback(JNIEnv* env,
bool RegisterApi(JNIEnv* env) { bool RegisterApi(JNIEnv* env) {
static const JNINativeMethod flutter_jni_methods[] = { static const JNINativeMethod flutter_jni_methods[] = {
// Start of methods from FlutterNativeView // Start of methods from FlutterJNI
{ {
.name = "nativeAttach", .name = "nativeAttach",
.signature = "(Lio/flutter/embedding/engine/FlutterJNI;Z)J", .signature = "(Lio/flutter/embedding/engine/FlutterJNI;Z)J",
......
...@@ -13,7 +13,10 @@ import io.flutter.embedding.android.FlutterActivityTest; ...@@ -13,7 +13,10 @@ import io.flutter.embedding.android.FlutterActivityTest;
import io.flutter.embedding.android.FlutterFragmentTest; import io.flutter.embedding.android.FlutterFragmentTest;
import io.flutter.embedding.engine.FlutterEngineCacheTest; import io.flutter.embedding.engine.FlutterEngineCacheTest;
import io.flutter.embedding.engine.systemchannels.PlatformChannelTest; import io.flutter.embedding.engine.systemchannels.PlatformChannelTest;
import io.flutter.embedding.engine.RenderingComponentTest;
import io.flutter.embedding.engine.renderer.FlutterRendererTest;
import io.flutter.util.PreconditionsTest; import io.flutter.util.PreconditionsTest;
import io.flutter.embedding.engine.FlutterJNITest;
@RunWith(Suite.class) @RunWith(Suite.class)
@SuiteClasses({ @SuiteClasses({
...@@ -23,6 +26,9 @@ import io.flutter.util.PreconditionsTest; ...@@ -23,6 +26,9 @@ import io.flutter.util.PreconditionsTest;
FlutterFragmentTest.class, FlutterFragmentTest.class,
// FlutterActivityAndFragmentDelegateTest.class, TODO(mklim): Fix and re-enable this // FlutterActivityAndFragmentDelegateTest.class, TODO(mklim): Fix and re-enable this
FlutterEngineCacheTest.class, FlutterEngineCacheTest.class,
FlutterJNITest.class,
RenderingComponentTest.class,
FlutterRendererTest.class,
PlatformChannelTest.class PlatformChannelTest.class
}) })
/** Runs all of the unit tests listed in the {@code @SuiteClasses} annotation. */ /** Runs all of the unit tests listed in the {@code @SuiteClasses} annotation. */
......
package io.flutter.embedding.engine;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import java.util.concurrent.atomic.AtomicInteger;
import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener;
import static org.junit.Assert.assertEquals;
@Config(manifest=Config.NONE)
@RunWith(RobolectricTestRunner.class)
public class FlutterJNITest {
@Test
public void itAllowsFirstFrameListenersToRemoveThemselvesInline() {
// --- Test Setup ---
FlutterJNI flutterJNI = new FlutterJNI();
AtomicInteger callbackInvocationCount = new AtomicInteger(0);
FlutterUiDisplayListener callback = new FlutterUiDisplayListener() {
@Override
public void onFlutterUiDisplayed() {
callbackInvocationCount.incrementAndGet();
flutterJNI.removeIsDisplayingFlutterUiListener(this);
}
@Override
public void onFlutterUiNoLongerDisplayed() {}
};
flutterJNI.addIsDisplayingFlutterUiListener(callback);
// --- Execute Test ---
flutterJNI.onFirstFrame();
// --- Verify Results ---
assertEquals(1, callbackInvocationCount.get());
// --- Execute Test ---
// The callback removed itself from the listener list. A second call doesn't call the callback.
flutterJNI.onFirstFrame();
// --- Verify Results ---
assertEquals(1, callbackInvocationCount.get());
}
}
package io.flutter.embedding.engine;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import java.util.concurrent.atomic.AtomicInteger;
import io.flutter.embedding.engine.renderer.FlutterRenderer;
import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener;
import static org.junit.Assert.assertEquals;
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 RenderingComponentTest {
@Test
public void flutterUiDisplayListenersCanRemoveThemselvesWhenInvoked() {
// Setup test.
FlutterJNI flutterJNI = new FlutterJNI();
FlutterRenderer flutterRenderer = new FlutterRenderer(flutterJNI);
AtomicInteger listenerInvocationCount = new AtomicInteger(0);
FlutterUiDisplayListener listener = new FlutterUiDisplayListener() {
@Override
public void onFlutterUiDisplayed() {
// This is the behavior we're testing, but we also verify that this method
// was invoked to ensure that this test behavior executed.
flutterRenderer.removeIsDisplayingFlutterUiListener(this);
// Track the invocation to ensure this method is called once, and only once.
listenerInvocationCount.incrementAndGet();
}
@Override
public void onFlutterUiNoLongerDisplayed() {}
};
flutterRenderer.addIsDisplayingFlutterUiListener(listener);
// Execute behavior under test.
// Pretend we are the native side and tell FlutterJNI that Flutter has rendered a frame.
flutterJNI.onFirstFrame();
// Verify results.
// If we got to this point without an exception, and if our listener was called one time,
// then the behavior under test is correct.
assertEquals(1, listenerInvocationCount.get());
}
@Test
public void flutterUiDisplayListenersAddedAfterFirstFrameAreAutomaticallyInvoked() {
// Setup test.
FlutterJNI flutterJNI = new FlutterJNI();
FlutterRenderer flutterRenderer = new FlutterRenderer(flutterJNI);
FlutterUiDisplayListener listener = mock(FlutterUiDisplayListener.class);
// Pretend we are the native side and tell FlutterJNI that Flutter has rendered a frame.
flutterJNI.onFirstFrame();
// Execute behavior under test.
flutterRenderer.addIsDisplayingFlutterUiListener(listener);
// Verify results.
verify(listener, times(1)).onFlutterUiDisplayed();
}
@Test
public void flutterUiDisplayListenersAddedAfterFlutterUiDisappearsAreNotInvoked() {
// Setup test.
FlutterJNI flutterJNI = new FlutterJNI();
FlutterRenderer flutterRenderer = new FlutterRenderer(flutterJNI);
FlutterUiDisplayListener listener = mock(FlutterUiDisplayListener.class);
// Pretend we are the native side and tell FlutterJNI that Flutter has rendered a frame.
flutterJNI.onFirstFrame();
// Pretend that rendering has stopped.
flutterJNI.onRenderingStopped();
// Execute behavior under test.
flutterRenderer.addIsDisplayingFlutterUiListener(listener);
// Verify results.
verify(listener, never()).onFlutterUiDisplayed();
}
}
package io.flutter.embedding.engine.renderer;
import android.view.Surface;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import io.flutter.embedding.engine.FlutterJNI;
import static junit.framework.TestCase.assertTrue;
import static org.junit.Assert.assertFalse;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@Config(manifest=Config.NONE)
@RunWith(RobolectricTestRunner.class)
public class FlutterRendererTest {
private FlutterJNI fakeFlutterJNI;
private Surface fakeSurface;
@Before
public void setup() {
fakeFlutterJNI = mock(FlutterJNI.class);
fakeSurface = mock(Surface.class);
}
@Test
public void itForwardsSurfaceCreationNotificationToFlutterJNI() {
// Setup the test.
Surface fakeSurface = mock(Surface.class);
FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
// Execute the behavior under test.
flutterRenderer.startRenderingToSurface(fakeSurface);
// Verify the behavior under test.
verify(fakeFlutterJNI, times(1)).onSurfaceCreated(eq(fakeSurface));
}
@Test
public void itForwardsSurfaceChangeNotificationToFlutterJNI() {
// Setup the test.
Surface fakeSurface = mock(Surface.class);
FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
flutterRenderer.startRenderingToSurface(fakeSurface);
// Execute the behavior under test.
flutterRenderer.surfaceChanged(100, 50);
// Verify the behavior under test.
verify(fakeFlutterJNI, times(1)).onSurfaceChanged(eq(100), eq(50));
}
@Test
public void itForwardsSurfaceDestructionNotificationToFlutterJNI() {
// Setup the test.
Surface fakeSurface = mock(Surface.class);
FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
flutterRenderer.startRenderingToSurface(fakeSurface);
// Execute the behavior under test.
flutterRenderer.stopRenderingToSurface();
// Verify the behavior under test.
verify(fakeFlutterJNI, times(1)).onSurfaceDestroyed();
}
@Test
public void itStopsRenderingToOneSurfaceBeforeRenderingToANewSurface() {
// Setup the test.
Surface fakeSurface2 = mock(Surface.class);
FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
flutterRenderer.startRenderingToSurface(fakeSurface);
// Execute behavior under test.
flutterRenderer.startRenderingToSurface(fakeSurface2);
// Verify behavior under test.
verify(fakeFlutterJNI, times(1)).onSurfaceDestroyed(); // notification of 1st surface's removal.
}
@Test
public void itStopsRenderingToSurfaceWhenRequested() {
// Setup the test.
FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
flutterRenderer.startRenderingToSurface(fakeSurface);
// Execute the behavior under test.
flutterRenderer.stopRenderingToSurface();
// Verify behavior under test.
verify(fakeFlutterJNI, times(1)).onSurfaceDestroyed();
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册