Flutter原生通信原理概覽

  • FlutterActivity

    我們知道南誊,原生連接Flutter需要用到繼承FlutterActivity或者FlutterFragment锻煌,以FlutterActivity為例,它的onCreate如下:

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
      switchLaunchThemeForNormalTheme();
    
      super.onCreate(savedInstanceState);
    
      delegate = new FlutterActivityAndFragmentDelegate(this);
      delegate.onAttach(this);
      delegate.onRestoreInstanceState(savedInstanceState);
    
      lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
    
      configureWindowForTransparency();
    
      setContentView(createFlutterView());
    
      configureStatusBarForFullscreenFlutterExperience();
    }
    

    可見(jiàn),自定義的子類(lèi)的onCreate中必須要先調(diào)用super.onCreate方法完成FlutterActivity的默認(rèn)配置。

    之前我們解析了createFlutterView方法焙格,知道了FlutterEngine渲染的UI通過(guò)FlutterView來(lái)添加到Activity中,現(xiàn)在我們來(lái)看前面的FlutterActivityAndFragmentDelegate夷都,和原生通信的邏輯也從它開(kāi)始眷唉。

  • FlutterActivityAndFragmentDelegate

    首先看一下它的onAttach方法:

    void onAttach(@NonNull Context context) {
      ensureAlive();
    
      if (flutterEngine == null) {
        setupFlutterEngine();
      }
    
      if (host.shouldAttachEngineToActivity()) {
        flutterEngine.getActivityControlSurface().attachToActivity(this, host.getLifecycle());
      }
      
      platformPlugin = host.providePlatformPlugin(host.getActivity(), flutterEngine);
    
      host.configureFlutterEngine(flutterEngine);
    }
    

    首先會(huì)調(diào)用setupFlutterEngine方法保證先創(chuàng)建FlutterEngine,然后調(diào)用host的configureFlutterEngine方法來(lái)對(duì)FlutterEngine進(jìn)行相關(guān)配置囤官,比如可以重寫(xiě)FlutterActivity的子類(lèi)的configureFlutterEngine來(lái)添加自定義的Plugin冬阳。

    看一下setupFlutterEngine方法:

    void setupFlutterEngine() {
    
      // First, check if the host wants to use a cached FlutterEngine.
      String cachedEngineId = host.getCachedEngineId();
      if (cachedEngineId != null) {
        flutterEngine = FlutterEngineCache.getInstance().get(cachedEngineId);
        isFlutterEngineFromHost = true;
        if (flutterEngine == null) {
          throw new IllegalStateException(
              "The requested cached FlutterEngine did not exist in the FlutterEngineCache: '"
                  + cachedEngineId
                  + "'");
        }
        return;
      }
    
      // Second, defer to subclasses for a custom FlutterEngine.
      flutterEngine = host.provideFlutterEngine(host.getContext());
      if (flutterEngine != null) {
        isFlutterEngineFromHost = true;
        return;
      }
    
      // Our host did not provide a custom FlutterEngine. Create a FlutterEngine to back our
      // FlutterView.
      flutterEngine =
          new FlutterEngine(
              host.getContext(),
              host.getFlutterShellArgs().toArray(),
              /*automaticallyRegisterPlugins=*/ false,
              /*willProvideRestorationData=*/ host.shouldRestoreAndSaveState());
      isFlutterEngineFromHost = false;
    }
    

    在方法的一開(kāi)始,首先會(huì)通過(guò)FlutterEngineCache.getInstance().get(cachedEngineId)方法嘗試從緩存中取FlutterEngine党饮,所以我們可以在子類(lèi)Activity中重寫(xiě)getCachedEngineId方法來(lái)設(shè)置在每一次重新打開(kāi)該Activity的時(shí)候都能使用之前的FlutterEngine摩泪,這會(huì)極大地提高性能。getCachedEngineId方法在FlutterActivity中有默認(rèn)實(shí)現(xiàn):

    @Override
    @Nullable
    public String getCachedEngineId() {
      return getIntent().getStringExtra(EXTRA_CACHED_ENGINE_ID);
    }
    

    可以看到劫谅,你也可以通過(guò)intent的方式傳遞cachedEngineId见坑。

    當(dāng)然我們需要事先創(chuàng)建FlutterEngine然后把它put到FlutterEngineCache中,這一步你可以放在Application類(lèi)的初始化中捏检,但不是更好的做法荞驴,我們接著往下看。

    如果cache中沒(méi)有拿到之前使用過(guò)的FlutterEngine的話就會(huì)嘗試調(diào)用子類(lèi)Activity重寫(xiě)的provideFlutterEngine方法來(lái)獲取贯城,所以你可以重寫(xiě)這個(gè)方法然后在這里創(chuàng)建FlutterEngine熊楼,然后可以在這里把它放到cache中,這就是比上面放在Application中更好的做法能犯,為什么呢鲫骗?因?yàn)榉旁贏pplication中的話,F(xiàn)lutterEngine并不一定會(huì)被使用踩晶,有可能使用過(guò)程中從沒(méi)有打開(kāi)過(guò)Flutter頁(yè)面执泰,這就會(huì)導(dǎo)致始終持有這個(gè)FlutterEngine對(duì)象,造成資源浪費(fèi)渡蜻;還有一個(gè)好處就是這樣一來(lái)每個(gè)頁(yè)面都有自己的獨(dú)立的FlutterEngine术吝,每個(gè)FlutterEngine的業(yè)務(wù)相對(duì)獨(dú)立而且不需要維護(hù)其他Flutter業(yè)務(wù)的相關(guān)信息计济。當(dāng)然如果是App的所有頁(yè)面都是Flutter頁(yè)面或者原生頁(yè)面占很少一部分的時(shí)候在Application中配置會(huì)更好。

    最后一步則是new一個(gè)新的FlutterEngine對(duì)象排苍,context和host相關(guān)聯(lián)沦寂。

    使用緩存FlutterEngine的作用一個(gè)是明顯地提高頁(yè)面打開(kāi)速度,再一個(gè)會(huì)保存之前Flutter頁(yè)面的操作信息淘衙。

    需要注意的是传藏,如果使用緩存id的話,則需要手動(dòng)設(shè)置切入點(diǎn)彤守,哪怕是main的默認(rèn)入口也要手動(dòng)設(shè)置漩氨。因?yàn)樵贔lutterActivity的onStart方法中調(diào)用了一個(gè)doInitialFlutterViewRun方法:

    private void doInitialFlutterViewRun() {
      // Don't attempt to start a FlutterEngine if we're using a cached FlutterEngine.
      if (host.getCachedEngineId() != null) {
        return;
      }
    
      ... ...
    
      String appBundlePathOverride = host.getAppBundlePath();
      if (appBundlePathOverride == null || appBundlePathOverride.isEmpty()) {
        appBundlePathOverride = FlutterInjector.instance().flutterLoader().findAppBundlePath();
      }
    
      // Configure the Dart entrypoint and execute it.
      DartExecutor.DartEntrypoint entrypoint =
          new DartExecutor.DartEntrypoint(
              appBundlePathOverride, host.getDartEntrypointFunctionName());
      flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint);
    }
    

    可見(jiàn),如果使用緩存id的話則不會(huì)走到下面的默認(rèn)切入點(diǎn)設(shè)置代碼遗增。

  • FlutterPlugin

    我們這里分析的是和原生通信,所以必須要看FlutterPlugin款青,它是通信的橋梁。

    前面我們說(shuō)到可以重寫(xiě)FlutterActivity的configureFlutterEngine方法來(lái)配置Plugin:

    @Override
        public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
            flutterEngine.getPlugins().add((FlutterPlugin) getPlugin());
            super.configureFlutterEngine(flutterEngine);
        }
    

    getPlugins方法獲取的是FlutterEngineConnectionRegistry,它的add方法中有如下代碼:

    if (has(plugin.getClass())) {
      return;
    }
    plugins.put(plugin.getClass(), plugin);
    plugin.onAttachedToEngine(pluginBinding);
    

    可見(jiàn)會(huì)按照plugin的class作為key保存到plugins中勘天,然后就調(diào)用plugin的onAttachedToEngine方法龙助,所以我們通常都會(huì)重寫(xiě)這個(gè)方法,在里面初始化各種Channel康震。

    下面看一個(gè)demo:

    class MyHomePlugin : FlutterPlugin, EventChannel.StreamHandler, MethodChannel.MethodCallHandler {
    
        private var batteryEventChannel : EventChannel? = null
        private var methodChannel : MethodChannel? = null
        private var batteryEventSink : EventChannel.EventSink? = null
    
        override fun onAttachedToEngine(binding : FlutterPlugin.FlutterPluginBinding) {
            batteryEventChannel = EventChannel(binding.binaryMessenger, "battery")
            batteryEventChannel?.setStreamHandler(this)
    
            methodChannel = MethodChannel(binding.binaryMessenger, "allMethod")
            methodChannel?.setMethodCallHandler(this)
        }
    
        override fun onDetachedFromEngine(binding : FlutterPlugin.FlutterPluginBinding) {
            batteryEventChannel?.setStreamHandler(null)
            batteryEventChannel = null
            methodChannel?.setMethodCallHandler(null)
            methodChannel = null
        }
    
        override fun onListen(arguments : Any?, events : EventChannel.EventSink?) {
            batteryEventSink = events
            events?.success(666)
              //events?.error("007","Wrong~",null)
            //events?.endOfStream()
        }
    
        override fun onCancel(arguments : Any?) {
            batteryEventSink?.endOfStream()
            batteryEventSink = null
        }
    
        override fun onMethodCall(call : MethodCall, result : MethodChannel.Result) {
            when(call.method){
                "getName"->{
                    LogUtil.d(this,"getName() has been called!")
                }
            }
        }
    }
    

    對(duì)于EventChannel來(lái)說(shuō)燎含,調(diào)用setStreamHandler方法設(shè)置一個(gè)EventChannel.StreamHandler,重寫(xiě)onListen和onCancel方法后即可接收Flutter發(fā)送的數(shù)據(jù)腿短,onListen回調(diào)中屏箍,使用一個(gè)變量持有events的引用,這樣在需要發(fā)送給Flutter的時(shí)候直接使用batteryEventSink發(fā)送即可橘忱,和MethodChannel不同赴魁,EventSink使用success、error和endOfStream方法發(fā)送數(shù)據(jù)钝诚,分別對(duì)應(yīng)Flutter監(jiān)聽(tīng)端的幾個(gè)回調(diào)颖御,F(xiàn)lutter端的監(jiān)聽(tīng)寫(xiě)法是:

    @override
    void initState() {
      super.initState();
      eventChannel.receiveBroadcastStream().listen(
        (event) {
          onReceiveBatteryChange(event);
        },
        onError: onReceiveBatteryWrong,
      );
    }
    

    這里調(diào)用了listen方法之后就會(huì)回調(diào)到上面原生代碼的onListen方法中,使用方法參數(shù)events調(diào)用相關(guān)方法后凝颇,最終又會(huì)回調(diào)到這里listen方法里設(shè)置的對(duì)應(yīng)回調(diào)函數(shù)潘拱。

    對(duì)于MethodChannel來(lái)說(shuō),以Flutter端發(fā)送拧略、原生接收的方向來(lái)看芦岂,F(xiàn)lutter端的發(fā)送代碼是:

    Future result = await methodChannel.invokeMethod("getName");
    

    原生接收需要調(diào)用setMethodCallHandler給MethodChannel設(shè)置MethodChannel.MethodCallHandler,重寫(xiě)其onMethodCall回調(diào)方法處理接收邏輯垫蛆。反過(guò)來(lái)盔腔,原生發(fā)送、Flutter接收的方向也是一樣的,只不過(guò)設(shè)置的內(nèi)容交換一下即可弛随。

  • 總結(jié)

    本文分析了FlutterActivity瓢喉、FlutterPlugin、Channel之間的連接關(guān)系舀透,以及FlutterEngine的緩存策略栓票,并知道了如何使用相關(guān)Channel來(lái)使Flutter端和原生交互起來(lái),至于EventChannel和MethodChannel的原理涉及的代碼比較多愕够,另起一篇來(lái)整理走贪。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市惑芭,隨后出現(xiàn)的幾起案子坠狡,更是在濱河造成了極大的恐慌,老刑警劉巖遂跟,帶你破解...
    沈念sama閱讀 222,807評(píng)論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件逃沿,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡幻锁,警方通過(guò)查閱死者的電腦和手機(jī)凯亮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)哄尔,“玉大人假消,你說(shuō)我怎么就攤上這事×虢樱” “怎么了富拗?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,589評(píng)論 0 363
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)鸣戴。 經(jīng)常有香客問(wèn)我媒峡,道長(zhǎng),這世上最難降的妖魔是什么葵擎? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,188評(píng)論 1 300
  • 正文 為了忘掉前任谅阿,我火速辦了婚禮,結(jié)果婚禮上酬滤,老公的妹妹穿的比我還像新娘签餐。我一直安慰自己,他們只是感情好盯串,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,185評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布氯檐。 她就那樣靜靜地躺著,像睡著了一般体捏。 火紅的嫁衣襯著肌膚如雪冠摄。 梳的紋絲不亂的頭發(fā)上糯崎,一...
    開(kāi)封第一講書(shū)人閱讀 52,785評(píng)論 1 314
  • 那天,我揣著相機(jī)與錄音河泳,去河邊找鬼沃呢。 笑死,一個(gè)胖子當(dāng)著我的面吹牛拆挥,可吹牛的內(nèi)容都是我干的薄霜。 我是一名探鬼主播,決...
    沈念sama閱讀 41,220評(píng)論 3 423
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼纸兔,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼惰瓜!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起汉矿,我...
    開(kāi)封第一講書(shū)人閱讀 40,167評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤崎坊,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后洲拇,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體奈揍,經(jīng)...
    沈念sama閱讀 46,698評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,767評(píng)論 3 343
  • 正文 我和宋清朗相戀三年呻待,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片队腐。...
    茶點(diǎn)故事閱讀 40,912評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蚕捉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出柴淘,到底是詐尸還是另有隱情迫淹,我是刑警寧澤,帶...
    沈念sama閱讀 36,572評(píng)論 5 351
  • 正文 年R本政府宣布为严,位于F島的核電站敛熬,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏第股。R本人自食惡果不足惜应民,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,254評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望夕吻。 院中可真熱鬧诲锹,春花似錦、人聲如沸涉馅。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,746評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)稚矿。三九已至庸诱,卻和暖如春捻浦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背桥爽。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,859評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工朱灿, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人聚谁。 一個(gè)月前我還...
    沈念sama閱讀 49,359評(píng)論 3 379
  • 正文 我出身青樓母剥,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親形导。 傳聞我的和親對(duì)象是個(gè)殘疾皇子环疼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,922評(píng)論 2 361

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