Flutter模塊化實(shí)踐

一纫事、背景

本文示例及源碼基于Flutter 1.22.4 ? channel stable ? https://github.com/flutter/flutter.git
Framework ? revision (9 weeks ago) ? 2020-11-13
Tools ? Dart 2.10.4

由于公司內(nèi)部需要對原有APP使用flutter進(jìn)行改造棠众,打算分業(yè)務(wù)進(jìn)行模塊化重構(gòu)查坪,故實(shí)踐了下flutter如何作為模塊嵌入到Android原有工程。官方文檔作為學(xué)習(xí)的第一手資料弛针,及時性和準(zhǔn)確性都能得到保證樟结,故先參考了https://flutter.cn/.....上模塊集成的教程進(jìn)行了一些示例demo的編寫无蜂。

二喇完、模塊集成

1.自動集成

  1. 使用android studio創(chuàng)建一個空的應(yīng)用工程.
  2. 通過File->New->New Module..創(chuàng)建flutter module,如果已有工程可以通過Import Flutter Module導(dǎo)入。
image.png

這樣flutter 模塊就自動集成好了蹬耘。

2.手動集成

通常我們需要把各個業(yè)務(wù)模塊拆分出來芝雪,每個團(tuán)隊(duì)成員負(fù)責(zé)一個模塊的開發(fā)工作,互不干擾综苔。當(dāng)需要集成調(diào)試的時候惩系,在原生殼工程中通過依賴aar或者源碼的方式進(jìn)行調(diào)試即可。源碼依賴的方式適用于flutter代碼與原生交互較多的場景如筛,如果業(yè)務(wù)A的開發(fā)不需要修改B業(yè)務(wù)的代碼堡牡,直接依賴B業(yè)務(wù)的aar包即可。git subsubmodule提供了這樣一種協(xié)作方式杨刨。git submodule 允許你將一個Git 倉庫當(dāng)作另外一個Git 倉庫的子目錄晤柄。通過git subsubmodule可以方便的管理業(yè)務(wù)模塊、基礎(chǔ)模塊等妖胀。具體步驟如下:

  • 通過如下命令行創(chuàng)建獨(dú)立module芥颈,
flutter create -t module login_module
  • 導(dǎo)入android studio運(yùn)行一下惠勒,會生成.android目錄,結(jié)構(gòu)如下
image.png
  • 把該項(xiàng)目用git管理起來,模塊是可以單獨(dú)運(yùn)行的爬坑,我們可以在上面開發(fā)業(yè)務(wù)邏輯.
  • 在原生android工程中執(zhí)行g(shù)it命令行添加子模塊纠屋,模塊有更新時在update一下即可
git submodule add {flutter module的倉庫地址}
git submodule update
  • 原生工程的setting.gradle加入如下配置:
setBinding(new Binding([gradle: this]))                                // new
evaluate(new File(                                                     // new// new
        'login_module/.android/include_flutter.groovy'         // new
))
  • 在工程app模塊的build.gradle文件中引入對 Flutter 模塊的依賴:
dependencies {
  implementation project(':flutter')
}

3.路由跳轉(zhuǎn)

我們使用自動集成的flutter module描述下如何跳轉(zhuǎn)到單個flutter頁面,添加如下代碼到Activity當(dāng)中:

   findViewById<View>(R.id.fab).setOnClickListener {
            startActivity(
                FlutterActivity.createDefaultIntent(this)
            )
        }

運(yùn)行APP,實(shí)際效果如下,出現(xiàn)了很明顯的黑屏現(xiàn)象,主要原因是flutterEngine初始化造成的卡頓,想要最小化這個延遲時間,可以在Application中提前初始化一個 FlutterEngine盾计,然后使用這個已經(jīng)預(yù)熱好的 FlutterEngine加載FlutterActivity:

image

三巾遭、源碼分析

FlutterActivity

通過android studio右側(cè)工具欄查看FlutterActivity的結(jié)構(gòu),點(diǎn)擊方法名可以快速定位到我們需要分析的方法


image.png

FlutterActivity的繼承結(jié)構(gòu)如下:

image.png

Host接口對通用方法進(jìn)行了抽象,FlutterActivityFlutterFragment負(fù)責(zé)實(shí)現(xiàn),內(nèi)部包括Lifecycle闯估、上下文、getInitialRoute吼和、getAppBundlePath等方法,我們可以繼承FlutterActivity重寫部分方法,實(shí)現(xiàn)自定義flutterEngine涨薪、自定義AppBundlePath等.
先從OnCreate開始:

@Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    switchLaunchThemeForNormalTheme();
    super.onCreate(savedInstanceState);
    lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
    //創(chuàng)建委托類
    delegate = new FlutterActivityAndFragmentDelegate(this);
    delegate.onAttach(this);
    delegate.onActivityCreated(savedInstanceState);
    configureWindowForTransparency();
     //createFlutterView會委托給delegate的onCreateView方法
    setContentView(createFlutterView());
    configureStatusBarForFullscreenFlutterExperience();
  }

FlutterActivityAndFragmentDelegate

委托類實(shí)現(xiàn)了 FlutterActivity和FlutterFragment中所有相同的Flutter邏輯.包括設(shè)置引擎、創(chuàng)建視圖炫乓、給flutter
分發(fā)生命周期事件刚夺、執(zhí)行dart代碼等功能.

  1. onCreateView方法負(fù)責(zé)ContentView的創(chuàng)建,內(nèi)部根據(jù)渲染模式的不同會創(chuàng)建FlutterTextureView或者FlutterSurfaceView,隨后將flutterEngine綁定到新建出來的FlutterView
