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

Android Embedding PR25: Prevent black rectangle when launching FlutterActivity (#8460)

上级 99da038d
......@@ -9,6 +9,8 @@ import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
......@@ -16,6 +18,7 @@ import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
......@@ -24,6 +27,7 @@ import android.widget.FrameLayout;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterShellArgs;
import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener;
import io.flutter.plugin.platform.PlatformPlugin;
import io.flutter.view.FlutterMain;
......@@ -61,7 +65,7 @@ import io.flutter.view.FlutterMain;
* {@code Fragment}.
*/
// TODO(mattcarroll): explain each call forwarded to Fragment (first requires resolution of PluginRegistry API).
public class FlutterActivity extends FragmentActivity {
public class FlutterActivity extends FragmentActivity implements OnFirstFrameRenderedListener {
private static final String TAG = "FlutterActivity";
// Meta-data arguments, processed from manifest XML.
......@@ -82,6 +86,10 @@ public class FlutterActivity extends FragmentActivity {
private static final int FRAGMENT_CONTAINER_ID = 609893468; // random number
private FlutterFragment flutterFragment;
// Used to cover the Activity until the 1st frame is rendered so as to
// avoid a brief black flicker from a SurfaceView version of FlutterView.
private View coverView;
/**
* Creates an {@link Intent} that launches a {@code FlutterActivity}, which executes
* a {@code main()} Dart entrypoint, and displays the "/" route as Flutter's initial route.
......@@ -147,10 +155,64 @@ public class FlutterActivity extends FragmentActivity {
Log.d(TAG, "onCreate()");
super.onCreate(savedInstanceState);
setContentView(createFragmentContainer());
showCoverView();
configureStatusBarForFullscreenFlutterExperience();
ensureFlutterFragmentCreated();
}
/**
* Cover all visible {@code Activity} area with a {@code View} that paints everything the same
* color as the {@code Window}.
* <p>
* This cover {@code View} should be displayed at the very beginning of the {@code Activity}'s
* lifespan and then hidden once Flutter renders its first frame. The purpose of this cover is to
* cover {@link FlutterSurfaceView}, which briefly displays a black rectangle before it can make
* itself transparent.
*/
private void showCoverView() {
// Create the coverView.
if (coverView == null) {
coverView = new View(this);
addContentView(coverView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
}
// Pain the coverView with the Window's background.
Drawable background = createCoverViewBackground();
if (background != null) {
coverView.setBackground(background);
} else {
// If we can't obtain a window background to replicate then we'd be guessing as to the least
// intrusive color. But there is no way to make an accurate guess. In this case we don't
// give the coverView any color, which means a brief black rectangle will be visible upon
// Activity launch.
}
}
@Nullable
private Drawable createCoverViewBackground() {
TypedValue typedValue = new TypedValue();
boolean hasBackgroundColor = getTheme().resolveAttribute(
android.R.attr.windowBackground,
typedValue,
true
);
if (hasBackgroundColor && typedValue.resourceId != 0) {
return getResources().getDrawable(typedValue.resourceId, getTheme());
} else {
return null;
}
}
/**
* Hides the cover {@code View}.
* <p>
* This method should be called when Flutter renders its first frame. See {@link #showCoverView()}
* for details.
*/
private void hideCoverView() {
coverView.setVisibility(View.GONE);
}
private void configureStatusBarForFullscreenFlutterExperience() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = getWindow();
......@@ -360,4 +422,9 @@ public class FlutterActivity extends FragmentActivity {
private boolean isDebuggable() {
return (getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
}
@Override
public void onFirstFrameRendered() {
hideCoverView();
}
}
......@@ -21,6 +21,7 @@ import android.view.ViewGroup;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterShellArgs;
import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener;
import io.flutter.plugin.platform.PlatformPlugin;
import io.flutter.view.FlutterMain;
......@@ -214,16 +215,27 @@ public class FlutterFragment extends Fragment {
@Nullable
private PlatformPlugin platformPlugin;
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 != null && 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.
setArguments(new Bundle());
}
public void prepareForNavigation() {
flutterView.setAlpha(0.0f);
}
/**
* The {@link FlutterEngine} that backs the Flutter content presented by this {@code Fragment}.
*
......@@ -304,6 +316,7 @@ public class FlutterFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
flutterView = new FlutterView(getContext(), getRenderMode());
flutterView.addOnFirstFrameRenderedListener(onFirstFrameRenderedListener);
// We post() the code that attaches the FlutterEngine to our FlutterView because there is
// some kind of blocking logic on the native side when the surface is connected. That lag
......@@ -433,6 +446,7 @@ public class FlutterFragment extends Fragment {
public void onDestroyView() {
super.onDestroyView();
Log.d(TAG, "onDestroyView()");
flutterView.removeOnFirstFrameRenderedListener(onFirstFrameRenderedListener);
flutterView.detachFromFlutterEngine();
}
......@@ -580,6 +594,16 @@ public class FlutterFragment extends Fragment {
: getActivity();
}
/**
* Invoked after the {@link FlutterView} within this {@code FlutterFragment} renders its first
* frame.
* <p>
* The owning {@code Activity} is also sent this message, if it implements
* {@link OnFirstFrameRenderedListener}. This method is invoked before the {@code Activity}'s
* version.
*/
protected void onFirstFrameRendered() {}
/**
* Provides a {@link FlutterEngine} instance to be used by a {@code FlutterFragment}.
* <p>
......
......@@ -12,7 +12,11 @@ import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import java.util.HashSet;
import java.util.Set;
import io.flutter.embedding.engine.renderer.FlutterRenderer;
import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener;
/**
* Paints a Flutter UI on a {@link android.view.Surface}.
......@@ -36,6 +40,8 @@ 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
......@@ -177,11 +183,33 @@ public class FlutterSurfaceView extends SurfaceView implements FlutterRenderer.R
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.d(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();
}
}
}
......@@ -13,7 +13,11 @@ import android.util.Log;
import android.view.Surface;
import android.view.TextureView;
import java.util.HashSet;
import java.util.Set;
import io.flutter.embedding.engine.renderer.FlutterRenderer;
import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener;
/**
* Paints a Flutter UI on a {@link SurfaceTexture}.
......@@ -37,6 +41,8 @@ public class FlutterTextureView extends TextureView implements FlutterRenderer.R
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
......@@ -181,9 +187,31 @@ public class FlutterTextureView extends TextureView implements FlutterRenderer.R
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.d(TAG, "onFirstFrameRendered()");
for (OnFirstFrameRenderedListener listener : onFirstFrameRenderedListeners) {
listener.onFirstFrameRendered();
}
}
}
......@@ -31,6 +31,7 @@ import java.util.Locale;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.renderer.FlutterRenderer;
import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener;
import io.flutter.plugin.editing.TextInputPlugin;
import io.flutter.view.AccessibilityBridge;
......@@ -147,6 +148,22 @@ public class FlutterView extends FrameLayout {
}
}
/**
* Adds the given {@code listener} to this {@code FlutterView}, to be notified upon Flutter's
* first rendered frame.
*/
public void addOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) {
renderSurface.addOnFirstFrameRenderedListener(listener);
}
/**
* Removes the given {@code listener}, which was previously added with
* {@link #addOnFirstFrameRenderedListener(OnFirstFrameRenderedListener)}.
*/
public void removeOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) {
renderSurface.removeOnFirstFrameRenderedListener(listener);
}
//------- Start: Process View configuration that Flutter cares about. ------
/**
* Sends relevant configuration data from Android to Flutter when the Android
......
......@@ -267,6 +267,18 @@ public class FlutterRenderer implements TextureRegistry {
* 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);
}
/**
......
......@@ -14,6 +14,7 @@ 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.plugin.common.*;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicBoolean;
......@@ -199,6 +200,12 @@ public class FlutterNativeView implements BinaryMessenger {
}
mFlutterView.onFirstFrame();
}
@Override
public void addOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) {}
@Override
public void removeOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) {}
}
private final class EngineLifecycleListenerImpl implements EngineLifecycleListener {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册