版本号0.1.54
看源码之前,我先去看下官方文档,对于其源码的设计说明,文中所说的原生都是指android

看完官方文档的说明,我有以下几个疑问

第一个:容器是怎么设计的?

第二个:native和flutter的channel的通道是如何设计的?

第三个:Flutter是适配层到底再做些什么?

中控中心FlutterBoost

单独拎出来讲讲,这个类比较简单,就是集合各个模块并让其初始化,同时也是该插件入口处,不管原生和flutter都一样,看源码也是从这里开始看起,但原生和flutter的初始化流程稍微有少许区别,主要还是因为原生是作为容器,flutter的容器是依赖于原生容器。

原生init

入口:/flutter_boost/example/android/app/src/main/java/com/taobao/idlefish/flutterboostexample/MyApplication.java

FlutterBoost.init从这里开始进入

 FlutterBoost.init(new Platform() {

            @Override
public Application getApplication() {
return MyApplication.this;
} @Override
public boolean isDebug() {
return true;
} @Override
public void openContainer(Context context, String url, Map<String, Object> urlParams, int requestCode, Map<String, Object> exts) {
PageRouter.openPageByUrl(context, url, urlParams, requestCode);
} @Override
public IFlutterEngineProvider engineProvider() {
//注意这里 覆写了createEngine
return new BoostEngineProvider() {
@Override
public BoostFlutterEngine createEngine(Context context) {
return new BoostFlutterEngine(context, new DartExecutor.DartEntrypoint(
context.getResources().getAssets(),
FlutterMain.findAppBundlePath(context),
"main"), "/");
}
};
} @Override
public int whenEngineStart() {
return ANY_ACTIVITY_CREATED;
}
}); BoostChannel.addActionAfterRegistered(new BoostChannel.ActionAfterRegistered() {
@Override
public void onChannelRegistered(BoostChannel channel) {
//platform view register should use FlutterPluginRegistry instread of BoostPluginRegistry
TextPlatformViewPlugin.register(FlutterBoost.singleton().engineProvider().tryGetEngine().getPluginRegistry());
}
});
}

上面大部分方法,做过android也知道是干嘛的,这里重点讲讲IFlutterEngineProvider这个接口,这里有3个方法,如下

/**
* a flutter engine provider
*/
public interface IFlutterEngineProvider { /**
* create flutter engine, we just hold a single instance now
* @param context
* @return
*/
BoostFlutterEngine createEngine(Context context); /**
* provide a flutter engine
* @param context
* @return
*/
BoostFlutterEngine provideEngine(Context context); /**
* may return null
* @return
*/
BoostFlutterEngine tryGetEngine();
}

抽象成接口,根据项目的实际情况,开发者可以自己实现flutter引擎,或采用官方源码里自己的实现类即BoostEngineProvider,但想不明白 createEngine 和provideEngine 到底有何区别,到底为何弄成两个方法,不就是个提供个flutter引擎实例吗?

看了下createEngine的实现,主要加载实例BoostFlutterEngine,这个实例看名字也清楚是进行flutter引擎的初始化,设置了dart默认入口点即main,设置了路由起点及插件的声明注册一类
然后去看provideEngine方法的实现,代码较少,如下

   @Override
public BoostFlutterEngine provideEngine(Context context) {
Utils.assertCallOnMainThread(); if (mEngine == null) {
FlutterShellArgs flutterShellArgs = new FlutterShellArgs(new String[0]);
FlutterMain.ensureInitializationComplete(
context.getApplicationContext(), flutterShellArgs.toArray()); mEngine = createEngine(context.getApplicationContext()); final IStateListener stateListener = FlutterBoost.sInstance.mStateListener;
if(stateListener != null) {
stateListener.onEngineCreated(mEngine);
}
}
return mEngine;
}

初始化flutter参数及增加一个回调,没什么特别之处,然后去翻了下flutter.jar的FlutterActivity源码,它的flutter引擎初始化最后是追踪到FlutterFragment,关键代码如下

public void onAttach(Context context) {
super.onAttach(context);
//这里初始化flutter参数
this.initializeFlutter(this.getContextCompat());
if (this.flutterEngine == null) {
//这里是初始化flutter引擎
this.setupFlutterEngine();
} this.platformPlugin = new PlatformPlugin(this.getActivity(), this.flutterEngine.getPlatformChannel());
}

这里是连在一起的,flutter源码没有翻来覆去全看一遍,闲鱼进行这样的接口设计应该是有一定的原因

这里再单独讲下插件的注册,我们知道native是作为插件库需要原生项目依赖,在初始化中,注意一下插件的注册,是用反射实现的,如下
路径:flutter_boost/android/src/main/java/com/idlefish/flutterboost/BoostFlutterView.java 中的init方法,代码如下

  private void init() {
...
mFlutterEngine.startRun((Activity)getContext());
...
}

跟随startRun方法深入,就会找到FlutterBoost.singleton().platform().registerPlugins(mBoostPluginRegistry),跟着下去,会发现使用反射方式来实现插件注册 如下代码

 @Override
public void registerPlugins(PluginRegistry registry) {
try {
Class clz = Class.forName("io.flutter.plugins.GeneratedPluginRegistrant");
Method method = clz.getDeclaredMethod("registerWith",PluginRegistry.class);
method.invoke(null,registry);
}catch (Throwable t){
throw new RuntimeException(t);
}
}

毕竟引擎初始化框架重新编写了,所以在插件的注册上也改变了,init的原生部分就讲解到此

flutter

