flutter_boot android和flutter源碼閱讀

版本號0.1.54

看源碼之前藤为,我先去看下官方文檔料身,對于其源碼的設(shè)計說明,文中所說的原生都是指android

看完官方文檔的說明,我有以下幾個疑問

> 第一個:容器是怎么設(shè)計的轻黑?

> 第二個:native和flutter的channel的通道是如何設(shè)計的?

> 第三個:Flutter是適配層到底再做些什么徘意?

## 中控中心FlutterBoost

單獨拎出來講講苔悦,這個類比較簡單,就是集合各個模塊并讓其初始化椎咧,同時也是該插件入口處玖详,不管原生和flutter都一樣,看源碼也是從這里開始看起勤讽,但原生和flutter的初始化流程稍微有少許區(qū)別蟋座,主要還是因為原生是作為容器,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();

}

```

抽象成接口,根據(jù)項目的實際情況驯遇,開發(fā)者可以自己實現(xiàn)flutter引擎芹彬,或采用官方源碼里自己的實現(xiàn)類即BoostEngineProvider,但想不明白 createEngine 和provideEngine 到底有何區(qū)別叉庐,到底為何弄成兩個方法舒帮,不就是個提供個flutter引擎實例嗎?

看了下createEngine的實現(xiàn)陡叠,主要加載實例BoostFlutterEngine玩郊,這個實例看名字也清楚是進行flutter引擎的初始化,設(shè)置了dart默認入口點即main枉阵,設(shè)置了路由起點及插件的聲明注冊一類

然后去看provideEngine方法的實現(xiàn)译红,代碼較少,如下

```

? @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參數(shù)及增加一個回調(diào)兴溜,沒什么特別之處临庇,然后去翻了下flutter.jar的FlutterActivity源碼反璃,它的flutter引擎初始化最后是追蹤到FlutterFragment,關(guān)鍵代碼如下

```

public void onAttach(Context context) {

? ? ? ? super.onAttach(context);

? ? ? ? //這里初始化flutter參數(shù)

? ? ? ? this.initializeFlutter(this.getContextCompat());

? ? ? ? if (this.flutterEngine == null) {

? ? ? ? //這里是初始化flutter引擎

? ? ? ? ? ? this.setupFlutterEngine();

? ? ? ? }

? ? ? ? this.platformPlugin = new PlatformPlugin(this.getActivity(), this.flutterEngine.getPlatformChannel());

? ? }

```

這里是連在一起的假夺,flutter源碼沒有翻來覆去全看一遍,閑魚進行這樣的接口設(shè)計應(yīng)該是有一定的原因

這里再單獨講下插件的注冊斋攀,我們知道native是作為插件庫需要原生項目依賴已卷,在初始化中,注意一下插件的注冊淳蔼,是用反射實現(xiàn)的侧蘸,如下

路徑: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)讳癌,跟著下去,會發(fā)現(xiàn)使用反射方式來實現(xiàn)插件注冊 如下代碼

```

@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的源碼查看前,大家務(wù)必先去看看flutter的初始化流程旦袋,Navigator源碼解析及Route源碼解析骤菠,因為不曉得相關(guān)初始化流程及Navigator的設(shè)計原理,里面的關(guān)鍵調(diào)用 大家都可能看不明白疤孕,我這邊可能也是直接就過了商乎,這里給個鏈接大家可以去看看

[Flutter 源碼解析](https://note.youdao.com/)

```

@override

