版本號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 源碼解析](https://note.youdao.com/)
```
@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,如果還不能理解可以看看[這篇文章](http://www.lxweimin.com/p/8733e2ab2571)
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,這里有個[文章介紹](https://segmentfault.com/a/1190000011935445),這里我簡單講解下,我們可以想想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中并沒發現,可能是以前版本留下的