入口:/flutterProject/flutter_boost/example/lib/main.dart
flutter的源码查看前,大家务必先去看看flutter的初始化流程,Navigator源码解析及Route源码解析,因为不晓得相关初始化流程及Navigator的设计原理,里面的关键调用 大家都可能看不明白,我这边可能也是直接就过了,这里给个链接大家可以去看看

Flutter 源码解析

@override
void initState() {
super.initState();
print('_MyAppState initState');
///路由注册,原生通过MethodChannel通道来启动对应的flutter页面
FlutterBoost.singleton.registerPageBuilders({
'first': (pageName, params, _) => FirstRouteWidget(),
'second': (pageName, params, _) => SecondRouteWidget(),
'tab': (pageName, params, _) => TabRouteWidget(),
'flutterFragment': (pageName, params, _) => FragmentRouteWidget(params), ///可以在native层通过 getContainerParams 来传递参数
'flutterPage': (pageName, params, _) {
print("flutterPage params:$params"); return FlutterRouteWidget();
},
});
} @override
Widget build(BuildContext context) {
print('_MyAppState build');
return MaterialApp(
title: 'Flutter Boost example',
builder: FlutterBoost.init(postPush: _onRoutePushed),
home: Container());
}
///flutter 路由push 监听,每启动一个新的flutter页面 就回调该方法
void _onRoutePushed(
String pageName, String uniqueId, Map params, Route route, Future _) {
print('pageName'+pageName+"\n");
}

先跟随FlutterBoost.singleton进去看看,其构造函数如下

FlutterBoost(){
Logger.log('FlutterBoost 构造函数');
ContainerCoordinator(_boostChannel);
}

跟随着ContainerCoordinator去看看,ContainerCoordinator和BoostChannel如何一起来维护通信通道,ContainerCoordinator构造函数如下

ContainerCoordinator(BoostChannel channel) {
assert(_instance == null); _instance = this; channel.addEventListener("lifecycle",
(String name, Map arguments) => _onChannelEvent(arguments)); channel.addMethodHandler((MethodCall call) => _onMethodCall(call));
}

增加native生命周期监听,增加方法监听,再去看看_onChannelEvent和_onMethodCall方法就应该清楚ContainerCoordinator其实就是翻译员,将与原生通信的协议进行解释翻译,根据传过来的事件名,方法名 逐一进行需要的框架处理,BoostChannel其实是声明通道,将接收和发送功能进行封装,接收natvie传来的信息,将从flutter的信息发送到native,当然也做了一部分的框架业务处理,将event和method事件进行区分
分发,个人觉得将该功能直接丢至ContainerCoordinator处理可能更好点,应该是出于为了区分event和method特意在BoostChannel进行处理

接下来看看ContainerCoordinator对于native传过来的通信数据处理,代码如下,就分为之前说的event和method两类,代码注释也写了

 /// 对native 整个应用的 生命周期 进行抽象出的几个行为事件,让flutter做相应的处理
/// android端 基本上除了有回退事件的处理,剩余的生命周期 仅仅是做了监听没做任何处理
/// 分别是回退处理 android才有
/// foreground 本应用是处于前台
/// background 本应用是处于后台
/// scheduleFrame 触发一帧的绘制,但ios和android 都没找到发送该事件的代码,老版本遗留代码?
Future<dynamic> _onChannelEvent(dynamic event) {
...
} /// 对native view生命周期(在android 就是activity)进行抽象出的 几个行为事件,
/// 让flutter做相应的框架处理
Future<dynamic> _onMethodCall(MethodCall call) {
...
}

这里就不讲Method的处理逻辑,后面会结合容器部分重点讲

接下来再回到main.dart文件,跟随FlutterBoost.init方法进去看一下,就是初始化BoostContainerManager,不再深入,后面会结合起来一起讲BoostContainerManager

channle

这模块代码比较少,先从这模块开始讲起

我们知道原生和flutter之间的通信就是通过MethodChannel这个类实现的(原生和flutter的类名一样),前面有讲flutter的boost_channel.dart的作用,native的BoostChannel其实也一样,将接收和发送功能进行封装,接收flutter传来的信息,将从native的信息发送到flutter

原生部分

前面原生初始化 讲到插件的注册是通过反射实现的,GeneratedPluginRegistrant.java当中的registerWith方法我们接下去看一下,注册的时候做了哪些事,路径lutter_boost/android/src/main/java/com/idlefish/flutterboost/BoostChannel.java

public static void registerWith(PluginRegistry.Registrar registrar) {
sInstance = new BoostChannel(registrar);
//通道注册后,处理flutter的method 调用处理
for(ActionAfterRegistered a : sActions) {
a.onChannelRegistered(sInstance);
}
//状态监听 回调
if(FlutterBoost.sInstance != null) {
final IStateListener stateListener = FlutterBoost.sInstance.mStateListener;
if (stateListener != null) {
stateListener.onChannelRegistered(registrar, sInstance);
}
} sActions.clear();
}

看到了吧,BoostChannel的实例化是在插件注册的时候进行的,继续深入,如下代码

  private BoostChannel(PluginRegistry.Registrar registrar){
mMethodChannel = new MethodChannel(registrar.messenger(), "flutter_boost"); mMethodChannel.setMethodCallHandler(new MethodChannel.MethodCallHandler() {
@Override
public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) { if (methodCall.method.equals("__event__")) {
String name = methodCall.argument("name");
Map args = methodCall.argument("arguments"); Object[] listeners = null;
synchronized (mEventListeners) {
Set<EventListener> set = mEventListeners.get(name);
if (set != null) {
listeners = set.toArray();
}
} if(listeners != null) {
for(Object o:listeners) {
((EventListener)o).onEvent(name,args);
}
}
}else{
Object[] handlers;
synchronized (mMethodCallHandlers) {
handlers = mMethodCallHandlers.toArray();
} for(Object o:handlers) {
((MethodChannel.MethodCallHandler)o).onMethodCall(methodCall,result);
}
}
}
});
}