? void initState() {

? ? super.initState();

? ? print('_MyAppState initState');

? ? ///路由注冊,原生通過MethodChannel通道來啟動對應(yīng)的flutter頁面

? ? FlutterBoost.singleton.registerPageBuilders({

? ? ? 'first': (pageName, params, _) => FirstRouteWidget(),

? ? ? 'second': (pageName, params, _) => SecondRouteWidget(),

? ? ? 'tab': (pageName, params, _) => TabRouteWidget(),

? ? ? 'flutterFragment': (pageName, params, _) => FragmentRouteWidget(params),

? ? ? ///可以在native層通過 getContainerParams 來傳遞參數(shù)

? ? ? '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 監(jiān)聽祭阀,每啟動一個新的flutter頁面 就回調(diào)該方法

? void _onRoutePushed(

? ? ? String pageName, String uniqueId, Map params, Route route, Future _) {

? ? print('pageName'+pageName+"\n");

? }

```

先跟隨FlutterBoost.singleton進去看看鹉戚,其構(gòu)造函數(shù)如下

```

FlutterBoost(){

? ? Logger.log('FlutterBoost 構(gòu)造函數(shù)');

? ? ContainerCoordinator(_boostChannel);

? }

```

跟隨著ContainerCoordinator去看看,ContainerCoordinator和BoostChannel如何一起來維護通信通道专控,ContainerCoordinator構(gòu)造函數(shù)如下

```

ContainerCoordinator(BoostChannel channel) {

? ? assert(_instance == null);

? ? _instance = this;

? ? channel.addEventListener("lifecycle",

? ? ? ? (String name, Map arguments) => _onChannelEvent(arguments));

? ? channel.addMethodHandler((MethodCall call) => _onMethodCall(call));

? }

```

增加native生命周期監(jiān)聽抹凳,增加方法監(jiān)聽,再去看看_onChannelEvent和_onMethodCall方法就應(yīng)該清楚ContainerCoordinator其實就是翻譯員踩官,將與原生通信的協(xié)議進行解釋翻譯却桶,根據(jù)傳過來的事件名,方法名 逐一進行需要的框架處理蔗牡,BoostChannel其實是聲明通道颖系,將接收和發(fā)送功能進行封裝,接收natvie傳來的信息辩越,將從flutter的信息發(fā)送到native嘁扼,當然也做了一部分的框架業(yè)務(wù)處理,將event和method事件進行區(qū)分

分發(fā)黔攒,個人覺得將該功能直接丟至ContainerCoordinator處理可能更好點趁啸,應(yīng)該是出于為了區(qū)分event和method特意在BoostChannel進行處理

接下來看看ContainerCoordinator對于native傳過來的通信數(shù)據(jù)處理强缘,代碼如下,就分為之前說的event和method兩類不傅,代碼注釋也寫了

```

/// 對native 整個應(yīng)用的 生命周期 進行抽象出的幾個行為事件旅掂,讓flutter做相應(yīng)的處理

? /// android端 基本上除了有回退事件的處理,剩余的生命周期 僅僅是做了監(jiān)聽沒做任何處理

? /// 分別是回退處理 android才有

? /// foreground? 本應(yīng)用是處于前臺

? /// background? 本應(yīng)用是處于后臺

? /// scheduleFrame 觸發(fā)一幀的繪制,但ios和android 都沒找到發(fā)送該事件的代碼访娶,老版本遺留代碼商虐?

? Future<dynamic> _onChannelEvent(dynamic event) {

? ? ...

? }

? /// 對native view生命周期(在android 就是activity)進行抽象出的 幾個行為事件,

? /// 讓flutter做相應(yīng)的框架處理

? Future<dynamic> _onMethodCall(MethodCall call) {

? ...

? }

```

這里就不講Method的處理邏輯崖疤,后面會結(jié)合容器部分重點講

接下來再回到main.dart文件秘车,跟隨FlutterBoost.init方法進去看一下,就是初始化BoostContainerManager劫哼,不再深入叮趴,后面會結(jié)合起來一起講BoostContainerManager

## channle

這模塊代碼比較少,先從這模塊開始講起

> 我們知道原生和flutter之間的通信就是通過MethodChannel這個類實現(xiàn)的(原生和flutter的類名一樣)权烧,前面有講flutter的boost_channel.dart的作用眯亦,native的BoostChannel其實也一樣,將接收和發(fā)送功能進行封裝豪嚎,接收flutter傳來的信息搔驼,將從native的信息發(fā)送到flutter

### 原生部分

前面原生初始化 講到插件的注冊是通過反射實現(xiàn)的,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 調(diào)用處理

? ? ? ? for(ActionAfterRegistered a : sActions) {

? ? ? ? ? ? a.onChannelRegistered(sInstance);

? ? ? ? }

? ? ? ? //狀態(tài)監(jiān)聽 回調(diào)

? ? ? ? if(FlutterBoost.sInstance != null) {

? ? ? ? ? ? final IStateListener stateListener = FlutterBoost.sInstance.mStateListener;

? ? ? ? ? ? if (stateListener != null) {

? ? ? ? ? ? ? ? stateListener.onChannelRegistered(registrar, sInstance);

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? sActions.clear();

? ? }

```

看到了吧扔字,BoostChannel的實例化是在插件注冊的時候進行的囊嘉,繼續(xù)深入,如下代碼

```

? 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);

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? });

? ? }

```

對于通道上的數(shù)據(jù)分為兩類event和method革为,都是和flutter一一對應(yīng)的扭粱,前面flutter初始化中,也講過震檩,在原生這邊event 沒有框架上的業(yè)務(wù)處理琢蛤,但提供了回調(diào),根據(jù)自己的業(yè)務(wù)是否需要增加監(jiān)聽

method的處理抛虏,去查看BoostMethodHandler,FlutterBoost.java作為內(nèi)部類存在博其,如下

```

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傻寂,所以這里就不在講了,但是講講兩邊的設(shè)計

兩邊都有個channel類携兵,主要都是用來接收和發(fā)送消息的

flutter專門有一個類ContainerCoordinator.dart疾掰,中文翻譯過來就是集裝箱協(xié)調(diào)員,用于通信事件的統(tǒng)一處理徐紧,就是將從原生接收到的信息進行處理个绍,但是在原生那邊并沒有類似的類,而是將這個工作放在FlutterBoost.java這個內(nèi)部類中浪汪,個人覺得為了保持統(tǒng)一可以專門抽象出個類,將該功能放置該類中凛虽,放在FlutterBoost.java不能保持高度統(tǒng)一且不雅觀吧

講到這里死遭,其實通道的設(shè)計大家應(yīng)該理解得差不多了(解決開頭提出的問題)

> native和flutter的channel的通道是如何設(shè)計的?

## 容器

### 原生部分

閑魚的棧管理方案凯旋,是將棧的管理都放置原生呀潭,所以在原生必須暴露棧的管理,讓項目接入方能在原有棧的解決方案上 融合進閑魚的棧管理方案至非,所以頁面的打開就是入口處钠署,從該入口處去查看容器的設(shè)計,先從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配置一個路由趣惠,還是前端的思想借鑒過來狸棍,一個統(tǒng)一的界面打開處,根據(jù)路由路徑味悄,判斷是原生view還是FlutterView草戈,分別打開不同的Activity

接入方,在這里可以根據(jù)自身的原生棧管理再進行抽象封裝就ok了

接下來看看哪里調(diào)用了openPageByUrl(注意是下面那個)方法侍瑟,發(fā)現(xiàn)正是我們一開始框架初始化的時候在調(diào)用唐片,如下,文件路徑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);

? ? ? ? ? ? }

```

再查看是哪里調(diào)用了該方法涨颜,一直追蹤到FlutterBoost.java费韭,關(guān)鍵代碼如下

```

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再進行相應(yīng)的頁面處理揽思,雖然短短一句話,但其中的邏輯及代碼量還是很多的...

前面已經(jīng)講了通道部分见擦,這里再貼點關(guān)鍵代碼,原生將自身的生命周期通過通道告知flutter卢未,關(guān)鍵代碼如下

```

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,現(xiàn)在已經(jīng)找到了MethodChannelProxy類戳葵,那我們就繼續(xù)回找(注意我這里講解都是從冰山一角再慢慢往上查噩翠,最終再將冰山一起探索完畢)MethodChannelProxy是作為ContainerRecord.java的內(nèi)部類存在见芹。接下來我們來看ContainerRecord類徘铝,其實現(xiàn)了IContainerRecord接口淹魄,再繼續(xù)深究找到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();

}

```

該接口是通過對原生的生命周期再結(jié)合flutter的生命周期特色及android自身的特性(有回退物理鍵)抽象出來的包蓝,繼續(xù)回到接下來我們來看ContainerRecord類,發(fā)現(xiàn)MethodChannelProxy類其實就是做個代理功能零酪,看名字也清楚月腋,在接口方法被調(diào)用的時候妓肢,通知flutter

接下來看看IContainerRecord的方法被調(diào)用處卷拘,追蹤到BoostFlutterActivity.java和BoostFlutterFragment.java,這里我們只看Activity瓣蛀,F(xiàn)ragment基本差不多纫塌。我們看到BoostFlutterActivity在走onCreate生命周期方法時诊县,創(chuàng)建了mSyncer,關(guān)鍵代碼如下

```

? @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() 繼續(xù)深入措左,追蹤到FlutterViewContainerManager類依痊,關(guān)鍵代碼如下

```

@Override

? ? public IOperateSyncer generateSyncer(IFlutterViewContainer container) {

? ? ? ? Utils.assertCallOnMainThread();

? ? ? ? //創(chuàng)建容器記錄實例

? ? ? ? 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實例在這里創(chuàng)建,同時將接口引用返給Activity怎披,這樣就和原生View的生命周期關(guān)聯(lián)起來了

注意到這里我們已經(jīng)追蹤到FlutterViewContainerManager.java類胸嘁,已經(jīng)可以從上帝視角去看了。

剛剛從容器打開出入一直追蹤到FlutterViewContainerManager.java類,該類看名字就清楚就是容器的管理者凉逛,容器創(chuàng)建性宏、打開、關(guān)閉状飞、銷毀毫胜、彈出、移除等等工作都是在這兒诬辈,這里最關(guān)鍵的generateSyncer方法剛剛追蹤的時候已經(jīng)講過酵使。這里再重點講講該類的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);

? ? ? ? }

? ? }

```

單獨拎出來講焙糟,主要是本人好奇 目標頁 向 起始面 如何傳輸數(shù)據(jù)的口渔,

在純ntive就是靠著onActivityResult回調(diào)拿到目標頁傳回的數(shù)據(jù),該方法就是處理目標頁傳回來后的處理

在混合棧中 就分為3種情況

1.native-flutter

2.flutter-native

3.flutter-flutter

native和ntive就不用說了穿撮,都用不到該框架

---

第一種情況:native-flutter

demo自身當中并沒有相關(guān)的演示代碼搓劫,于是我按照原生是如何接受傳回來的數(shù)據(jù)去進行更改,改了兩處如下混巧,類路徑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;

? ? ? ? }

? ? }

