Skip to content

Commit

Permalink
Android Embedding PR24: Allow FlutterActivity to provide an engine, a…
Browse files Browse the repository at this point in the history
…lso adjust FlutterFragment timing to avoid Activity launch lag. (flutter#8448)
  • Loading branch information
matthew-carroll committed Apr 5, 2019
1 parent 08a1335 commit 6bc33b5
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
Expand Down Expand Up @@ -81,14 +82,35 @@ public class FlutterActivity extends FragmentActivity {
private static final int FRAGMENT_CONTAINER_ID = 609893468; // random number
private FlutterFragment flutterFragment;

/**
* 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.
*/
public static Intent createDefaultIntent(@NonNull Context launchContext) {
return createBuilder().build(launchContext);
}

/**
* Creates an {@link IntentBuilder}, which can be used to configure an {@link Intent} to
* launch a {@code FlutterActivity}.
*/
public static IntentBuilder createBuilder() {
return new IntentBuilder(FlutterActivity.class);
}

/**
* Builder to create an {@code Intent} that launches a {@code FlutterActivity} with the
* desired configuration.
*/
public static class IntentBuilder {
private final Class<? extends FlutterActivity> activityClass;
private String dartEntrypoint = DEFAULT_DART_ENTRYPOINT;
private String initialRoute = DEFAULT_INITIAL_ROUTE;

protected IntentBuilder(@NonNull Class<? extends FlutterActivity> activityClass) {
this.activityClass = activityClass;
}

/**
* The name of the initial Dart method to invoke, defaults to "main".
*/
Expand All @@ -108,16 +130,21 @@ public IntentBuilder initialRoute(@NonNull String initialRoute) {
return this;
}

/**
* Creates and returns an {@link Intent} that will launch a {@code FlutterActivity} with
* the desired configuration.
*/
@NonNull
public Intent build(@NonNull Context context) {
return new Intent(context, FlutterActivity.class)
return new Intent(context, activityClass)
.putExtra(EXTRA_DART_ENTRYPOINT, dartEntrypoint)
.putExtra(EXTRA_INITIAL_ROUTE, initialRoute);
}
}

@Override
public void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "onCreate()");
super.onCreate(savedInstanceState);
setContentView(createFragmentContainer());
configureStatusBarForFullscreenFlutterExperience();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
Expand Down Expand Up @@ -206,6 +208,7 @@ protected static Bundle createArgsBundle(@Nullable String dartEntrypoint,

@Nullable
private FlutterEngine flutterEngine;
private boolean isFlutterEngineFromActivity;
@Nullable
private FlutterView flutterView;
@Nullable
Expand All @@ -217,6 +220,10 @@ public FlutterFragment() {
setArguments(new Bundle());
}

public void prepareForNavigation() {
flutterView.setAlpha(0.0f);
}

/**
* The {@link FlutterEngine} that backs the Flutter content presented by this {@code Fragment}.
*
Expand All @@ -236,7 +243,7 @@ public void onAttach(Context context) {
// When "retain instance" is true, the FlutterEngine will survive configuration
// changes. Therefore, we create a new one only if one does not already exist.
if (flutterEngine == null) {
createFlutterEngine();
setupFlutterEngine();
}

// Regardless of whether or not a FlutterEngine already existed, the PlatformPlugin
Expand All @@ -258,55 +265,63 @@ private void initializeFlutter(@NonNull Context context) {
}

/**
* Creates a new FlutterEngine instance.
*
* Subclasses can instantiate their own {@link FlutterEngine} by overriding
* {@link #onCreateFlutterEngine(Context)}.
*
* Subclasses can alter the {@link FlutterEngine} after creation by overriding
* {@link #onFlutterEngineCreated(FlutterEngine)}.
*/
private void createFlutterEngine() {
// Create a FlutterEngine to back our FlutterView.
flutterEngine = onCreateFlutterEngine(getActivity());

// Allow subclasses to customize FlutterEngine as desired.
onFlutterEngineCreated(flutterEngine);
}

/**
* Hook for subclasses to customize the creation of the {@code FlutterEngine}.
*
* By default, this method returns a standard {@link FlutterEngine} without any modification.
* Obtains a reference to a FlutterEngine to back this {@code FlutterFragment}.
* <p>
* First, the {@link FragmentActivity} that owns this {@code FlutterFragment} is
* given the opportunity to provide a {@link FlutterEngine} as a {@link FlutterEngineProvider}.
* <p>
* If the owning {@link FragmentActivity} does not implement {@link FlutterEngineProvider}, or
* chooses to return {@code null}, then a new {@link FlutterEngine} is instantiated. Subclasses
* may override this method to provide a {@link FlutterEngine} of choice.
*/
@NonNull
protected FlutterEngine onCreateFlutterEngine(@NonNull Context context) {
Log.d(TAG, "onCreateFlutterEngine()");
return new FlutterEngine(context);
}
protected void setupFlutterEngine() {
// First, defer to the FragmentActivity that owns us to see if it wants to provide a
// FlutterEngine.
FragmentActivity attachedActivity = getActivity();
if (attachedActivity instanceof FlutterEngineProvider) {
// Defer to the Activity that owns us to provide a FlutterEngine.
Log.d(TAG, "Deferring to attached Activity to provide a FlutterEngine.");
FlutterEngineProvider flutterEngineProvider = (FlutterEngineProvider) attachedActivity;
flutterEngine = flutterEngineProvider.getFlutterEngine(getContext());
if (flutterEngine != null) {
isFlutterEngineFromActivity = true;
}
}

/**
* Hook for subclasses to customize the {@link FlutterEngine} owned by this {@link FlutterFragment}
* after the {@link FlutterEngine} has been instantiated.
*
* Consider using this method to connect desired Flutter plugins to this {@code Fragment}'s
* {@link FlutterEngine}.
*/
protected void onFlutterEngineCreated(@NonNull FlutterEngine flutterEngine) {
// no-op
// If flutterEngine is null then either our Activity is not a FlutterEngineProvider,
// or our Activity decided that it didn't want to provide a FlutterEngine. Either way,
// we will now create a FlutterEngine for this FlutterFragment.
if (flutterEngine == null) {
// Create a FlutterEngine to back our FlutterView.
Log.d(TAG, "Our attached Activity did not want to provide a FlutterEngine. Creating a "
+ "new FlutterEngine for this FlutterFragment.");
flutterEngine = new FlutterEngine(getContext());
isFlutterEngineFromActivity = false;
}
}

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
flutterView = new FlutterView(getContext(), getRenderMode());
flutterView.attachToFlutterEngine(flutterEngine);

// TODO(mattcarroll): the following call should exist here, but the plugin system needs to be revamped.
// The existing attach() method does not know how to handle this kind of FlutterView.
//flutterEngine.getPluginRegistry().attach(this, getActivity());

doInitialFlutterViewRun();
// 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
// causes launching Activitys to wait a second or two before launching. By post()'ing this
// behavior we are able to move this blocking logic to after the Activity's launch.
// TODO(mattcarroll): figure out how to avoid blocking the MAIN thread when connecting a surface
new Handler().post(new Runnable() {
@Override
public void run() {
flutterView.attachToFlutterEngine(flutterEngine);

// TODO(mattcarroll): the following call should exist here, but the plugin system needs to be revamped.
// The existing attach() method does not know how to handle this kind of FlutterView.
//flutterEngine.getPluginRegistry().attach(this, getActivity());

doInitialFlutterViewRun();
}
});

return flutterView;
}
Expand Down Expand Up @@ -431,7 +446,7 @@ public void onDetach() {
platformPlugin = null;

// Destroy our FlutterEngine if we're not set to retain it.
if (!retainFlutterIsolateAfterFragmentDestruction()) {
if (!retainFlutterEngineAfterFragmentDestruction() && !isFlutterEngineFromActivity) {
flutterEngine.destroy();
flutterEngine = null;
}
Expand All @@ -447,7 +462,7 @@ public void onDetach() {
// TODO(mattcarroll): consider a dynamic determination of this preference based on whether the
// engine was created automatically, or if the engine was provided manually.
// Manually provided engines should probably not be destroyed.
protected boolean retainFlutterIsolateAfterFragmentDestruction() {
protected boolean retainFlutterEngineAfterFragmentDestruction() {
return false;
}

Expand Down Expand Up @@ -564,4 +579,31 @@ private Context getContextCompat() {
? getContext()
: getActivity();
}

/**
* Provides a {@link FlutterEngine} instance to be used by a {@code FlutterFragment}.
* <p>
* {@link FlutterEngine} instances require significant time to warm up. Therefore, a developer
* might choose to hold onto an existing {@link FlutterEngine} and connect it to various
* {@link FlutterActivity}s and/or {@code FlutterFragments}.
* <p>
* If the {@link FragmentActivity} that owns this {@code FlutterFragment} implements
* {@code FlutterEngineProvider}, that {@link FlutterActivity} will be given an opportunity
* to provide a {@link FlutterEngine} instead of the {@code FlutterFragment} creating a
* new one. The {@link FragmentActivity} can provide an existing, pre-warmed {@link FlutterEngine},
* if desired.
* <p>
* See {@link #setupFlutterEngine()} for more information.
*/
public interface FlutterEngineProvider {
/**
* Returns the {@link FlutterEngine} that should be used by a child {@code FlutterFragment}.
* <p>
* This method may return a new {@link FlutterEngine}, an existing, cached {@link FlutterEngine},
* or null to express that the {@code FlutterEngineProvider} would like the {@code FlutterFragment}
* to provide its own {@code FlutterEngine} instance.
*/
@Nullable
FlutterEngine getFlutterEngine(@NonNull Context context);
}
}

0 comments on commit 6bc33b5

Please sign in to comment.