对于通道上的数据分为两类event和method,都是和flutter一一对应的,前面flutter初始化中,也讲过,在原生这边event 没有框架上的业务处理,但提供了回调,根据自己的业务是否需要增加监听

method的处理,去查看BoostMethodHandler,FlutterBoost.java作为内部类存在,如下

class BoostMethodHandler implements MethodChannel.MethodCallHandler {

        @Override
public void onMethodCall(MethodCall methodCall, final MethodChannel.Result result) {
switch (methodCall.method) {
case "pageOnStart":
...
break;
case "openPage":
...
break;
case "closePage":
...
break;
case "onShownContainerChanged":
...
break;
default:
{
result.notImplemented();
}
}
}
}

看这些switch的case处理,大概也猜出来是干嘛的了,是flutter通知native的页面行为事件,例openPage,closePage等等 ,在初始化中,讲了挺多flutter的chanell,所以这里就不在讲了,但是讲讲两边的设计
两边都有个channel类,主要都是用来接收和发送消息的
flutter专门有一个类ContainerCoordinator.dart,中文翻译过来就是集装箱协调员,用于通信事件的统一处理,就是将从原生接收到的信息进行处理,但是在原生那边并没有类似的类,而是将这个工作放在FlutterBoost.java这个内部类中,个人觉得为了保持统一可以专门抽象出个类,将该功能放置该类中,放在FlutterBoost.java不能保持高度统一且不雅观吧

讲到这里,其实通道的设计大家应该理解得差不多了(解决开头提出的问题)

native和flutter的channel的通道是如何设计的?

容器

原生部分

闲鱼的栈管理方案,是将栈的管理都放置原生,所以在原生必须暴露栈的管理,让项目接入方能在原有栈的解决方案上 融合进闲鱼的栈管理方案,所以页面的打开就是入口处,从该入口处去查看容器的设计,先从demo中的PageRouter.java看起,如下代码

public class PageRouter {

    public static final String NATIVE_PAGE_URL = "sample://nativePage";
public static final String FLUTTER_PAGE_URL = "sample://flutterPage";
public static final String FLUTTER_FRAGMENT_PAGE_URL = "sample://flutterFragmentPage"; public static boolean openPageByUrl(Context context, String url,Map params) {
return openPageByUrl(context, url,params, 0);
} public static boolean openPageByUrl(Context context, String url, Map params, int requestCode) {
try {
if (url.startsWith(FLUTTER_PAGE_URL)) {
context.startActivity(new Intent(context, FlutterPageActivity.class));
return true;
} else if (url.startsWith(FLUTTER_FRAGMENT_PAGE_URL)) {
context.startActivity(new Intent(context, FlutterFragmentPageActivity.class));
return true;
} else if (url.startsWith(NATIVE_PAGE_URL)) {
context.startActivity(new Intent(context, NativePageActivity.class));
return true;
} else {
return false;
}
} catch (Throwable t) {
return false;
}
}
}

如果大家用过阿里的Aroute路由框架,就会觉得很亲切,将每个View配置一个路由,还是前端的思想借鉴过来,一个统一的界面打开处,根据路由路径,判断是原生view还是FlutterView,分别打开不同的Activity

接入方,在这里可以根据自身的原生栈管理再进行抽象封装就ok了

接下来看看哪里调用了openPageByUrl(注意是下面那个)方法,发现正是我们一开始框架初始化的时候在调用,如下,文件路径flutter_boost/example/android/app/src/main/java/com/taobao/idlefish/flutterboostexample/MyApplication.java

 @Override
public void openContainer(Context context, String url, Map<String, Object> urlParams, int requestCode, Map<String, Object> exts) {
PageRouter.openPageByUrl(context, url, urlParams, requestCode);
}

再查看是哪里调用了该方法,一直追踪到FlutterBoost.java,关键代码如下

case "openPage":
{
try {
Map<String,Object> params = methodCall.argument("urlParams");
Map<String,Object> exts = methodCall.argument("exts");
String url = methodCall.argument("url"); mManager.openContainer(url, params, exts, new FlutterViewContainerManager.OnResult() {
@Override
public void onResult(Map<String, Object> rlt) {
if (result != null) {
result.success(rlt);
}
}
});
}catch (Throwable t){
result.error("open page error",t.getMessage(),t);
}
}

Flutter 通过channel通道 通知原生 要打开一个新的页面,然后原生将自身的生命周期通过通道告知flutter,flutter再进行相应的页面处理,虽然短短一句话,但其中的逻辑及代码量还是很多的...

前面已经讲了通道部分,这里再贴点关键代码,原生将自身的生命周期通过通道告知flutter,关键代码如下

private class MethodChannelProxy {
private int mState = STATE_UNKNOW; private void create() {
...
}
private void appear() {
...
}
private void disappear() {
...
}
}
private void destroy() {
..
}
public void invokeChannel(String method, String url, Map params, String uniqueId) {
...
}
public void invokeChannelUnsafe(String method, String url, Map params, String uniqueId) {
..
}
}
public static String genUniqueId(Object obj) {
return System.currentTimeMillis() + "-" + obj.hashCode();
}
}