```

還有記得修改調(diào)起的Flutter頁面是'second',因為demo中只有它才有傳回數(shù)據(jù)咧党,實現(xiàn)原理這里我簡單描述秘蛔,不詳細講了,就是flutter在關(guān)閉頁面的時候,傳回數(shù)據(jù)深员,如下

```

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!'),

? ? ? ? ),

? ? ? ),

? ? );

? }

}

```

跟蹤關(guān)閉代碼邏輯负蠕,通過之前建立的通信通道,傳過去相關(guān)方法倦畅,即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,所以回調(diào)也得依賴原生

原生我們知道生命周期里就有回調(diào)方法芭概,即onActivityResult方法赛不,但是Flutter并沒有該方法,閑魚的混合框架里也并沒有專門把這個生命周期給抽出來罢洲,本人更傾向于把這個給抽出來踢故,這樣框架也比較清晰。不過現(xiàn)在很多原生業(yè)務(wù)都已經(jīng)很少用這種方式進行頁面?zhèn)髦等敲纾驗闃I(yè)務(wù)復(fù)雜起來殿较,用這種方式反而更麻煩,所以原生就出現(xiàn)了很多eventBus類似的通信框架桩蓉,所以設(shè)計混合椥敝框架的時候,就直接忽略,而直接用自帶的flutter api來實現(xiàn)該功能玷或,怎么實現(xiàn)的?繼續(xù)看

先看下invokeMethod這個方法蔬胯,原生和flutter都會有個回調(diào)函數(shù),flutter頁面拿到目標頁的數(shù)據(jù)傳回就是采用該方法氛濒,接下來咱們?nèi)タ磃lutter頁面打開native頁面開始看起鹅髓,類路徑flutterProject/flutter_boost/example/lib/simple_page_widgets.dart,關(guān)鍵代碼如下:

```

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),

