Because the startup process of Flutter is different from that of the original Android, there will be some differences if you use an Android hotfix framework such as Tinker. Therefore, we need to understand the startup process of Flutter on Android first, and then we can conduct targeted treatment and hotfix.
Flutter startup process on android
The entry of flutter application is the same as android application class. This can be found in android’s manifast file. The default entry of Flutter application is Flutterappllication. Because Flutter runs on Android, it uses a different engine than the native one. The general architecture of Flutter is the same, and the default flutterappllication is relatively simple. Flutterinjector.instance ().flutterLoader().startInitialization(this); This line of code calls startInitialization(this) of flutterLoader to initialize flutter. FlutterLoader Finds Flutter resources in an application APK and also loads Flutter’s native library. The purpose of this class is to find resource files in APK and load the local libraries of Flutter, which is equivalent to configuring flutter’s environment and resources locally on Android. Next we look at the startInitialization(this) method, which takes some important code and explains it
/* This method mainly loads the Flutter engine to support the local JNI response, finds and interprets the DART code we wrote in the APP APK, and this method has no effect multiple times because the flutterLoader itself is designed as a singleton. There's a Settings variable inside that's going to be assigned in this method, */ * Starts initialization of the native system. * * <p>This loads the Flutter engine's native library to enable subsequent JNI calls. This also * starts locating and unpacking Dart resources packaged in the app's * * <p>Calling this method multiple times has no effect. APK. * * <p>Calling this method multiple times has no effect. if (Looper.myLooper() ! = Looper.getMainLooper()) { throw new IllegalStateException("startInitialization must be called on the main thread"); } // The rest of the main logic executes a callable callback, Perform some initialization logic in the background thread Callable<InitResult> initTask =new Callable<InitResult>() // Some main logic if is selected here (flutterinjector.instance ().shouldloadNative ()) {// Load the flutter core library system.loadLibrary ("flutter"); } // Prefetch the default font manager as soon as possible on a background thread. // It helps to reduce time cost of Engine setup that blocks the platform thread. To prevent engine startup too time-consuming Executors. NewSingleThreadExecutor (). The execute (new Runnable () {@ Override public void the run () { FlutterJNI.nativePrefetchDefaultFontManager(); }});Copy the code
So that’s what our startInitialization(this) does, after the application initialization is complete, we’ll execute the mainActivity that we’ve specified, The default mainActivity of flutter is inherited from FlutterActivity. When you open FlutterActivity, you will find that it doesn’t have much logical code. Main is mainly performed FlutterActivityAndFragmentDelegate the proxy class method, in each of the activity lifecycle is call the agent method, such as his oncreate as follows.
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { switchLaunchThemeForNormalTheme(); super.onCreate(savedInstanceState); lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE); / / get the proxy class delegate = new FlutterActivityAndFragmentDelegate (this); delegate.onAttach(this); / / call the method that the proxy class perform oncreate logic delegate. OnActivityCreated (savedInstanceState); / / the activity based operation configureWindowForTransparency (); setContentView(createFlutterView()); configureStatusBarForFullscreenFlutterExperience(); }Copy the code
So we have to follow up the proxy class FlutterActivityAndFragmentDelegate
If flutterEngine is not initialized, setupFlutterengine is called. The main initialization logic is flutterEngine = new flutterEngine (host-.getContext (), host-.getFlutterShellargs ().toarray (), /*automaticallyRegisterPlugins=*/ false, /*willProvideRestorationData=*/ host.shouldRestoreAndSaveState()); isFlutterEngineFromHost = false;Copy the code
Then we follow up the initialization of FlutterEngine. It can be seen that the core of initialization is finally classified here, and the core code of initialization execution is extracted when the engine is initialized
if (flutterLoader == null) {
flutterLoader = FlutterInjector.instance().flutterLoader();
}
flutterLoader.startInitialization(context.getApplicationContext());
flutterLoader.ensureInitializationComplete(context, dartVmArgs);
Copy the code
Can be found in the engine is actually a call before we said flutterLoader start and ensure method to initialize and load APK resources, we then see no see before ensureInitializationComplete method
Initialized (initialized) {return; // Initialized (initialized) {return; } // The shellArgs is the core of the whole method. The initialization here is mainly to create a new shell, and then add all the flutter resources initialized and acquired to the shell. Finally, the shell data is passed into FlutterJNI for loading. List<String> shellArgs = new ArrayList<>(); // DEFAULT_LIBRARY, Is "libflutter. So" shellArgs. Add (" icu - native - lib - path = "+ flutterApplicationInfo. NativeLibraryDir + File. The separator + DEFAULT_LIBRARY); / / here flutterApplicationInfo aotSharedLibraryName is the dart code we write / / packaged into libapp. So shellArgs. Add (" - "+ AOT_SHARED_LIBRARY_NAME + "=" + flutterApplicationInfo.aotSharedLibraryName); // There are other resources and files that, when all added to the shell, will initialize FlutterJNI, Initialized to true // if (FlutterInjector.instance().shouldloadNative ()) {flutterjni.nativeInit (applicationContext, shellArgs.toArray(new String[0]), kernelPath, result.appStoragePath, result.engineCachesPath, initTimeMillis); } initialized = true;Copy the code
By now, the program has basically completed the initialization of most resources and codes. Next, FlutterView and others will build the environment of flutter rendering, form the platform channel, transmit the platform setting information to DART, and set up some android life cycle monitoring and operations. I won’t go into it here.
How does Flutter achieve thermal repair
This is where we can start making changes to our Flutter project on Android so that hot fixes can be implemented on Flutter. Since most of our development code is written in libapp.so, we need to find a method related to this so resource and have thought about how to change it. Following the process of initialization, we know that our code is in ensureInitializationComplete to load, at the same time its address is stored in a flutterApplicationInfo, The first thing we want to do is to make a change to flutterApplicationInfo to load the fixed so library, but since this class is configured from the beginning, we need to use reflection to fetch and then dynamically change the storage resource address, changing the libapp address to the fixed libapp address. And make sure the changes are completed before ensureInitializationComplete calls. Before we solve the resource address problem, we need to think about how to add our own reflection methods to Ensure. By looking at the source code, we can find a method in flutterActivity (not a proxy)
@Nullable @Override public FlutterEngine provideFlutterEngine(@NonNull Context context) { // No-op. Hook for subclasses. return null; }Copy the code
This method actually inherited from FlutterActivityAndFragmentDelegate. Host, because flutterActivity itself also inherited from the Host, the Host class itself is an interface class, which implements the method to manage the proxy class
/**
* Returns the {@link FlutterEngine} that should be rendered to a {@link FlutterView}.
*
* <p>If {@code null} is returned, a new {@link FlutterEngine} will be created automatically.
*/
@Nullable
FlutterEngine provideFlutterEngine(@NonNull Context context);
Copy the code
This method provides a flutterengine for proxy classes. If it is not implemented, the Flutterengine will be created automatically. So we can use this method to create our own FlutterEngine. The startInitialization and ensureInitialization methods will not be called many times. Call these two methods before creating the FlutterEngine. Add our reflection method in the middle to change the path of libapp.
public FlutterEngine provideFlutterEngine(@NonNull Context context){ FlutterInjector.instance().flutterLoader().startInitialization(getApplication()); // Add our own reflection method hotfixPath(); FlutterInjector.instance().flutterLoader().ensureInitializationComplete(getApplication(), getFlutterShellArgs().toArray()); // Returns flutterEngine, skipping its automatic creation. return new FlutterEngine(context, getFlutterShellArgs().toArray(), false, shouldRestoreAndSaveState()); }Copy the code
conclusion
This is the end of the process. Depending on the details and implementation of the project, there are other Android hotfix frameworks that can also be used to make hotfixes by understanding the principles of flutter loading and adjusting the details.