无论是纯flutter应用还是flutter混合开发应用,flutter编写的代码都需要一个容器进行加载展示给用户,这与webview从本质上是一样的。因此有必要学习这个容器的主要功能、用法,以及在必要的时候去开发实现一些扩展功能或者修改一些默认行为,以便更好地与APP现有的运行框架融合。本文从flutter提供的embedding包入手分析,通过梳理flutter的加载过程了解其核心代码,以便后期进行定制开发。
embedding包核心代码分析
本节所讨论的代码位于io.flutter.embedding
包中,包括.android
,.engine
。
FlutterActivity
当我们运行flutter的example工程时,其MainActivity就是继承该类。通过查看源码可以发现这个Activity有两种启动方式:NewEngine
和CachedEngine
,分别对应flutter engine的两种管理策略。我们看下这两种启动方式的参数区别:
NewEngineIntentBuilder | CachedEngineIntentBuilder |
---|---|
Class<? extends FlutterActivity> activityClass :启动activity类名,可以是FlutterActivity的子类,因此这里可以自定义 | Class<? extends FlutterActivity> activityClass |
String initialRoute :初始路由地址,默认“/” | String cachedEngineId :FlutterEngineCache的存储key |
String backgroundMode :背景模式,分为透明(对应FlutterTextureView)和不透明(对应FlutterSurfaceView)两种。默认不透明,除非有明确的需求,否则不建议使用透明 | String backgroundMode |
可以看到除了个别参数不一致,其余参数都是一样的。
NewEngine
方式因为每次都要构造一个新的engine实例,因此需要指定一个initialRoute;CachedEngine
方式由于需要从FlutterEngineCache
中获取缓存的engine,因此需要指定cachedEngineId。
从onCreate
方法开始,我们看到核心逻辑都在FlutterActivityAndFragmentDelegate
中,而Activity/Fragment的功能被抽象为FlutterActivityAndFragmentDelegate.Host
接口以统一Activity/Fragment二者的行为。这个接口是整个embedding包的重点之一,因此我们重点看下里面有哪些方法。
FlutterActivityAndFragmentDelegate.Host
由于接口方法比较多的,我把这些接口分为几类:
参数配置类
@NonNull Context getContext()
:返回activity本身@Nullable Activity getActivity()
:返回activity本身@NonNull Lifecycle getLifecycle()
:返回lifecycle实例@NonNull FlutterShellArgs getFlutterShellArgs()
:flutter命令参数,参见FlutterShellArgs
@Nullable String getCachedEngineId()
:获取engine缓存key,用于engine复用场景boolean shouldDestroyEngineWithHost()
:是否当host销毁时同时销毁engine,intent传入@NonNullString getDartEntrypointFunctionName()
:dart执行入口方法名,默认“main”,manifest中配置@NonNull String getAppBundlePath()
:自定义bundle路径,仅测试用,实际运行采用的是FlutterLoader#findAppBundlePath()
@Nullable String getInitialRoute()
:初始路由地址,支持intent传入和manifest中配置(当flutter页面作为启动页时)。当这个方法返回null且shouldHandleDeeplinking返回true时,初始路由地址将由Intent.getData()决定boolean shouldHandleDeeplinking()
:是否处理Deeplinking,默认false。仅当getInitialRoute()返回null时生效,manifest中配置@NonNull RenderMode getRenderMode()
:surface(默认)/texture两种模式,用于初始化FlutterView,intent传入@NonNull TransparencyMode getTransparencyMode()
:opaque(默认)/transparent两种模式,用于初始化FlutterView,intent传入boolean shouldAttachEngineToActivity()
:控制该activity中的FlutterFragment是否自动attach其engine到activity中,默认true。用于engine复用场景boolean shouldRestoreAndSaveState()
:控制是否执行onRestore/onSave InstanceState方法,默认为true,在engine复用场景需要考虑实际情况
功能类
@Nullable SplashScreen provideSplashScreen()
来源:io.flutter.embedding.android.SplashScreenProvider
,提供flutter页面启动屏,默认null。可以通过在manifest中配置drawable资源,或者自己覆写此方法返回完全自定义的SplashScreen,参见DrawableSplashScreen
。@Nullable FlutterEngine provideFlutterEngine(@NonNull Context context)
来源:io.flutter.embedding.android.FlutterEngineProvider
,提供一个engine实例给FlutterFragment
用,通常FlutterFragment
不应该自己new一个engine,而应该托付给FlutterActivity
去生成。默认返回null,这样FlutterActivityAndFragmentDelegate
内部会自己去生成。调用入口在io.flutter.embedding.android.FlutterActivityAndFragmentDelegate#setupFlutterEngine
。@Nullable PlatformPlugin providePlatformPlugin(@Nullable Activity activity, @NonNull FlutterEngine flutterEngine)
提供一个PlatformPlugin
实例,在FlutterFragment
和FlutterActivity
中都是直接new了一个并返回,没有托付关系。boolean popSystemNavigator()
来源:io.flutter.plugin.platform.PlatformPlugin.PlatformPluginDelegate
,处理原生页面栈退出逻辑,默认返回false,交由flutter framework处理,默认逻辑是finish activity或pop fragment(代码在io.flutter.plugin.platform.PlatformPlugin#popSystemNavigator
)。如果想自己处理,则可以覆写此方法。
生命周期类
void detachFromFlutterEngine()
当engine被attach到其他activity时回调,此时应当停止当前activity与engine的交互。void configureFlutterEngine(@NonNull FlutterEngine flutterEngine)
来源:io.flutter.embedding.android.FlutterEngineConfigurator
,当engine被创建后并attach到FragmentActivity后回调,默认调用GeneratedPluginRegister.registerGeneratedPlugins(flutterEngine)
。void cleanUpFlutterEngine(@NonNull FlutterEngine flutterEngine)
来源:io.flutter.embedding.android.FlutterEngineConfigurator
,engine detach或destroy前回调,与configureFlutterEngine()回调呼应,默认空实现。void onFlutterSurfaceViewCreated(@NonNull FlutterSurfaceView flutterSurfaceView)
FlutterSurfaceView被创建后attach前回调,默认空实现void onFlutterTextureViewCreated(@NonNull FlutterTextureView flutterTextureView)
FlutterTextureView被创建后attach前回调,默认空实现void onFlutterUiDisplayed()
FlutterView开始绘制时回调,默认为一个无关实现void onFlutterUiNoLongerDisplayed()
FlutterView停止绘制时回调,默认空实现
终于把Host的方法讲完了,由于这些方法都是供
FlutterActivityAndFragmentDelegate
调用的,所以真正的加载流程还得看这个类。
FlutterActivityAndFragmentDelegate
本节介绍FlutterActivityAndFragmentDelegate
几个比较重要的方法。
void onAttach(@NonNull Context context)
- 调用处
- activity的onCreate方法
- fragment的onAttach方法
创建FlutterActivityAndFragmentDelegate实例,并随即调用了其onAttach方法。
- 代码分析
//io.flutter.embedding.android.FlutterActivityAndFragmentDelegate#onAttach
void onAttach(@NonNull Context context) {
ensureAlive();
// 1. 当且仅当flutterEngine为null时创建engine实例
if (flutterEngine == null) {
setupFlutterEngine();
}
// 2. 通知flutter插件其已attach到activity中,插件收到onAttachToActivity等回调
if (host.shouldAttachEngineToActivity()) {
flutterEngine.getActivityControlSurface().attachToActivity(this, host.getLifecycle());
}
// 3. 创建新的PlatformPlugin实例
platformPlugin = host.providePlatformPlugin(host.getActivity(), flutterEngine);
// 4. 执行engine配置的hook回调
host.configureFlutterEngine(flutterEngine);
}
看下FlutterEngine是怎么来的,重点关注其构造参数:
// io.flutter.embedding.android.FlutterActivityAndFragmentDelegate#setupFlutterEngine
/* package */ void setupFlutterEngine() {
// 1. 如果是engine复用,则返回复用的engine
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;
}
// 2. 如果用户自己继承FlutterActivity并重写provideFlutterEngine返回了实例,则返回用户自己的
flutterEngine = host.provideFlutterEngine(host.getContext());
if (flutterEngine != null) {
isFlutterEngineFromHost = true;
return;
}
// 3. 默认创建的engine实例
flutterEngine =
new FlutterEngine(
host.getContext(),
host.getFlutterShellArgs().toArray(),
/*automaticallyRegisterPlugins=*/ false,
/*willProvideRestorationData=*/ host.shouldRestoreAndSaveState());
isFlutterEngineFromHost = false;
}
- isFlutterEngineFromHost:这个参数会影响到engine的销毁逻辑,可以理解为engine是否为用户自己生成(维护)的一个标记。
- automaticallyRegisterPlugins:是否在engine构造方法中注册插件。默认的engine填的是false,也就是默认是不会在构造engine时注册插件,通过前面的分析我们知道默认会在
Host#configureFlutterEngine
中才去注册插件。
接下来是ActivityControlSurface#attachToActivity
:
//io.flutter.embedding.engine.FlutterEngineConnectionRegistry#attachToActivity(ExclusiveAppComponent<android.app.Activity>, Lifecycle)
@Override
public void attachToActivity(@NonNull ExclusiveAppComponent<Activity> exclusiveActivity, @NonNull Lifecycle lifecycle) {
if (this.exclusiveActivity != null) {
this.exclusiveActivity.detachFromFlutterEngine();
}
// If we were already attached to an app component, detach from it.
detachFromAppComponent();
if (this.activity != null) {
throw new AssertionError("Only activity or exclusiveActivity should be set");
}
this.exclusiveActivity = exclusiveActivity;
attachToActivityInternal(exclusiveActivity.getAppComponent(), lifecycle);
}
private void attachToActivityInternal(@NonNull Activity activity, @NonNull Lifecycle lifecycle) {
this.activityPluginBinding = new FlutterEngineActivityPluginBinding(activity, lifecycle);
// 激活PlatformViewsController:初始化PlatformViewsChannel实例,这样PlatformView才真正可用.
flutterEngine
.getPlatformViewsController()
.attach(activity, flutterEngine.getRenderer(), flutterEngine.getDartExecutor());
// 调用ActivityAware回调,更新attach状态.
for (ActivityAware activityAware : activityAwarePlugins.values()) {
if (isWaitingForActivityReattachment) {
activityAware.onReattachedToActivityForConfigChanges(activityPluginBinding);
} else {
activityAware.onAttachedToActivity(activityPluginBinding);
}
}
isWaitingForActivityReattachment = false;
}
从这里回过头去看
host.shouldAttachEngineToActivity()
这个值,当我们想要复用engine时,即同一个engine要attach到不同的activity时,应该返回true还是false呢?其实还是应该返回true的,我想这也是这个方法默认返回true的原因。不过也有特殊情况:当同一个activity中包含多个FlutterFragment
,当这些FlutterFragment
共享同一个engine时,由于PlatformViewsController
只能attach一次(见PlatformViewsController#attach
),此时第二个FlutterFragment
执行flutterEngine.getActivityControlSurface().attachToActivity()
时就会奔溃。
怎么办呢?其实就要判断当前activity是否已存在engine实例。FlutterActivity
嵌套FlutterFragment
的情况是不会发生的,所以我们只要考虑同一个activity多个FlutterFragment
的情况。具体代码实现应该是在FlutterFragment
的subclass中,重写shouldAttachEngineToActivity:
- 判断当前activity是否是
FlutterActivity
,如果是则返回true或super实现;- 获取activity的fragmentManager,遍历所有fragments,当自身不是第一个
FlutterFragment
实例时,返回false,否则返回true;- 作为2的备用方案:为activity实例设置一个tag,shouldAttachEngineToActivity()中先读取这个tag,若为null则返回true,随机设置tag为非空;
接着是创建PlatformPlugin
,这个比较简单了。由于PlatformPlugin
是FlutterActivityAndFragmentDelegate
实例的成员变量且没有共享机制,所以每次都构建新的对象就行了。
最后是configureFlutterEngine()回调的执行,也比较简单,当activity为FlutterActivity及其子类时,最终是执行了FlutterActivity#configureFlutterEngine
:
// io.flutter.embedding.android.FlutterActivity#configureFlutterEngine
@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
GeneratedPluginRegister.registerGeneratedPlugins(flutterEngine);
}
反射调用了:
/**
* Generated file. Do not edit.
* This file is generated by the Flutter tool based on the
* plugins that support the Android platform.
*/
@Keep
public final class GeneratedPluginRegistrant {
public static void registerWith(@NonNull FlutterEngine flutterEngine) {
flutterEngine.getPlugins().add(new XXXPlugin());
flutterEngine.getPlugins().add(new YYYPlugin());
...
}
}
最终的添加方法:
//io.flutter.embedding.engine.FlutterEngineConnectionRegistry#add(io.flutter.embedding.engine.plugins.FlutterPlugin)
@Override
public void add(@NonNull FlutterPlugin plugin) {
// 先做了Class检查,所以放心,不会把同一个插件的多个实例重复添加
if (has(plugin.getClass())) {
return;
}
// 缓存plugin Class,并调用插件onAttachedToEngine回调
// 注意:这里可以知道该回调在engine生命周期内只会调用一次,除非手动remove掉
plugins.put(plugin.getClass(), plugin);
plugin.onAttachedToEngine(pluginBinding);
// 缓存ActivityAware class并调用ActivityAware#onAttachedToActivity回调.
if (plugin instanceof ActivityAware) {
ActivityAware activityAware = (ActivityAware) plugin;
activityAwarePlugins.put(plugin.getClass(), activityAware);
if (isAttachedToActivity()) {
activityAware.onAttachedToActivity(activityPluginBinding);
}
}
// 缓存ServiceAware class并调用ServiceAware#onAttachedToService回调.
if (plugin instanceof ServiceAware) {
ServiceAware serviceAware = (ServiceAware) plugin;
serviceAwarePlugins.put(plugin.getClass(), serviceAware);
if (isAttachedToService()) {
serviceAware.onAttachedToService(servicePluginBinding);
}
}
// 缓存BroadcastReceiverAware class并调用BroadcastReceiverAware#onAttachedToBroadcastReceiver回调.
if (plugin instanceof BroadcastReceiverAware) {
BroadcastReceiverAware broadcastReceiverAware = (BroadcastReceiverAware) plugin;
broadcastReceiverAwarePlugins.put(plugin.getClass(), broadcastReceiverAware);
if (isAttachedToBroadcastReceiver()) {
broadcastReceiverAware.onAttachedToBroadcastReceiver(broadcastReceiverPluginBinding);
}
}
// 缓存ContentProviderAware class并调用ContentProviderAware#onAttachedToContentProvider回调.
if (plugin instanceof ContentProviderAware) {
ContentProviderAware contentProviderAware = (ContentProviderAware) plugin;
contentProviderAwarePlugins.put(plugin.getClass(), contentProviderAware);
if (isAttachedToContentProvider()) {
contentProviderAware.onAttachedToContentProvider(contentProviderPluginBinding);
}
}
}
View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)
- 调用处
- activity的onCreate方法
- fragment的onCreateView方法
- 代码分析
//io.flutter.embedding.android.FlutterActivityAndFragmentDelegate#onCreateView
@NonNull View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
// 构建FlutterView实例
if (host.getRenderMode() == RenderMode.surface) {
FlutterSurfaceView flutterSurfaceView =
new FlutterSurfaceView(
host.getActivity(), host.getTransparencyMode() == TransparencyMode.transparent);
host.onFlutterSurfaceViewCreated(flutterSurfaceView);
flutterView = new FlutterView(host.getActivity(), flutterSurfaceView);
} else {
FlutterTextureView flutterTextureView = new FlutterTextureView(host.getActivity());
host.onFlutterTextureViewCreated(flutterTextureView);
flutterView = new FlutterView(host.getActivity(), flutterTextureView);
}
// flutter第一帧绘制完成回调
flutterView.addOnFirstFrameRenderedListener(flutterUiDisplayListener);
// 创建flutter启动动画
flutterSplashView = new FlutterSplashView(host.getContext());
...
flutterSplashView.displayFlutterViewWithSplash(flutterView, host.provideSplashScreen());
// flutterView连接engine,初始化各种插件支持交互,attach flutterRenderer准备绘制
flutterView.attachToFlutterEngine(flutterEngine);
return flutterSplashView;
}
这个方法也是最关键的方法之一,从这里我们可以了解到FlutterView
的创建和初始化流程。
void onStart()
- 调用处
- activity的onStart方法
- fragment的onStart方法
- 代码分析
//io.flutter.embedding.android.FlutterActivityAndFragmentDelegate#onStart
void onStart() {
ensureAlive();
doInitialFlutterViewRun();
}
看下这个doInitialFlutterViewRun()
做了什么:
// io.flutter.embedding.android.FlutterActivityAndFragmentDelegate#doInitialFlutterViewRun
private void doInitialFlutterViewRun() {
// engine复用时,dart运行入口完全交给用户自己掌控,framework就不管了.
if (host.getCachedEngineId() != null) {
return;
}
// configuration变更导致的方法重入,不处理
if (flutterEngine.getDartExecutor().isExecutingDart()) {
return;
}
// 运行dart的入口方法前,先设置好initialRoute
// 1. dart侧通过:window.defaultRouteName获取这个初始路由
// 2. 同时也说明了,java与dart的通讯不是运行了dart入口方法之后才能进行,而是engine初始化完毕就可以开始
String initialRoute = host.getInitialRoute();
if (initialRoute == null) {
initialRoute = maybeGetInitialRouteFromIntent(host.getActivity().getIntent());
if (initialRoute == null) {
initialRoute = DEFAULT_INITIAL_ROUTE;
}
}
flutterEngine.getNavigationChannel().setInitialRoute(initialRoute);
// 构造dart运行入口,并执行之
String appBundlePathOverride = host.getAppBundlePath();
if (appBundlePathOverride == null || appBundlePathOverride.isEmpty()) {
appBundlePathOverride = FlutterInjector.instance().flutterLoader().findAppBundlePath();
}
DartExecutor.DartEntrypoint entrypoint =
new DartExecutor.DartEntrypoint(
appBundlePathOverride, host.getDartEntrypointFunctionName());
flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint);
}
在这个方法中,真正启动执行了dart代码,也看到了initialRoute
是如何设置给engine的。
看到这里不知道你是否跟我一样有个疑问:
NavigationChannel#setInitialRoute
执行的时候,dart的main方法还没有运行,也就是dart vm那边相关的channel还有被初始化,难道channel消息也具有“sticky”特性吗?后面章节将对此有所介绍。
void onResume()
- 调用处
- activity的onResume方法
- fragment的onResume方法
- 代码分析
void onResume() {
ensureAlive();
// 通知flutter当前界面处于Resumed状态
flutterEngine.getLifecycleChannel().appIsResumed();
}
void onPause()
- 调用处
- activity的onPause方法
- fragment的onPause方法
- 代码分析
//io.flutter.embedding.android.FlutterActivityAndFragmentDelegate#onPause
void onPause() {
ensureAlive();
// 通知flutter当前界面处于Inactive状态
flutterEngine.getLifecycleChannel().appIsInactive();
}
void onStop()
- 调用处
- activity的onStop方法
- fragment的onStop方法
- 代码分析
//io.flutter.embedding.android.FlutterActivityAndFragmentDelegate#onStop
void onStop() {
ensureAlive();
// 通知flutter当前界面处于Paused状态
flutterEngine.getLifecycleChannel().appIsPaused();
}
从onStop开始,
FlutterActivity
在调用delegate的方法前,都要做delegate的非空检查。这是因为在engine复用的场景下,启动一个新的FlutterActivity
复用当前页面的engine并finish当前页面时,会触发当前页面的FlutterActivity#detachFromFlutterEngine
回调,导致delegate被release掉。
void onDestroyView()
- 调用处
- activity的onDestroy/detachFromFlutterEngine方法
- fragment的onDestroyView/detachFromFlutterEngine方法
- 代码分析
与onCreateView相呼应,用于FlutterView
相应的资源回收工作。
//io.flutter.embedding.android.FlutterActivityAndFragmentDelegate#onDestroyView
void onDestroyView() {
ensureAlive();
// flutterView断开engine,从而释放相关资源
flutterView.detachFromFlutterEngine();
flutterView.removeOnFirstFrameRenderedListener(flutterUiDisplayListener);
}
void onDetach()
- 调用处
- activity的onDestroy/detachFromFlutterEngine方法
- fragment的onDetach/detachFromFlutterEngine方法
- 代码分析
//io.flutter.embedding.android.FlutterActivityAndFragmentDelegate#onDetach
void onDetach() {
ensureAlive();
// 与onAttach中configureFlutterEngine()相呼应.
host.cleanUpFlutterEngine(flutterEngine);
// 通知flutter插件其已从activity detach,插件收到onDetachFromActivity等回调
if (host.shouldAttachEngineToActivity()) {
if (host.getActivity().isChangingConfigurations()) {
flutterEngine.getActivityControlSurface().detachFromActivityForConfigChanges();
} else {
flutterEngine.getActivityControlSurface().detachFromActivity();
}
}
// Null out the platformPlugin to avoid a possible retain cycle between the plugin, this
// Fragment,
// and this Fragment's Activity.
if (platformPlugin != null) {
platformPlugin.destroy();
platformPlugin = null;
}
flutterEngine.getLifecycleChannel().appIsDetached();
// Destroy our FlutterEngine if we're not set to retain it.
if (host.shouldDestroyEngineWithHost()) {
flutterEngine.destroy();
if (host.getCachedEngineId() != null) {
FlutterEngineCache.getInstance().remove(host.getCachedEngineId());
}
flutterEngine = null;
}
}
FlutterFragment
与FlutterActivity
一样,由于公共逻辑都被抽取到了FlutterActivityAndFragmentDelegate
中,包括生命周期方法内的调用逻辑大部分是相同的。而在Host部分FlutterFragment
的代码就与FlutterActivity
有所区别了,比如下面:
//io.flutter.embedding.android.FlutterFragment
@Override
@Nullable
public SplashScreen provideSplashScreen() {
FragmentActivity parentActivity = getActivity();
if (parentActivity instanceof SplashScreenProvider) {
SplashScreenProvider splashScreenProvider = (SplashScreenProvider) parentActivity;
return splashScreenProvider.provideSplashScreen();
}
return null;
}
@Override
@Nullable
public FlutterEngine provideFlutterEngine(@NonNull Context context) {
// Defer to the FragmentActivity that owns us to see if it wants to provide a
// FlutterEngine.
FlutterEngine flutterEngine = null;
FragmentActivity attachedActivity = getActivity();
if (attachedActivity instanceof FlutterEngineProvider) {
// Defer to the Activity that owns us to provide a FlutterEngine.
FlutterEngineProvider flutterEngineProvider = (FlutterEngineProvider) attachedActivity;
flutterEngine = flutterEngineProvider.provideFlutterEngine(getContext());
}
return flutterEngine;
}
@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
FragmentActivity attachedActivity = getActivity();
if (attachedActivity instanceof FlutterEngineConfigurator) {
((FlutterEngineConfigurator) attachedActivity).configureFlutterEngine(flutterEngine);
}
}
@Override
public void cleanUpFlutterEngine(@NonNull FlutterEngine flutterEngine) {
FragmentActivity attachedActivity = getActivity();
if (attachedActivity instanceof FlutterEngineConfigurator) {
((FlutterEngineConfigurator) attachedActivity).cleanUpFlutterEngine(flutterEngine);
}
}
@Override
public void onFlutterUiDisplayed() {
FragmentActivity attachedActivity = getActivity();
if (attachedActivity instanceof FlutterUiDisplayListener) {
((FlutterUiDisplayListener) attachedActivity).onFlutterUiDisplayed();
}
}
@Override
public void onFlutterUiNoLongerDisplayed() {
FragmentActivity attachedActivity = getActivity();
if (attachedActivity instanceof FlutterUiDisplayListener) {
((FlutterUiDisplayListener) attachedActivity).onFlutterUiNoLongerDisplayed();
}
}
可以看到FlutterFragment
是把上述方法的实现托付给宿主activity的,虽然FlutterActivity
可以满足其需求,但是我们不可能在FlutterActivity
里面去嵌套FlutterFragment
来使用。我们分析一下,当我们用一个普通的Activity去加载FlutterFragment
实例会上述方法会有什么影响:
SplashScreen provideSplashScreen()
此方法返回一个自定义启动动画,FlutterFragment
默认没有实现,由于attachedActivity是普通Activity,固然也没有实现SplashScreenProvider
接口,于是flutter页面打开时将没有自定义动画,会导致黑屏问题。FlutterEngine provideFlutterEngine(Context context)
此方法返回一个engine实例,返回null时FlutterFragment
的delegate会new一个默认的engine,看似没有问题,但是这个默认的engine的automaticallyRegisterPlugins
为false,也就是不会执行插件注册代码,同时FlutterFragment#configureFlutterEngine()
中默认也没有执行插件注册,会导致flutter插件失效。void configureFlutterEngine(FlutterEngine flutterEngine)
配置engine的hook入口,可以不实现,但是就像前面提到的,会使得flutter插件失效。void cleanUpFlutterEngine(FlutterEngine flutterEngine)
无影响void void onFlutterUiDisplayed()
无影响void void onFlutterUiNoLongerDisplayed()
无影响
为解决上述问题,我们有两种选择:
- Activity继承于SDK提供的
FlutterFragmentActivity
; - 继续使用我们自己的Activity,自定义
FlutterFragment
重写相关方法解决相应问题。
使用FlutterFragmentActivity
此类主要是构造一个FlutterFragment
并将之添加到页面中,其他Host实现与FlutterActivity
完全一致,因此可以解决上面的问题。但是有2点明显的限制:
- 由于Java的单继承特性,我们的原生Activity通常都是要继承于BaseActivity的,故无法继承
FlutterFragmentActivity
; - 从
FlutterFragmentActivity#onCreate
的实现来看,这个activity也是一个flutter full page的页面,无法显示类似FlutterFragment
与其他原生fragment一起放在ViewPager里面进行展示的效果。
出于上述原因,推荐使用自定义FlutterFragment
来解决问题。
使用自定义FlutterFragment
明确问题之后,解决方案就简单了:
public class FlutterProxyFragment extends FlutterFragment {
@Nullable
@Override
public FlutterEngine provideFlutterEngine(@NonNull Context context) {
// 【插件注册问题】方案一:当父类没有生成engine实例时,创建一个,并指定automaticallyRegisterPlugins为true
FlutterEngine engine = super.provideFlutterEngine(context);
if (engine == null) {
// Host activity is not a subclass of flutter activity, do it by ourselves
engine= new FlutterEngine(
getContext(),
getFlutterShellArgs().toArray(),
/*automaticallyRegisterPlugins=*/ true,
/*willProvideRestorationData=*/ shouldRestoreAndSaveState());
}
return engine;
}
@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
super.configureFlutterEngine(flutterEngine);
FragmentActivity attachedActivity = getActivity();
if (attachedActivity instanceof FlutterEngineConfigurator) {
((FlutterEngineConfigurator) attachedActivity).configureFlutterEngine(flutterEngine);
} else {
// 【插件注册问题】方案二:当父类不是FlutterEngineConfigurator时,自己调用注册插件的方法
GeneratedPluginRegister.registerGeneratedPlugins(flutterEngine);
}
}
@Override
public SplashScreen provideSplashScreen() {
// 【启动动画问题】方案:增加逻辑判断
SplashScreen splashScreen = super.provideSplashScreen();
if (splashScreen != null) return splashScreen;
return new LoadingSplashScreen();
}
...
}
上述代码似乎是解决了问题,但其实还有很多细节没有解决。我们对照FlutterFragmentActivity
源码,可以发现下述代码:
@Override
public void onPostResume() {
super.onPostResume();
flutterFragment.onPostResume();
}
@Override
protected void onNewIntent(@NonNull Intent intent) {
// Forward Intents to our FlutterFragment in case it cares.
flutterFragment.onNewIntent(intent);
super.onNewIntent(intent);
}
@Override
public void onBackPressed() {
flutterFragment.onBackPressed();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
flutterFragment.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
@Override
public void onUserLeaveHint() {
flutterFragment.onUserLeaveHint();
}
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
flutterFragment.onTrimMemory(level);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
flutterFragment.onActivityResult(requestCode, resultCode, data);
}
这些需要activity辅助调用的方法都被贴心地打上了
@ActivityCallThrough
标记,妈妈再也不用担心我有遗漏了!
这些方法,要么不是Fragment固有的回调方法,要么虽是固有回调方法,但是必须通过fragment调用才能生效。以onActivityResult为例,只有调用fragment.startActivityForResult
时该回调才会被触发,我们在编写flutter插件时,通常是这样的流程:
public class MyPlugin implements FlutterPlugin, MethodCallHandler, ActivityAware, PluginRegistry.ActivityResultListener{
private MethodChannel channel;
private Activity host;
ActivityPluginBinding binding;
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result){...}
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
channel = new MethodChannel(binding.getBinaryMessenger(), "myplugin");
channel.setMethodCallHandler(this);
final FlutterEngine engine = binding.getFlutterEngine();
engine.getPlatformViewsController().getRegistry().registerViewFactory(
"my_view_factory",
new MyViewFactory(engine.getDartExecutor().getBinaryMessenger()));
}
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
channel.setMethodCallHandler(null);
channel = null;
}
@Override
public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) {
mDelegate.setActivity(binding.getActivity());
// 保存activity引用,在onMethodCall中执行相应方法时调用host.startActivityForResult跳转
// 然后在onActivityResult中处理后续数据回调逻辑
host = binding.getActivity();
this.binding = binding;
// 绑定回调
binding.addActivityResultListener(this);
}
@Override
public void onDetachedFromActivityForConfigChanges() {
onDetachedFromActivity();
}
@Override
public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) {
onAttachedToActivity(binding);
}
@Override
public void onDetachedFromActivity() {
binding.removeActivityResultListener(this);
binding = null;
host = null;
}
@Override
public boolean onActivityResult(int requestCode, int resultCode, Intent data) {...}
}
也就是说即便使用FlutterFragment
,我们还是使用activity.startActivityForResult
启动页面,所以FlutterFragment#onActivityResult
并不会回调,插件的onActivityResult也就无法回调了。这里提供一些解决思路:
- 监听返回按键事件
public class FlutterProxyFragment extends FlutterFragment {
private OnBackPressedCallback mOnBackPressedCallback;
private OnBackPressedCallback fetchOnBackPressedCallback(){
if (mOnBackPressedCallback == null) {
mOnBackPressedCallback = new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
onBackPressed();
}
};
}
return mOnBackPressedCallback;
}
protected boolean watchOnBackPress(){
return getArguments().getBoolean("WATCH_ON_BACK_PRESS",false);
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
if (watchOnBackPress() && context instanceof ComponentActivity && !(context instanceof FlutterFragmentActivity)) {
ComponentActivity activity = (ComponentActivity) context;
activity.getOnBackPressedDispatcher().addCallback(activity, fetchOnBackPressedCallback());
}
}
...
}
- 监听
ComponentCallbacks2
事件
public class FlutterProxyFragment extends FlutterFragment {
private ComponentCallbacks2 mComponentCallbacks;
private ComponentCallbacks2 fetchComponentCallbacks2(){
if (mComponentCallbacks==null){
mComponentCallbacks=new ComponentCallbacks2() {
@Override
public void onTrimMemory(int level) {
FlutterProxyFragment.this.onTrimMemory(level);
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {}
@Override
public void onLowMemory() {}
};
}
return mComponentCallbacks;
}
final List<Runnable> clearUpTasks = new ArrayList<>();
private void clearUp(){
Iterator<Runnable> iterator = clearUpTasks.iterator();
while (iterator.hasNext()) {
iterator.next().run();
iterator.remove();
}
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
if (context instanceof ComponentActivity && !(context instanceof FlutterFragmentActivity)) {
Application application = (Application) context.getApplicationContext();
application.registerComponentCallbacks(fetchComponentCallbacks2());
clearUpTasks.add(() -> application.unregisterComponentCallbacks(fetchComponentCallbacks2()));
}
}
@Override
public void onDetach() {
clearUp();
super.onDetach();
}
...
}
- 监听其他事件
/**
* 定义相关事件的接口类
*/
public interface BaseEventListener{
default void onPostResumeEvent(){}
default void void onNewIntentEvent(@NonNull Intent intent){}
default void onRequestPermissionsResultEvent(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults){}
default void void onUserLeaveHintEvent(){}
default void onActivityResultEvent(int requestCode, int resultCode, @Nullable Intent data){}
}
/**
* 定义注册中心
*/
public interface BaseEventRegistry{
void addBaseEventListener(@NonNull BaseEventListener l);
void removeBaseEventListener(@NonNull BaseEventListener l);
}
/**
* 让BaseActivity实现该接口
*/
public class BaseActivity extends Activity implements BaseEventRegistry{
private Set<BaseEventListener> listeners = new LinkedHashSet();
@Override
public void addBaseEventListener(@NonNull BaseEventListener l){
listeners.add(l);
}
@Override
public void removeBaseEventListener(@NonNull BaseEventListener l){
listeners.remove(l);
}
@Override
public void onPostResume() {
super.onPostResume();
for (BaseEventListener listener : listeners) {
listener.onPostResumeEvent();
}
}
@Override
protected void onNewIntent(@NonNull Intent intent) {
for (BaseEventListener listener : listeners) {
listener.onNewIntentEvent(intent);
}
super.onNewIntent(intent);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
for (BaseEventListener listener : listeners) {
listener.onRequestPermissionsResultEvent(requestCode, permissions, grantResults);
}
}
@Override
public void onUserLeaveHint() {
for (BaseEventListener listener : listeners) {
listener.onUserLeaveHintEvent();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
for (BaseEventListener listener : listeners) {
listener.onActivityResultEvent(requestCode, resultCode, data);
}
}
}
//准备工作完毕,改造FlutterProxyFragment=======================================================================
public class FlutterProxyFragment extends FlutterFragment {
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
if (context instanceof BaseEventRegistry) {
((BaseEventRegistry) context).addBaseEventListener(this);
clearUpTasks.add(() -> ((BaseEventRegistry) context).removeBaseEventListener(this));
}
}
@Override
public void onNewIntentEvent(@NonNull Intent intent) {
this.onNewIntent(intent);
}
@Override
public void onPostResumeEvent() {
this.onPostResume();
}
@Override
public void onUserLeaveHintEvent() {
this.onUserLeaveHint();
}
@Override
public void onActivityResultEvent(int requestCode, int resultCode, @Nullable Intent data) {
this.onActivityResult(requestCode, resultCode, data);
}
@Override
public void onRequestPermissionsResultEvent(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
this.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
...
}
FlutterView
真正渲染flutter页面的控件,归属FlutterActivityAndFragmentDelegate
。主要分为两个部分:
RenderSurface
:对接FlutterEngine
的FlutterRenderer
,用于画面的绘制;- Plugins/Bridge/Processor:对接
FlutterEngine
的各种Channel,用于用户交互事件处理;
由于都需要对接engine,只有attach engine后才能开始工作,当然用完还需要detach。
- attach调用链
flutterView.attachToFlutterEngine(FlutterEngine flutterEngine)
flutterEngine.getPlatformViewsController().attachToView(Veiw view)
- detach调用链
flutterView.detachFromFlutterEngine()
flutterEngine.getPlatformViewsController().detachFromView()
从这一点看,
PlatformView
和FlutterView
流程是一样的
RenderSurface
归属FlutterView
。用于呈现flutter的图像数据(FlutterRenderer
管理,实例归属engine),作用相当于画布或者屏幕,目前flutter支持三种RenderSurface
:
io.flutter.embedding.android.FlutterImageView
:利用普通eView将flutter图像渲染成一张图片,不支持flutter交互;io.flutter.embedding.android.FlutterSurfaceView
:利用SurfaceView将flutter图像渲染成界面,支持交互;io.flutter.embedding.android.FlutterTextureView
:利用TextureView将flutter图像渲染成界面,支持交互;
FlutterEngine
创建和维护与flutter交互的相关FlutterRenderer
、各种Channel等,默认由FlutterActivityAndFragmentDelegate
创建和回收实例,也支持由用于在FlutterActivity
/FlutterFragment
等Host中返回自己维护的engine实例。
XXXChannel
功能型Channel种类和功能简介
这些功能型Channel底层通讯其实都是借助三种通讯型Channel实现的,后面会专门介绍。
engine在构造方法中实例化了各种功能型Channel用于特定领域的原生和dart间的通讯,包括:
AccessibilityChannel
DeferredComponentChannel
KeyEventChannel
LifecycleChannel
LocalizationChannel
MouseCursorChannel
NavigationChannel
:用于java和dart之间路由交互。PlatformChannel
RestorationChannel
SettingsChannel
SystemChannel
TextInputChannel
有些Channel是直接使用,而有些套了一层壳成了XXXPlugin。由于功能型Channel数量比较多,不是本文的重点就不逐一详细介绍了,后面会介绍一些比较常用的Channel。
NavigationChannel
这个是我比较感兴趣的模块,看下Java端代码:
//io.flutter.embedding.engine.systemchannels.NavigationChannel
public class NavigationChannel {
@NonNull public final MethodChannel channel;
public NavigationChannel(@NonNull DartExecutor dartExecutor) {
this.channel = new MethodChannel(dartExecutor, "flutter/navigation", JSONMethodCodec.INSTANCE);
}
public void setInitialRoute(@NonNull String initialRoute) {
// 前面分析过了,这个方法是在run entry point之前调用的
channel.invokeMethod("setInitialRoute", initialRoute);
}
public void pushRoute(@NonNull String route) {
channel.invokeMethod("pushRoute", route);
}
public void popRoute() {
channel.invokeMethod("popRoute", null);
}
public void setMethodCallHandler(@Nullable MethodChannel.MethodCallHandler handler) {
channel.setMethodCallHandler(handler);
}
}
可以说是简洁得不能再简洁了,看下dart端:
//sdk/packages/flutter/lib/src/services/system_channels.dart:50
//全局单例的channel实例
static const MethodChannel navigation = OptionalMethodChannel(
'flutter/navigation',
JSONMethodCodec(),
);
//sdk/packages/flutter/lib/src/widgets/binding.dart:
mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
@override
void initInstances() {
super.initInstances();
...
// 为navigation设置回调
SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
...
}
Future<dynamic> _handleNavigationInvocation(MethodCall methodCall) {
// 对比java那边的代码发现:
// 1. 少了"setInitialRoute"
// 2. 多了'pushRouteInformation'
switch (methodCall.method) {
case 'popRoute':
return handlePopRoute();
case 'pushRoute':
return handlePushRoute(methodCall.arguments as String);
case 'pushRouteInformation':
return _handlePushRouteInformation(methodCall.arguments as Map<dynamic, dynamic>);
}
return Future<dynamic>.value();
}
@protected
Future<void> handlePopRoute() async {
// 依次挨个关闭页面
for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.from(_observers)) {
if (await observer.didPopRoute())
return;
}
// 没有可以关闭的页面,则直接关闭FlutterActivity
SystemNavigator.pop();
}
@protected
@mustCallSuper
Future<void> handlePushRoute(String route) async {
for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.from(_observers)) {
if (await observer.didPushRoute(route))
return;
}
}
...
}
那么这个缺失的setInitialRoute
是在哪里实现的呢?后面dart侧启动过程简介对此有分析。
通讯型Channel类型及实现简介
channel可以分为:
MethodChannel
BasicMessageChannel<T>
EventChannel
我们先看下Java和dart端MethodChannel
的构造方法:
- Java端:
public MethodChannel(BinaryMessenger messenger, String name, MethodCodec codec) {
this.messenger = messenger;
this.name = name;
this.codec = codec;
}
public BasicMessageChannel(
@NonNull BinaryMessenger messenger, @NonNull String name, @NonNull MessageCodec<T> codec) {
this.messenger = messenger;
this.name = name;
this.codec = codec;
}
public EventChannel(BinaryMessenger messenger, String name, MethodCodec codec) {
this.messenger = messenger;
this.name = name;
this.codec = codec;
}
- dart端:
//sdk/packages/flutter/lib/src/services/platform_channel.dart
const MethodChannel(this.name, [this.codec = const StandardMethodCodec(), BinaryMessenger? binaryMessenger ])
: assert(name != null),
assert(codec != null),
_binaryMessenger = binaryMessenger;
const BasicMessageChannel(this.name, this.codec, { BinaryMessenger? binaryMessenger })
: assert(name != null),
assert(codec != null),
_binaryMessenger = binaryMessenger;
const EventChannel(this.name, [this.codec = const StandardMethodCodec(), BinaryMessenger? binaryMessenger])
: assert(name != null),
assert(codec != null),
_binaryMessenger = binaryMessenger;
可以看到在构造Channel实例时,所需的参数是一模一样的,需要传入三个参数:
BinaryMessenger messenger
数据发送通道,无论哪种Channel,走的通道都是这个。BinaryMessenger
本身是一个接口,其真正的实现类是DartMessenger
,其他均为代理类,而最终发送数据的方法都是走的FlutterJNI
实例的native方法。String name
渠道名称,可以认为是渠道的ID。MethodCodec/MessageCodec<T> codec
数据编解码器,用于channel通讯时接口实参的编解码。
任何通讯型Channel在最终都是通过BinaryMessenger#send(String, ByteBuffer, BinaryMessenger.BinaryReply)
向flutter发送消息,且通过BinaryMessenger#setMessageHandler(String, BinaryMessageHandler)
接收回执并分发回调。
了解jsbridge这类Java-JS通讯框架的读者可能闻到了熟悉的味道,是的,流程基本是一样的。
BinaryMessenger
BinaryMessenger
本身是一个接口,真正的实现类是DartMessenger
,所以channel的整个调用链是:
FunctionChannel(Optional) -> CommunicationChannel -> BinaryMessenger(DartMessenger) -> FlutterJNI
再去扒一下这其中涉及到的FlutterJNI的方法:
- void dispatchEmptyPlatformMessage(@NonNull String channel, int responseId)
- void dispatchPlatformMessage(@NonNull String channel, @Nullable ByteBuffer message, int position, int responseId)
- void invokePlatformMessageEmptyResponseCallback(int responseId)
- void invokePlatformMessageResponseCallback(int responseId, @Nullable ByteBuffer message, int position)
好家伙!四个方法实现了channel核心功能,果然大道至简。既然到了这里了不妨再深入一步,前面提到:
在构造通讯型channel时,需要传入一个name,而这个name是作为ID使用的,那如果编码过程不规范,不同的flutter plguin工程在构造各自的channel时使用了相同的name会有什么后果呢?我们从消息发送侧和消息接收侧两个角度来分别分析:
-
发送侧
由于最终都是落到DartMessenger
进行发送,该实例由DartExecutor
维护,而DartExecutor
又归属engine实例,也就是一个engine实例其实对应一个DartMessenger
实例,所以发送侧不会有影响。 -
接收侧
每个Channel实例setMessageHandler实际都是保存在DartMessenger
中,看下它是怎么保存的:
//io.flutter.embedding.engine.dart.DartMessenger#setMessageHandler
@NonNull private final Map<String, BinaryMessenger.BinaryMessageHandler> messageHandlers;
@Override
public void setMessageHandler(@NonNull String channel, @Nullable BinaryMessenger.BinaryMessageHandler handler) {
if (handler == null) {
messageHandlers.remove(channel);
} else {
messageHandlers.put(channel, handler);
}
}
所以在同一个engine实例中,如果存在两个同名channel,消息接收回调会有覆盖问题,导致前一个回调失效。
通过上面的分析我们可以得出结论:
- 在构造Channel时一定要有良好的编码规范,保证name的唯一性;
- 要做好Channel的生命周期管理,及时回收Channel,尤其是及时移除MessageHandler,防止内存泄漏;
MethodCodec/MessageCodec<T>
对于MethodCodec
,Android端默认提供了两种实现:
- `StandardMethodCodec`: 单例,真正编解码交给了`StandardMessageCodec`实例
- `JSONMethodCodec`:单例,真正编解码交给了`JSONMessageCodec`实例,而`JSONMessageCodec`编解码交给了`StringCodec`实例
与之对应的,flutter端也提供了两种实现:
- `StandardMethodCodec`:真正编解码交给了`StandardMessageCodec`实例
- `JSONMethodCodec`:真正编解码交给了`JSONMessageCodec`实例,而`JSONMessageCodec`实例真正编解码交给了`StringCodec`实例
而对于MessageCodec
,flutter framework提供了4种实现:
MessageCodec编解码器 | java数据类型 | dart数据类型 | 使用情况 |
---|---|---|---|
StandardMessageCodec |
|
|
|
JSOMessageCodec | UTF-8编码的json对象 | UTF-8编码的json对象 |
|
BinaryCodec | ByteBuffer | ByteData | – |
StringCodec | UTF-8编码的String | UTF-8编码的String |
|
所以,对于数据编解码器,粗略可以得到下面的结论:
- MethodCodec是MessageCodec的包装类,底层其实都是MessageCodec在干活;
- java和dart端Channel对应的编解码器必须是对应的才能正常完成传输数据编解码;
FlutterJNI
实例由engine持有和销毁。dart代码调用门面类,所以用的地方有点多:FlutterEngine
、DartExecutor
、各种Channel、FlutterRenderer
等等。
DartExecutor
实例由engine创建和维护,用于原生和flutter通信。乍看似乎作用跟FlutterJNI
有点重合,但我们作为上层应用开发基本只要和DartExecutor
打交道就好了,直接使用FlutterJNI
不仅繁杂还容易出现兼容性问题,况且DartExecutor
已经为我们封装好了友好的API接口。
DartMessenger
归属DartExecutor
,是真正发送消息的BinaryMessenger
,前面介绍Channel时已经介绍过,这里略过。
dart侧启动过程简介
本来不想写dart的流程,但由于提到了java层的各种Channel,这里不得不稍微提一下。先看看java是怎么启动dart的main方法的,在前面介绍的FlutterActivityAndFragmentDelegate
中:
// io.flutter.embedding.android.FlutterActivityAndFragmentDelegate#doInitialFlutterViewRun
private void doInitialFlutterViewRun() {
...
// 运行dart的入口方法前,先设置好initialRoute
// 1. dart侧通过:window.defaultRouteName获取这个初始路由
// 2. 同时也说明了,java与dart的通讯不是运行了dart入口方法之后才能进行,而是engine初始化完毕就可以开始
String initialRoute = host.getInitialRoute();
if (initialRoute == null) {
initialRoute = maybeGetInitialRouteFromIntent(host.getActivity().getIntent());
if (initialRoute == null) {
initialRoute = DEFAULT_INITIAL_ROUTE;
}
}
flutterEngine.getNavigationChannel().setInitialRoute(initialRoute);
// 构造dart运行入口,并执行之
String appBundlePathOverride = host.getAppBundlePath();
if (appBundlePathOverride == null || appBundlePathOverride.isEmpty()) {
appBundlePathOverride = FlutterInjector.instance().flutterLoader().findAppBundlePath();
}
DartExecutor.DartEntrypoint entrypoint =
new DartExecutor.DartEntrypoint(
appBundlePathOverride, host.getDartEntrypointFunctionName());
flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint);
}
默认情况下,我们没有在host.getDartEntrypointFunctionName()
中返回自定义的入口点名称,所以就走默认的,即“main”。那如果要自定义dart入口方法该怎么做呢?
- java侧:
在host.getDartEntrypointFunctionName()
返回自定义FlutterActivity
/FlutterFragment
,覆写getDartEntrypointFunctionName()
方法:
class MyFlutterActivity extends FlutterActivity{
@NonNull
public String getDartEntrypointFunctionName() {
// 判断是否使用自定义dart入口
boolean useCustomDartEntry = true;
if(useCustomDartEntry){
// 比如我们自定义的入口叫"userMain"
return "userMain";
} else {
// 默认从manifest/application的meta-data读取
return super.getDartEntrypointFunctionName();
}
}
}
- dart侧:
void main() {
runApp(getFirstScreen(window.defaultRouteName));
}
/// 采用@pragma('vm:entry-point')来标记入口函数
@pragma('vm:entry-point')
void userMain() {
//耗时方法要异步执行
SPUtil().init().then((v){
runApp(MaterialApp(
home: Text("这是userMain"),
));
});
}
flutter侧那么多的channel又是什么时候初始化的呢?我们先从runApp
这个方法为入口开始寻觅:
//sdk/packages/flutter/lib/src/widgets/binding.dart
void runApp(Widget app) {
//初始化WidgetsBinding实例
WidgetsFlutterBinding.ensureInitialized()
//异步执行WidgetsBinding#attachRootWidget方法,更新root widget
..scheduleAttachRootWidget(app)
//执行SchedulerBinding#scheduleWarmUpFrame立即渲染一个启动页,而不必等系统"Vsync"信号
..scheduleWarmUpFrame();
}
WidgetsBinding是所有binding的胶水类,初始化WidgetsBinding实例也就初始化了flutter bindings,但是还是无法解释runApp
执行之前,java端navigationChannel.setInitialRoute(initialRoute)
如何生效。于是继续扒window.defaultRouteName
,这里window是SingletonFlutterWindow
的全局实例(单例),defaultRouteName则调用了PlatformDispatcher#defaultRouteName
,而PlatformDispatcher也是个单例,相关代码:
/// 调用native方法获取原生侧设置的InitialRoute,接口文档也强调[`FlutterView.setInitialRoute`]
/// 和[`FlutterViewController.setInitialRoute`]必须在运行dart入口方法前调用
String get defaultRouteName => _defaultRouteName();
String _defaultRouteName() native 'PlatformConfiguration_defaultRouteName';
至此终于有了思路,或许是用c层做桥接做了InitialRoute的缓存而已,也不存在runApp
执行之前java与dart发生交互的事情。
FlutterRenderer
归属engine,处理flutter图像渲染相关的功能,涉及到相关flutterJNI接口的调用,了解不多,这里略过。
FlutterEngineConnectionRegistry
归属engine,实现了PluginRegistry
、ActivityControlSurface
、ServiceControlSurface
、 BroadcastReceiverControlSurface
、 ContentProviderControlSurface
接口,是flutter插件的注册中心,并赋予插件与四大组件之间基本的attach/detach回调和组件独有的回调能力。
提到flutter插件需要额外关注插件的注册时机,只有执行了注册,插件才能正常使用。通过前面的代码分析,我们可以总结有以下两个注册时机:
FlutterEngine
构造方法
当automaticallyRegisterPlugins
为true时,会调用FlutterEngine#registerPlugins
进行注册;FlutterEngineConfigurator#configureFlutterEngine(@NonNull FlutterEngine flutterEngine)
在FlutterActivity
和FlutterFragmentActivity
的方法重写中,调用了GeneratedPluginRegister#registerGeneratedPlugins()
完成了插件注册;
源码看到这里的时候,一度发生逻辑混乱,
PluginRegistry
的实现类一会只有一个,一会有多个,仔细看发现有两个同名接口:io.flutter.embedding.engine.plugins.PluginRegistry
和io.flutter.plugin.common.PluginRegistry
,而后者已经被废弃。读者在看flutter源码的时候要注意以embedding包为主。
PlatformViewsController
归属engine,用于实现PlatformViews功能,对接engine的PlatformViewsChannel
。前面也提到过,需要engine调用getActivityControlSurface().attachToActivity()
->PlatformViewsController#attach
才真正完成初始化。
其他
上面介绍的几个类是整个flutter运行的核心类(类似于bootstrap class),了解这些类的作用我们就从宏观上了解了flutter的运行细节。剩下就是flutter各个具体的功能模块了,按需了解即可,这里简要介绍一下。
PlatformPlugin
归属FlutterActivityAndFragmentDelegate
,对接engine的PlatformChannel
。我们从PlatformPlugin#mPlatformMessageHandler
可以了解到其主要负责处理一些琐碎的系统服务功能,包括:
- 触觉的震动、提示音;
- 屏幕方向;
- 任务管理器中任务描述信息(TaskDescription);
- 系统界面overlay相关:状态栏、导航栏显示等;
- 页面栈回退处理:activity/fragment,对接dart端的SystemNavigator;
- 读写系统剪贴板内容;
LocalizationPlugin
归属engine,对接engine的LocalizationChannel
,处理时区相关逻辑。支持获取当前时区和通知flutter时区变更。
engine管理策略
前面我们把flutter核心模块的代码基本过了一遍,对相应的类功能和用法也有了了解,本节开始将介绍一些开发相关的内容。
首要需要确定的是engine管理策略,因为不同的管理策略的代码实现复杂度差异较大,内存使用和性能方面也有所差异。flutter engine的管理模式有两种:
- 多engine模式:每次启动flutter页面都创建新的engine实例;
- 单engine模式:整个APP复用同一个engine实例;
关于engine管理策略的选择我认为没有好坏之分,否则flutter官方没有必要同时支持了这两种方式。这就跟我们在使用WebView
时是选择用单例还是创建新的实例一样,应该根据APP的业务场景来定。
多engine策略
广义上的engine隔离策略,每个flutter页面都创建新的engine,由于engine实例之间的堆内存是独立和隔离的,dart侧的缓存不可共享,需要原生搭桥进行通讯。这种策略:
- 优点:
- 实现简单,开发代码量少,不需要维护复杂的页面栈;
- flutter framework默认的策略,因此兼容性最好;
- 缺点:
- 无法使用flutter侧内存缓存;
- 多engine内存消耗会稍多;
- 每次启动flutter页面都要经历engine初始化过程;
实际上针对多engine策略的缺陷,flutter已做了优化工作:
FlutterEngine#spawn
可用于在已有engine的基础上以极低的内存和时间成本创建第二个engine,详见FlutterEngineGroup
,使用方法比较简单这里不详细介绍了;- release版的flutter编译成果物engine初始化时间实测是小于300毫秒的,用户几乎感受不到;
页面实例构建
对于activity:
public class FlutterProxyActivity extends FlutterActivity {
static Intent newEngineIntent(Context context,String pageUri){
Intent intent = new FlutterActivity.NewEngineIntentBuilder(FlutterProxyActivity.class)
.backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.opaque)
.initialRoute(pageUri)
.build(context);
if (!(context instanceof Activity)) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
return intent;
}
...
}
对于fragment:
public class FlutterProxyFragment extends FlutterFragment {
public static Fragment newEngineFragment(String pageUri) {
return new NewEngineFragmentBuilder(FlutterProxyFragment.class)
.renderMode(RenderMode.texture)
.initialRoute(pageUri)
.build();
}
...
}
这里只列举了最常见的启动参数,完全可以自定义更多参数
引擎管理
flutter默认策略
每次开启一个flutter页面时(包括activity/fragment),都创建新的engine。engine实例生命周期跟随页面实例,即页面创建同时创建engine,页面销毁同时销毁engine。这种方案的优点是实现简单,缺点就像前面提到的,内存占用问题和启动耗时问题。
FlutterEngineGroup
方案
从前面关于FlutterEngine
和FlutterActivityAndFragmentDelegate
的分析可知,默认创建engine实例调用的是engine的构造方法。FlutterEngine
同时还提供了FlutterEngine#spawn(@NonNull Context context, @NonNull DartEntrypoint dartEntrypoint)
来构建engine,从而加速实例化过程和节省内存消耗。那么优化后的方案可以这样:
- 启动第一个flutter页面时,走默认构造方法创建engine实例,并缓存engine实例;
- 启动第二个flutter页面时,走
engine#spawn
创建新的实例,并缓存engine实例,第n个flutter页面亦如是; - 关闭flutter页面时,同时销毁engine并移除engine缓存,第n个flutter页面亦如是;
这个方案的优点是从第二个flutter页面开始启动速度和内存占用都是比较理想的,并且flutter给我们提供了现成的工具类FlutterEngineGroup
。用法也很简单:
// 创建engine并启动dart vm
FlutterEngineGroup engineGroup = new FlutterEngineGroup(this);
DartExecutor.DartEntrypoint dartEntrypoint = new DartExecutor.DartEntrypoint(
FlutterInjector.instance().flutterLoader().findAppBundlePath(), "entry_0"
);
FlutterEngine topEngine = engineGroup.createAndRunEngine(this, dartEntrypoint);
// engine可以自己也缓存一份,也可以不缓存
FlutterEngineCache.getInstance().put("entry_0", topEngine);
// 如果缓存了engine,就要自己负责移除
FlutterEngineCache.getInstance().remove("entry_0")
强烈建议看下FlutterEngineGroup的实现原理,这样可以帮助我们实现各种自定义多engine方案。
自定义策略
如果对上面的方案不满意,可以实现自己的方案,比如:
- 启动第一个flutter页面时,走默认构造方法创建engine实例,并缓存engine实例;
- 启动第二个flutter页面时,走
engine#spawn
创建新的实例,第n个flutter页面亦如是; - 关闭第二个flutter页面时,同时销毁engine,第n个flutter页面亦如是;
- 关闭第一个flutter页面时,不销毁engine;
- flutter全部关闭,再次启动第一个flutter页面,由于存在一个缓存的engine实例,于是直接使用该实例;
- 从第2步开始循环操作;
路由管理
每个engine实例在创建时可以传入initialRoute指定初始页面,并且通过URL可实现待参跳转
窗口管理
在一次flutter会话中,建议直接在当前FlutterActivity
push新的flutter页面。如果这期间打开了原生的activity并覆盖在FlutterActivity
之上,此时又需要打开一个flutter页面,那么要分情况讨论:
- 新的flutter页面跟上一个是同一个会话
此时应该finishUntil上一个FlutterActivity
实例,并在该实例中push新的flutter页面。 - 新的flutter页面是一个全新的会话
此时直接创建一个新的FlutterActivity
实例并start即可。
不过这种逻辑也并不能绝对的,上述逻辑可以作为默认逻辑,也应该提供配置项让用户强制指定新flutter页面的窗口策略。比如:
- new:新开
FlutterActivity
; - current:直接在上个
FlutterActivity
打开,不管是否被覆盖; - back:对上前面的finishUntil上一个
FlutterActivity
实例; - default:上面的默认逻辑,作为缺省值;
单engine策略
广义上也就是engine复用策略。
页面实例构建
对于activity:
public class FlutterProxyActivity extends FlutterActivity {
static Intent cachedEngineIntent(Context context, String engineId) {
Intent intent = new CachedEngineIntentBuilder(FlutterProxyActivity.class, engineId)
.backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.opaque)
.destroyEngineWithActivity(true)
.build(context);
if (!(context instanceof Activity)) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
return intent;
}
...
}
对于fragment:
public class FlutterProxyFragment extends FlutterFragment {
private static final String TAG = "FlutterProxyFragment";
public static Fragment cachedEngineFragment(String engineId) {
CachedEngineFragmentBuilder builder = new CachedEngineFragmentBuilder(FlutterProxyFragment.class,engineId)
.renderMode(RenderMode.texture)
.destroyEngineWithFragment(false);
return builder.build();
}
...
}
引擎管理
由于整个APP复用一个engine,只需要关注engine的初始化和销毁时机即可。
- 初始化
- 懒加载:即应用启动时先不初始化engine,直到第一个flutter页面渲染时才初始化
- 立即加载:应用启动时立即开启线程初始化一个engine
- 销毁
- 销毁:当所有flutter页面销毁时,销毁engine,从而减少APP内存占用
- 不销毁:保持一个engine实例,不去销毁,随时快速加载flutter页面
路由管理
需要借助自定义channel实现页面更新。
窗口管理
同多engine策略,不过单engine在实现上会困难得多,因为所有FlutterActivity
都共享engine,FlutterActivity
的页面栈需要随时整体切换刷新。实现上可以参考flutter_boost等一众相关开源项目。