? ? ? ? ? ? ? ? )),

? ? ? ? ? ? ///后面的參數(shù)會在native的IPlatform.startActivity方法回調(diào)中拼接到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 跟蹤下去执桌,發(fā)現(xiàn)最終調(diào)用的就是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伴逸,異步的回調(diào)函數(shù)膘壶,拿到原生頁面的回傳數(shù)據(jù)。這里的邏輯很簡單漱竖,重點是原生那邊怎么保存該回調(diào)畜伐,然后在關(guān)閉容器的時候進行調(diào)用 回調(diào)函數(shù)以此將數(shù)據(jù)傳給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方法慎框,傳入了一個回調(diào)函數(shù),最后調(diào)用

result.success(rlt);笨枯,數(shù)據(jù)就傳回flutter頁面,接下來我們跟蹤去看看如何去保存該回調(diào)并 最終調(diào)用回調(diào)

繼續(xù)跟蹤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類型參數(shù)洲敢,就是保存回調(diào)函數(shù),通過每個Flutter頁面容器的uniqueId做key(只有Flutter頁面會建立容器)睦优,但前提是該起始容器打開的時候必須傳入Key汗盘,不然就無法回調(diào)询一,因為找不到該回調(diào)了尸执。這就會出現(xiàn)一個問題缓醋,就是我們第一個打開的Flutter頁面并不是通過onePage打開的,而是直接通過Context.startActivity方法打開褪贵,那么就不會保存該回調(diào)抗俄,也就無法將目標頁的數(shù)據(jù)傳回起始頁了,已經(jīng)反饋給閑魚官方了槽卫,本人想過幾種方式歼培,為了這個簡單的功能茸塞,就破壞整體框架得不償失,等閑魚官方更優(yōu)雅的解決方式吧

