給現有 App 引入 Flutter Module

最近 Flutter 很火,相信長得帥的人都已經對它都有了初步的瞭解。
不過因爲目前默認使用 Flutter 做爲框架接管整個 App 進行開發,不夠靈活:一方面使用純 Flutter 開發須要在項目開始以前仔細評估是否有難以實現的功能;另外一方面現有的 App 想使用 Flutter 的話很難所有轉換過去。
很容易想到在現有的 App 的基礎上加入 Flutter 做爲部分畫面/功能的實現是一個理想的方案,也更有利於作技術嘗試和風險控制。 實際上目前 Flutter 官方提供了兩種方案用於給現有 App 加入 Flutter Module,另外還有一些第三方的方案,最近我作了一些嘗試,分享一些成果。
須要注意的是, 給現有 App 引入 Flutter Module 的功能還在實驗性的階段, APIs 和工具鏈處於未穩定階段,且須要切換到 master 分支(不穩定)使用。java

Android

建立一個 Flutter module

假設在 some/path/MyApp 下是 Android 項目目錄android

cd some/path
flutter create -t module --org com.example flutter_to_app
複製代碼

會在 some/path/flutter_to_app生成一個 Flutter Moduleshell

宿主 App 設置

須要在app/build.gradle裏設置緩存

android {
  //...
  compileOptions {
    sourceCompatibility 1.8
    targetCompatibility 1.8
  }
}
複製代碼

讓 App 依賴 Flutter Module

有兩種方案,直接依賴源代碼和 aar 產物。bash

1. 依賴生成的 aar

cd ~/Documents/Android/flutter_to_app
flutter build aar
複製代碼
// MyApp/app/build.gradle

android {
  // ...
}

repositories {
  maven {
  //可使用相對路徑或者絕對路徑
    url 'some/path/flutter_to_app/build/host/outputs/repo'
  }
}

dependencies {
  // ...
  releaseCompile ('com.example. flutter_to_app:flutter_release:1.0@aar') {
    transitive = true
  }
}
複製代碼

能夠用 flutter build aar --debug 生成 debug 依賴app

// MyApp/app/build.gradle

dependencies {
  // ...
  debugCompile ('com.example.my_flutter:flutter_debug:1.0@aar') {
    transitive = true
  }
}
複製代碼

2.直接依賴源碼

依賴 aar 的方式有點麻煩,還須要到 Module 中編譯,因此也能夠直接依賴源碼編譯框架

在宿主 App settings.gradle加入maven

// MyApp/settings.gradle
include ':app'
...                                     
setBinding(new Binding([gradle: this]))                                 
evaluate(new File(                                                     
 settingsDir.parentFile,                                                
  'flutter_to_app/.android/include_flutter.groovy'                          
))  
複製代碼

上面的File()路徑是 flutter module 相對 host app 的路徑。binding 和 include_flutter.groovy 腳本引入 flutter module 自己和相關的 plugin。ide

最後,依賴模塊:工具

// MyApp/app/build.gradle
dependencies {
  implementation project(':flutter')
}
複製代碼

在 Android 項目中使用 Flutter Module

目前有兩種方式實現,分別在

  1. io.flutter.facade.*
  2. io.flutter.embedding.android.*

兩個包下, 第一種已經被 deprecated ,第二種還處於 technical preview 階段,因此兩種版本的 API 都還不穩定,但能夠大概看一下兩種方式。

之前的方式(deprecated) ( io.flutter.facade )

經過使用 Flutter.createView:

fab.setOnClickListener(new View.OnClickListener() {
  @Override
  public void onClick(View view) {
    View flutterView = Flutter.createView(
      MainActivity.this,
      getLifecycle(),
      "route1"
    );
    FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(600, 800);
    layout.leftMargin = 100;
    layout.topMargin = 200;
    addContentView(flutterView, layout);
  }
});
複製代碼

經過使用 Flutter.createFragment:

// MyApp/app/src/main/java/some/package/SomeActivity.java
fab.setOnClickListener(new View.OnClickListener() {
  @Override
  public void onClick(View view) {
    FragmentTransaction tx = getSupportFragmentManager().beginTransaction();
    tx.replace(R.id.someContainer, Flutter.createFragment("route1"));
    tx.commit();
  }
});
複製代碼

建立ViewFragment都很是簡單,可是實際測試下來,啓動 View (FlutterFragment實際上也是經過 createView 來生成視圖的)會有啓動時間,體驗沒那麼無縫。

新的方式( io.flutter.embedding.android.* )

經過 FlutterView ( 繼承自 FrameLayout )

實例化 FlutterView 嵌入 Native
FlutterView flutterView = new FlutterView(this);
FrameLayout frameLayout = findViewById(R.id.framelayout);
frameLayout.addView(flutterView);
//建立一個 FlutterView 就能夠了,這個時候還不會渲染。
//調用下面代碼後纔會渲染
flutterView.attachToFlutterEngine(flutterEngine);
複製代碼

經過 FlutterFragment 打開

經過 xml
<fragment
    android:id="@+id/flutterfragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:name="io.flutter.embedding.android.FlutterFragment"
    />
複製代碼
直接實例化
flutterFragment = new FlutterFragment.createDefault();
複製代碼

經過 FlutterActivity 打開