ok,现在已经找到了MethodChannelProxy类,那我们就继续回找(注意我这里讲解都是从冰山一角再慢慢往上查,最终再将冰山一起探索完毕)MethodChannelProxy是作为ContainerRecord.java的内部类存在。接下来我们来看ContainerRecord类,其实现了IContainerRecord接口,再继续深究找到IOperateSyncer接口,代码如下

public interface IOperateSyncer {

    void onCreate();

    void onAppear();

    void onDisappear();

    void onDestroy();

    void onBackPressed();

    void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults);

    void onNewIntent(Intent intent);

    void onActivityResult(int requestCode, int resultCode, Intent data);

    void onContainerResult(int requestCode, int resultCode, Map<String,Object> result);

    void onUserLeaveHint();

    void onTrimMemory(int level);

    void onLowMemory();
}

该接口是通过对原生的生命周期再结合flutter的生命周期特色及android自身的特性(有回退物理键)抽象出来的,继续回到接下来我们来看ContainerRecord类,发现MethodChannelProxy类其实就是做个代理功能,看名字也清楚,在接口方法被调用的时候,通知flutter

接下来看看IContainerRecord的方法被调用处,追踪到BoostFlutterActivity.java和BoostFlutterFragment.java,这里我们只看Activity,Fragment基本差不多。我们看到BoostFlutterActivity在走onCreate生命周期方法时,创建了mSyncer,关键代码如下

   @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); configureWindowForTransparency(); mSyncer = FlutterBoost.singleton().containerManager().generateSyncer(this); mFlutterEngine = createFlutterEngine();
mFlutterView = createFlutterView(mFlutterEngine); setContentView(mFlutterView); mSyncer.onCreate(); configureStatusBarForFullscreenFlutterExperience();
}

FlutterBoost.singleton().containerManager().generateSyncer() 继续深入,追踪到FlutterViewContainerManager类,关键代码如下

@Override
public IOperateSyncer generateSyncer(IFlutterViewContainer container) {
Utils.assertCallOnMainThread();
//创建容器记录实例
ContainerRecord record = new ContainerRecord(this, container);
if (mRecordMap.put(container, record) != null) {
Debuger.exception("container:" + container.getContainerUrl() + " already exists!");
}
mRefs.add(new ContainerRef(record.uniqueId(),container));
//讲接口引用返回
return record;
}

ContainerRecord实例在这里创建,同时将接口引用返给Activity,这样就和原生View的生命周期关联起来了

注意到这里我们已经追踪到FlutterViewContainerManager.java类,已经可以从上帝视角去看了。

刚刚从容器打开出入一直追踪到FlutterViewContainerManager.java类,该类看名字就清楚就是容器的管理者,容器创建、打开、关闭、销毁、弹出、移除等等工作都是在这儿,这里最关键的generateSyncer方法刚刚追踪的时候已经讲过。这里再重点讲讲该类的setContainerResult方法,如下

void setContainerResult(IContainerRecord record,int requestCode, int resultCode, Map<String,Object> result) {

        IFlutterViewContainer target = findContainerById(record.uniqueId());
if(target == null) {
Debuger.exception("setContainerResult error, url="+record.getContainer().getContainerUrl());
} if (result == null) {
result = new HashMap<>();
} result.put("_requestCode__",requestCode);
result.put("_resultCode__",resultCode); final OnResult onResult = mOnResults.remove(record.uniqueId());
if(onResult != null) {
onResult.onResult(result);
}
}

单独拎出来讲,主要是本人好奇 目标页 向 起始面 如何传输数据的,
在纯ntive就是靠着onActivityResult回调拿到目标页传回的数据,该方法就是处理目标页传回来后的处理

在混合栈中 就分为3种情况

1.native-flutter
2.flutter-native
3.flutter-flutter

native和ntive就不用说了,都用不到该框架


第一种情况:native-flutter
demo自身当中并没有相关的演示代码,于是我按照原生是如何接受传回来的数据去进行更改,改了两处如下,类路径flutterProject/flutter_boost/example/android/app/src/main/java/com/taobao/idlefish/flutterboostexample/MainActivity.java

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
data.getSerializableExtra(IFlutterViewContainer.RESULT_KEY);
Debuger.log("MainActivityResult"+data.getSerializableExtra(IFlutterViewContainer.RESULT_KEY).toString());
}
还有一处,类路径flutterProject/flutter_boost/example/android/app/src/main/java/com/taobao/idlefish/flutterboostexample/PageRouter.java
如下

public static boolean openPageByUrl(Context context, String url, Map params, int requestCode) {
try {
if (url.startsWith(FLUTTER_PAGE_URL)) {
//接受目标页的回传必须通过startActivityForResult进行打开
((Activity)context).startActivityForResult(new Intent(context, FlutterPageActivity.class),requestCode);
return true;
} else if (url.startsWith(FLUTTER_FRAGMENT_PAGE_URL)) {
context.startActivity(new Intent(context, FlutterFragmentPageActivity.class));
return true;
} else if (url.startsWith(NATIVE_PAGE_URL)) {
context.startActivity(new Intent(context, NativePageActivity.class));
return true;
} else {
// context.startActivity(new Intent(context, FlutterTwoPageActivity.class));
return false;
}
} catch (Throwable t) {
return false;
}
}

还有记得修改调起的Flutter页面是'second',因为demo中只有它才有传回数据,实现原理这里我简单描述,不详细讲了,就是flutter在关闭页面的时候,传回数据,如下

class SecondRouteWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Route"),
),
body: Center(
child: RaisedButton(
onPressed: () {
// Navigate back to first route when tapped. BoostContainerSettings settings =
BoostContainer.of(context).settings;
FlutterBoost.singleton.close(settings.uniqueId,
result: {"result": "data from second"});
},
child: Text('Go back with result!'),
),
),
);
}
}

跟踪关闭代码逻辑,通过之前建立的通信通道,传过去相关方法,即closePage,原生接收到之后的逻辑代码处理如下(类文件路径flutterProject/flutter_boost/android/src/main/java/com/idlefish/flutterboost/FlutterBoost.java):

 case "closePage":
{
try {
String uniqueId = methodCall.argument("uniqueId");
Map<String,Object> resultData = methodCall.argument("result");
Map<String,Object> exts = methodCall.argument("exts"); mManager.closeContainer(uniqueId, resultData,exts);
result.success(true);
}catch (Throwable t){
result.error("close page error",t.getMessage(),t);
}
}

追踪closeContainer,一直追踪到BoostFlutterActivity.java的finishContainer方法,如下

@Override
public void finishContainer(Map<String,Object> result) {
if(result != null) {
FlutterBoost.setBoostResult(this,new HashMap<>(result));
finish();
}else{
finish();
}
}

再跟踪下去,逻辑很明朗了就不详细讲了


第二种情况:flutter-native
闲鱼的混合栈方案里,每个flutter都有自己的独立原生宿主View,所以回调也得依赖原生

原生我们知道生命周期里就有回调方法,即onActivityResult方法,但是Flutter并没有该方法,闲鱼的混合框架里也并没有专门把这个生命周期给抽出来,本人更倾向于把这个给抽出来,这样框架也比较清晰。不过现在很多原生业务都已经很少用这种方式进行页面传值,因为业务复杂起来,用这种方式反而更麻烦,所以原生就出现了很多eventBus类似的通信框架,所以设计混合栈框架的时候,就直接忽略,而直接用自带的flutter api来实现该功能,怎么实现的?继续看

先看下invokeMethod这个方法,原生和flutter都会有个回调函数,flutter页面拿到目标页的数据传回就是采用该方法,接下来咱们去看flutter页面打开native页面开始看起,类路径flutterProject/flutter_boost/example/lib/simple_page_widgets.dart,关键代码如下:

InkWell(
child: Container(
padding: const EdgeInsets.all(8.0),
margin: const EdgeInsets.all(8.0),
color: Colors.yellow,
child: Text(
'open native page',
style: TextStyle(fontSize: 22.0, color: Colors.black),
)), ///后面的参数会在native的IPlatform.startActivity方法回调中拼接到url的query部分。
///例如:sample://nativePage?aaa=bbb
onTap: () =>
FlutterBoost.singleton.open("sample://nativePage", urlParams: {
"query": {"aaa": "bbb"}
}).then((Map value) {
print(
"call me when page is finished. did recieve second route result $value");
}),
)

FlutterBoost.singleton.open 跟踪下去,发现最终调用的就是invokeMethod,
如下

  Future<Map<dynamic,dynamic>> open(String url,{Map<dynamic,dynamic> urlParams,Map<dynamic,dynamic> exts}){

    Map<dynamic, dynamic> properties = new Map<dynamic, dynamic>();
properties["url"] = url;
properties["urlParams"] = urlParams;
properties["exts"] = exts;
return channel.invokeMethod<Map<dynamic,dynamic>>(
'openPage', properties);
}

然后返回的Future,异步的回调函数,拿到原生页面的回传数据。这里的逻辑很简单,重点是原生那边怎么保存该回调,然后在关闭容器的时候进行调用 回调函数以此将数据传给Flutter

接下来看原生对于openPage的处理,之前在讲通道的时候提过,类路径
flutterProject/flutter_boost/android/src/main/java/com/idlefish/flutterboost/FlutterBoost.java,代码如下

  case "openPage":
{
try {
Map<String,Object> params = methodCall.argument("urlParams");
Map<String,Object> exts = methodCall.argument("exts");
String url = methodCall.argument("url");
mManager.openContainer(url, params, exts, new FlutterViewContainerManager.OnResult() {
@Override
public void onResult(Map<String, Object> rlt) {
if (result != null) {
result.success(rlt);
}
}
});
}catch (Throwable t){
result.error("open page error",t.getMessage(),t);
}
}
break;

重点看 mManager.openContainer方法,传入了一个回调函数,最后调用
result.success(rlt);,数据就传回flutter页面,接下来我们跟踪去看看如何去保存该回调并 最终调用回调

继续跟踪openContainer方法,代码如下:

void openContainer(String url, Map<String, Object> urlParams, Map<String, Object> exts,OnResult onResult) {
Context context = FlutterBoost.singleton().currentActivity();
if(context == null) {
context = FlutterBoost.singleton().platform().getApplication();
} if(urlParams == null) {
urlParams = new HashMap<>();
} int requestCode = 0;
final Object v = urlParams.remove("requestCode");
if(v != null) {
requestCode = Integer.valueOf(String.valueOf(v));
} final String uniqueId = ContainerRecord.genUniqueId(url);
urlParams.put(IContainerRecord.UNIQ_KEY,uniqueId);
if(onResult != null) {
mOnResults.put(uniqueId,onResult);
} FlutterBoost.singleton().platform().openContainer(context,url,urlParams,requestCode,exts);
}