繼續(xù)這個mOnResults這個參數(shù)钾虐,驗證我們的猜想,看看哪里在使用倔监,剛剛只是寫入回調(diào)函數(shù)浩习,就找到setContainerResult這個方法,就回到剛剛說要重點講的方法那了套利,關(guān)鍵代碼如下:

```

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);

? ? ? ? }

? ? }

```

看看哪里有調(diào)用這個方法雀瓢,發(fā)現(xiàn)有兩處泪掀,一處就是原生的生命周期onDestroy的時候异赫,代碼如下:

```

? ? @Override

? ? public void onDestroy() {

? ? ? ? ...

? ? ? ? mManager.setContainerResult(this,-1,-1,null);

? ? ? ? ...

? ? }

```

這個是當前頁面銷毀的時候,但并沒有數(shù)據(jù)傳回塔拳,明顯不是

ok靠抑,繼續(xù)看另外追蹤后的一處關(guān)鍵代碼,

代碼如下:

```

@Override

? ? protected void onActivityResult(int requestCode, int resultCode, Intent data) {

? ? ...

? ? ? ? mSyncer.onContainerResult(requestCode,resultCode,result);

? ? }

```

ok颂碧,F(xiàn)lutter起始頁拿到native傳回的數(shù)據(jù)

---

第三種情況:flutter-flutter

這里不細講了,因為就是第一種和第二種的邏輯區(qū)分肌似,無非目標頁不太一樣诉瓦,傳值就是第一種情況的邏輯垦搬,拿值就是第二種情況的邏輯

原生關(guān)于容器部分,再講下IFlutterViewContainer.java和IContainerRecord.java 這兩個類对雪,因為容器部分主要就是圍繞 這兩個抽象出來的接口進行一系列的框架實現(xiàn)米绕,這里面做了相當多的抽象,IContainerRecord.java比較好理解迈套,對原生View 生命周期的部分方法抽象桑李,例:onCreate方法窿给,通知flutter頁面可以做一些初始化工作(這里面就涉及到flutter容器部分了),還有引擎部分的部分方法抽象等

IFlutterViewContainer.java這個類主要是用于業(yè)務(wù)代碼使用的禁荒,你可以看它的實現(xiàn)類都是在demo當中角撞,然后抽象出的方法都是傳參勃痴,傳路由路徑沛申,容器關(guān)閉時的參數(shù)回傳等等

好了原生容器的講解就到此褐隆,應(yīng)該還是遺漏了不少細節(jié)的地方,本人覺得好理解就直接過去了

### Flutter部分

講這一部分之前衫贬,我們得先了解個flutter的一個widget 叫做Overlay固惯!

了解這玩意缴守,就能弄清楚混合棧是如何做flutter頁面的棧屡穗,這個組件最大的特點就是提供了動態(tài)的在Flutter的渲染樹上插入布局的特性。那豈不是很適合Toast這樣的場景烂斋? 是的去Google下汛骂,發(fā)現(xiàn)的全是用Overlay來做Toast功能

