Flutter Android 端 Activity/Fragment 流程源码分析
主要看 io.flutter.embedding.android.FlutterActivity
Flutter工程默认的mainActivity是继承自FlutterActivity,FlutterActivity 直接继承自 Activity,重点看下FlutterActivityAndFragmentDelegate.Host。
FlutterActivityAndFragmentDelegate.Host是 Flutter Android 平台层实现与标准 Activity/Fragment 之间的一个接口约定层,FlutterActivity 实现了这个接口的一系列方法,Flutter 在平台 SDK 层包装都是 FlutterActivityAndFragmentDelegate 来负责的,FlutterActivityAndFragmentDelegate 和核心是 FlutterView 和 FlutterEngine 的创建关联与管理调度。
通常一个 engine 的整个 Flutter Dart 无论页面栈多少级,终归在安卓端都是一个 Activity 或者 Fragment 承载,抑或 View,端侧仅仅算是一个容器而已。
—> FlutterActivity.onCreate()
—> FlutterActivityAndFragmentDelegate.onAttach()
—> FlutterActivityAndFragmentDelegate.setupFlutterEngine();
FlutterEngine 是一个独立的 Flutter 运行环境容器,通过它可以在 Android 应用程序中运行 Dart 代码。FlutterEngine 中的 Dart 代码可以在后台执行,也可以使用附带的 FlutterRenderer 和 Dart 代码将 Dart 端 UI 效果渲染到屏幕上,渲染可以开始和停止,从而允许 FlutterEngine 从 UI 交互转移到仅进行数据处理,然后又返回到 UI 交互的能力。
App 每个进程中创建第一个 FlutterEngine 实例的时候会加载 Flutter 引擎的原生库并启动 Dart VM(VM 存活生命周期跟随进程),随后同进程中其他的 FlutterEngines 将在同一个 VM 实例上运行,但在运行 DartExecutor 时将拥有自己的 Dart Isolate。每个 Isolate 都是一个独立的 Dart 环境,除非通过 Isolate 端口,否则无法相互通信。[参见官方文档]
初始化使用到了FlutterInjector、FlutterLoader、ResourceExtractor、ApplicationInfoLoader、VsyncWaiter
接下来我们分析
FlutterInjector 相关分析
FlutterInjector 是 Android 平台与 Flutter Engine 真正桥梁的管理灵魂控制类,一个全局单例的注入管理类角色。
FlutterLoader 这个类的职责是在应用 APK 中查找 Flutter 资源并加载 Flutter 原生库。
public class FlutterLoader {
//......
//步骤7、创建实例,传递FlutterJNI实例,下一小节分析FlutterJNI。
public FlutterLoader() {
this(new FlutterJNI());
}
public FlutterLoader(@NonNull FlutterJNI flutterJNI) {
this.flutterJNI = flutterJNI;
}
//......
//步骤8、初始化结果结构体。
private static class InitResult {
final String appStoragePath;
final String engineCachesPath;
final String dataDirPath;
//......
}
//步骤9、FlutterEngine实例化中调用,重要开始入口。
public void startInitialization(@NonNull Context applicationContext) {
startInitialization(applicationContext, new Settings());
}
//步骤10、加载Flutter Engine native so库,定位Dart resources在apk中的路径。
//必须在主线程调用此方法。
public void startInitialization(@NonNull Context applicationContext, @NonNull Settings settings) {
//......
if (Looper.myLooper() != Looper.getMainLooper()) {
throw new IllegalStateException("startInitialization must be called on the main thread");
}
final Context appContext = applicationContext.getApplicationContext();
this.settings = settings;
initStartTimestampMillis = SystemClock.uptimeMillis();
//步骤11、通过ApplicationInfoLoader的load获取FlutterApplicationInfo信息。
//里面主要都是flutterAssetsDir、nativeLibraryDir、aotSharedLibraryName等信息,下一小节专门分析FlutterApplicationInfo。
flutterApplicationInfo = ApplicationInfoLoader.load(appContext);
//步骤12、平台VSYNC信号相关初始化设置,下面单独小节分析。
VsyncWaiter.getInstance((WindowManager) appContext.getSystemService(Context.WINDOW_SERVICE))
.init();
//步骤13、由于需要访问磁盘,异步调用执行初始化相关东西。
Callable initTask =
new Callable() {
@Override
public InitResult call() {
//步骤14、异步调用initResources得到ResourceExtractor。
ResourceExtractor resourceExtractor = initResources(appContext);
//步骤15、通过flutterJNI调用加载框架so,即libflutter.so
flutterJNI.loadLibrary();
//步骤16、异步之上再异步先尽可能快获取默认的font manager。
Executors.newSingleThreadExecutor()
.execute(
new Runnable() {
@Override
public void run() {
flutterJNI.prefetchDefaultFontManager();
}
});
//步骤17、等待ResourceExtractor异步任务结束。
if (resourceExtractor != null) {
resourceExtractor.waitForCompletion();
}
//步骤18、返回异步执行的结果结构。
return new InitResult(
PathUtils.getFilesDir(appContext),
PathUtils.getCacheDirectory(appContext),
PathUtils.getDataDirectory(appContext));
}
};
//步骤19、通过线程池提交Callable并返回一个Future实例。
initResultFuture = Executors.newSingleThreadExecutor().submit(initTask);
}
//......
//步骤20、提取apk中assets文件为未压缩的在磁盘中,上面步骤14调用。
private ResourceExtractor initResources(@NonNull Context applicationContext) {
ResourceExtractor resourceExtractor = null;
//步骤21、如果是debug或jit模式resourceExtractor才不为空。
if (BuildConfig.DEBUG || BuildConfig.JIT_RELEASE) {
//步骤22、路径为context.getDir("flutter"),也就是私有沙盒下的flutter目录。
final String dataDirPath = PathUtils.getDataDirectory(applicationContext);
//步骤23、获取apk的应用包名。
final String packageName = applicationContext.getPackageName();
final PackageManager packageManager = applicationContext.getPackageManager();
final AssetManager assetManager = applicationContext.getResources().getAssets();
//步骤24、拿着一堆apk信息和目录实例化一个ResourceExtractor供后续调用。
resourceExtractor =
new ResourceExtractor(dataDirPath, packageName, packageManager, assetManager);
//步骤25、debug及jit模式填充路径。flutterApplicationInfo来自上面步骤11实例化构建。
//默认资源路径:flutter_assets/vm_snapshot_data
//默认资源路径:flutter_assets/isolate_snapshot_data
//默认资源路径:flutter_assets/kernel_blob.bin
resourceExtractor
.addResource(fullAssetPathFrom(flutterApplicationInfo.vmSnapshotData))
.addResource(fullAssetPathFrom(flutterApplicationInfo.isolateSnapshotData))
.addResource(fullAssetPathFrom(DEFAULT_KERNEL_BLOB));
//步骤26、开始执行resourceExtractor。
resourceExtractor.start();
}
return resourceExtractor;
}
//......
}
到此初始化就异步开始了,我们需要阻塞等待他的执行结束,如下:
public class FlutterLoader {
//......
//步骤27、阻塞直到上面步骤10的startInitialization方法执行完毕。
//一般紧挨在startInitialization方法后调用。
public void ensureInitializationComplete(
@NonNull Context applicationContext, @Nullable String[] args) {
//......
try {
//步骤28、阻塞等待上面步骤19的线程池Callable initTask执行完毕。
InitResult result = initResultFuture.get();
//步骤29、构建一堆参数供初始化使用。
List shellArgs = new ArrayList<>();
shellArgs.add("--icu-symbol-prefix=_binary_icudtl_dat");
//步30、flutterApplicationInfo在上面步骤11构建。
//路径为:[ApplicationInfo.nativeLibraryDir]/libflutter.so
shellArgs.add(
"--icu-native-lib-path="
+ flutterApplicationInfo.nativeLibraryDir
+ File.separator
+ DEFAULT_LIBRARY);
if (args != null) {
//步31、把启动FlutterActivity或FlutterFragment时intent中传递的dartVmArgs参数加入列表。
//譬如:trace-startup=true等
Collections.addAll(shellArgs, args);
}
String kernelPath = null;
//步32、debug、jit模式设置一些参数路径啥的。
if (BuildConfig.DEBUG || BuildConfig.JIT_RELEASE) {
String snapshotAssetPath =
result.dataDirPath + File.separator + flutterApplicationInfo.flutterAssetsDir;
kernelPath = snapshotAssetPath + File.separator + DEFAULT_KERNEL_BLOB;
shellArgs.add("--" + SNAPSHOT_ASSET_PATH_KEY + "=" + snapshotAssetPath);
shellArgs.add("--" + VM_SNAPSHOT_DATA_KEY + "=" + flutterApplicationInfo.vmSnapshotData);
shellArgs.add(
"--" + ISOLATE_SNAPSHOT_DATA_KEY + "=" + flutterApplicationInfo.isolateSnapshotData);
} else {
//步骤33、release模式重点!!!!
//前几天还有网友微信我说想让分析flutter appso热更新,这就是核心啊,就这一句话。
//--aot-shared-library-name=缺省libapp.so,可以通过清单文件meta-data配置so的名称为自定义值,配置name为io.flutter.embedding.engine.loader.FlutterLoader.aot-shared-library-name。
shellArgs.add(
"--" + AOT_SHARED_LIBRARY_NAME + "=" + flutterApplicationInfo.aotSharedLibraryName);
//步骤34、--aot-shared-library-name再配置一个apk安装后包路径下so的全路径地址。
shellArgs.add(
"--"
+ AOT_SHARED_LIBRARY_NAME
+ "="
+ flutterApplicationInfo.nativeLibraryDir
+ File.separator
+ flutterApplicationInfo.aotSharedLibraryName);
}
//步骤35、一堆同理的参数路径配置啥的。
shellArgs.add("--cache-dir-path=" + result.engineCachesPath);
if (flutterApplicationInfo.domainNetworkPolicy != null) {
shellArgs.add("--domain-network-policy=" + flutterApplicationInfo.domainNetworkPolicy);
}
if (settings.getLogTag() != null) {
shellArgs.add("--log-tag=" + settings.getLogTag());
}
//......
//步骤36、进行框架真正的native层代码初始化,传入我们准备的一切。
flutterJNI.init(
applicationContext,
shellArgs.toArray(new String[0]),
kernelPath,
result.appStoragePath,
result.engineCachesPath,
initTimeMillis);
initialized = true;
} catch (Exception e) {
Log.e(TAG, "Flutter initialization failed.", e);
throw new RuntimeException(e);
}
}
//......
//步骤37、dart使用资源resources的路径。
//本质可以通过meta-data配置name为io.flutter.embedding.engine.loader.FlutterLoader.flutter-assets-dir。
//缺省为 flutter_assets。
@NonNull
public String findAppBundlePath() {
return flutterApplicationInfo.flutterAssetsDir;
}
//......
//步骤38、flutter plugin是否自动配置注册,默认是true,同样可以通过meta-data配置变更。
@NonNull
public boolean automaticallyRegisterPlugins() {
return flutterApplicationInfo.automaticallyRegisterPlugins;
}
//......
}
可以看到,FlutterLoader 在调用 ensureInitializationComplete 方法时会将编译进 apk 中的 Flutter 相关libapp.so、assets 下面资源路径等各种安卓平台路径进行拼接传递给 flutterJNI 的 init 初始化。也就是说,Flutter Engine 拿到的关于 Flutter App 的各种原始资源路径都来自安卓平台解析传递,对于 Engine 来说就是一个 File path 的概念。这也就给我们进行 File path 重定向提供了思路,带来的国内团队骚操作就是衍生出了 Flutter app.so 热更新能力。
ResourceExtractor 相关分析
class ResourceExtractor {
//......
//步骤39、依据标准安卓系统获取支持的abi列表
private static final String[] SUPPORTED_ABIS = getSupportedAbis();
//步骤40、构造方法,在上面步骤24中被调用。
ResourceExtractor(
@NonNull String dataDirPath,
@NonNull String packageName,
@NonNull PackageManager packageManager,
@NonNull AssetManager assetManager) {
mDataDirPath = dataDirPath;
mPackageName = packageName;
mPackageManager = packageManager;
mAssetManager = assetManager;
mResources = new HashSet<>();
}
//......
//步骤41、新建一个ExtractTask并执行,本质是一个AsyncTask。
ResourceExtractor start() {
//......
mExtractTask =
new ExtractTask(mDataDirPath, mResources, mPackageName, mPackageManager, mAssetManager);
mExtractTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
return this;
}
//......
private static class ExtractTask extends AsyncTask {
//......
@Override
protected Void doInBackground(Void... unused) {
final File dataDir = new File(mDataDirPath);
//......
//步骤42、从apk提取释放资源,也就是通过流读取assets下flutter资源释放到对应目录下。
if (!extractAPK(dataDir)) {
return null;
}
//......
return null;
}
//......
}
}
ResourceExtractor 类主要通过线程池异步解析安装好的 apk 文件,释放 assets 路径下 Flutter 相关的资源,为 Flutter Engine 使用提供便利路径。
ApplicationInfoLoader 相关分析
ApplicationInfoLoader 的职责犹如其名,就是依据配置或者安装好的 apk 进行各种路径、信息拼接获取。
public final class ApplicationInfoLoader {
//步骤43、一堆可以在AndroidManifest.xml中通过meta-data自定义的name属性。
public static final String PUBLIC_AOT_SHARED_LIBRARY_NAME =
FlutterLoader.class.getName() + '.' + FlutterLoader.AOT_SHARED_LIBRARY_NAME;
public static final String PUBLIC_VM_SNAPSHOT_DATA_KEY =
FlutterLoader.class.getName() + '.' + FlutterLoader.VM_SNAPSHOT_DATA_KEY;
public static final String PUBLIC_ISOLATE_SNAPSHOT_DATA_KEY =
FlutterLoader.class.getName() + '.' + FlutterLoader.ISOLATE_SNAPSHOT_DATA_KEY;
public static final String PUBLIC_FLUTTER_ASSETS_DIR_KEY =
FlutterLoader.class.getName() + '.' + FlutterLoader.FLUTTER_ASSETS_DIR_KEY;
public static final String NETWORK_POLICY_METADATA_KEY = "io.flutter.network-policy";
public static final String PUBLIC_AUTOMATICALLY_REGISTER_PLUGINS_METADATA_KEY =
"io.flutter." + FlutterLoader.AUTOMATICALLY_REGISTER_PLUGINS_KEY;
//......
//步骤44、上面步骤11调用获取一个FlutterApplicationInfo实例。
public static FlutterApplicationInfo load(@NonNull Context applicationContext) {
ApplicationInfo appInfo = getApplicationInfo(applicationContext);
//......
//步骤43、一堆基础路径信息,自定义取不到就用缺省值。
return new FlutterApplicationInfo(
getString(appInfo.metaData, PUBLIC_AOT_SHARED_LIBRARY_NAME),
getString(appInfo.metaData, PUBLIC_VM_SNAPSHOT_DATA_KEY),
getString(appInfo.metaData, PUBLIC_ISOLATE_SNAPSHOT_DATA_KEY),
getString(appInfo.metaData, PUBLIC_FLUTTER_ASSETS_DIR_KEY),
getNetworkPolicy(appInfo, applicationContext),
appInfo.nativeLibraryDir,
clearTextPermitted,
getBoolean(appInfo.metaData, PUBLIC_AUTOMATICALLY_REGISTER_PLUGINS_METADATA_KEY, true));
}
}
通过分析我们知道,只要能够在flutterLoader.ensureInitializationComplete拼接好后的关键内容是 -- aot-shared-library-name=libapp.so --aot-shared-library-name=nativeLibraryDir/libapp.so,
我已我们就仅需要将我们下发的补丁so文件路径替换成FlutterApplicationInfo.aotSharedLibraryName字段的值,那么flutter启动后就是执行我们下发的内容了。
由于FlutterLoader中的FlutterApplicationInfo是私有的并且FlutterApplicationInfo中的aotSharedLibraryName是final修饰的,所以我们不可能通过常规手段去修改FlutterApplicationInfo.aotSharedLibraryName的值,只能通过反射去修改了
public class MyFlutterLoader extends FlutterLoader {
private static MyFlutterLoader instance;
private static final String FIX_SO = "hotlibapp.so";
@NonNull
public static MyFlutterLoader getInstance() {
if (instance == null) {
instance = new MyFlutterLoader();
}
return instance;
}
@Override
public void ensureInitializationComplete(
@NonNull Context applicationContext, @Nullable String[] args) {
File filesDir = applicationContext.getFilesDir();
File soFile = new File(filesDir, FIX_SO);//实际项目就是从服务端获取到so文件,然后放置到这个路径下
if (soFile.exists()) {
Log.i("--ensureInitializationComplete--", "补丁放置的路径:" + soFile.getAbsolutePath());
try {
//1.拿到flutterApplicationInfo字段
Field flutterApplicationInfoField = FlutterLoader.class.getDeclaredField("flutterApplicationInfo");
flutterApplicationInfoField.setAccessible(true);
FlutterApplicationInfo flutterApplicationInfo = (FlutterApplicationInfo) flutterApplicationInfoField.get(this);
assert flutterApplicationInfo != null;
Log.i("========", "--aot-shared-library-name=" + flutterApplicationInfo.nativeLibraryDir + flutterApplicationInfo.aotSharedLibraryName);
//2.拿到aotSharedLibraryName并修改为我们的so路径
Field aotSharedLibraryNameField = FlutterApplicationInfo.class.getDeclaredField("aotSharedLibraryName");
aotSharedLibraryNameField.setAccessible(true);
// aotSharedLibraryNameField.set(flutterApplicationInfo, File.separator+soFile.getName());
aotSharedLibraryNameField.set(flutterApplicationInfo, soFile.getAbsolutePath());
//3.拿到nativeLibraryDir并修改为我们的so路径
// Field nativeLibraryDirField = FlutterApplicationInfo.class.getDeclaredField("nativeLibraryDir");
// nativeLibraryDirField.setAccessible(true);
// nativeLibraryDirField.set(flutterApplicationInfo, applicationContext.getFilesDir().getAbsolutePath());
Log.i("========", "--aot-shared-library-name=" + flutterApplicationInfo.nativeLibraryDir + flutterApplicationInfo.aotSharedLibraryName);
super.ensureInitializationComplete(applicationContext, args);
} catch (Exception e) {
e.printStackTrace();
}
}else{
Log.i("--ensureInitializationComplete--", "load fail. 补丁不存在");
}
}
}
参考自codedeveloper的文章 - 知乎
- 《Flutter Android 工程结构及应用层编译源码深入分析》
- 《Flutter 命令本质之 Flutter tools 机制源码深入分析》
- 《Flutter 的 runApp 与三棵树诞生流程源码分析》
- 《Flutter Android 端 Activity/Fragment 流程源码分析》
- 《Flutter Android 端 FlutterInjector 及依赖流程源码分析》
- 《Flutter 的 runApp 与三棵树诞生流程源码分析》
- 《Flutter Android 端 FlutterEngine Java 相关流程源码分析》
- 《Flutter Android 端 FlutterView 相关流程源码分析》