forked from firka/flutter
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:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user