注意 这里有个mOnResults的Map类型参数,就是保存回调函数,通过每个Flutter页面容器的uniqueId做key(只有Flutter页面会建立容器),但前提是该起始容器打开的时候必须传入Key,不然就无法回调,因为找不到该回调了。这就会出现一个问题,就是我们第一个打开的Flutter页面并不是通过onePage打开的,而是直接通过Context.startActivity方法打开,那么就不会保存该回调,也就无法将目标页的数据传回起始页了,已经反馈给闲鱼官方了,本人想过几种方式,为了这个简单的功能,就破坏整体框架得不偿失,等闲鱼官方更优雅的解决方式吧

继续这个mOnResults这个参数,验证我们的猜想,看看哪里在使用,刚刚只是写入回调函数,就找到setContainerResult这个方法,就回到刚刚说要重点讲的方法那了,关键代码如下:

void setContainerResult(IContainerRecord record,int requestCode, int resultCode, Map<String,Object> result) {
....
final OnResult onResult = mOnResults.remove(record.uniqueId());
Debuger.log("setContainerResult uniqueId "+record.uniqueId());
if(onResult != null) {
Debuger.log("onResult has result");
onResult.onResult(result);
}
}

看看哪里有调用这个方法,发现有两处,一处就是原生的生命周期onDestroy的时候,代码如下:

    @Override
public void onDestroy() {
...
mManager.setContainerResult(this,-1,-1,null);
...
}

这个是当前页面销毁的时候,但并没有数据传回,明显不是

ok,继续看另外追踪后的一处关键代码,
代码如下:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
...
mSyncer.onContainerResult(requestCode,resultCode,result);
}

ok,Flutter起始页拿到native传回的数据


第三种情况:flutter-flutter
这里不细讲了,因为就是第一种和第二种的逻辑区分,无非目标页不太一样,传值就是第一种情况的逻辑,拿值就是第二种情况的逻辑

原生关于容器部分,再讲下IFlutterViewContainer.java和IContainerRecord.java 这两个类,因为容器部分主要就是围绕 这两个抽象出来的接口进行一系列的框架实现,这里面做了相当多的抽象,IContainerRecord.java比较好理解,对原生View 生命周期的部分方法抽象,例:onCreate方法,通知flutter页面可以做一些初始化工作(这里面就涉及到flutter容器部分了),还有引擎部分的部分方法抽象等

IFlutterViewContainer.java这个类主要是用于业务代码使用的,你可以看它的实现类都是在demo当中,然后抽象出的方法都是传参,传路由路径,容器关闭时的参数回传等等

好了原生容器的讲解就到此,应该还是遗漏了不少细节的地方,本人觉得好理解就直接过去了

Flutter部分

讲这一部分之前,我们得先了解个flutter的一个widget 叫做Overlay!

了解这玩意,就能弄清楚混合栈是如何做flutter页面的栈,这个组件最大的特点就是提供了动态的在Flutter的渲染树上插入布局的特性。那岂不是很适合Toast这样的场景? 是的去Google下,发现的全是用Overlay来做Toast功能

基于Overlay的特性,就可以用全屏非透明的Overlay,每增加一个flutter页面就增加一个包含自定义的Widget的OverlayEntry,然后覆盖在上一个OverlayEntry上,用户反正看到的只是覆盖在最顶层的OverlayEntry,如果还不能理解可以看看这篇文章

ok,背景交代完毕,现在要去看闲鱼如何设计的这个容器及页面栈,我们就从打开第一个flutter页面作为入口开始看起。类路径:flutterProject/flutter_boost/example/android/app/src/main/java/com/taobao/idlefish/flutterboostexample/PageRouter.java,第一个打开的Flutter页面是FlutterPageActivity.java,前面在讲通道设计的时候,讲到过原生生命周期和Flutter生命周期的绑定,提到过一个抽象出来的接口IOperateSyncer.java,先从onCreate方法开始看起,经过生命周期绑定调用,生成原生容器,提取定义好的通道参数,然后经过通道通信,最后追踪到flutter的didInitPageContainer的方法处理
(类路径flutterProject/flutter_boost/lib/container/container_coordinator.dart)

继续跟踪,中间会经过生命周期的监听调用,最终调用_createContainerSettings方法
(类路径flutterProject/flutter_boost/lib/container/container_coordinator.dart),代码如下

BoostContainerSettings _createContainerSettings(
String name, Map params, String pageId) {
Widget page; final BoostContainerSettings routeSettings = BoostContainerSettings(
uniqueId: pageId,
name: name,
params: params,
builder: (BuildContext ctx) {
//Try to build a page using keyed builder.
if (_pageBuilders[name] != null) {
page = _pageBuilders[name](name, params, pageId);
} //Build a page using default builder.
if (page == null && _defaultPageBuilder != null) {
page = _defaultPageBuilder(name, params, pageId);
} assert(page != null);
Logger.log('build widget:$page for page:$name($pageId)'); return page;
}); return routeSettings;
}

根据方法,再过一遍代码,就是flutter页面容器的参数配置,同时找到一开始注册好的page页面

接下来跟踪原生的appear方法,同样的一阵信号传输...,最终进入flutter的ContainerCoordinator.dart类中的didShowPageContainer方法,继续跟踪,追踪到flutter_boost/lib/container/container_coordinator.dart的showContainer方法

注意的是 flutter容器初始化的过程中做了很多兼容工作,兼容ios兼容android,毕竟两个平台的生命周期是有所差别,但最终要抽象成一样的生命周期,所以要做不少的兼容工作,例如连续2次(didInitPageContainer和didShowPageContainer)进行初始化flutter容器参数

