preface
FlutterApplicaiton and FlutterActivity are analyzed in the previous article Flutter Android startup source code analysis (A). In FlutterActivity source part FlutterActivityAndFragmentDelegate this class has been mentioned, this article we mainly analyze the are doing it
Flutter Android startup
FlutterActivityAndFragmentDelegate
- role
- Delegate of the same Flutter logic between FlutterActivity and FlutterFragment
- Why use this class
- Given that you can place a Fragment in an Activity, it makes more sense to use a FlutterFragment in a FlutterActivity. The Fragment support library adds 100K binary sizes to applications, while full Flutter applications do not require binary hits. Therefore, it is concluded that Flutter must be based on AOSP(Android Open Source Project: Android open source project Activity provides FlutterActivity and a separate FlutterFragment for developers adding applications
- FlutterActivity and FlutterFragment are independent of each other, so a proxy-delegate class is required to handle the same logic between them
Member variables
// Host implementation object FlutterActivity @nonnull private Host Host; // Flutter implementation environment @nullable private FlutterEngine FlutterEngine; @nullable private FlutterSplashView FlutterSplashView; // Flutter UI @Nullable private FlutterView flutterView; @nullable private PlatformPlugin plugin; FlutterEngine private Boolean isFlutterEngineFromHost;Copy the code
The constructor
FlutterActivityAndFragmentDelegate(@NonNull Host host) {
this.host = host;
}
Copy the code
In the onCreate method FlutterActivity instantiation FlutterActivityAndFragmentDelegate and pass the current FA context object,
Then call onAttach, onActivityCreated, and onCreateView. Let’s analyze these three methods one by one
@override protected void onCreate(@nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); delegate = new FlutterActivityAndFragmentDelegate(this); delegate.onAttach(this); delegate.onActivityCreated(savedInstanceState); setContentView(createFlutterView()); } @NonNull private View createFlutterView() { return delegate.onCreateView( null /* inflater */, null /* container */, null /* savedInstanceState */); }Copy the code
onAttach
void onAttach(@NonNull Context context) {
ensureAlive();
if (flutterEngine == null) {
// 1
setupFlutterEngine();
}
// 2
platformPlugin = host.providePlatformPlugin(host.getActivity(), flutterEngine);
// 3
if (host.shouldAttachEngineToActivity()) {
flutterEngine
.getActivityControlSurface()
.attachToActivity(host.getActivity(), host.getLifecycle());
}
// 4
host.configureFlutterEngine(flutterEngine);
}
Copy the code
-
Instantiation FlutterEngine
void setupFlutterEngine() { String cachedEngineId = host.getCachedEngineId(); if (cachedEngineId ! = null) { flutterEngine = FlutterEngineCache.getInstance().get(cachedEngineId); isFlutterEngineFromHost = true; if (flutterEngine == null) { throw new IllegalStateException( "The requested cached FlutterEngine did not exist in the FlutterEngineCache: '" + cachedEngineId + "'"); } return; } flutterEngine = host.provideFlutterEngine(host.getContext()); if (flutterEngine ! = null) { isFlutterEngineFromHost = true; return; } flutterEngine = new FlutterEngine( host.getContext(), host.getFlutterShellArgs().toArray(), /*automaticallyRegisterPlugins=*/ false, /*willProvideRestorationData=*/ host.shouldRestoreAndSaveState()); isFlutterEngineFromHost = false; }Copy the code
First, it checks whether there is a cached EngineId. If there is a cached EngineId, it gets it from the FlutterEngineCache cache and instantiates it.
If FlutterActivity does not provide an Engine, the Host implementation class will be checked.
// FlutterActivity @Nullable @Override public FlutterEngine provideFlutterEngine(@NonNull Context context) { // No-op. Hook for subclasses. return null; } Copy the code
A new Engine is instantiated directly and isFlutterEngineFromHost is set to false, indicating that the Engine is not provided by the Host implementation class
Let’s look briefly at the constructor of FlutterEngine
public FlutterEngine( @NonNull Context context, @NonNull FlutterLoader flutterLoader, @NonNull FlutterJNI flutterJNI, @NonNull PlatformViewsController platformViewsController, @Nullable String[] dartVmArgs, boolean automaticallyRegisterPlugins, boolean waitForRestorationData) { this.dartExecutor = new DartExecutor(flutterJNI, context.getAssets()); this.dartExecutor.onAttachedToJNI(); accessibilityChannel = new AccessibilityChannel(dartExecutor, flutterJNI); keyEventChannel = new KeyEventChannel(dartExecutor); lifecycleChannel = new LifecycleChannel(dartExecutor); localizationChannel = new LocalizationChannel(dartExecutor); mouseCursorChannel = new MouseCursorChannel(dartExecutor); navigationChannel = new NavigationChannel(dartExecutor); platformChannel = new PlatformChannel(dartExecutor); restorationChannel = new RestorationChannel(dartExecutor, waitForRestorationData); settingsChannel = new SettingsChannel(dartExecutor); systemChannel = new SystemChannel(dartExecutor); textInputChannel = new TextInputChannel(dartExecutor); this.localizationPlugin = new LocalizationPlugin(context, localizationChannel); this.flutterJNI = flutterJNI; flutterLoader.startInitialization(context.getApplicationContext()); flutterLoader.ensureInitializationComplete(context, dartVmArgs); flutterJNI.addEngineLifecycleListener(engineLifecycleListener); flutterJNI.setPlatformViewsController(platformViewsController); flutterJNI.setLocalizationPlugin(localizationPlugin); attachToJni(); // TODO(mattcarroll): FlutterRenderer is temporally coupled to attach(). Remove that coupling if // possible. this.renderer = new FlutterRenderer(flutterJNI); this.platformViewsController = platformViewsController; this.platformViewsController.onAttachedToJNI(); this.pluginRegistry = new FlutterEnginePluginRegistry(context.getApplicationContext(), this, flutterLoader); if (automaticallyRegisterPlugins) { registerPlugins(); }}Copy the code
- DartExecutor instantiation
- Some system channels are initialized
- FlutterJNI, PlatformViewsController, FlutterRender and FlutterLoader are initialized
- Instantiation FlutterEnginePluginRegistry plug-in registry
FlutterRender works in conjunction with the provided RenderSurface to draw Flutter pixels into the Android View hierarchy. FlutterRender manages render textures and forwards some Java calls to local Flutter code via JNI
The FlutterLoader is the local library of a Flutter that looks up Flutter resources in the application APK and loads Flutter
These two we can go to have a look, not to mention
-
Instantiation PlatformPlugin
// FlutterActivity @Nullable @Override public PlatformPlugin providePlatformPlugin( @Nullable Activity activity, @NonNull FlutterEngine flutterEngine) { if (activity != null) { return new PlatformPlugin(getActivity(), flutterEngine.getPlatformChannel()); } else { return null; } } Copy the code
You can see that FlutterActivity overrides this method and instantiates a new PlatformPlugin
One caveat: Whenever FlutterFragment connects to a new Activity, a PlatformPlugin needs to be created again
PlatformPlugin is a native Plugin provided by the platform, such as status bar operation and clipboard Plugin
-
attachToActivity
// FlutterActivity @Override public boolean shouldAttachEngineToActivity() { return true; } Copy the code
The default is to return true to establish an association between the Activity and the Engine
The attachToActivity method notifies all plug-ins currently connected to the FlutterEngine to attach them to the Activity
/ / FlutterEnginePluginRegistry plug-in registry @ Override public void attachToActivity (@ NonNull Activity to Activity, @NonNull Lifecycle lifecycle) { detachFromAndroidComponent(); this.activity = activity; this.activityPluginBinding = new FlutterEngineActivityPluginBinding(activity, lifecycle); flutterEngine .getPlatformViewsController() .attach(activity, flutterEngine.getRenderer(), flutterEngine.getDartExecutor()); for (ActivityAware activityAware : activityAwarePlugins.values()) { if (isWaitingForActivityReattachment) { activityAware.onReattachedToActivityForConfigChanges(activityPluginBinding); } else { activityAware.onAttachedToActivity(activityPluginBinding); } } isWaitingForActivityReattachment = false; }Copy the code
FlutterEnginePluginRegistry you are interested in this class may have a look, is actually a Engine plug-in registry, don’t do too much analysis here
It is instantiated in the constructor of FlutterEngine
-
configureFlutterEngine
// FlutterActivity @Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { registerPlugins(flutterEngine); } Copy the code
Register native plug-ins in Pubspec.yaml, so as mentioned in the previous article, the new version helps us with automatic registration.
onActivityCreated
void onActivityCreated(@Nullable Bundle bundle) {
Bundle pluginState = null;
byte[] frameworkState = null;
if (bundle != null) {
pluginState = bundle.getBundle(PLUGINS_RESTORATION_BUNDLE_KEY);
frameworkState = bundle.getByteArray(FRAMEWORK_RESTORATION_BUNDLE_KEY);
}
if (host.shouldRestoreAndSaveState()) {
flutterEngine.getRestorationChannel().setRestorationData(frameworkState);
}
if (host.shouldAttachEngineToActivity()) {
flutterEngine.getActivityControlSurface().onRestoreInstanceState(pluginState);
}
}
Copy the code
We won’t do too much analysis of this, but provide recovery data for plug-ins and activities
onCreateView
View onCreateView(
LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
ensureAlive();
if (host.getRenderMode() == RenderMode.surface) {
FlutterSurfaceView flutterSurfaceView =
new FlutterSurfaceView(
host.getActivity(), host.getTransparencyMode() == TransparencyMode.transparent);
host.onFlutterSurfaceViewCreated(flutterSurfaceView);
flutterView = new FlutterView(host.getActivity(), flutterSurfaceView);
} else {
FlutterTextureView flutterTextureView = new FlutterTextureView(host.getActivity());
host.onFlutterTextureViewCreated(flutterTextureView);
flutterView = new FlutterView(host.getActivity(), flutterTextureView);
}
flutterView.addOnFirstFrameRenderedListener(flutterUiDisplayListener);
flutterSplashView = new FlutterSplashView(host.getContext());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
flutterSplashView.setId(View.generateViewId());
} else {
flutterSplashView.setId(486947586);
}
flutterSplashView.displayFlutterViewWithSplash(flutterView, host.provideSplashScreen());
flutterView.attachToFlutterEngine(flutterEngine);
return flutterSplashView;
}
Copy the code
-
The render mode is judged first
// FlutterActivity @NonNull @Override public RenderMode getRenderMode() { return getBackgroundMode() == BackgroundMode.opaque ? RenderMode.surface : RenderMode.texture; } @NonNull protected BackgroundMode getBackgroundMode() { if (getIntent().hasExtra(EXTRA_BACKGROUND_MODE)) { return BackgroundMode.valueOf(getIntent().getStringExtra(EXTRA_BACKGROUND_MODE)); } else { return BackgroundMode.opaque; }}Copy the code
Rendermode. surface and rendermode. texture. The Flutter framework uses surface to render by default
-
Then instantiate an opaque FlutterSurfaceView, and set the onFlutterSurfaceViewCreated callback
public enum BackgroundMode { /** Indicates a FlutterActivity with an opaque background. This is the default. */ opaque, /** Indicates a FlutterActivity with a transparent background. */ transparent } Copy the code
-
Then create a FlutterView with the FlutterSurfaceView that is used to display the Flutter UI on Android devices
flutterView = new FlutterView(host.getActivity(), flutterSurfaceView); Copy the code
TextureView is the same, not analyzed
-
A listener is then added to draw the first frame
flutterView.addOnFirstFrameRenderedListener(flutterUiDisplayListener); Copy the code
-
Then will create a FlutterSplashView, then call displayFlutterViewWithSplash method displayed on the FlutterView splash screen interface, until made the first frame
flutterSplashView.displayFlutterViewWithSplash(flutterView, host.provideSplashScreen()); //FlutterActivity @Nullable @Override public SplashScreen provideSplashScreen() { Drawable manifestSplashDrawable = getSplashScreenFromManifest(); if (manifestSplashDrawable ! = null) { return new DrawableSplashScreen(manifestSplashDrawable); } else { return null; } } @Nullable @SuppressWarnings("deprecation") private Drawable getSplashScreenFromManifest() { try { ActivityInfo activityInfo = getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); Bundle metadata = activityInfo.metaData; int splashScreenId = metadata ! = null ? metadata.getInt(SPLASH_SCREEN_META_DATA_KEY) : 0; return splashScreenId ! = 0? Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP ? getResources().getDrawable(splashScreenId, getTheme()) : getResources().getDrawable(splashScreenId) : null; } catch (PackageManager.NameNotFoundException e) { // This is never expected to happen. return null; } } // FlutterActivityLaunchConfigs static final String SPLASH_SCREEN_META_DATA_KEY ="io.flutter.embedding.android.SplashScreenDrawable"; // AndroidManifest.xml <meta-data android:name="io.flutter.embedding.android.SplashScreenDrawable" android:resource="@drawable/launch_background" />Copy the code
You can see that FlutterActivity overwrites provideSplashScreen to return the splash screen drawable we set in Androidmanifest.xml
-
flutterView.attachToFlutterEngine(flutterEngine);
This FlutterView is then wired to the given FlutterEngine. This FlutterView will begin rendering the UI drawn by the given FlutterEngine. And the FlutterView will also start forwarding interaction events from the FlutterView to the given FlutterEngine e.g. user touch events, accessibility events, keyboard events, etc
-
Return flutterSplashView to FlutterActivity. Until now, FlutterActivityAndFragmentDelegate onAttach method has been analyzed completely
-
SplashScreenDrawable can also be added to solve android’s problem of black screen startup — the setContentView of MainActivity will not be closed until it receives a callback from the first frame of the Flutter drawing
Next, let’s examine the onStart method
onStart
// FlutterActivity
@Override
protected void onStart() {
super.onStart();
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_START);
delegate.onStart();
}
Copy the code
// FlutterActivityAndFragmentDelegate
void onStart() {
Log.v(TAG, "onStart()");
ensureAlive();
doInitialFlutterViewRun();
}
Copy the code
Can see FlutterActivity. OnStart calls the FlutterActivityAndFragmentDelegate. OnStart method, then the call doInitialFlutterViewRun method
This method is the first time Dart has been run in FlutterView
private void doInitialFlutterViewRun() { // 1 if (host.getCachedEngineId() ! = null) { return; } if (flutterEngine.getDartExecutor().isExecutingDart()) { return; } // 2 if (host.getInitialRoute() ! = null) { flutterEngine.getNavigationChannel().setInitialRoute(host.getInitialRoute()); } // 3 DartExecutor.DartEntrypoint entrypoint = new DartExecutor.DartEntrypoint( host.getAppBundlePath(), host.getDartEntrypointFunctionName()); flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint); }Copy the code
-
First check whether the engine cache ID is empty, which is implemented in FlutterActivity
// FlutterActivity @Override @Nullable public String getCachedEngineId() { return getIntent().getStringExtra(EXTRA_CACHED_ENGINE_ID); } Copy the code
To clarify, the Flutter framework instantiates a new FlutterEngine each time, as mentioned above, so it is always null. Specific usage scenarios will be discussed later
The Dart is then determined to be executed
-
The initial path to receive the Flutter application before executing the Dart code is set to ensure that the initial path is applied in a timely manner.
//FlutterActivity @NonNull public String getInitialRoute() { if (getIntent().hasExtra(EXTRA_INITIAL_ROUTE)) { return getIntent().getStringExtra(EXTRA_INITIAL_ROUTE); } try { ActivityInfo activityInfo = getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); Bundle metadata = activityInfo.metaData; String desiredInitialRoute = metadata ! = null ? metadata.getString(INITIAL_ROUTE_META_DATA_KEY) : null; return desiredInitialRoute ! = null ? desiredInitialRoute : DEFAULT_INITIAL_ROUTE; } catch (PackageManager.NameNotFoundException e) { return DEFAULT_INITIAL_ROUTE; }}Copy the code
The initialization path can be set in the Intent or in the Android Manifest. Definition specifies INITIAL_ROUTE_META_DATA_KEY
We won’t talk too much about it here, but this finally returns DEFAULT_INITIAL_ROUTE, the default path
//FlutterActivityLaunchConfigs static final String DEFAULT_INITIAL_ROUTE = "/"; Copy the code
-
Then configure the Dart code to execute the entry point and execute it
Host. getAppBundlePath returns the default path
private static final String DEFAULT_FLUTTER_ASSETS_DIR = "flutter_assets"; Copy the code
Host. GetDartEntrypointFunctionName () returns is also the default startup method name,
static final String DEFAULT_DART_ENTRYPOINT = "main"; Copy the code
Dart is our main method
import 'package:flutter/material.dart'; void main() { runApp(MyApp()); } Copy the code
//DartExecutor configuration, boot and start executing Dart code. public void executeDartEntrypoint(@NonNull DartEntrypoint dartEntrypoint) { if (isApplicationRunning) { Log.w(TAG, "Attempted to run a DartExecutor that is already running."); return; } Log.v(TAG, "Executing Dart entrypoint: " + dartEntrypoint); flutterJNI.runBundleAndSnapshotFromLibrary( dartEntrypoint.pathToBundle, dartEntrypoint.dartEntrypointFunctionName, null, assetManager); isApplicationRunning = true; }Copy the code
You can see that isApplicationRunning = true is set to indicate that the program is running,
//DartExecutor public boolean isExecutingDart() { return isApplicationRunning; } Copy the code
This variable is also used to determine whether the DART code is executing
-
The FlutterJNI class is the interface between the Java code embedded with Flutter and the C/C ++ code of the Flutter engine. Go and see for yourself
-
At this point, a Flutter program is up and running, followed by executing the Dart code and our own business logic
Next, let’s look at the LifecycleChannel LifecycleChannel
void onResume() {
Log.v(TAG, "onResume()");
ensureAlive();
flutterEngine.getLifecycleChannel().appIsResumed();
}
Copy the code
It mainly sends Android life cycle events to Flutter, as do onPause, onStop, and onDetach
FlutterActivity onDestroy
@Override
protected void onDestroy() {
super.onDestroy();
delegate.onDestroyView();
delegate.onDetach();
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
}
Copy the code
Here is mainly to do some clear reference, destroy some operations of resources. You can read the source code
conclusion
Through this article we can see roughly
-
FlutterActivityAndFragmentDelegate FlutterActivity work is how to the agent
-
How FlutterActivityAndFragmentDelegate FlutterEngine and FlutterActivity together, and implement the dart code, start the application
-
FlutterRender, FlutterJNI, FlutterLoader you can look at the source code, the basic startup process is like this
-
On Android, Flutter is loaded into an embed by default as an Activity. The view is controlled by FlutterView, which renders the Flutter contents as views or textures according to their composition and Z-ordering requirements.
-
Review of Flutter architecture
In the next article, I will use an Android project to embed the Flutter Module hybrid project to give you a more intuitive view of how Flutter is represented on Android
My technical level is limited, too in-depth analysis will not involve. If there are any mistakes, please correct them promptly. Of course, you are welcome to give me some guidance, we progress together