一 Android项目结构
二 Flutter Engine
三 程序初始化
FlutterMain
initConfig
initAot
initResources
APK和本地文件
四 总结
前面已经简单看过一个全新的Android Flutter应用程序的创建、编译以及安装包的结构。现在可以来看看Flutter程序是如何在Android上运行的。分析的应用程序是之前使用flutter create创建的Demo。功能就是点击界面上的【+】,然后界面上的数字递增显示。 我们只打开其中的Android项目。 flutter项目在最外层的lib/main.dart文件中。
整个Demo的Android项目很简单,只有一个MainActivity页面和一个GeneratedPluginRegistrant文件。
看一下Activity中的代码, 这也太简单了,仅仅是调用了一下 GeneratedPluginRegistrant的方法, 没有任何UI相关的代码。
public class MainActivity extends FlutterActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
}
}
而GeneratedPluginRegistrant 也非常简单
/**
* Generated file. Do not edit.
*/
public final class GeneratedPluginRegistrant {
public static void registerWith(PluginRegistry registry) {
if (alreadyRegisteredWith(registry)) {
return;
}
}
private static boolean alreadyRegisteredWith(PluginRegistry registry) {
final String key = GeneratedPluginRegistrant.class.getCanonicalName();
if (registry.hasPlugin(key)) {
return true;
}
registry.registrarFor(key);
return false;
}
}
这个就是这个Demo的Android部分的全部代码? 看一下AndroidManifest.xml 有没有什么有用的东西?
原来指定了自定义的 FlutterApplication , 但是我们代码里并没有这个类,点击去看一下这个类所在的位置。
最终发现是在flutter.jar中,路径是:flutter/bin/cache/artifacts/engine/android-arm/flutter.jar。这个就是我们前面分析Flutter SDK时看到的flutter.jar,它的主要作用就是提供Android和Flutter Engine交互的桥梁,把Flutter App的容器嵌入到Android项目中, 以及完成的Engine的功能。所以我们可以通过分析它的源码来看一下Android和Flutter之间的一些基本的交互。
从整个Flutter应用的External Libraries中可以看到,引用到的库主要分为:
这些库可以被自动引入是因为在Android Studio中安装了Flutter和Dart的插件,而Flutter可以被编译也是因为使用了Flutter SDK提供 gradle 插件。
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
类似Gradle Plugin for Android,这个插件完成了编译Flutter程序以及和Android程序打包在一起的功能。所以整个打包流程我们是可以进行定制的。
Flutter.jar的源码并不在Flutter SDK中,而是在Flutter Engine的源码中。GitHub地址: https://github.com/flutter/engine
前面我们看过Flutter的架构,Engine是整个Flutter的核心部分, 我们可以看一下gihub上的介绍:
The Flutter Engine is a portable runtime for hosting Flutter applications. It implements Flutter’s core libraries, including animation and graphics, file and network I/O, accessibility support, plugin architecture, and a Dart runtime and compile toolchain. Most developers will interact with Flutter via the Flutter Framework, which provides a modern, reactive framework, and a rich set of platform, layout and foundation widgets.
Flutter Engine是使用C++编写的,最终会生成flutter.so 文件,类似Android的虚拟机 libart.so。正常来说开发一个Flutter直接和Flutter Framework打交到就可以了。 但是Flutter目前在Android或IOS上运行还是需要一个容器,所以Engine提供了一个jar包来完成这个功能(Shell)。另外Engine在不同平台上行为是一直的,因为引入了Embedded层来适配不同平台。
Flutter Engine的源码从git下载之后默认是master分支,看了一下不像Flutter SDK还有不同channel的分支,那么当前使用的Flutter SDK对应的是Engine那个提交的代码呢?
➜ io git:(3757390fa) ✗ flutter --version
Flutter 1.2.1 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 8661d8aecd (7 weeks ago) • 2019-02-14 19:19:53 -0800
Engine • revision 3757390fa4
Tools • Dart 2.1.2 (build 2.1.2-dev.0.0 0a7dcf17eb)
查看一下flutter的版本,标红的就是当前SDK使用的Engine的版本,使用这个revision可以直接切换到对应的commit (这个revision不是完整的commit ID,用了着么多年git我才知道可以用部分commit来切换,当然太短了就可能有重复了)
flutter_engine git:(master) ✗ git checkout 3757390fa4
HEAD is now at 3757390fa Roll src/third_party/dart ecd7a88606..0a7dcf17eb (4 commits)
flutter_engine git:(3757390fa) ✗ git log
commit 3757390fa4b00d2d261bfdf5182d2e87c9113ff9 (HEAD)
Author: Ben Konyi
Date: Wed Feb 13 13:45:39 2019 -0800
这样就成功切换到了SDK对应的Engine版本。如果想从Android Studio中通过jar直接查看到源码,点击Choose Sources..关联一下就可以了。关联之后就变成java文件了,这样分析源码就很方便了。
关联的代码位置: flutter_engine/shell/platform/android/io (IOS的源码在platform/darwin 下)
对于Android程序来说,启动后最先执行的就是Application,这个Demo中,直接使用了FlutterApplication,内部调用了FlutterMain进行初始化。大多情况我们有自己的Application,这是可以继承FlutterApplication或则直接使用FlutterMain进行初始化。
/**
* Flutter implementation of {@link android.app.Application}, managing
* application-level global initializations.
*/
public class FlutterApplication extends Application {
@Override
@CallSuper
public void onCreate() {
super.onCreate();
FlutterMain.startInitialization(this);
}
private Activity mCurrentActivity = null;
public Activity getCurrentActivity() {
return mCurrentActivity;
}
public void setCurrentActivity(Activity mCurrentActivity) {
this.mCurrentActivity = mCurrentActivity;
}
}
FlutterMain这个class主要的作用就是初始化Flutter的Engine, 调用startInitialization方法进行初始化
public static void startInitialization(Context applicationContext, Settings settings) {
if (Looper.myLooper() != Looper.getMainLooper()) {
throw new IllegalStateException("startInitialization must be called on the main thread");
}
// Do not run startInitialization more than once.
if (sSettings != null) {
return;
}
sSettings = settings;
long initStartTimestampMillis = SystemClock.uptimeMillis();
initConfig(applicationContext);
initAot(applicationContext);
initResources(applicationContext);
System.loadLibrary("flutter");
long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis;
nativeRecordStartTimestamp(initTimeMillis);
}
整个初始化比较清晰:
初始化的核心就在第三步,所以主要看一下这里面做的事情。
主要是从程序的manifest文件中读取meta-data的配置,这些配置项主要是指定编译相关的一些信息,主页包括:
从名字就可以看出,名主要是指定编译后生成的vm和app代码文件的名字,已经assets目录名字。 下面是相关的默认值,我们之前在分析APK包结构的时候已经看过了。
String DEFAULT_AOT_SHARED_LIBRARY_PATH= "app.so";
String DEFAULT_AOT_VM_SNAPSHOT_DATA = "vm_snapshot_data";
String DEFAULT_AOT_VM_SNAPSHOT_INSTR = "vm_snapshot_instr";
String DEFAULT_AOT_ISOLATE_SNAPSHOT_DATA = "isolate_snapshot_data";
String DEFAULT_AOT_ISOLATE_SNAPSHOT_INSTR = "isolate_snapshot_instr";
String DEFAULT_FLX = "app.flx";
String DEFAULT_KERNEL_BLOB = "kernel_blob.bin";
String DEFAULT_FLUTTER_ASSETS_DIR = "flutter_assets";
说是初始化,从代码看其实是检查APK包中 aot文件是否完整。
private static void initAot(Context applicationContext) {
Set assets = listAssets(applicationContext, "");
sIsPrecompiledAsBlobs = assets.containsAll(Arrays.asList(
sAotVmSnapshotData,
sAotVmSnapshotInstr,
sAotIsolateSnapshotData,
sAotIsolateSnapshotInstr
));
sIsPrecompiledAsSharedLibrary = assets.contains(sAotSharedLibraryPath);
if (sIsPrecompiledAsBlobs && sIsPrecompiledAsSharedLibrary) {
throw new RuntimeException(
"Found precompiled app as shared library and as Dart VM snapshots.");
}
}
这里解释一些,在使用flutter build命令编译的时候,对于Android有一个选项, 这个可以利用NDK直接把flutter程序编译成一个so。
--build-shared-library Whether to prefer compiling to a *.so file (android only).
这个方法的主要作用是检查当前app目录下的Flutter应用的文件版本和APK中的是否一致,如果不一致会使用APK中的资源文件来更新本地的。整个初始化过程主要是使用了3个类, 这3个类内部都是使用了AsyncTask执行相关操作:
ResourceCleaner:这个类是用来清理程序关闭时,没有清理的无用资源。主要是清理Cache目录下以.org.chromium.Chromium.开头的文件,这个动作会延迟5S执行。
ResourceUpdater:这个类提供了动态更新资源文件的功能,可以在meta-data中进行设置,开启动态更新功能并指定下载的path包的地址。下载的文件在Files目录下patch.download,下载完成后会重命名为patch.install。 meta-data包括:
ResourceExtractor:从名字看这个类的主要作用是从APK中解压出Flutter相关的资源文件到本地。前面下载patch时还有一个逻辑就是,如果是立即安装模式,需要等待下载完成,才开始执行ResourceExtractor。下面重点看一下ResourceExtractor的功能。
在开始执行之前,先添加相关的资源到ResourceExtractor中。 会发现vm和isolate的文件被添加了2次。这里主要是处理Debug和Release包,前面我们看APK包结构的时候发现debug所有文件都在flutter_assets目录下,所以这里会使用fromFlutterAssets, 全部保存在ResourceExtractor中的mResources列表中。
sResourceExtractor = new ResourceExtractor(context);
sResourceExtractor
.addResource(fromFlutterAssets(sFlx))
.addResource(fromFlutterAssets(sAotVmSnapshotData))
.addResource(fromFlutterAssets(sAotVmSnapshotInstr))
.addResource(fromFlutterAssets(sAotIsolateSnapshotData))
.addResource(fromFlutterAssets(sAotIsolateSnapshotInstr))
.addResource(fromFlutterAssets(DEFAULT_KERNEL_BLOB));
if (sIsPrecompiledAsSharedLibrary) {
sResourceExtractor
.addResource(sAotSharedLibraryPath);
} else {
sResourceExtractor
.addResource(sAotVmSnapshotData)
.addResource(sAotVmSnapshotInstr)
.addResource(sAotIsolateSnapshotData)
.addResource(sAotIsolateSnapshotInstr);
}
sResourceExtractor.start();
看一下里面的AsyncTask具体干了什么:
private class ExtractTask extends AsyncTask {
ExtractTask() { }
@Override
protected Void doInBackground(Void... unused) {
final File dataDir = new File(PathUtils.getDataDirectory(mContext));
ResourceUpdater resourceUpdater = FlutterMain.getResourceUpdater();
if (resourceUpdater != null) {
// Protect patch file from being overwritten by downloader while
// it's being extracted since downloading happens asynchronously.
resourceUpdater.getInstallationLock().lock();
}
try {
if (resourceUpdater != null) {
File updateFile = resourceUpdater.getDownloadedPatch();
File activeFile = resourceUpdater.getInstalledPatch();
if (updateFile.exists()) {
JSONObject manifest = resourceUpdater.readManifest(updateFile);
if (resourceUpdater.validateManifest(manifest)) {
// Graduate patch file as active for asset manager.
if (activeFile.exists() && !activeFile.delete()) {
Log.w(TAG, "Could not delete file " + activeFile);
return null;
}
if (!updateFile.renameTo(activeFile)) {
Log.w(TAG, "Could not create file " + activeFile);
return null;
}
}
}
}
final String timestamp = checkTimestamp(dataDir);
if (timestamp == null) {
return null;
}
deleteFiles();
if (!extractUpdate(dataDir)) {
return null;
}
if (!extractAPK(dataDir)) {
return null;
}
if (timestamp != null) {
try {
new File(dataDir, timestamp).createNewFile();
} catch (IOException e) {
Log.w(TAG, "Failed to write resource timestamp");
}
}
return null;
} finally {
if (resourceUpdater != null) {
resourceUpdater.getInstallationLock().unlock();
}
}
}
}
代码主要操作如下:
String expectedTimestamp = TIMESTAMP_PREFIX + getVersionCode(packageInfo) + "-" + packageInfo.lastUpdateTime;
if (patchNumber != null) {
expectedTimestamp += "-" + patchNumber + "-" + patchFile.lastModified();
} else {
expectedTimestamp += "-" + patchFile.lastModified();
}
到此为止,一个Flutter应用在Application启动是的操作就分析完了,整体很简单,就是把APK中的flutter资源解压到本地,然后支持动态更新。安装一个debug的flutter包到手机运行一下,然后看一下:
和我们前面代码分析的一样,APK包中的flutter assets(和代码相关的资源)都被拷贝到了本地,并且生成了一个timestamp文件。如果开开启了动态更新,当下载了patch.zip后会重新更新到本地。如果APK重新安装了也会重新拷贝。
final File dataDir = new File(PathUtils.getDataDirectory(mContext));
public static String getDataDirectory(Context applicationContext) {
return applicationContext.getDir("flutter", Context.MODE_PRIVATE).getPath();
}
Flutter代码相关的数据都存放在本地flutter目录下,要注意这里使用getDir,系统会自动加上app_前缀,所以我们看到实际是存放在app_flutter目录下。
这一篇主要介绍了Flutter的Android程序在启动时如何初始化Flutter相关资源的, 主要由FlutterMain来负责,相对比较简单。最主要代码是动态更新那一块,中使用了BSDiff进行差异更新。在实际开发过程中实际可以根据实际情况来重写这一块代码来完成热更新的功能。不过因为目前都是AOT代码,Release包没法做到HotReload,但是重启更新也提供了热更新的能力。
如果本文对您有帮助,可以扫描下方二维码打赏!您的支持是我的动力!