继续看showContainer方法,代码如下

 void showContainer(BoostContainerSettings settings) {
if (settings.uniqueId == _onstage.settings.uniqueId) {
_onShownContainerChanged(null, settings.uniqueId);
return;
} final int index = _offstage.indexWhere((BoostContainer container) =>
container.settings.uniqueId == settings.uniqueId);
//页面的重新显示
if (index > -1) {
_offstage.add(_onstage);
_onstage = _offstage.removeAt(index); setState(() {}); for (BoostContainerObserver observer in FlutterBoost
.singleton.observersHolder
.observersOf<BoostContainerObserver>()) {
observer(ContainerOperation.Onstage, _onstage.settings);
}
Logger.log('ContainerObserver#2 didOnstage');
} else {
//push flutter栈
pushContainer(settings);
}
}

这里的逻辑很简单,重点看下pushContainer方法,代码如下

 void pushContainer(BoostContainerSettings settings) {
assert(settings.uniqueId != _onstage.settings.uniqueId);
assert(_offstage.every((BoostContainer container) =>
container.settings.uniqueId != settings.uniqueId));
//将当前页面的add
_offstage.add(_onstage);
//需要push的页面容器创建
_onstage = BoostContainer.obtain(widget.initNavigator, settings); setState(() {});
//观察者回调
for (BoostContainerObserver observer in FlutterBoost
.singleton.observersHolder
.observersOf<BoostContainerObserver>()) {
observer(ContainerOperation.Push, _onstage.settings);
}
Logger.log('ContainerObserver#2 didPush');
}

flutter的容器的创建,调用setState方法,跟随进去,发现一个东西,一般flutter页面开发都用不着,就是SchedulerBinding,这里有个文章介绍,这里我简单讲解下,我们可以想想flutter的启动流程中,肯定是有个调度节点,例如:Widget什么时候处理build,什么时候处理动画计算等,就是调度。我们如果要写框架,肯定是要对flutter的调度 得清楚,这样才能写出闲鱼这样的混合栈方案,代码如下

@override
void setState(VoidCallback fn) {
Logger.log('BoostContainerManager setState');
if (SchedulerBinding.instance.schedulerPhase ==
SchedulerPhase.persistentCallbacks) {
//主要在下一帧之前,做一些清理工作或者准备工作
SchedulerBinding.instance.addPostFrameCallback((Duration duration) {
Logger.log('BoostContainerManager persistentCallbacks');
_refreshOverlayEntries();
});
} else {
Logger.log('BoostContainerManager '+SchedulerBinding.instance.schedulerPhase.toString());
_refreshOverlayEntries();
} fn();
//return super.setState(fn);
}

如果当前调度的是SchedulerPhase.persistentCallbacks,那么就加一个回调,在persistent之后进行调用_refreshOverlayEntries方法,
SchedulerPhase.persistentCallbacks 是处理build/layout/paint工作

可以这么理解,SchedulerPhase.persistentCallbacks就是在搭建舞台,舞台搭建好了,那么表演者就可以上台表演了 即调用_refreshOverlayEntries方法

继续查看_refreshOverlayEntries方法,代码如下

 void _refreshOverlayEntries() {
final OverlayState overlayState = _overlayKey.currentState; if (overlayState == null) {
return;
} if (_leastEntries != null && _leastEntries.isNotEmpty) {
for (_ContainerOverlayEntry entry in _leastEntries) {
entry.remove();
}
} final List<BoostContainer> containers = <BoostContainer>[];
containers.addAll(_offstage); assert(_onstage != null, 'Should have a least one BoostContainer');
containers.add(_onstage);
//一层层的entry覆盖上去
_leastEntries = containers
.map<_ContainerOverlayEntry>(
(BoostContainer container) => _ContainerOverlayEntry(container))
.toList(growable: false); overlayState.insertAll(_leastEntries); SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) {
final String now = _onstage.settings.uniqueId;
if (_lastShownContainer != now) {
final String old = _lastShownContainer;
_lastShownContainer = now;
_onShownContainerChanged(old, now);
}
//将焦点切换至当前的BoostContainerState
updateFocuse();
});
}

调用OverlayState的insertAll方法,将_leastEntries 覆盖上去,push flutter页面的讲解就到这人,pop其实也一样,将当前的页面栈弹出,当然也有特殊的业务处理,例如非当前的栈弹出,而是某个flutter页弹出,这里就不细讲,逻辑还是比较清晰好理解

其实本人在看完flutter的源码之后,对于BoostContainer.dart比较有疑问,其实是对其背后的对于Navigator和Overlay有疑问,BoostContainer要继承的是Navigator,这明明是个导航控制器,其实刚刚给出的文章里面讲得非常通俗易懂了。我自己疑问的原因主要是认为一个flutter app应该就只有一个Navigator,其实主要是flutter业务开发做多了而进去的误区。闲鱼的混合栈中的flutter页面栈管理就跟平常的flutter页面栈很不一样。其实最好的理解方式,自己写一个最简单的类似的flutter页面管理,然后再看那篇文章,就豁然开朗了。

容器讲解就到此了,解决疑问中的第一个问题

第一个:容器是怎么设计的?

适配层

适配层 只有原生才需要做相应的工作,看之前,想想如果要做适配层,要做哪些适配?
做过flutter业务开发,肯定在软键盘上面花过不少心思去做相应的界面适配工作~
的确,看原生代码里就有个XInputConnectionAdaptor.java的类,其实要看适配层,要花不少精力的,要弄清楚flutter的启动流程,然后重写FlutterView即XFlutterView,其实跟官方提供的FlutterView改动并不是很多