>FlutterActivityAndFragmentDelegate.java
@NonNull
  View onCreateView(
      LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
      ...
    if (host.getRenderMode() == RenderMode.surface) {
      flutterView = new FlutterView(host.getActivity(), flutterSurfaceView);
    } else {
      FlutterTextureView flutterTextureView = new FlutterTextureView(host.getActivity());
        ...
      flutterView = new FlutterView(host.getActivity(), flutterTextureView);
    }
    flutterSplashView = new FlutterSplashView(host.getContext());
    ...
    flutterSplashView.displayFlutterViewWithSplash(flutterView, host.provideSplashScreen());
    flutterView.attachToFlutterEngine(flutterEngine);
    return flutterSplashView;
  }

  1. 調(diào)用flutterView#attachToFlutterEngine方法之后flutterEngine開始渲染FlutterView,此時已經(jīng)可以轉(zhuǎn)發(fā)來自

FlutterView的事件到綁定的flutterEngine了,例如用戶觸摸事件,無障礙服務(wù)事件末捣,鍵盤事件等侠姑。

3.FlutterActivityOnStart時調(diào)用代理類相應(yīng)的方法,內(nèi)部開始執(zhí)行dart代碼:

>FlutterActivityAndFragmentDelegate.java 
void onStart() {
    Log.v(TAG, "onStart()");
    ensureAlive();
    doInitialFlutterViewRun();
  }

4.doInitialFlutterViewRun方法內(nèi)部會先判斷dart是否被執(zhí)行過,如果是話就不再執(zhí)行了,接著獲取到資源路徑并加載

>FlutterActivityAndFragmentDelegate.java 
private void doInitialFlutterViewRun() {
    if (host.getCachedEngineId() != null) {
      return;
    }
    if (flutterEngine.getDartExecutor().isExecutingDart()) {
      return;
    }

    //STEP1 通過flutter/navigation方法通道往dart發(fā)送設(shè)置初始化路由的事件
    if (host.getInitialRoute() != null) {
      flutterEngine.getNavigationChannel().setInitialRoute(host.getInitialRoute());
    }
    //STEP2 獲取bundle資源路徑
    String appBundlePathOverride = host.getAppBundlePath();
    if (appBundlePathOverride == null || appBundlePathOverride.isEmpty()) {
      appBundlePathOverride = FlutterInjector.instance().flutterLoader().findAppBundlePath();
    }

    //STEP3 這里可以自定義manifest.xnl當(dāng)中的io.flutter.Entrypoint屬性設(shè)置dart代碼的執(zhí)行入口,默認(rèn)情況是main方法
    DartExecutor.DartEntrypoint entrypoint =
        new DartExecutor.DartEntrypoint(
            appBundlePathOverride, host.getDartEntrypointFunctionName());
    //STEP4 調(diào)用C++層代碼將main方法喚起,后面就是dart層的工作了,也就是進(jìn)行widget解析渲染等.
    flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint);
  }

5.看上面代碼標(biāo)注的STEP1,全局搜索flutter/navigation關(guān)鍵字,找到SystemChannels.navigation 是通道在dart中具體的實(shí)現(xiàn),ALT+單擊 navigation 發(fā)現(xiàn)調(diào)用處可能是在WidgetsBinding中,但是這里并沒有對IinitialRoute進(jìn)行處理,在app.dart下對初始路由有說明.

>binding.dart
mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
  @override
  void initInstances() {
    super.initInstances();
    ...
    SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
  }
}

查看app.dart中的 initialRoute 注釋可以知道,初始路由的值是在window.dartdefaultRouteName,該值可以在Android中通過NewEngineIntentBuilder#initialRoute設(shè)置.

以上是flutterActivity加載頁面的基本流程,搭建可用的混合路由方案還需要很多細(xì)節(jié)要處理箩做,比如閃屏莽红、engine復(fù)用、路由堆棧切換等等邦邦。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末安吁,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子燃辖,更是在濱河造成了極大的恐慌鬼店,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件黔龟,死亡現(xiàn)場離奇詭異妇智,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)氏身,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進(jìn)店門巍棱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人观谦,你說我怎么就攤上這事拉盾。” “怎么了豁状?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵捉偏,是天一觀的道長倒得。 經(jīng)常有香客問我,道長夭禽,這世上最難降的妖魔是什么霞掺? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮讹躯,結(jié)果婚禮上菩彬,老公的妹妹穿的比我還像新娘。我一直安慰自己潮梯,他們只是感情好骗灶,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著秉馏,像睡著了一般耙旦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上萝究,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天免都,我揣著相機(jī)與錄音,去河邊找鬼帆竹。 笑死绕娘,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的栽连。 我是一名探鬼主播险领,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼升酣!你這毒婦竟也來了舷暮?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤噩茄,失蹤者是張志新(化名)和其女友劉穎下面,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绩聘,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡沥割,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了凿菩。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片机杜。...
    茶點(diǎn)故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖衅谷,靈堂內(nèi)的尸體忽然破棺而出椒拗,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布蚀苛,位于F島的核電站在验,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏堵未。R本人自食惡果不足惜腋舌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望渗蟹。 院中可真熱鬧块饺,春花似錦、人聲如沸雌芽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽世落。三九已至想诅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間岛心,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工篮灼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留忘古,地道東北人。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓诅诱,卻偏偏與公主長得像髓堪,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子娘荡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評論 2 348

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