flutter_boot android和flutter源碼閱讀(排版后)

版本號(hào)0.1.54

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

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

第一個(gè):容器是怎么設(shè)計(jì)的?

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

第三個(gè):Flutter是適配層到底再做些什么?

中控中心FlutterBoost

單獨(dú)拎出來講講,這個(gè)類比較簡(jiǎn)單,就是集合各個(gè)模塊并讓其初始化,同時(shí)也是該插件入口處,不管原生和flutter都一樣,看源碼也是從這里開始看起,但原生和flutter的初始化流程稍微有少許區(qū)別,主要還是因?yàn)樵亲鳛槿萜?,flutter的容器是依賴于原生容器。

原生init

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

FlutterBoost.init從這里開始進(jìn)入


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也知道是干嘛的,這里重點(diǎn)講講IFlutterEngineProvider這個(gè)接口,這里有3個(gè)方法,如下


/**

* 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ù)項(xiàng)目的實(shí)際情況,開發(fā)者可以自己實(shí)現(xiàn)flutter引擎,或采用官方源碼里自己的實(shí)現(xiàn)類即BoostEngineProvider,但想不明白 createEngine 和provideEngine 到底有何區(qū)別,到底為何弄成兩個(gè)方法,不就是個(gè)提供個(gè)flutter引擎實(shí)例嗎?

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

然后去看provideEngine方法的實(shí)現(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ù)及增加一個(gè)回調(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源碼沒有翻來覆去全看一遍,閑魚進(jìn)行這樣的接口設(shè)計(jì)應(yīng)該是有一定的原因

這里再單獨(dú)講下插件的注冊(cè),我們知道native是作為插件庫需要原生項(xiàng)目依賴,在初始化中,注意一下插件的注冊(cè),是用反射實(shí)現(xiàn)的,如下

路徑:flutter_boost/android/src/main/java/com/idlefish/flutterboost/BoostFlutterView.java 中的init方法,代碼如下


  private void init() {

...

        mFlutterEngine.startRun((Activity)getContext());

...

    }

跟隨startRun方法深入,就會(huì)找到FlutterBoost.singleton().platform().registerPlugins(mBoostPluginRegistry),跟著下去,會(huì)發(fā)現(xiàn)使用反射方式來實(shí)現(xiàn)插件注冊(cè) 如下代碼


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

        }

    }

畢竟引擎初始化框架重新編寫了,所以在插件的注冊(cè)上也改變了,init的原生部分就講解到此

flutter

入口:/flutterProject/flutter_boost/example/lib/main.dart

flutter的源碼查看前,大家務(wù)必先去看看flutter的初始化流程,Navigator源碼解析及Route源碼解析,因?yàn)椴粫缘孟嚓P(guān)初始化流程及Navigator的設(shè)計(jì)原理,里面的關(guān)鍵調(diào)用 大家都可能看不明白,我這邊可能也是直接就過了,這里給個(gè)鏈接大家可以去看看

Flutter 源碼解析


@override

  void initState() {

    super.initState();

    print('_MyAppState initState');

    ///路由注冊(cè),原生通過MethodChannel通道來啟動(dòng)對(duì)應(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)聽,每啟動(dòng)一個(gè)新的flutter頁面 就回調(diào)該方法

  void _onRoutePushed(

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

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

  }

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


FlutterBoost(){

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

    ContainerCoordinator(_boostChannel);

  }

跟隨著ContainerCoordinator去看看,ContainerCoordinator和BoostChannel如何一起來維護(hù)通信通道,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其實(shí)就是翻譯員,將與原生通信的協(xié)議進(jìn)行解釋翻譯,根據(jù)傳過來的事件名,方法名 逐一進(jìn)行需要的框架處理,BoostChannel其實(shí)是聲明通道,將接收和發(fā)送功能進(jìn)行封裝,接收natvie傳來的信息,將從flutter的信息發(fā)送到native,當(dāng)然也做了一部分的框架業(yè)務(wù)處理,將event和method事件進(jìn)行區(qū)分

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

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


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

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

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

  /// foreground  本應(yīng)用是處于前臺(tái)

  /// background  本應(yīng)用是處于后臺(tái)

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

  Future<dynamic> _onChannelEvent(dynamic event) {

    ...

  }

  /// 對(duì)native view生命周期(在android 就是activity)進(jìn)行抽象出的 幾個(gè)行為事件,

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

  Future<dynamic> _onMethodCall(MethodCall call) {

  ...

  }

這里就不講Method的處理邏輯,后面會(huì)結(jié)合容器部分重點(diǎn)講

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

channle

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

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

原生部分

前面原生初始化 講到插件的注冊(cè)是通過反射實(shí)現(xiàn)的,GeneratedPluginRegistrant.java當(dāng)中的registerWith方法我們接下去看一下,注冊(cè)的時(shí)候做了哪些事,路徑lutter_boost/android/src/main/java/com/idlefish/flutterboost/BoostChannel.java


public static void registerWith(PluginRegistry.Registrar registrar) {

        sInstance = new BoostChannel(registrar);

        //通道注冊(cè)后,處理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的實(shí)例化是在插件注冊(cè)的時(shí)候進(jìn)行的,繼續(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);

                    }

                }

            }

        });

    }

對(duì)于通道上的數(shù)據(jù)分為兩類event和method,都是和flutter一一對(duì)應(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è)計(jì)

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

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

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

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

容器

原生部分

閑魚的棧管理方案,是將棧的管理都放置原生,所以在原生必須暴露棧的管理,讓項(xiàng)目接入方能在原有棧的解決方案上 融合進(jìn)閑魚的棧管理方案,所以頁面的打開就是入口處,從該入口處去查看容器的設(shè)計(jì),先從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路由框架,就會(huì)覺得很親切,將每個(gè)View配置一個(gè)路由,還是前端的思想借鑒過來,一個(gè)統(tǒng)一的界面打開處,根據(jù)路由路徑,判斷是原生view還是FlutterView,分別打開不同的Activity

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

接下來看看哪里調(diào)用了openPageByUrl(注意是下面那個(gè))方法,發(fā)現(xiàn)正是我們一開始框架初始化的時(shí)候在調(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通道 通知原生 要打開一個(gè)新的頁面,然后原生將自身的生命周期通過通道告知flutter,flutter再進(jìn)行相應(yīng)的頁面處理,雖然短短一句話,但其中的邏輯及代碼量還是很多的...

前面已經(jīng)講了通道部分,這里再貼點(diǎn)關(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類,其實(shí)現(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();

}

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

接下來看看IContainerRecord的方法被調(diào)用處,追蹤到BoostFlutterActivity.java和BoostFlutterFragment.java,這里我們只看Activity,F(xiàn)ragment基本差不多。我們看到BoostFlutterActivity在走onCreate生命周期方法時(shí),創(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)建容器記錄實(shí)例

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

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

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

        }

    }

單獨(dú)拎出來講,主要是本人好奇 目標(biāo)頁 向 起始面 如何傳輸數(shù)據(jù)的,

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

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

1.native-flutter

2.flutter-native

3.flutter-flutter

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


第一種情況:native-flutter

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

                //接受目標(biāo)頁的回傳必須通過startActivityForResult進(jìn)行打開

                ((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',因?yàn)閐emo中只有它才有傳回?cái)?shù)據(jù),實(shí)現(xiàn)原理這里我簡(jiǎn)單描述,不詳細(xì)講了,就是flutter在關(guān)閉頁面的時(shí)候,傳回?cái)?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();

        }

    }

再跟蹤下去,邏輯很明朗了就不詳細(xì)講了


第二種情況:flutter-native

閑魚的混合棧方案里,每個(gè)flutter都有自己的獨(dú)立原生宿主View,所以回調(diào)也得依賴原生

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

先看下invokeMethod這個(gè)方法,原生和flutter都會(huì)有個(gè)回調(diào)函數(shù),flutter頁面拿到目標(biāo)頁的數(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ù)會(huì)在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ù)。這里的邏輯很簡(jiǎn)單,重點(diǎn)是原生那邊怎么保存該回調(diào),然后在關(guān)閉容器的時(shí)候進(jìn)行調(diào)用 回調(diào)函數(shù)以此將數(shù)據(jù)傳給Flutter

接下來看原生對(duì)于openPage的處理,之前在講通道的時(shí)候提過,類路徑

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;

重點(diǎn)看 mManager.openContainer方法,傳入了一個(gè)回調(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);

    }

注意 這里有個(gè)mOnResults的Map類型參數(shù),就是保存回調(diào)函數(shù),通過每個(gè)Flutter頁面容器的uniqueId做key(只有Flutter頁面會(huì)建立容器),但前提是該起始容器打開的時(shí)候必須傳入Key,不然就無法回調(diào),因?yàn)檎也坏皆摶卣{(diào)了。這就會(huì)出現(xiàn)一個(gè)問題,就是我們第一個(gè)打開的Flutter頁面并不是通過onePage打開的,而是直接通過Context.startActivity方法打開,那么就不會(huì)保存該回調(diào),也就無法將目標(biāo)頁的數(shù)據(jù)傳回起始頁了,已經(jīng)反饋給閑魚官方了,本人想過幾種方式,為了這個(gè)簡(jiǎn)單的功能,就破壞整體框架得不償失,等閑魚官方更優(yōu)雅的解決方式吧

繼續(xù)這個(gè)mOnResults這個(gè)參數(shù),驗(yàn)證我們的猜想,看看哪里在使用,剛剛只是寫入回調(diào)函數(shù),就找到setContainerResult這個(gè)方法,就回到剛剛說要重點(diǎn)講的方法那了,關(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)用這個(gè)方法,發(fā)現(xiàn)有兩處,一處就是原生的生命周期onDestroy的時(shí)候,代碼如下:


    @Override

    public void onDestroy() {

        ...

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

        ...

    }

這個(gè)是當(dāng)前頁面銷毀的時(shí)候,但并沒有數(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

這里不細(xì)講了,因?yàn)榫褪堑谝环N和第二種的邏輯區(qū)分,無非目標(biāo)頁不太一樣,傳值就是第一種情況的邏輯,拿值就是第二種情況的邏輯

原生關(guān)于容器部分,再講下IFlutterViewContainer.java和IContainerRecord.java 這兩個(gè)類,因?yàn)槿萜鞑糠种饕褪菄@ 這兩個(gè)抽象出來的接口進(jìn)行一系列的框架實(shí)現(xiàn),這里面做了相當(dāng)多的抽象,IContainerRecord.java比較好理解,對(duì)原生View 生命周期的部分方法抽象,例:onCreate方法,通知flutter頁面可以做一些初始化工作(這里面就涉及到flutter容器部分了),還有引擎部分的部分方法抽象等

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

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

Flutter部分

講這一部分之前,我們得先了解個(gè)flutter的一個(gè)widget 叫做Overlay!

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

基于Overlay的特性,就可以用全屏非透明的Overlay,每增加一個(gè)flutter頁面就增加一個(gè)包含自定義的Widget的OverlayEntry,然后覆蓋在上一個(gè)OverlayEntry上,用戶反正看到的只是覆蓋在最頂層的OverlayEntry,如果還不能理解可以看看這篇文章

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

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

繼續(xù)跟蹤,中間會(huì)經(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ù)配置,同時(shí)找到一開始注冊(cè)好的page頁面

接下來跟蹤原生的appear方法,同樣的一陣信號(hào)傳輸...,最終進(jìn)入flutter的ContainerCoordinator.dart類中的didShowPageContainer方法,繼續(xù)跟蹤,追蹤到flutter_boost/lib/container/container_coordinator.dart的showContainer方法

注意的是 flutter容器初始化的過程中做了很多兼容工作,兼容ios兼容android,畢竟兩個(gè)平臺(tái)的生命周期是有所差別,但最終要抽象成一樣的生命周期,所以要做不少的兼容工作,例如連續(xù)2次(didInitPageContainer和didShowPageContainer)進(jìn)行初始化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);

    }

  }

這里的邏輯很簡(jiǎn)單,重點(diǎn)看下pushContainer方法,代碼如下


void pushContainer(BoostContainerSettings settings) {

    assert(settings.uniqueId != _onstage.settings.uniqueId);

    assert(_offstage.every((BoostContainer container) =>

        container.settings.uniqueId != settings.uniqueId));

    //將當(dāng)前頁面的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方法,跟隨進(jìn)去,發(fā)現(xiàn)一個(gè)東西,一般flutter頁面開發(fā)都用不著,就是SchedulerBinding,這里有個(gè)文章介紹,這里我簡(jiǎn)單講解下,我們可以想想flutter的啟動(dòng)流程中,肯定是有個(gè)調(diào)度節(jié)點(diǎn),例如:Widget什么時(shí)候處理build,什么時(shí)候處理動(dòng)畫計(jì)算等,就是調(diào)度。我們?nèi)绻獙懣蚣?,肯定是要?duì)flutter的調(diào)度 得清楚,這樣才能寫出閑魚這樣的混合棧方案,代碼如下


@override

  void setState(VoidCallback fn) {

    Logger.log('BoostContainerManager setState');

    if (SchedulerBinding.instance.schedulerPhase ==

        SchedulerPhase.persistentCallbacks) {

        //主要在下一幀之前,做一些清理工作或者準(zhǔn)備工作

      SchedulerBinding.instance.addPostFrameCallback((Duration duration) {

        Logger.log('BoostContainerManager persistentCallbacks');

        _refreshOverlayEntries();

      });

    } else {

      Logger.log('BoostContainerManager '+SchedulerBinding.instance.schedulerPhase.toString());

      _refreshOverlayEntries();

    }

    fn();

    //return super.setState(fn);

  }

如果當(dāng)前調(diào)度的是SchedulerPhase.persistentCallbacks,那么就加一個(gè)回調(diào),在persistent之后進(jìn)行調(diào)用_refreshOverlayEntries方法,

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

可以這么理解,SchedulerPhase.persistentCallbacks就是在搭建舞臺(tái),舞臺(tái)搭建好了,那么表演者就可以上臺(tái)表演了 即調(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);

      }

      //將焦點(diǎn)切換至當(dāng)前的BoostContainerState

      updateFocuse();

    });

  }

調(diào)用OverlayState的insertAll方法,將_leastEntries 覆蓋上去,push flutter頁面的講解就到這人,pop其實(shí)也一樣,將當(dāng)前的頁面棧彈出,當(dāng)然也有特殊的業(yè)務(wù)處理,例如非當(dāng)前的棧彈出,而是某個(gè)flutter頁彈出,這里就不細(xì)講,邏輯還是比較清晰好理解

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

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

第一個(gè):容器是怎么設(shè)計(jì)的?

適配層

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

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

的確,看原生代碼里就有個(gè)XInputConnectionAdaptor.java的類,其實(shí)要看適配層,要花不少精力的,要弄清楚flutter的啟動(dòng)流程,然后重寫FlutterView即XFlutterView,其實(shí)跟官方提供的FlutterView改動(dòng)并不是很多


遺留問題:

因?yàn)?存在第一個(gè)打開的Flutter頁面無法將數(shù)據(jù)傳回起始頁問題,

后來又去翻了下通道的相關(guān)代碼,發(fā)現(xiàn)有這么一個(gè)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)該就是第一個(gè)flutter頁面的打開邏輯,但是在flutter的demo中并沒發(fā)現(xiàn),可能是以前版本留下的

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容