未验证 提交 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
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/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/KeyEventChannel.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/LifecycleChannel.java
......
......@@ -170,7 +170,8 @@ action("flutter_shell_java") {
"io/flutter/embedding/engine/plugins/shim/ShimPluginRegistry.java",
"io/flutter/embedding/engine/plugins/shim/ShimRegistrar.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/KeyEventChannel.java",
"io/flutter/embedding/engine/systemchannels/LifecycleChannel.java",
......@@ -412,6 +413,9 @@ action("robolectric_tests") {
"test/io/flutter/embedding/android/FlutterActivityTest.java",
"test/io/flutter/embedding/android/FlutterFragmentTest.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/util/PreconditionsTest.java",
]
......
......@@ -886,7 +886,18 @@ public class FlutterActivity extends Activity
}
@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.
......
......@@ -25,7 +25,7 @@ import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterEngineCache;
import io.flutter.embedding.engine.FlutterShellArgs;
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.view.FlutterMain;
......@@ -83,10 +83,15 @@ import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
private boolean isFlutterEngineFromHost;
@NonNull
private final OnFirstFrameRenderedListener onFirstFrameRenderedListener = new OnFirstFrameRenderedListener() {
private final FlutterUiDisplayListener flutterUiDisplayListener = new FlutterUiDisplayListener() {
@Override
public void onFirstFrameRendered() {
host.onFirstFrameRendered();
public void onFlutterUiDisplayed() {
host.onFlutterUiDisplayed();
}
@Override
public void onFlutterUiNoLongerDisplayed() {
host.onFlutterUiNoLongerDisplayed();
}
};
......@@ -228,7 +233,7 @@ import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
* <p>
* {@code inflater} and {@code container} may be null when invoked from an {@code Activity}.
* <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.
*/
@NonNull
......@@ -236,7 +241,7 @@ import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
Log.v(TAG, "Creating FlutterView.");
ensureAlive();
flutterView = new FlutterView(host.getActivity(), host.getRenderMode(), host.getTransparencyMode());
flutterView.addOnFirstFrameRenderedListener(onFirstFrameRenderedListener);
flutterView.addOnFirstFrameRenderedListener(flutterUiDisplayListener);
flutterSplashView = new FlutterSplashView(host.getContext());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
......@@ -391,12 +396,12 @@ import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
/**
* Invoke this from {@code Activity#onDestroy()} or {@code Fragment#onDestroyView()}.
* <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() {
Log.v(TAG, "onDestroyView()");
ensureAlive();
flutterView.removeOnFirstFrameRenderedListener(onFirstFrameRenderedListener);
flutterView.removeOnFirstFrameRenderedListener(flutterUiDisplayListener);
}
/**
......@@ -695,9 +700,13 @@ import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
boolean shouldAttachEngineToActivity();
/**
* Invoked by this delegate when its {@link FlutterView} has rendered its first Flutter
* frame.
* Invoked by this delegate when its {@link FlutterView} starts painting pixels.
*/
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;
import io.flutter.Log;
import io.flutter.embedding.engine.FlutterEngine;
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.view.FlutterMain;
......@@ -567,21 +567,6 @@ public class FlutterFragment extends Fragment implements FlutterActivityAndFragm
// implementation for details about why it exists.
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() {
// 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.
......@@ -951,21 +936,40 @@ public class FlutterFragment extends Fragment implements FlutterActivityAndFragm
}
/**
* Invoked after the {@link FlutterView} within this {@code FlutterFragment} renders its first
* frame.
* Invoked after the {@link FlutterView} within this {@code FlutterFragment} starts rendering
* 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>
* This method forwards {@code onFirstFrameRendered()} to its attached {@code Activity}, if
* the attached {@code Activity} implements {@link OnFirstFrameRenderedListener}.
* This method forwards {@code onFlutterUiNoLongerDisplayed()} 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 onFirstFrameRendered() {
public void onFlutterUiNoLongerDisplayed() {
FragmentActivity attachedActivity = getActivity();
if (attachedActivity instanceof OnFirstFrameRenderedListener) {
((OnFirstFrameRenderedListener) attachedActivity).onFirstFrameRendered();
if (attachedActivity instanceof FlutterUiDisplayListener) {
((FlutterUiDisplayListener) attachedActivity).onFlutterUiNoLongerDisplayed();
}
}
......
......@@ -16,7 +16,7 @@ import android.widget.FrameLayout;
import io.flutter.Log;
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}
......@@ -51,13 +51,18 @@ import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener;
};
@NonNull
private final OnFirstFrameRenderedListener onFirstFrameRenderedListener = new OnFirstFrameRenderedListener() {
private final FlutterUiDisplayListener flutterUiDisplayListener = new FlutterUiDisplayListener() {
@Override
public void onFirstFrameRendered() {
public void onFlutterUiDisplayed() {
if (splashScreen != null) {
transitionToFlutter();
}
}
@Override
public void onFlutterUiNoLongerDisplayed() {
// no-op
}
};
@NonNull
......@@ -114,7 +119,7 @@ import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener;
) {
// If we were displaying a previous FlutterView, remove it.
if (this.flutterView != null) {
this.flutterView.removeOnFirstFrameRenderedListener(onFirstFrameRenderedListener);
this.flutterView.removeOnFirstFrameRenderedListener(flutterUiDisplayListener);
removeView(this.flutterView);
}
// If we were displaying a previous splash screen View, remove it.
......@@ -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.
splashScreenView = splashScreen.createSplashView(getContext(), splashScreenState);
addView(this.splashScreenView);
flutterView.addOnFirstFrameRenderedListener(onFirstFrameRenderedListener);
flutterView.addOnFirstFrameRenderedListener(flutterUiDisplayListener);
} else if (isSplashScreenTransitionNeededNow()) {
Log.v(TAG, "Showing an immediate splash transition to Flutter due to previously interrupted transition.");
splashScreenView = splashScreen.createSplashView(getContext(), splashScreenState);
......
......@@ -12,12 +12,10 @@ import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import java.util.HashSet;
import java.util.Set;
import io.flutter.Log;
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}.
......@@ -34,7 +32,7 @@ import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener;
* desired, consider using a {@link FlutterView} which provides all of these behaviors and
* 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 final boolean renderTransparently;
......@@ -42,8 +40,6 @@ public class FlutterSurfaceView extends SurfaceView implements FlutterRenderer.R
private boolean isAttachedToFlutterRenderer = false;
@Nullable
private FlutterRenderer flutterRenderer;
@NonNull
private Set<OnFirstFrameRenderedListener> onFirstFrameRenderedListeners = new HashSet<>();
// 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
......@@ -51,7 +47,7 @@ public class FlutterSurfaceView extends SurfaceView implements FlutterRenderer.R
private final SurfaceHolder.Callback surfaceCallback = new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
Log.v(TAG, "SurfaceHolder.Callback.surfaceCreated()");
Log.v(TAG, "SurfaceHolder.Callback.startRenderingToSurface()");
isSurfaceAvailableForRendering = true;
if (isAttachedToFlutterRenderer) {
......@@ -69,7 +65,7 @@ public class FlutterSurfaceView extends SurfaceView implements FlutterRenderer.R
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
Log.v(TAG, "SurfaceHolder.Callback.surfaceDestroyed()");
Log.v(TAG, "SurfaceHolder.Callback.stopRenderingToSurface()");
isSurfaceAvailableForRendering = false;
if (isAttachedToFlutterRenderer) {
......@@ -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.
*/
......@@ -123,6 +137,12 @@ public class FlutterSurfaceView extends SurfaceView implements FlutterRenderer.R
setAlpha(0.0f);
}
@Nullable
@Override
public FlutterRenderer getAttachedRenderer() {
return flutterRenderer;
}
/**
* Invoked by the owner of this {@code FlutterSurfaceView} when it wants to begin rendering
* a Flutter UI to this {@code FlutterSurfaceView}.
......@@ -140,12 +160,15 @@ public class FlutterSurfaceView extends SurfaceView implements FlutterRenderer.R
Log.v(TAG, "Attaching to FlutterRenderer.");
if (this.flutterRenderer != null) {
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;
isAttachedToFlutterRenderer = true;
this.flutterRenderer.addIsDisplayingFlutterUiListener(flutterUiDisplayListener);
// 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.
if (isSurfaceAvailableForRendering) {
......@@ -173,6 +196,8 @@ public class FlutterSurfaceView extends SurfaceView implements FlutterRenderer.R
// Make the SurfaceView invisible to avoid showing a black rectangle.
setAlpha(0.0f);
this.flutterRenderer.removeIsDisplayingFlutterUiListener(flutterUiDisplayListener);
flutterRenderer = null;
isAttachedToFlutterRenderer = false;
} else {
......@@ -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.");
}
flutterRenderer.surfaceCreated(getHolder().getSurface());
flutterRenderer.startRenderingToSurface(getHolder().getSurface());
}
// FlutterRenderer must be non-null.
......@@ -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.");
}
flutterRenderer.surfaceDestroyed();
}
/**
* 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();
}
flutterRenderer.stopRenderingToSurface();
}
}
......@@ -12,12 +12,9 @@ import android.util.AttributeSet;
import android.view.Surface;
import android.view.TextureView;
import java.util.HashSet;
import java.util.Set;
import io.flutter.Log;
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}.
......@@ -34,15 +31,13 @@ import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener;
* desired, consider using a {@link FlutterView} which provides all of these behaviors and
* 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 boolean isSurfaceAvailableForRendering = false;
private boolean isAttachedToFlutterRenderer = false;
@Nullable
private FlutterRenderer flutterRenderer;
@NonNull
private Set<OnFirstFrameRenderedListener> onFirstFrameRenderedListeners = new HashSet<>();
// 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
......@@ -111,6 +106,12 @@ public class FlutterTextureView extends TextureView implements FlutterRenderer.R
setSurfaceTextureListener(surfaceTextureListener);
}
@Nullable
@Override
public FlutterRenderer getAttachedRenderer() {
return flutterRenderer;
}
/**
* Invoked by the owner of this {@code FlutterTextureView} when it wants to begin rendering
* a Flutter UI to this {@code FlutterTextureView}.
......@@ -128,7 +129,7 @@ public class FlutterTextureView extends TextureView implements FlutterRenderer.R
Log.v(TAG, "Attaching to FlutterRenderer.");
if (this.flutterRenderer != null) {
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;
......@@ -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.");
}
flutterRenderer.surfaceCreated(new Surface(getSurfaceTexture()));
flutterRenderer.startRenderingToSurface(new Surface(getSurfaceTexture()));
}
// FlutterRenderer must be non-null.
......@@ -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.");
}
flutterRenderer.surfaceDestroyed();
}
/**
* 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();
}
flutterRenderer.stopRenderingToSurface();
}
}
......@@ -37,7 +37,8 @@ import java.util.Set;
import io.flutter.Log;
import io.flutter.embedding.engine.FlutterEngine;
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.platform.PlatformViewsController;
import io.flutter.view.AccessibilityBridge;
......@@ -74,9 +75,9 @@ public class FlutterView extends FrameLayout {
// Internal view hierarchy references.
@Nullable
private FlutterRenderer.RenderSurface renderSurface;
private final Set<OnFirstFrameRenderedListener> onFirstFrameRenderedListeners = new HashSet<>();
private boolean didRenderFirstFrame;
private RenderSurface renderSurface;
private final Set<FlutterUiDisplayListener> flutterUiDisplayListeners = new HashSet<>();
private boolean isFlutterUiDisplayed;
// Connections to a Flutter execution context.
@Nullable
......@@ -108,13 +109,22 @@ public class FlutterView extends FrameLayout {
}
};
private final OnFirstFrameRenderedListener onFirstFrameRenderedListener = new OnFirstFrameRenderedListener() {
private final FlutterUiDisplayListener flutterUiDisplayListener = new FlutterUiDisplayListener() {
@Override
public void onFirstFrameRendered() {
didRenderFirstFrame = true;
public void onFlutterUiDisplayed() {
isFlutterUiDisplayed = true;
for (OnFirstFrameRenderedListener listener : onFirstFrameRenderedListeners) {
listener.onFirstFrameRendered();
for (FlutterUiDisplayListener listener : flutterUiDisplayListeners) {
listener.onFlutterUiDisplayed();
}
}
@Override
public void onFlutterUiNoLongerDisplayed() {
isFlutterUiDisplayed = false;
for (FlutterUiDisplayListener listener : flutterUiDisplayListeners) {
listener.onFlutterUiNoLongerDisplayed();
}
}
};
......@@ -228,23 +238,23 @@ public class FlutterView extends FrameLayout {
* </ol>
*/
public boolean hasRenderedFirstFrame() {
return didRenderFirstFrame;
return isFlutterUiDisplayed;
}
/**
* Adds the given {@code listener} to this {@code FlutterView}, to be notified upon Flutter's
* first rendered frame.
*/
public void addOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) {
onFirstFrameRenderedListeners.add(listener);
public void addOnFirstFrameRenderedListener(@NonNull FlutterUiDisplayListener listener) {
flutterUiDisplayListeners.add(listener);
}
/**
* Removes the given {@code listener}, which was previously added with
* {@link #addOnFirstFrameRenderedListener(OnFirstFrameRenderedListener)}.
* {@link #addOnFirstFrameRenderedListener(FlutterUiDisplayListener)}.
*/
public void removeOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) {
onFirstFrameRenderedListeners.remove(listener);
public void removeOnFirstFrameRenderedListener(@NonNull FlutterUiDisplayListener listener) {
flutterUiDisplayListeners.remove(listener);
}
//------- Start: Process View configuration that Flutter cares about. ------
......@@ -580,9 +590,9 @@ public class FlutterView extends FrameLayout {
// Instruct our FlutterRenderer that we are now its designated RenderSurface.
FlutterRenderer flutterRenderer = this.flutterEngine.getRenderer();
didRenderFirstFrame = flutterRenderer.hasRenderedFirstFrame();
flutterRenderer.attachToRenderSurface(renderSurface);
flutterRenderer.addOnFirstFrameRenderedListener(onFirstFrameRenderedListener);
isFlutterUiDisplayed = flutterRenderer.isDisplayingFlutterUi();
renderSurface.attachToRenderer(flutterRenderer);
flutterRenderer.addIsDisplayingFlutterUiListener(flutterUiDisplayListener);
// Initialize various components that know how to process Android View I/O
// in a way that Flutter understands.
......@@ -631,8 +641,8 @@ public class FlutterView extends FrameLayout {
// 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
// with a FlutterView that is only partially attached to a FlutterEngine.
if (didRenderFirstFrame) {
onFirstFrameRenderedListener.onFirstFrameRendered();
if (isFlutterUiDisplayed) {
flutterUiDisplayListener.onFlutterUiDisplayed();
}
}
......@@ -674,9 +684,9 @@ public class FlutterView extends FrameLayout {
// Instruct our FlutterRenderer that we are no longer interested in being its RenderSurface.
FlutterRenderer flutterRenderer = flutterEngine.getRenderer();
didRenderFirstFrame = false;
flutterRenderer.removeOnFirstFrameRenderedListener(onFirstFrameRenderedListener);
flutterRenderer.detachFromRenderSurface();
isFlutterUiDisplayed = false;
flutterRenderer.removeIsDisplayingFlutterUiListener(flutterUiDisplayListener);
flutterRenderer.stopRenderingToSurface();
flutterEngine = null;
}
......@@ -685,7 +695,8 @@ public class FlutterView extends FrameLayout {
*/
@VisibleForTesting
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
import io.flutter.embedding.engine.plugins.contentprovider.ContentProviderControlSurface;
import io.flutter.embedding.engine.plugins.service.ServiceControlSurface;
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.KeyEventChannel;
import io.flutter.embedding.engine.systemchannels.LifecycleChannel;
......@@ -51,8 +52,8 @@ import io.flutter.plugin.platform.PlatformViewsController;
* invoked twice on the same {@code FlutterEngine}.
*
* 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
* a {@link io.flutter.embedding.android.FlutterView} as a {@link FlutterRenderer.RenderSurface}.
* {@link FlutterRenderer} and then attach a {@link RenderSurface}. Consider using
* 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
public class FlutterEngine implements LifecycleOwner {
......@@ -118,8 +119,8 @@ public class FlutterEngine implements LifecycleOwner {
* to begin executing Dart code within this {@code FlutterEngine}.
*
* A new {@code FlutterEngine} will not display any UI until a
* {@link io.flutter.embedding.engine.renderer.FlutterRenderer.RenderSurface} is registered. See
* {@link #getRenderer()} and {@link FlutterRenderer#attachToRenderSurface(FlutterRenderer.RenderSurface)}.
* {@link RenderSurface} is registered. See
* {@link #getRenderer()} and {@link FlutterRenderer#startRenderingToSurface(RenderSurface)}.
*
* A new {@code FlutterEngine} does not come with any Flutter plugins attached. To attach plugins,
* see {@link #getPlugins()}.
......@@ -221,7 +222,7 @@ public class FlutterEngine implements LifecycleOwner {
* The rendering system associated with this {@code FlutterEngine}.
*
* 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}.
*/
@NonNull
......
......@@ -12,18 +12,19 @@ import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import android.support.annotation.VisibleForTesting;
import android.view.Surface;
import android.view.SurfaceHolder;
import java.nio.ByteBuffer;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import io.flutter.Log;
import io.flutter.embedding.engine.FlutterEngine.EngineLifecycleListener;
import io.flutter.embedding.engine.dart.PlatformMessageHandler;
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.common.StandardMessageCodec;
import io.flutter.view.AccessibilityBridge;
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
* {@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:
*
* <ol>
* <li>{@link #addEngineLifecycleListener(EngineLifecycleListener)}</li>
* <li>{@link #addOnFirstFrameRenderedListener(OnFirstFrameRenderedListener)}</li>
* <li>{@link #addIsDisplayingFlutterUiListener(FlutterUiDisplayListener)}</li>
* </ol>
*
* To facilitate platform messages between Java and Dart running in Flutter, register a handler:
......@@ -155,15 +156,13 @@ public class FlutterJNI {
@Nullable
private Long nativePlatformViewId;
@Nullable
private FlutterRenderer.RenderSurface renderSurface;
@Nullable
private AccessibilityDelegate accessibilityDelegate;
@Nullable
private PlatformMessageHandler platformMessageHandler;
@NonNull
private final Set<EngineLifecycleListener> engineLifecycleListeners = new HashSet<>();
private final Set<EngineLifecycleListener> engineLifecycleListeners = new CopyOnWriteArraySet<>();
@NonNull
private final Set<OnFirstFrameRenderedListener> firstFrameListeners = new HashSet<>();
private final Set<FlutterUiDisplayListener> flutterUiDisplayListeners = new CopyOnWriteArraySet<>();
@NonNull
private final Looper mainLooper; // cached to avoid synchronization on repeat access.
......@@ -233,58 +232,46 @@ public class FlutterJNI {
//----- Start Render Surface Support -----
/**
* Sets the {@link FlutterRenderer.RenderSurface} delegate for the attached Flutter context.
* <p>
* Flutter expects a user interface to exist on the platform side (Android), and that interface
* 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).
* Adds a {@link FlutterUiDisplayListener}, which receives a callback when Flutter's
* engine notifies {@code FlutterJNI} that Flutter is painting pixels to the {@link Surface} that
* was provided to Flutter.
*/
@UiThread
public void setRenderSurface(@Nullable FlutterRenderer.RenderSurface renderSurface) {
public void addIsDisplayingFlutterUiListener(@NonNull FlutterUiDisplayListener listener) {
ensureRunningOnMainThread();
this.renderSurface = renderSurface;
flutterUiDisplayListeners.add(listener);
}
/**
* Adds a {@link OnFirstFrameRenderedListener}, which receives a callback when Flutter's
* engine notifies {@code FlutterJNI} that the first frame of a Flutter UI has been rendered
* to the {@link Surface} that was provided to Flutter.
* Removes a {@link FlutterUiDisplayListener} that was added with
* {@link #addIsDisplayingFlutterUiListener(FlutterUiDisplayListener)}.
*/
@UiThread
public void addOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) {
public void removeIsDisplayingFlutterUiListener(@NonNull FlutterUiDisplayListener listener) {
ensureRunningOnMainThread();
firstFrameListeners.add(listener);
flutterUiDisplayListeners.remove(listener);
}
/**
* Removes a {@link OnFirstFrameRenderedListener} that was added with
* {@link #addOnFirstFrameRenderedListener(OnFirstFrameRenderedListener)}.
*/
// Called by native to notify first Flutter frame rendered.
@SuppressWarnings("unused")
@VisibleForTesting
@UiThread
public void removeOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) {
void onFirstFrame() {
ensureRunningOnMainThread();
firstFrameListeners.remove(listener);
for (FlutterUiDisplayListener listener : flutterUiDisplayListeners) {
listener.onFlutterUiDisplayed();
}
}
// Called by native to notify first Flutter frame rendered.
@SuppressWarnings("unused")
// TODO(mattcarroll): get native to call this when rendering stops.
@VisibleForTesting
@UiThread
private void onFirstFrame() {
void onRenderingStopped() {
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) {
listener.onFirstFrameRendered();
for (FlutterUiDisplayListener listener : flutterUiDisplayListeners) {
listener.onFlutterUiNoLongerDisplayed();
}
}
......@@ -331,6 +318,7 @@ public class FlutterJNI {
public void onSurfaceDestroyed() {
ensureRunningOnMainThread();
ensureAttachedToNative();
onRenderingStopped();
nativeSurfaceDestroyed(nativePlatformViewId);
}
......@@ -431,7 +419,6 @@ public class FlutterJNI {
* See {@link AccessibilityBridge} for an example of an {@link AccessibilityDelegate} and the
* surrounding responsibilities.
*/
// TODO(mattcarroll): move AccessibilityDelegate definition into FlutterJNI. FlutterJNI should be the basis of dependencies, not the other way round.
@UiThread
public void setAccessibilityDelegate(@Nullable AccessibilityDelegate accessibilityDelegate) {
ensureRunningOnMainThread();
......@@ -772,7 +759,7 @@ public class FlutterJNI {
/**
* Removes the given {@code engineLifecycleListener}, which was previously added using
* {@link #addOnFirstFrameRenderedListener(OnFirstFrameRenderedListener)}.
* {@link #addIsDisplayingFlutterUiListener(FlutterUiDisplayListener)}.
*/
@UiThread
public void removeEngineLifecycleListener(@NonNull EngineLifecycleListener engineLifecycleListener) {
......
......@@ -17,95 +17,85 @@ import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicLong;
import io.flutter.Log;
import io.flutter.embedding.android.FlutterView;
import io.flutter.embedding.engine.FlutterJNI;
import io.flutter.view.TextureRegistry;
/**
* WARNING: THIS CLASS IS EXPERIMENTAL. DO NOT SHIP A DEPENDENCY ON THIS CODE.
* IF YOU USE IT, WE WILL BREAK YOU.
*
* {@code FlutterRenderer} works in tandem with a provided {@link RenderSurface} to create an
* interactive Flutter UI.
*
* {@code FlutterRenderer} manages textures for rendering, and forwards some Java calls to native Flutter
* code via JNI. The corresponding {@link RenderSurface} is used as a delegate to carry out
* certain actions on behalf of this {@code FlutterRenderer} within an Android view hierarchy.
*
* {@link FlutterView} is an implementation of a {@link RenderSurface}.
* Represents the rendering responsibilities of a {@code FlutterEngine}.
* <p>
* {@code FlutterRenderer} works in tandem with a provided {@link RenderSurface} to paint Flutter
* pixels to an Android {@code View} hierarchy.
* <p>
* {@code FlutterRenderer} manages textures for rendering, and forwards some Java calls to native
* Flutter code via JNI. The corresponding {@link RenderSurface} provides the Android
* {@link Surface} that this renderer paints.
* <p>
* {@link io.flutter.embedding.android.FlutterSurfaceView} and
* {@link io.flutter.embedding.android.FlutterTextureView} are implementations of
* {@link RenderSurface}.
*/
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public class FlutterRenderer implements TextureRegistry {
private static final String TAG = "FlutterRenderer";
@NonNull
private final FlutterJNI flutterJNI;
@NonNull
private final AtomicLong nextTextureId = new AtomicLong(0L);
private RenderSurface renderSurface;
private boolean hasRenderedFirstFrame = false;
@Nullable
private Surface surface;
private boolean isDisplayingFlutterUi = false;
private final OnFirstFrameRenderedListener onFirstFrameRenderedListener = new OnFirstFrameRenderedListener() {
@NonNull
private final FlutterUiDisplayListener flutterUiDisplayListener = new FlutterUiDisplayListener() {
@Override
public void onFirstFrameRendered() {
hasRenderedFirstFrame = true;
public void onFlutterUiDisplayed() {
isDisplayingFlutterUi = true;
}
@Override
public void onFlutterUiNoLongerDisplayed() {
isDisplayingFlutterUi = false;
}
};
public FlutterRenderer(@NonNull 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},
* false otherwise.
* Returns true if this {@code FlutterRenderer} is painting pixels to an Android {@code View}
* hierarchy, false otherwise.
*/
public boolean isAttachedTo(@NonNull RenderSurface renderSurface) {
return this.renderSurface == renderSurface;
}
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 isDisplayingFlutterUi() {
return isDisplayingFlutterUi;
}
public boolean hasRenderedFirstFrame() {
return hasRenderedFirstFrame;
}
public void addOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) {
flutterJNI.addOnFirstFrameRenderedListener(listener);
/**
* Adds a listener that is invoked whenever this {@code FlutterRenderer} starts and stops painting
* pixels to an Android {@code View} hierarchy.
*/
public void addIsDisplayingFlutterUiListener(@NonNull FlutterUiDisplayListener listener) {
flutterJNI.addIsDisplayingFlutterUiListener(listener);
if (hasRenderedFirstFrame) {
listener.onFirstFrameRendered();
if (isDisplayingFlutterUi) {
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 -----
// 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
public SurfaceTextureEntry createSurfaceTexture() {
Log.v(TAG, "Creating a SurfaceTexture.");
......@@ -180,19 +170,57 @@ public class FlutterRenderer implements TextureRegistry {
}
//------ 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);
}
// 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) {
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();
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
......@@ -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.
*
......
......@@ -5,16 +5,20 @@
package io.flutter.embedding.engine.renderer;
/**
* Listener invoked after Flutter paints its first frame since being initialized.
*
* WARNING: THIS CLASS IS EXPERIMENTAL. DO NOT SHIP A DEPENDENCY ON THIS CODE.
* IF YOU USE IT, WE WILL BREAK YOU.
* Listener invoked when Flutter starts and stops rendering pixels to an Android {@code View}
* hierarchy.
*/
public interface OnFirstFrameRenderedListener {
public interface FlutterUiDisplayListener {
/**
* A {@link FlutterRenderer} has painted its first frame since being initialized.
*
* This method will not be invoked if this listener is added after the first frame is rendered.
* Flutter started painting pixels to an Android {@code View} hierarchy.
* <p>
* 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;
import io.flutter.embedding.engine.FlutterJNI;
import io.flutter.embedding.engine.FlutterEngine.EngineLifecycleListener;
import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.embedding.engine.renderer.FlutterRenderer;
import io.flutter.embedding.engine.renderer.FlutterRenderer.RenderSurface;
import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener;
import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener;
import io.flutter.plugin.common.*;
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 {
private static final String TAG = "FlutterNativeView";
......@@ -34,6 +27,21 @@ public class FlutterNativeView implements BinaryMessenger {
private final Context mContext;
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) {
this(context, false);
}
......@@ -42,7 +50,7 @@ public class FlutterNativeView implements BinaryMessenger {
mContext = context;
mPluginRegistry = new FlutterPluginRegistry(this, context);
mFlutterJNI = new FlutterJNI();
mFlutterJNI.setRenderSurface(new RenderSurfaceImpl());
mFlutterJNI.addIsDisplayingFlutterUiListener(flutterUiDisplayListener);
this.dartExecutor = new DartExecutor(mFlutterJNI, context.getAssets());
mFlutterJNI.addEngineLifecycleListener(new EngineLifecycleListenerImpl());
attach(this, isBackgroundView);
......@@ -58,6 +66,7 @@ public class FlutterNativeView implements BinaryMessenger {
mPluginRegistry.destroy();
dartExecutor.onDetachedFromJNI();
mFlutterView = null;
mFlutterJNI.removeIsDisplayingFlutterUiListener(flutterUiDisplayListener);
mFlutterJNI.detachFromNativeAndReleaseResources();
applicationIsRunning = false;
}
......@@ -143,48 +152,6 @@ public class FlutterNativeView implements BinaryMessenger {
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 {
// Called by native to notify when the engine is restarted (cold reload).
@SuppressWarnings("unused")
......
......@@ -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.
public void onFirstFrame() {
didRenderFirstFrame = true;
......
......@@ -6,6 +6,7 @@ package io.flutter.view;
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.
* Entries may be embedded into the Flutter view using the
......
......@@ -486,7 +486,7 @@ static void InvokePlatformMessageEmptyResponseCallback(JNIEnv* env,
bool RegisterApi(JNIEnv* env) {
static const JNINativeMethod flutter_jni_methods[] = {
// Start of methods from FlutterNativeView
// Start of methods from FlutterJNI
{
.name = "nativeAttach",
.signature = "(Lio/flutter/embedding/engine/FlutterJNI;Z)J",
......
......@@ -13,7 +13,10 @@ import io.flutter.embedding.android.FlutterActivityTest;
import io.flutter.embedding.android.FlutterFragmentTest;
import io.flutter.embedding.engine.FlutterEngineCacheTest;
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.embedding.engine.FlutterJNITest;
@RunWith(Suite.class)
@SuiteClasses({
......@@ -23,6 +26,9 @@ import io.flutter.util.PreconditionsTest;
FlutterFragmentTest.class,
// FlutterActivityAndFragmentDelegateTest.class, TODO(mklim): Fix and re-enable this
FlutterEngineCacheTest.class,
FlutterJNITest.class,
RenderingComponentTest.class,
FlutterRendererTest.class,
PlatformChannelTest.class
})
/** 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.
先完成此消息的编辑!
想要评论请 注册