探索Flutter跨平台运行机制

终于,又满足了自己的一个好奇心!

利用中秋假期时间探索了Flutter是如何在Android、iOS上运行起来的,因此,这篇文章主要分享Flutter在Android、iOS上的运行机制。至于Flutter是什么?Flutter Framework是如何架构的?Dart是什么?等等一些好奇点,待后续有时间、有机会再探索。

首先,创建一个新的Flutter项目

可以看到android、ios、lib三个主要目录,个人更熟悉Android一些,那么就从android开始探索吧。

在settings.gradle、build.gradle、app/build.gradle等编译配置文件中发现以下重要信息

可以看到,编译过程中引入了Flutter SDK中的flutter.gradle编译配置文件,具体查看flutter.gradle可以发现以下重要信息

可以看到在编译过程中,会在bin、cache等目录下寻找flutter.jar,如果没有会执行flutter precahe命令,在Flutter SDK中缓存flutter.jar文件,然后将其打包到apk中。继续向下看,还发现如下代码

这段代码初步看起来是在gradle构建过程中插入了flutterBuild和copyFlutterAssets的task,flutterBuild通过执行flutter指令将flutter相关代码进行编译,然后copyFlutterAssets将之前编译生成的flutter assets复制到Android Assets路径下,接下来通过实际编译验证。在编译过程中看到如下关键输出

可以看到flutterBuild的确执行了flutter build aot、flutter build bundle命令,其中flutter build aot --target带的参数正是lib/main.dart,编译生成产物输出到build/app/intermediates/flutter/release文件夹下。flutter build bundle 同样带了--target lib/main.dart,在build/app/intermediates/flutter/release下生成了flutter_assets文件夹。具体详情如下

至此,编译流程就已经探索完成了,接下来探索运行流程,在android/app下找到AndroidManifest文件,发现以下内容

看到了FlutterApplication以及MainActivity,通过查看代码MainActivity是继承自FlutterActivity的。接下来就需要查看FlutterApplication、FlutterActivity等源码了。根据常识这部分源码就是刚编译过程中看到的flutter.jar的内容,在Flutter SDK中未找到源码,苦苦寻找,在github flutter仓库中发现了engin工程,源码居然深藏于此。

首先,查看FlutterApplication代码

可以主要通过FlutterMain进行了初始化工作了,简单查看FlutterMain的代码

可以看到initAot、initResource方法,也就是编译过程中提到的flutterBuile生成的相关文件。最后load了flutter.so native文件。接下来查看FlutterActivity源码

可以发现,核心代码基本都在Delegate类里边,继续查看

可以发现,给Activity设置了FlutterView作为ContentView,然后调用了FlutterView.runFromBundle。首先查看FlutterView

可以发现FlutterView继承自Android SurfaceView,并且在SurfaceHolder.Callback surfaceCreated中调用了nativeSurfaceCreated,并将surface传入进去进行绘制显示。同时,在FlutterView的构造方法中也看到实例化了mFlutterNavigationChannel、mFlutterSystemChannel等MethodChannel、BasicMessageChannel等对象。接着查看FlutterView.runFromBundle,通过代码调用最终调用到了FlutterNativeView的runFromBundleInternal

可以看到最终调用了nativeRunBundleAndSnapshotFromLibrary,并且传入了bundlePath,entrypoint,Android AssertManager。之前编译生成的flutter assets就存放在这些位置。在platform_view_android_jni.cc中找到nativeRunBundleAndSnapshotFromLibrary的实现

可以看到根据传入的参数构造出了RunConfiguration对象,然后Launch FlutterApp。

至此,Flutter在Android平台运行机制的探索就告一段落了。总结整体流程,编译阶段将lib下dart文件编译生成flutter目录下的flutter_assets以及snapshot等文件,然后打包进Android Apk。运行阶段,生成FlutterView并设置给Acitivity ContentView,FlutterView将surface暴露给底层渲染引擎,同时也会生成通信channel,最后将编译生成的文件通过nativeRunBundleAndSnapshotFromLibrary暴露给flutter底层引擎,以此启动FlutterApp,底层引擎运行过程中通过FlutterNativeView与FlutterView进行交互。

至于底层引擎,后续有机会继续深入探索,接下来探索iOS平台运行机制。

用Xcode打开之前创建工程的ios目录,可以看到如下编译hook

可以看到了引入了Flutter SDK中的xcode_backend.sh脚本,具体查看

可以看到将Flutter.framework复制到了工程目录下的Flutter文件夹内,继续查看

可以看到同样执行了flutter build aot、flutter build bundle并将编译产物复制到了工程Flutter目录下,结果如下

在iOS编译生成的.app文件中同样也可以看到flutter_assets

至此,iOS编译流程也探索完成了,接下来同样探索运行流程

在Main.storyboard中可以发现,其使用了FlutterViewController,接下来在Flutter SDK中找到FlutterViewController

紧接着查看FlutterViewController.mm源码

可以发现同样创建了FlutterView以及setup了用来通信的channel,首先来看FlutterView

可以发现,FlutterView的layerClass返回了CAEAGLLayer,详情可以了解CAEAGLLayer - Core Animation。接着查看FlutterViewController的init方法,最后调用到了performCommonViewControllerInitialization,具体查看

可以发现依次调用了setupShell、setupChannel等,查看setupShell方法

可以发现通过task_runners、dartProject setting等Launch了FlutterApp,具体查看FlutterDartProject

可以发现initWithFlutterAssets进而调用DefaultSettingForProcess将编译成生成的flutter_assets暴露给底层引擎。

至此,Flutter在iOS平台运行机制的探索也告一段落了。总结整体流程,编译阶段通过调用Flutter SDK编译脚本,编译生成flutter_assets文件夹,并复制到工程路径下,然后打包.app文件,运行阶段主界面通过FlutterViewController运行,FlutterViewController loadView显示FlutterView,FlutterView暴露surface给底层引擎,FlutterViewController init方法内调用了setupShell启动Flutter App,setupChannel通信相关,在setupShell通过FlutterDartProject将flutter_assets暴露给底层引擎。

综合来看,Android、iOS平台的运行机制基本一致,编译阶段生成flutter_assets,并打包进平台安装包。运行阶段暴露渲染surface给底层引擎,同时通过平台API将之前编译生成的flutter_assets暴露给底层引擎。

对Flutter Framework架构、Flutter engine、Dart编译等同样好奇,但愿后续有时间、机会能够继续探索!

发布于 2018-09-24