基于Overlay的特性评腺,就可以用全屏非透明的Overlay,每增加一個flutter頁面就增加一個包含自定義的Widget的OverlayEntry蝶念,然后覆蓋在上一個OverlayEntry上媒殉,用戶反正看到的只是覆蓋在最頂層的OverlayEntry侥钳,如果還不能理解可以看看[這篇文章](http://www.reibang.com/p/8733e2ab2571)

ok柄错,背景交代完畢苦酱,現(xiàn)在要去看閑魚如何設(shè)計的這個容器及頁面棧疫萤,我們就從打開第一個flutter頁面作為入口開始看起扯饶。類路徑:flutterProject/flutter_boost/example/android/app/src/main/java/com/taobao/idlefish/flutterboostexample/PageRouter.java池颈,第一個打開的Flutter頁面是FlutterPageActivity.java躯砰,前面在講通道設(shè)計的時候,講到過原生生命周期和Flutter生命周期的綁定兰怠,提到過一個抽象出來的接口IOperateSyncer.java,先從onCreate方法開始看起揭保,經(jīng)過生命周期綁定調(diào)用秸侣,生成原生容器娜庇,提取定義好的通道參數(shù),然后經(jīng)過通道通信励负,最后追蹤到flutter的didInitPageContainer的方法處理

(類路徑flutterProject/flutter_boost/lib/container/container_coordinator.dart)

繼續(xù)跟蹤继榆,中間會經(jīng)過生命周期的監(jiān)聽調(diào)用略吨,最終調(diào)用_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;

? }

```

根據(jù)方法秽之,再過一遍代碼,就是flutter頁面容器的參數(shù)配置跨细,同時找到一開始注冊好的page頁面

接下來跟蹤原生的appear方法河质,同樣的一陣信號傳輸...掀鹅,最終進入flutter的ContainerCoordinator.dart類中的didShowPageContainer方法乐尊,繼續(xù)跟蹤,追蹤到flutter_boost/lib/container/container_coordinator.dart的showContainer方法

注意的是 flutter容器初始化的過程中做了很多兼容工作昏滴,兼容ios兼容android谣殊,畢竟兩個平臺的生命周期是有所差別姻几,但最終要抽象成一樣的生命周期势告,所以要做不少的兼容工作咱台,例如連續(xù)2次(didInitPageContainer和didShowPageContainer)進行初始化flutter容器參數(shù)

繼續(xù)看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的頁面容器創(chuàng)建

? ? _onstage = BoostContainer.obtain(widget.initNavigator, settings);

? ? setState(() {});

? ? //觀察者回調(diào)

? ? for (BoostContainerObserver observer in FlutterBoost

? ? ? ? .singleton.observersHolder

? ? ? ? .observersOf<BoostContainerObserver>()) {

? ? ? observer(ContainerOperation.Push, _onstage.settings);

? ? }

? ? Logger.log('ContainerObserver#2 didPush');

? }

```

flutter的容器的創(chuàng)建允粤,調(diào)用setState方法类垫,跟隨進去,發(fā)現(xiàn)一個東西,一般flutter頁面開發(fā)都用不著购撼,就是SchedulerBinding谴仙,這里有個[文章介紹](https://segmentfault.com/a/1190000011935445)晃跺,這里我簡單講解下,我們可以想想flutter的啟動流程中凌盯,肯定是有個調(diào)度節(jié)點驰怎,例如:Widget什么時候處理build,什么時候處理動畫計算等县忌,就是調(diào)度症杏。我們?nèi)绻獙懣蚣芾鞑隙ㄊ且獙lutter的調(diào)度 得清楚凡简,這樣才能寫出閑魚這樣的混合棧方案潘鲫,代碼如下

```

@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);

? }

```

如果當前調(diào)度的是SchedulerPhase.persistentCallbacks浊竟,那么就加一個回調(diào)津畸,在persistent之后進行調(diào)用_refreshOverlayEntries方法肉拓,

SchedulerPhase.persistentCallbacks 是處理build/layout/paint工作

可以這么理解暖途,SchedulerPhase.persistentCallbacks就是在搭建舞臺驻售,舞臺搭建好了更米,那么表演者就可以上臺表演了 即調(diào)用_refreshOverlayEntries方法

繼續(xù)查看_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();

? ? });

? }

