Android Embedding PR22: Polish - FlutterActivity Intent factories, FlutterFragment control of render modes, FlutterSurfaceView transparent until rendering is ready. (flutter/engine#8317)

This commit is contained in:
Matt Carroll
2019-03-27 14:57:23 -07:00
committed by GitHub
parent c7035c4121
commit 87ff155831
3 changed files with 186 additions and 62 deletions

View File

@@ -9,6 +9,7 @@ import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@@ -16,10 +17,13 @@ import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.FrameLayout;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterShellArgs;
import io.flutter.plugin.platform.PlatformPlugin;
import io.flutter.view.FlutterMain;
/**
@@ -60,12 +64,16 @@ public class FlutterActivity extends FragmentActivity {
private static final String TAG = "FlutterActivity";
// Meta-data arguments, processed from manifest XML.
private static final String DART_ENTRYPOINT_META_DATA_KEY = "io.flutter.Entrypoint";
private static final String INITIAL_ROUTE_META_DATA_KEY = "io.flutter.InitialRoute";
protected static final String DART_ENTRYPOINT_META_DATA_KEY = "io.flutter.Entrypoint";
protected static final String INITIAL_ROUTE_META_DATA_KEY = "io.flutter.InitialRoute";
// Intent extra arguments.
public static final String EXTRA_DART_ENTRYPOINT = "dart_entrypoint";
public static final String EXTRA_INITIAL_ROUTE = "initial_route";
protected static final String EXTRA_DART_ENTRYPOINT = "dart_entrypoint";
protected static final String EXTRA_INITIAL_ROUTE = "initial_route";
// Default configuration.
protected static final String DEFAULT_DART_ENTRYPOINT = "main";
protected static final String DEFAULT_INITIAL_ROUTE = "/";
// FlutterFragment management.
private static final String TAG_FLUTTER_FRAGMENT = "flutter_fragment";
@@ -73,13 +81,58 @@ public class FlutterActivity extends FragmentActivity {
private static final int FRAGMENT_CONTAINER_ID = 609893468; // random number
private FlutterFragment flutterFragment;
/**
* Builder to create an {@code Intent} that launches a {@code FlutterActivity} with the
* desired configuration.
*/
public static class IntentBuilder {
private String dartEntrypoint = DEFAULT_DART_ENTRYPOINT;
private String initialRoute = DEFAULT_INITIAL_ROUTE;
/**
* The name of the initial Dart method to invoke, defaults to "main".
*/
@NonNull
public IntentBuilder dartEntrypoint(@NonNull String dartEntrypoint) {
this.dartEntrypoint = dartEntrypoint;
return this;
}
/**
* The initial route that a Flutter app will render in this {@link FlutterFragment},
* defaults to "/".
*/
@NonNull
public IntentBuilder initialRoute(@NonNull String initialRoute) {
this.initialRoute = initialRoute;
return this;
}
@NonNull
public Intent build(@NonNull Context context) {
return new Intent(context, FlutterActivity.class)
.putExtra(EXTRA_DART_ENTRYPOINT, dartEntrypoint)
.putExtra(EXTRA_INITIAL_ROUTE, initialRoute);
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(createFragmentContainer());
configureStatusBarForFullscreenFlutterExperience();
ensureFlutterFragmentCreated();
}
private void configureStatusBarForFullscreenFlutterExperience() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(0x40000000);
window.getDecorView().setSystemUiVisibility(PlatformPlugin.DEFAULT_SYSTEM_UI);
}
}
/**
* Creates a {@link FrameLayout} with an ID of {@code #FRAGMENT_CONTAINER_ID} that will contain
* the {@link FlutterFragment} displayed by this {@code FlutterActivity}.
@@ -125,12 +178,13 @@ public class FlutterActivity extends FragmentActivity {
*/
@NonNull
protected FlutterFragment createFlutterFragment() {
return FlutterFragment.newInstance(
getDartEntrypoint(),
getInitialRoute(),
getAppBundlePath(),
FlutterShellArgs.fromIntent(getIntent())
);
return new FlutterFragment.Builder()
.dartEntrypoint(getDartEntrypoint())
.initialRoute(getInitialRoute())
.appBundlePath(getAppBundlePath())
.flutterShellArgs(FlutterShellArgs.fromIntent(getIntent()))
.renderMode(FlutterView.RenderMode.surface)
.build();
}
@Override
@@ -216,7 +270,7 @@ public class FlutterActivity extends FragmentActivity {
* <p>
* Subclasses may override this method to directly control the Dart entrypoint.
*/
@Nullable
@NonNull
protected String getDartEntrypoint() {
if (getIntent().hasExtra(EXTRA_DART_ENTRYPOINT)) {
return getIntent().getStringExtra(EXTRA_DART_ENTRYPOINT);
@@ -228,9 +282,10 @@ public class FlutterActivity extends FragmentActivity {
PackageManager.GET_META_DATA|PackageManager.GET_ACTIVITIES
);
Bundle metadata = activityInfo.metaData;
return metadata != null ? metadata.getString(DART_ENTRYPOINT_META_DATA_KEY) : null;
String desiredDartEntrypoint = metadata != null ? metadata.getString(DART_ENTRYPOINT_META_DATA_KEY) : null;
return desiredDartEntrypoint != null ? desiredDartEntrypoint : DEFAULT_DART_ENTRYPOINT;
} catch (PackageManager.NameNotFoundException e) {
return null;
return DEFAULT_DART_ENTRYPOINT;
}
}
@@ -251,7 +306,7 @@ public class FlutterActivity extends FragmentActivity {
* <p>
* Subclasses may override this method to directly control the initial route.
*/
@Nullable
@NonNull
protected String getInitialRoute() {
if (getIntent().hasExtra(EXTRA_INITIAL_ROUTE)) {
return getIntent().getStringExtra(EXTRA_INITIAL_ROUTE);
@@ -263,9 +318,10 @@ public class FlutterActivity extends FragmentActivity {
PackageManager.GET_META_DATA|PackageManager.GET_ACTIVITIES
);
Bundle metadata = activityInfo.metaData;
return metadata != null ? metadata.getString(INITIAL_ROUTE_META_DATA_KEY) : null;
String desiredInitialRoute = metadata != null ? metadata.getString(INITIAL_ROUTE_META_DATA_KEY) : null;
return desiredInitialRoute != null ? desiredInitialRoute : DEFAULT_INITIAL_ROUTE;
} catch (PackageManager.NameNotFoundException e) {
return null;
return DEFAULT_INITIAL_ROUTE;
}
}

View File

@@ -61,53 +61,92 @@ public class FlutterFragment extends Fragment {
private static final String ARG_INITIAL_ROUTE = "initial_route";
private static final String ARG_APP_BUNDLE_PATH = "app_bundle_path";
private static final String ARG_FLUTTER_INITIALIZATION_ARGS = "initialization_args";
private static final String ARG_FLUTTERVIEW_RENDER_MODE = "flutterview_render_mode";
/**
* Factory method that creates a new {@link FlutterFragment} with a default configuration.
* <ul>
* <li>default Dart entrypoint of "main"</li>
* <li>initial route of "/"</li>
* <li>default app bundle location</li>
* <li>no special engine arguments</li>
* </ul>
* @return new {@link FlutterFragment}
*/
public static FlutterFragment newInstance() {
return newInstance(
null,
null,
null,
null
);
}
/**
* Factory method that creates a new {@link FlutterFragment} with the given configuration.
* Builder that creates a new {@code FlutterFragment} with {@code arguments} that correspond
* to the values set on this {@code Builder}.
* <p>
* @param dartEntrypoint the name of the initial Dart method to invoke, defaults to "main"
* @param initialRoute the first route that a Flutter app will render in this {@link FlutterFragment},
* defaults to "/"
* @param appBundlePath the path to the app bundle which contains the Dart app to execute, defaults
* to {@link FlutterMain#findAppBundlePath(Context)}
* @param flutterShellArgs any special configuration arguments for the Flutter engine
*
* @return a new {@link FlutterFragment}
* To create a {@code FlutterFragment} with default {@code arguments}, invoke {@code build()}
* immeidately:
* {@code
* FlutterFragment fragment = new FlutterFragment.Builder().build();
* }
*/
public static FlutterFragment newInstance(@Nullable String dartEntrypoint,
@Nullable String initialRoute,
@Nullable String appBundlePath,
@Nullable FlutterShellArgs flutterShellArgs) {
FlutterFragment frag = new FlutterFragment();
public static class Builder {
private String dartEntrypoint = "main";
private String initialRoute = "/";
private String appBundlePath = null;
private FlutterShellArgs shellArgs = null;
private FlutterView.RenderMode renderMode = FlutterView.RenderMode.surface;
Bundle args = createArgsBundle(
dartEntrypoint,
initialRoute,
appBundlePath,
flutterShellArgs
);
frag.setArguments(args);
/**
* The name of the initial Dart method to invoke, defaults to "main".
*/
@NonNull
public Builder dartEntrypoint(@NonNull String dartEntrypoint) {
this.dartEntrypoint = dartEntrypoint;
return this;
}
return frag;
/**
* The initial route that a Flutter app will render in this {@link FlutterFragment},
* defaults to "/".
*/
@NonNull
public Builder initialRoute(@NonNull String initialRoute) {
this.initialRoute = initialRoute;
return this;
}
/**
* The path to the app bundle which contains the Dart app to execute, defaults
* to {@link FlutterMain#findAppBundlePath(Context)}
*/
@NonNull
public Builder appBundlePath(@NonNull String appBundlePath) {
this.appBundlePath = appBundlePath;
return this;
}
/**
* Any special configuration arguments for the Flutter engine
*/
@NonNull
public Builder flutterShellArgs(@NonNull FlutterShellArgs shellArgs) {
this.shellArgs = shellArgs;
return this;
}
/**
* Render Flutter either as a {@link FlutterView.RenderMode#surface} or a
* {@link FlutterView.RenderMode#texture}. You should use {@code surface} unless
* you have a specific reason to use {@code texture}. {@code texture} comes with
* a significant performance impact, but {@code texture} can be displayed
* beneath other Android {@code View}s and animated, whereas {@code surface}
* cannot.
*/
@NonNull
public Builder renderMode(@NonNull FlutterView.RenderMode renderMode) {
this.renderMode = renderMode;
return this;
}
@NonNull
public FlutterFragment build() {
FlutterFragment frag = new FlutterFragment();
Bundle args = createArgsBundle(
dartEntrypoint,
initialRoute,
appBundlePath,
shellArgs,
renderMode
);
frag.setArguments(args);
return frag;
}
}
/**
@@ -118,16 +157,16 @@ public class FlutterFragment extends Fragment {
* wants to this {@link Bundle}. Example:
* <pre>{@code
* public static MyFlutterFragment newInstance(String myNewArg) {
* // Create an instance of our subclass Fragment.
* // Create an instance of your subclass Fragment.
* MyFlutterFragment myFrag = new MyFlutterFragment();
*
* // Create the Bundle or args that FlutterFragment understands.
* Bundle args = FlutterFragment.createArgsBundle(...);
*
* // Add our new args to the bundle.
* // Add your new args to the bundle.
* args.putString(ARG_MY_NEW_ARG, myNewArg);
*
* // Give the args to our subclass Fragment.
* // Give the args to your subclass Fragment.
* myFrag.setArguments(args);
*
* // Return the newly created subclass Fragment.
@@ -139,13 +178,20 @@ public class FlutterFragment extends Fragment {
* @param initialRoute the first route that a Flutter app will render in this {@link FlutterFragment}, defaults to "/"
* @param appBundlePath the path to the app bundle which contains the Dart app to execute
* @param flutterShellArgs any special configuration arguments for the Flutter engine
* @param renderMode render Flutter either as a {@link FlutterView.RenderMode#surface} or a
* {@link FlutterView.RenderMode#texture}. You should use {@code surface} unless
* you have a specific reason to use {@code texture}. {@code texture} comes with
* a significant performance impact, but {@code texture} can be displayed
* beneath other Android {@code View}s and animated, whereas {@code surface}
* cannot.
*
* @return Bundle of arguments that configure a {@link FlutterFragment}
*/
protected static Bundle createArgsBundle(@Nullable String dartEntrypoint,
@Nullable String initialRoute,
@Nullable String appBundlePath,
@Nullable FlutterShellArgs flutterShellArgs) {
@Nullable FlutterShellArgs flutterShellArgs,
@Nullable FlutterView.RenderMode renderMode) {
Bundle args = new Bundle();
args.putString(ARG_INITIAL_ROUTE, initialRoute);
args.putString(ARG_APP_BUNDLE_PATH, appBundlePath);
@@ -154,6 +200,7 @@ public class FlutterFragment extends Fragment {
if (null != flutterShellArgs) {
args.putStringArray(ARG_FLUTTER_INITIALIZATION_ARGS, flutterShellArgs.toArray());
}
args.putString(ARG_FLUTTERVIEW_RENDER_MODE, renderMode != null ? renderMode.name() : FlutterView.RenderMode.surface.name());
return args;
}
@@ -252,7 +299,7 @@ public class FlutterFragment extends Fragment {
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
flutterView = new FlutterView(getContext());
flutterView = new FlutterView(getContext(), getRenderMode());
flutterView.attachToFlutterEngine(flutterEngine);
// TODO(mattcarroll): the following call should exist here, but the plugin system needs to be revamped.
@@ -326,6 +373,18 @@ public class FlutterFragment extends Fragment {
return getArguments().getString(ARG_DART_ENTRYPOINT, "main");
}
/**
* Returns the desired {@link FlutterView.RenderMode} for the {@link FlutterView} displayed in
* this {@code FlutterFragment}.
*
* Defaults to {@link FlutterView.RenderMode#surface}.
*/
@NonNull
protected FlutterView.RenderMode getRenderMode() {
String renderModeName = getArguments().getString(ARG_FLUTTERVIEW_RENDER_MODE, FlutterView.RenderMode.surface.name());
return FlutterView.RenderMode.valueOf(renderModeName);
}
// TODO(mattcarroll): determine why this can't be in onResume(). Comment reason, or move if possible.
public void onPostResume() {
Log.d(TAG, "onPostResume()");

View File

@@ -90,6 +90,10 @@ public class FlutterSurfaceView extends SurfaceView implements FlutterRenderer.R
// Grab a reference to our underlying Surface and register callbacks with that Surface so we
// can monitor changes and forward those changes on to native Flutter code.
getHolder().addCallback(surfaceCallback);
// Keep this SurfaceView transparent until Flutter has a frame ready to render. This avoids
// displaying a black rectangle in our place.
setAlpha(0.0f);
}
/**
@@ -136,6 +140,9 @@ public class FlutterSurfaceView extends SurfaceView implements FlutterRenderer.R
disconnectSurfaceFromRenderer();
}
// Make the SurfaceView invisible to avoid showing a black rectangle.
setAlpha(0.0f);
flutterRenderer = null;
isAttachedToFlutterRenderer = false;
} else {
@@ -174,5 +181,7 @@ public class FlutterSurfaceView extends SurfaceView implements FlutterRenderer.R
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);
}
}