遗留问题:
因为 存在第一个打开的Flutter页面无法将数据传回起始页问题,
后来又去翻了下通道的相关代码,发现有这么一个flutter向原生的pageOnStart方法,类路径flutterProject/flutter_boost/android/src/main/java/com/idlefish/flutterboost/FlutterBoost.java,代码如下

 case "pageOnStart":
{
Map<String, Object> pageInfo = new HashMap<>(); try {
IContainerRecord record = mManager.getCurrentTopRecord(); if (record == null) {
record = mManager.getLastGenerateRecord();
} if(record != null) {
pageInfo.put("name", record.getContainer().getContainerUrl());
pageInfo.put("params", record.getContainer().getContainerUrlParams());
pageInfo.put("uniqueId", record.uniqueId());
} result.success(pageInfo);
} catch (Throwable t) {
result.error("no flutter page found!",t.getMessage(),t);
}
}
break;

看了下代码,应该就是第一个flutter页面的打开逻辑,但是在flutter的demo中并没发现,可能是以前版本留下的

flutter_boot android和flutter源码阅读记录的更多相关文章

  1. EventBus源码解析 源码阅读记录

    EventBus源码阅读记录 repo地址: greenrobot/EventBus EventBus的构造 双重加锁的单例. static volatile EventBus defaultInst ...

  2. Vue2.x 响应式部分源码阅读记录

    之前也用了一段时间Vue,对其用法也较为熟练了,但是对各种用法和各种api使用都是只知其然而不知其所以然.最近利用空闲时间尝试的去看看Vue的源码,以便更了解其具体原理实现,跟着学习学习. Proxy ...

  3. underscore源码阅读记录

    这几天有大神推荐读underscore源码,趁着项目测试的空白时间,看了一下. 整个underscore包括了常用的工具函数,下面以1.3.3源码为例分析一下. _.size = function(o ...

  4. ReactiveCocoa 源码阅读记录。

    1:RACSingle 需要订阅信号 RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACS ...

  5. Linux内核源码阅读记录一之分析存储在不同段中的函数调用过程

    在写驱动的过程中,对于入口函数与出口函数我们会用一句话来修饰他们:module_init与module_exit,那会什么经过修饰后,内核就能狗调用我们编写的入口函数与出口函数呢?下面就来分析内核调用 ...

  6. JQuery源码阅读记录

    新建html文件,在浏览器中打开文件,在控制台输入consoole.log(window);新建html文件,引入JQuery后在浏览器中打开,在控制台同样输入consoole.log(window) ...

  7. vue 源码阅读记录

    0.webpack默认引入的是vue.runtime.common.js,并不是vue.js,功能有略微差别,不影响使用 1.阅读由ts编译后的js: 入口>构造函数 >定义各类方法 &g ...

  8. underscore源码阅读记录(二)

    引自underscore.js context参数用法 _.each(list, iteratee, [context]); context为上下文,如果传递了context参数,则把iterator ...

  9. RIPS源码阅读记录(二)

    Author: tr1ple 这部分主要分析scanner.php的逻辑,在token流重构完成后,此时ini_get是否包含auto_prepend_file或者auto_append_file 取 ...

随机推荐

  1. Python的os,shutil和sys模块

    *********OS*********** os.sep 可以取代操作系统特定的路径分隔符.windows下为 '\\' os.name 字符串指示你正在使用的平台.比如对于Windows,它是'n ...

  2. Spring Boot 注解之ObjectProvider源码追踪

    最近依旧在学习阅读Spring Boot的源代码,在此过程中涉及到很多在日常项目中比较少见的功能特性,对此深入研究一下,也挺有意思,这也是阅读源码的魅力之一.这里写成文章,分享给大家. 自动配置中的O ...

  3. Spring Cloud - 切换Ribbon的负载均衡模式

    Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现.通过Spring Cloud的封装,可以让我们轻松地将面向服务的REST模 ...

  4. Sql 修改表结构

    添加字段 alter table 表名 add 字段名 nvarchar(100) not null 修改字段 alter table 表名 alter column 字段名 int not null ...

  5. 阿里架构师花近十年时间整理出来的Java核心知识pdf(Java岗)

    由于细节内容实在太多啦,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容! 整理了一份Java核心知识点.覆盖了JVM.锁.并发.Java反射.Spring原理.微服务.Zooke ...

  6. Spring的相关注解

    说明写在最前面:摘录于   博客园--受伤滴小萝卜   文章 文章链接受伤滴小萝卜文章--Spring注解 本文章只用作学习和帮助其他人学习记录使用 Spring 注解学习笔记 声明Bean的注解: ...

  7. 2019-2020-12 20199317 《Linux内核原理与分析》 第十二周作业

    SET-UID程序漏洞实验 1  实验简介 Set-UID 是 Unix 系统中的一个重要的安全机制.当一个 Set-UID 程序运行的时候,它被假设为具有拥有者的权限.例如,如果程序的拥有者是roo ...

  8. gulp+webpack+angular1的一点小经验(第三部分使用一些angular1的插件ui-bootstrap与highcharts)

    第一个要介绍的是我们的麻烦制造器:angular-ui-bootstrap ui-bootstrap可以有很多通用的插件给大家用,比如弹窗啊(modal),翻页控件啊(pagination),为什么说 ...

  9. Java修炼——面向对象_抽象类和抽象方法

    抽象类和抽象方法 什么是抽象类? 使用 abstract 修饰的类称为抽象类 public abstract class Person { } 抽象类的特征 1) 抽象类不可以创建对象 2) 抽象类可 ...

  10. jQuery中的层级选择器

    话不多说,请看效果: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> &l ...