```

調(diào)用OverlayState的insertAll方法,將_leastEntries 覆蓋上去征峦,push flutter頁面的講解就到這人迟几,pop其實也一樣,將當前的頁面棧彈出栏笆,當然也有特殊的業(yè)務(wù)處理瘤旨,例如非當前的棧彈出存哲,而是某個flutter頁彈出祟偷,這里就不細講,邏輯還是比較清晰好理解

其實本人在看完flutter的源碼之后嵌施,對于BoostContainer.dart比較有疑問吗伤,其實是對其背后的對于Navigator和Overlay有疑問足淆,BoostContainer要繼承的是Navigator族奢,這明明是個導航控制器,其實剛剛給出的文章里面講得非常通俗易懂了廊敌。我自己疑問的原因主要是認為一個flutter app應(yīng)該就只有一個Navigator,其實主要是flutter業(yè)務(wù)開發(fā)做多了而進去的誤區(qū)薪缆。閑魚的混合棧中的flutter頁面棧管理就跟平常的flutter頁面棧很不一樣。其實最好的理解方式减拭,自己寫一個最簡單的類似的flutter頁面管理拧粪,然后再看那篇文章可霎,就豁然開朗了。

容器講解就到此了,解決疑問中的第一個問題

> 第一個:容器是怎么設(shè)計的旷余?

## 適配層

適配層 只有原生才需要做相應(yīng)的工作正卧,看之前,想想如果要做適配層砾跃,要做哪些適配抽高?

做過flutter業(yè)務(wù)開發(fā)壁熄,肯定在軟鍵盤上面花過不少心思去做相應(yīng)的界面適配工作~

的確草丧,看原生代碼里就有個XInputConnectionAdaptor.java的類,其實要看適配層诈泼,要花不少精力的岖赋,要弄清楚flutter的啟動流程唐断,然后重寫FlutterView即XFlutterView,其實跟官方提供的FlutterView改動并不是很多

---

遺留問題:

因為 存在第一個打開的Flutter頁面無法將數(shù)據(jù)傳回起始頁問題斤程,

后來又去翻了下通道的相關(guān)代碼忿墅,發(fā)現(xiàn)有這么一個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;

```

看了下代碼痕支,應(yīng)該就是第一個flutter頁面的打開邏輯,但是在flutter的demo中并沒發(fā)現(xiàn)花嘶,可能是以前版本留下的

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市隘击,隨后出現(xiàn)的幾起案子闸度,更是在濱河造成了極大的恐慌,老刑警劉巖窄赋,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件楼熄,死亡現(xiàn)場離奇詭異,居然都是意外死亡错敢,警方通過查閱死者的電腦和手機稚茅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來欺税,“玉大人晚凿,你說我怎么就攤上這事〗粤茫” “怎么了扛吞?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵滥比,是天一觀的道長盲泛。 經(jīng)常有香客問我寺滚,道長,這世上最難降的妖魔是什么蚁孔? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮另伍,結(jié)果婚禮上温艇,老公的妹妹穿的比我還像新娘中贝。我一直安慰自己蝎土,他們只是感情好蒜撮,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布取逾。 她就那樣靜靜地躺著砾隅,像睡著了一般晴埂。 火紅的嫁衣襯著肌膚如雪儒洛。 梳的紋絲不亂的頭發(fā)上狼速,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天捷枯,我揣著相機與錄音淮捆,去河邊找鬼。 笑死棘街,一個胖子當著我的面吹牛遭殉,可吹牛的內(nèi)容都是我干的险污。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼狼电,長吁一口氣:“原來是場噩夢啊……” “哼削祈!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起网杆,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后物舒,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體焕盟,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年特幔,在試婚紗的時候發(fā)現(xiàn)自己被綠了咨演。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡敬辣,死狀恐怖雪标,靈堂內(nèi)的尸體忽然破棺而出零院,到底是詐尸還是另有隱情溉跃,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布告抄,位于F島的核電站撰茎,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏打洼。R本人自食惡果不足惜龄糊,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一逆粹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧炫惩,春花似錦僻弹、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至筋蓖,卻和暖如春卸耘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背粘咖。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工蚣抗, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人瓮下。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓翰铡,卻偏偏與公主長得像,于是被迫代替她去往敵國和親讽坏。 傳聞我的和親對象是個殘疾皇子两蟀,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容