转载请注明出处:https://blog.csdn.net/llew2011/article/details/104075796
在Google发布Flutter之后我一直对它保持很高的热情,尤其是Flutter支持热更新,但在1.7.8版本之后由于某些原因Google屏蔽了热更新功能,这着实让让人失落……于是抽了点时间研究了一下Flutter的加载过程,目的是找到可以实现热更新的方式,在阅读文章之前需要小伙伴们对Flutter的编译模式有一定了解,目前我使用的Flutter版本为dev分支的1.14.0,Dart版本为2.8.0,如下所示:
要实现Flutter的热更新功能就要先弄清楚Flutter的加载流程,比如Flutter引擎什么时机加载,引擎加载完毕后libapp.so文件什么时机加载等,因此我们先分析一下Flutter的加载过程。
首先新建Flutter项目flutter_hotfix,然后运行该项目,如下所示:
项目启动后运行如上所示,页面上展示了一个文案和计数器数值,点击+
按钮计数器的值就会自增。接下来我们看下在Manifest中配置的启动信息,打开AndroidManifext.xml
文件,如下:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.llew.flutter.flutter_hotfix">
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<application
android:name="io.flutter.app.FlutterApplication"
android:label="flutter_hotfix"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>
Manifest默认配置了当前项目的Application为FlutterApplication
,启动页面是MainActivity
,另外配置的meta-data信息我们先不关注,我们先看FlutterApplication
类(在flutter.jar包中),如下所示:
public class FlutterApplication extends Application {
@Override
@CallSuper
public void onCreate() {
super.onCreate();
FlutterMain.startInitialization(this);
}
private Activity mCurrentActivity = null;
// 省略set/get方法
}
FlutterApplication
继承Application,它定义了Activity类型的mCurrentActivity属性并给其添加了set/get方法,另外FlutterApplication
重写了onCreate()方法,在onCreate()方法中仅调用了FlutterMain的startInitialization()静态方法进行初始化操作,FlutterMain源码如下:
public class FlutterMain {
/**
* Starts initialization of the native system.
* @param applicationContext The Android application context.
*/
public static void startInitialization(@NonNull Context applicationContext) {
if (isRunningInRobolectricTest) {
return;
}
FlutterLoader.getInstance().startInitialization(applicationContext);
}
/**
* 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 APK.
* <p>
* Calling this method multiple times has no effect.
*
* @param applicationContext The Android application context.
* @param settings Configuration settings.
*/
public static void startInitialization(@NonNull Context applicationContext, @NonNull Settings settings) {
if (isRunningInRobolectricTest) {
return;
}
FlutterLoader.Settings newSettings = new FlutterLoader.Settings();
newSettings.setLogTag(settings.getLogTag());
FlutterLoader.getInstance().startInitialization(applicationContext, newSettings);
}
// 省略相关代码
}
FlutterMain定义了一系列静态方法,而这些方法内部又委托FlutterLoader实现具体功能,FlutterLoader看名字就知道是一个加载类,它可能是我们实现热更新的入口类。FlutterLoader对外提供了一个静态的getInstance()方法便于获取其实例(该方法是单例的),其startInitialization()源码如下:
public class FlutterLoader {
private static final String TAG = "FlutterLoader";
// 省略部分属性
private static FlutterLoader instance;
/**
* Returns a singleton {@code FlutterLoader} instance.
* <p>
* The returned instance loads Flutter native libraries in the standard way. A singleton object
* is used instead of static methods to facilitate testing without actually running native
* library linking.
*/
@NonNull
public static FlutterLoader getInstance() {
if (instance == null) {
instance = new FlutterLoader();
}
return instance;
}
/**
* Starts initialization of the native system.
* @param applicationContext The Android application context.
*/
public void startInitialization(@NonNull Context applicationContext) {
startInitialization(applicationContext, new Settings());
}
/**
* 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 APK.
* <p>
* Calling this method multiple times has no effect.
*
* @param applicationContext The Android application context.
* @param settings Configuration settings.
*/
public void startInitialization(@NonNull Context applicationContext, @NonNull Settings settings) {
// 允许多次调用
// Do not run startInitialization more than once.
if (this.settings != null) {
return;
}
// 只允许在主线程运行
if (Looper.myLooper() != Looper.getMainLooper()) {
throw new IllegalStateException("startInitialization must be called on the main thread");
}
this.settings = settings;
long initStartTimestampMillis = SystemClock.uptimeMillis();
// 初始化配置信息
initConfig(applicationContext);
// 初始化资源信息
initResources(applicationContext);
// 加载Flutter引擎
System.loadLibrary("flutter");
VsyncWaiter
.getInstance((WindowManager) applicationContext.getSystemService(Context.WINDOW_SERVICE))
.init();
// We record the initialization time using SystemClock because at the start of the
// initialization we have not yet loaded the native library to call into dart_tools_api.h.
// To get Timeline timestamp of the start of initialization we simply subtract the delta
// from the Timeline timestamp at the current moment (the assumption is that the overhead
// of the JNI call is negligible).
long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis;
// 记录加载时间
FlutterJNI.nativeRecordStartTimestamp(initTimeMillis);
}
/**
* 根据AndroidManifest文件初始化相关配置信息
* Initialize our Flutter config values by obtaining them from the
* manifest XML file, falling back to default values.
*/
private void initConfig(@NonNull Context applicationContext) {
Bundle metadata = getApplicationInfo(applicationContext).metaData;
// There isn't a `<meta-data>` tag as a direct child of `<application>` in
// `AndroidManifest.xml`.
if (metadata == null) {
return;
}
aotSharedLibraryName = metadata.getString(PUBLIC_AOT_SHARED_LIBRARY_NAME, DEFAULT_AOT_SHARED_LIBRARY_NAME);
flutterAssetsDir = metadata.getString(PUBLIC_FLUTTER_ASSETS_DIR_KEY, DEFAULT_FLUTTER_ASSETS_DIR);
vmSnapshotData = metadata.getString(PUBLIC_VM_SNAPSHOT_DATA_KEY, DEFAULT_VM_SNAPSHOT_DATA);
isolateSnapshotData = metadata.getString(PUBLIC_ISOLATE_SNAPSHOT_DATA_KEY, DEFAULT_ISOLATE_SNAPSHOT_DATA);
}
/**
* Extract assets out of the APK that need to be cached as uncompressed
* files on disk.
*/
private void initResources(@NonNull Context applicationContext) {
// 先清空资源
new ResourceCleaner(applicationContext).start();
// 只在DEBUG或者JIT_RELEASE模式下执行
if (BuildConfig.DEBUG || BuildConfig.JIT_RELEASE) {
final String dataDirPath = PathUtils.getDataDirectory(applicationContext);
final String packageName = applicationContext.getPackageName();
final PackageManager packageManager = applicationContext.getPackageManager();
final AssetManager assetManager = applicationContext.getResources().getAssets();
resourceExtractor = new ResourceExtractor(dataDirPath, packageName, packageManager, assetManager);
// In debug/JIT mode these assets will be written to disk and then
// mapped into memory so they can be provided to the Dart VM.
resourceExtractor
.addResource(fullAssetPathFrom(vmSnapshotData))
.addResource(fullAssetPathFrom(isolateSnapshotData))
.addResource(fullAssetPathFrom(DEFAULT_KERNEL_BLOB));
resourceExtractor.start();
}
}
// 省略部分代码
}
FlutterLoader的startInitialization()方法核心就是初始化相关资源并加载Flutter引擎libflutter.so,该方法根据内部属性settings是否非空做个开关从而允许多次调用,然后调用initConfig()和initResources()方法进行相关初始化操作,需要注意的是initResources()方法内仅在DEBUG和JIT_RELEASE模式下才执行,也就是说在我们正式发布RELEASE版本的时候initResources()方法没有多大用处,然后执行System.loadLibrary(“flutter”)加载Flutter引擎,最后调用native方法记录Flutter引擎的加载时间。
到这里我们分析完了Flutter引擎的加载时机,然而我们Flutter项目打包出来的libapp.so还没有被加载,那它是什么时机加载的呢?我们继续往下分析代码,根据Manifest的配置,启动页面配置的是MainActivity,因此我们需要分析下MainActivity,它的源码如下所示:
public class MainActivity extends FlutterActivity {
@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
}
}
MainActivity继承FlutterActivity,它仅重写了configureFlutterEngine()方法,一般情况下创建完Activity后会在其onCreate()方法中调用setContentView()为当前Activity添加视图,可MainActivity没有重写onCreate()方法也没setContentView()的调用并且项目运行后界面正常渲染,那只能说明它的父类FlutterActivity调用了setContentView()方法,我们看下FlutterActivity的源码,如下所示:
public class FlutterActivity extends Activity
implements FlutterActivityAndFragmentDelegate.Host, LifecycleOwner {
private static final String TAG = "FlutterActivity";
// Delegate that runs all lifecycle and OS hook logic that is common between
// FlutterActivity and FlutterFragment. See the FlutterActivityAndFragmentDelegate
// implementation for details about why it exists.
@VisibleForTesting
protected FlutterActivityAndFragmentDelegate delegate;
@NonNull
private LifecycleRegistry lifecycle;
public FlutterActivity() {
lifecycle = new LifecycleRegistry(this);
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
// 省略代码
super.onCreate(savedInstanceState);
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
// 初始化delegate实例并持有当前Activity
delegate = new FlutterActivityAndFragmentDelegate(this);
delegate.onAttach(this);
delegate.onActivityCreated(savedInstanceState);
configureWindowForTransparency();
// 调用setContentView()方法为当前Activity添加视图
setContentView(createFlutterView());
configureStatusBarForFullscreenFlutterExperience();
}
@NonNull
private View createFlutterView() {
return delegate.onCreateView(
null /* inflater */,
null /* container */,
null /* savedInstanceState */);
}
@Override
protected void onStart() {
super.onStart();
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_START);
// 处理生命周期
delegate.onStart();
}
// 省略部分代码
}
FlutterActivity继承Activity并实现了Host和LifecycleOwner接口并定义了一个重量级属性delegate,delegate是FlutterActivityAndFragmentDelegate类型,它是FlutterActivity的大管家,FlutterActivity的众多回调都委托给了delegate,FlutterActivityAndFragmentDelegate构造方法如下所示:
final class FlutterActivityAndFragmentDelegate {
// host就是我们传递进来的FlutterActivity实例
private Host host;
FlutterActivityAndFragmentDelegate(@NonNull Host host) {
this.host = host;
}
}
我们继续看FlutterActivity的onCreate()方法,初始化delegate的时候传递的this(也就是delegate持有了当前FlutterActivity对象),初始化后依次调用了它的onAttach()和onActivityCreated()方法,最后调用了setContentView()方法为当前Activity添加视图,而在调用setContentView()的时候也是委托delegate的onCreateView()方法给当前当前Activity创建视图。我们先看下delegate的onAttach()方法,源码如下:
void onAttach(@NonNull Context context) {
// 确保host进行过初始化
ensureAlive();
if (flutterEngine == null) {
setupFlutterEngine();
}
platformPlugin = host.providePlatformPlugin(host.getActivity(), flutterEngine);
if (host.shouldAttachEngineToActivity()) {
Log.d(TAG, "Attaching FlutterEngine to the Activity that owns this Fragment.");
flutterEngine.getActivityControlSurface().attachToActivity(
host.getActivity(),
host.getLifecycle()
);
}
host.configureFlutterEngine(flutterEngine);
}
onAttach()方法首先调用ensureAlive()确保host实例化过,然后判断flutterEngine是否初始化过,在应用首次启动时flutterEngine为null,所以执行setupFlutterEngine()先初始化flutterEngine,setupFlutterEngine源码如下:
void setupFlutterEngine() {
Log.d(TAG, "Setting up FlutterEngine.");
// host是FlutterActivity实例,FlutterActivity的getCachedEngineId()方法返回null
// First, check if the host wants to use a cached FlutterEngine.
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;
}
// 调用FlutterActivity的provideFlutterEngine()方法,该方法默认返回null
// Second, defer to subclasses for a custom FlutterEngine.
flutterEngine = host.provideFlutterEngine(host.getContext());
if (flutterEngine != null) {
isFlutterEngineFromHost = true;
return;
}
// Our host did not provide a custom FlutterEngine. Create a FlutterEngine to back our
// FlutterView.
Log.d(TAG, "No preferred FlutterEngine was provided. Creating a new FlutterEngine for"
+ " this FlutterFragment.");
// 创建FlutterEngine实例
flutterEngine = new FlutterEngine(host.getContext(), host.getFlutterShellArgs().toArray());
isFlutterEngineFromHost = false;
}
setupFlutterEngine()方法的职责是初始化flutterEngine,首先根据host的getCachedEngineId()方法判断是否使用缓存的FlutterEngine,因为host是FlutterActivity实例而FlutterActivity的getCachedEngineId()方法通过Intent获取的,默认返回的是null,所以接着执行host的provideFlutterEngine()方法,而FlutterActivity的provideFlutterEngine()方法默认返回的也是null,所以最后直接调用了FlutterEngine的两个参数的构造方法创建一个FlutterEngine实例,FlutterEngine的构造方法如下:
public FlutterEngine(@NonNull Context context, @Nullable String[] dartVmArgs) {
// 调用FlutterLoader的getInstance()方法返回FlutterLoader实例
this(context, FlutterLoader.getInstance(), new FlutterJNI(), dartVmArgs, true);
}
public FlutterEngine(
@NonNull Context context,
@NonNull FlutterLoader flutterLoader,
@NonNull FlutterJNI flutterJNI
) {
this(context, flutterLoader, flutterJNI, null, true);
}
public FlutterEngine(
@NonNull Context context,
@NonNull FlutterLoader flutterLoader,
@NonNull FlutterJNI flutterJNI,
@Nullable String[] dartVmArgs,
boolean automaticallyRegisterPlugins
) {
this.flutterJNI = flutterJNI;
// 再次调用FlutterLoader的startInitialization()方法开始初始化Flutter引擎,防止没有初始化的情况
flutterLoader.startInitialization(context);
// 调用FlutterLoader的ensureInitializationComplete()方法加载Flutter引擎需要的资源文件libapp.so
flutterLoader.ensureInitializationComplete(context, dartVmArgs);
flutterJNI.addEngineLifecycleListener(engineLifecycleListener);
attachToJni();
// 省略部分代码
}
FlutterEngine目前提供了三个构造方法,最后执行的是含有4个参数的构造方法,在该构造方法内部调用了FlutterLoader的startInitialization()和ensureInitializationComplete()方法,再次调用startInitialization()的目的是保证Flutter引擎已经加载过,那么调用ensureInitializationComplete()方法是做什么的了?我们看下它的源码,如下:
/**
* Blocks until initialization of the native system has completed.
* <p>
* Calling this method multiple times has no effect.
*
* @param applicationContext The Android application context.
* @param args Flags sent to the Flutter runtime.
*/
public void ensureInitializationComplete(@NonNull Context applicationContext, @Nullable String[] args) {
// 如果初始化过直接返回
if (initialized) {
return;
}
// 必须在主线程调用
if (Looper.myLooper() != Looper.getMainLooper()) {
throw new IllegalStateException("ensureInitializationComplete must be called on the main thread");
}
// 必须调用过startInitialization()方法
if (settings == null) {
throw new IllegalStateException("ensureInitializationComplete must be called after startInitialization");
}
try {
// 只有在DEBUG或者JIT_RELEASE模式下resourceExtractor才非空
if (resourceExtractor != null) {
resourceExtractor.waitForCompletion();
}
// shell参数
List<String> shellArgs = new ArrayList<>();
shellArgs.add("--icu-symbol-prefix=_binary_icudtl_dat");
ApplicationInfo applicationInfo = getApplicationInfo(applicationContext);
shellArgs.add("--icu-native-lib-path=" + applicationInfo.nativeLibraryDir + File.separator + DEFAULT_LIBRARY);
if (args != null) {
Collections.addAll(shellArgs, args);
}
String kernelPath = null;
if (BuildConfig.DEBUG || BuildConfig.JIT_RELEASE) {
// 在DEBUG或者JIT_RELEASE模式下加载asset下的资源
String snapshotAssetPath = PathUtils.getDataDirectory(applicationContext) + File.separator + flutterAssetsDir;
kernelPath = snapshotAssetPath + File.separator + DEFAULT_KERNEL_BLOB;
shellArgs.add("--" + SNAPSHOT_ASSET_PATH_KEY + "=" + snapshotAssetPath);
shellArgs.add("--" + VM_SNAPSHOT_DATA_KEY + "=" + vmSnapshotData);
shellArgs.add("--" + ISOLATE_SNAPSHOT_DATA_KEY + "=" + isolateSnapshotData);
} else {
// RELEASE模式下加载nativeLibraryDir下的libapp.so文件
shellArgs.add("--" + AOT_SHARED_LIBRARY_NAME + "=" + aotSharedLibraryName);
// 这里很重要,如果libapp.so加载失败,可以设置libapp.so的全路径
// Most devices can load the AOT shared library based on the library name
// with no directory path. Provide a fully qualified path to the library
// as a workaround for devices where that fails.
shellArgs.add("--" + AOT_SHARED_LIBRARY_NAME + "=" + applicationInfo.nativeLibraryDir + File.separator + aotSharedLibraryName);
}
shellArgs.add("--cache-dir-path=" + PathUtils.getCacheDirectory(applicationContext));
if (settings.getLogTag() != null) {
shellArgs.add("--log-tag=" + settings.getLogTag());
}
String appStoragePath = PathUtils.getFilesDir(applicationContext);
String engineCachesPath = PathUtils.getCacheDirectory(applicationContext);
// 调用native方法加载libapp.so
FlutterJNI.nativeInit(applicationContext, shellArgs.toArray(new String[0]),
kernelPath, appStoragePath, engineCachesPath);
initialized = true;
} catch (Exception e) {
Log.e(TAG, "Flutter initialization failed.", e);
throw new RuntimeException(e);
}
}
ensureInitializationComplete()方法和startInitialization()方法套路是一样的,它根据initialized字段允许我们多次调用,程序首次执行的时候initialized为false所以正常往下走,首先校验settings时候为null,因为settings是在startInitialization()方法中初始化的,如果为null就表示startInitialization()方法没有调用则直接抛异常,接着是判断resourceExtractor是否为null,resourceExtractor只有在DEBUG和JIT_RELEASE模式下才会初始化,所以此时resourceExtractor为null,然后定义了shellArgs集合,该集合装载的是Flutter引擎所需参数,具体参数可参考这里,继续看代码,在给shellArgs添加参数的时候有个判断,当编译模式为DEBUG和JIT_RELEASE的时候添加的参数和RELEASE模式下添加的参数是不同的,RELEASE模式下添加的是AOT_SHARED_LIBRARY_NAME参数并且该参数添加了两次,第一次添加的参数值是aotSharedLibraryName的值(aotSharedLibraryName在initConfig()进行的初始化,默认是libapp.so),第二次添加的值是applicationInfo.nativeLibraryDir + File.separator + aotSharedLibraryName,也就是libapp.so的全路径,在仔细看第二次添加AOT_SHARED_LIBRARY_NAME参数前的一段注释,这个注释的信息非常重要:
if (BuildConfig.DEBUG || BuildConfig.JIT_RELEASE) {
// 省略代码
} else {
// 第一次添加
shellArgs.add("--" + AOT_SHARED_LIBRARY_NAME + "=" + aotSharedLibraryName);
// 注意看这段注释,重要的事情说三遍
// 注意看这段注释,重要的事情说三遍
// 注意看这段注释,重要的事情说三遍
// 大部分的设备都可以直接加载libapp.so,如果加载失败就直接加载全路径的libapp.so
// Most devices can load the AOT shared library based on the library name
// with no directory path. Provide a fully qualified path to the library
// as a workaround for devices where that fails.
shellArgs.add("--" + AOT_SHARED_LIBRARY_NAME + "=" + applicationInfo.nativeLibraryDir + File.separator + aotSharedLibraryName);
}
根据注释我们可以得到一个非常重要的结论:第二次添加AOT_SHARED_LIBRARY_NAME参数的目的是兜底操作,就是第一次直接加载libapp.so库失败的时候Flutter引擎会再次尝试加载全路径的libapp.so库,从而防止libapp.so加载失败。根据这个结论我们可以思考一下,如果让第一次加载失败,也即是让aotSharedLibraryName传一个错误的值,然后第二次传递一个我们指定的路径值,那么Flutter引擎加载的就是我们指定的libapp.so了,这也就可以实现Flutter的热更新了!有了这个思路我们接下来就是考虑如何修改这个全路径的值。
代码分析到这我们知道了libapp.so是在FlutterActivity的onCreate()方法中被加载的,具体加载是通过FlutterLoader的ensureInitializationComplete()方法实现的,在ensureInitializationComplete()方法中如果可以实现修改这个路径,那么Flutter的热修复功能就可以实现了。
修改这个全路径的值有多种方式,第一种是修改使用的flutter.jar包下的FlutterLoader.java的代码,给其添加可以设置路径的方法;另外一种是我们自己创建一个Loader类继承FlutterLoader,然后把FlutterLoader的代码拷贝过来并添加可以设置路径的方法,然后使用反射替换掉FlutterLoader的instance实例为我们自己创建的Loader实例,通过这种替换的方式当外界调用FlutterLoader的getInstance()方法时返回的就是我们自己的Loader实例,这两种方式都可以实热修复,这里我们将采用第二种方式(还有其他方式也可以实现热修复,等我后续有时间了再写出来吧)。
由于篇幅原因,我将在下篇文章Flutter源码系列之《一》Flutter的热更新探索(下) 实现热更新功能,敬请期待……