在 AndroidManifest.xml 中註冊
<activity
        android:name="io.flutter.embedding.android.FlutterActivity"
        android:theme="@style/LaunchTheme"
        android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
        android:hardwareAccelerated="true"
        android:windowSoftInputMode="adjustResize"
        android:exported="true"
        />
複製代碼
默認啓動方式
//默認路由爲 '/'
    Intent defaultFlutter = new FlutterActivity.createDefaultIntent(currentActivity);
    startActivity(defaultFlutter);
複製代碼
啓動到指定路由
Intent customFlutter = new FlutterActivity.IntentBuilder()
      .initialRoute("someOtherRoute")
      .build(currentActivity);
    startActivity(customFlutter);
複製代碼

FlutterEngine 緩存機制

實際上,經過 API 和源碼能夠看出,新版的 Flutter 相關類io.flutter.embedding.android.*徹底從新設計了 Native 調用的方式,從包名(embedding)就能夠看出是但願嵌入 Native, 其中一個重要的變化是加入了 FlutterEngine 的緩存機制。 經過老的方式啓動 Flutter 的響應時間長包括了須要啓動FlutterEngine的時間,能夠理解爲冷啓動,並且從原生的不一樣Activity / ViewController 啓動 Flutter 都須要啓動一個新的 FlutterEngine,因此不只第一次啓動 Flutter 時間長 ,每次啓動都會須要一樣的時間。好比下面的狀況

Native A -> Flutter B -> Native C -> Flutter D

這樣從Native ANative B啓動時會實例化兩個FlutterEngine

這樣不只慢,對資源的開銷也更多。 爲了解決這個問題,新的解決方案引入了FlutterEngine 緩存機制。

1. 使用 FlutterEngineCache

// 實例化 FlutterEngine.
FlutterEngine flutterEngine = new FlutterEngine(context);

// 預熱
 flutterEngine
  .getDartExecutor()
  .executeDartEntrypoint(
    DartEntrypoint.createDefault()
  );
  
 //放入 FlutterEngineCache
  FlutterEngineCache
  .getInstance()
  .put("my_engine_id", flutterEngine);
  
  //啓動 Activity 的時候使用
  Intent intent = FlutterActivity
  .withCachedEngine("my_engine_id")
  .build();
  startActivity(intent);
  
  //實例化 Fragment
  FlutterFragment flutterFragment = FlutterFragment
  .withCachedEngine("my_engine_id")
  .build();
複製代碼

2. 繼承 FlutterFragment / FlutterActivity

自行處理存儲 FlutterEngine 的地方

public class MyFlutterFragment extends FlutterFragment {
  @Override
  @Nullable
  protected FlutterEngine provideFlutterEngine(@NonNull Context context) {
    //自行存儲 FlutterEngine 實例
    return MyFlutterEngine.getFlutterEngine();

    //好比 Application 中
    return ((MyApp) context.getApplication).getFlutterEngine();
  }
}
複製代碼
public class MyFlutterActivity extends FlutterActivity {
  @Nullable
  @Override
  public FlutterEngine provideFlutterEngine(@NonNull Context context) {
    FlutterEngine flutterEngine;
    //自行存儲 FlutterEngine 實例
    flutterEngine = MyFlutterEngineCache.getFlutterEngine();
    
    //好比 Application 中
    flutterEngine = ((MyApp) getApplication()).getFlutterEngine();

    return flutterEngine;
  }
}
複製代碼

3. 在 Activity 實現 FlutterEngineProvider 接口

public class MyActivity extends Activity implements FlutterEngineProvider {
  @Override
  @Nullable
  FlutterEngine provideFlutterEngine(@NonNull Context context) {
    //自行存儲 FlutterEngine 實例
    return MyFlutterEngine.getFlutterEngine();
    
    //好比 Application 中
    return ((MyApp) context.getApplication).getFlutterEngine();
  }
}
複製代碼

FlutterBoost 方案

新一代 Flutter-Native 混合解決方案。 FlutterBoost是一個Flutter插件,它能夠輕鬆地爲現有原生應用程序提供Flutter混合集成方案。FlutterBoost的理念是將Flutter像Webview那樣來使用。在現有應用程序中同時管理Native頁面和Flutter頁面並不是易事。 FlutterBoost幫你處理頁面的映射和跳轉,你只需關心頁面的名字和參數便可(一般能夠是URL)。

FlutterBoost 是閒魚開源處理 Flutter-Native 混合開發的解決方案,是一個熱門的方案,但和官方方案對比我認爲有兩個重要的異同點:

  1. 當時閒魚設計這個庫其中的一個重要目的就是爲了解決 FlutterEngine 沒法重用的問題(當時 Flutter 團隊尚未能夠處理 FlutterEngine 重用的方案),而如今 Flutter 團隊推出的新的解決方案也能夠解決這個問題。
  2. 目前 Flutter 官方的方案的細粒度更小,能夠經過 View 的方式調用 Flutter ,也就是說你能夠只將畫面中的某一個圖表用 Flutter 替換。

最後,官方的兩種方案一種已經被捨棄一種還處於實驗性階段,目前最新方案的Milestone 是12月,因此到時候再次評估可行性。而國內大廠基本上各自都有本身的解決方案,因此目前使用官方方案的話還須要仔細評估。

相關文章
相關標籤/搜索