成熟項目的Flutter快速引入以及Flutter、Native混合開發(fā)探究

閱讀須知:

  • 1.本篇文章基于 Android 平臺
  • 2.Flutter測試項目——測試胡本、Flutter容器項目——容器

本文分為以下章節(jié)牌柄,讀者可按需閱讀:

  • 1.成熟項目的Flutter快速引入——在已有項目中無縫引入Flutter作為開發(fā)的一種方式
  • 2.Flutter、Native混合開發(fā)——在一個頁面中同時使用 Flutter 與 Native 兩種技術(shù)的開發(fā)探究
  • 3.尾巴

Flutter測試項目Github

Flutter容器項目Github

一侧甫、成熟項目的Flutter快速引入

現(xiàn)在很多教程都停留在創(chuàng)建一個新的 Flutter 項目然后開始介紹如何使用這個項目開發(fā) Flutter珊佣。但是其實我們目前大部分使用 Flutter 的場景都是基于已經(jīng)成熟的項目。我們不可能因為使用 Flutter 而將原來的項目推到重來闺骚。這一節(jié)我就來介紹一種成熟項目無縫接入 Flutter 的方式舶沿。本章需要大家結(jié)合上面提到的 Github 項目代碼食用循狰。

1.閑魚以及美團的實踐

  • 1.目前很多廠商都有著自己的成熟項目的 Flutter 接入實踐宫屠,其中美團消恍、閑魚的實踐應(yīng)該已經(jīng)運行的比較久了煮落。他們的接入方式主要分下面幾步:
    • 1.理清楚 Flutter App 的構(gòu)建和運行方式启搂。
    • 2.修改 Flutter 項目的 Gradle 文件计维,將 Flutter 項目打包成 AAR 文件扫责。
    • 3.將 AAR 文件推送到 Maven 服務(wù)器上须板。
    • 4.主工程引入 Flutter 的 AAR 文件碰镜,和主工程一起編譯生成主 App。
  • 2.美團的實踐
  • 3.閑魚的實踐

2.我的實踐

從上面的介紹來看习瑰,閑魚绪颖、美團的實踐方式似乎有著一些不方便之處。比如說不能動態(tài)更新 Flutter 代碼甜奄、Flutter 的 AAR 和主工程一起編譯太具有侵入性等等(這里只是我自己淺薄的看法柠横,有異議的同學(xué)可以在評論區(qū)提出)。所以我這一節(jié)要介紹一種侵入性非常小的接入 Flutter 的方式课兄,簡單來說就一句話:動態(tài)加載 Flutter 生成的 Apk牍氛。接下來我會結(jié)合前面提到的兩個 github 項目里的代碼進行講解,大家一定要把這兩個項目 clone 下來烟阐,當(dāng)然能點個 star 就更好了搬俊。

(1).創(chuàng)建Flutter測試項目

image
  • 1.創(chuàng)建一個 Flutter Project紊扬,這個很簡單,網(wǎng)上教程很多我就不復(fù)述了唉擂。
  • 2.創(chuàng)建好了之后如圖1所示餐屎,我們需要在 app 目錄下的 build.gradle 文件中添加一些代碼,如代碼塊1所示楔敌。
----代碼塊1啤挎,本文發(fā)自簡書、掘金:何時夕-----
project.afterEvaluate {
    android.applicationVariants.all { variant ->
        def variantName = variant.name.capitalize()
        def buildTask = project.tasks.findByName("assemble${variantName}")
        if (buildTask) {
            def outputApk = variant.outputs[0].outputFile.path
            def classEntry = "*.dex"
            def soEntry = "lib/*"
            def metaEntry = "META-INF/*"
            def licenseEntry = "assets/flutter_assets/LICENSE"

            buildTask.doLast {
                println variant.outputs[0].outputFile.length()
                exec {
                    commandine 'sh', '-c', "zip -d ${outputApk} ${classEntry}"
                }
                exec {
                    commandLine 'sh', '-c', "zip -d ${outputApk} ${soEntry}"
                }
                exec {
                    commandLine 'sh', '-c', "zip -d ${outputApk} ${metaEntry}"
                }
                exec {
                    commandLine 'sh', '-c', "zip -d ${outputApk} ${licenseEntry}"
                }
            }
        }
    }
}

  • 3.這個代碼的主要功能是將 flutter 生成的 apk 中的 classes.dex卵凑、libflutter.so庆聘、META-INF 等等不需要的文件都刪掉,因為我們最終只需要用到 apk 中的 Dart 代碼與圖片資源勺卢。
  • 4.代碼加好之后伙判,我們用命令行運行 flutter build apk --debug,這樣就會生成一個 debug 版的 apk黑忱。其大小為 7.3 MB宴抚,沒有添加代碼塊1中的代碼之前 debug 版的 apk 大小為 33.5 MB「ι罚可以看見這個操作還是非常有有效果的菇曲。而如果是 release 版的 apk,其大小還會進一步縮小到 1.5 MB抚吠。

(2).創(chuàng)建Flutter容器項目

image
  • 1.有了 Flutter 的精簡 apk常潮,接下來我們需要用一個容器來加載這個 Flutter apk。具體代碼在前面我提到的Flutter 容器項目中楷力,接下來大家就跟隨我來看看這個容器是怎么加載 Flutter apk 的吧喊式。
  • 2.如圖2,項目中 Flutter 容器是以一個 Android Library 的形式存在的萧朝,這樣也方便大家能把這個 lib 引入到自己的工程中岔留。我們可以看見 lib 中直接引入的 Flutter.jar,這個 jar 分為 debug 版 和 release 版检柬。jar 中包含了 Flutter 的 java 層代碼献联,與 so 文件。debug 版本大小為 7.3MB 何址,release 版本則是 3.6MB里逆。這就是最終我們的 apk 會增大的大小,還是可以接受的头朱。而包含 Dart 代碼和資源的 apk运悲,我們可以通過動態(tài)下載來獲取。
----代碼塊2项钮,本文發(fā)自簡書班眯、掘金:何時夕-----
public class MainActivity extends AppCompatActivity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    RxPermissions permissions = new RxPermissions(this);
    permissions.setLogging(true);
    permissions.request(Manifest.permission.READ_EXTERNAL_STORAGE)
        .subscribe(aBoolean -> FlutterContainer.init(getApplication(), "/storage/emulated/0/flutter1.apk"));
    findViewById(R.id.aaa).setOnClickListener(v -> startActivity(new Intent(MainActivity.this, Main2Activity.class)));
  }
}

----代碼塊3希停,本文發(fā)自簡書、掘金:何時夕-----
public class FlutterContainer {
  private static final String TAG = "FlutterContainer";
  private static boolean sInitialized = false;
  private static Context sApplicationContext;

  private static String sFlutterInstallPath = "";

  public static void init(@NonNull Application applicationContext,
                          @NonNull FlutterEngine.PrepareFlutterPackage prepareFlutterPackage) {
    init(applicationContext, null, prepareFlutterPackage, null);
  }

  public static void init(@NonNull Application applicationContext,
                          @NonNull String flutterInstallPath) {
    init(applicationContext, flutterInstallPath, null, null);
  }

  public static void init(@NonNull Application applicationContext,
                          @NonNull String flutterInstallPath,
                          @Nullable FlutterEngine.Callback startCallBack) {
    init(applicationContext, flutterInstallPath, null, startCallBack);
  }

  public static void init(@NonNull Application applicationContext,
                          @NonNull FlutterEngine.PrepareFlutterPackage prepareFlutterPackage,
                          @Nullable FlutterEngine.Callback startCallBack) {
    init(applicationContext, null, prepareFlutterPackage, startCallBack);
  }

  /**
   * 只能在 app 啟動的時候初始化一次
   *
   * @param applicationContext
   */
  private static void init(@NonNull Application applicationContext, @Nullable String flutterInstallPath,
                           @Nullable FlutterEngine.PrepareFlutterPackage prepareFlutterPackage, @Nullable FlutterEngine.Callback startCallBack) {
    if (sInitialized) {
      return;
    }
    new FlutterManager(applicationContext);
    sInitialized = true;
    sApplicationContext = applicationContext;
    if (!TextUtils.isEmpty(flutterInstallPath)) {
      upgradeFlutterPackage(flutterInstallPath, startCallBack);
    } else if (prepareFlutterPackage != null) {
      upgradeFlutterPackage(prepareFlutterPackage, startCallBack);
    } else {
      Log.i(TAG, "FlutterContainer init no flutter package");
    }
  }

  /**
   * @param flutterInstallPath
   */
  public static void upgradeFlutterPackage(@NonNull String flutterInstallPath, @Nullable FlutterEngine.Callback startCallBack) {
    if (!sInitialized) {
      return;
    }
    FlutterManager.getInstance().resetFlutterPackage();
    sFlutterInstallPath = flutterInstallPath;
    FlutterManager.getInstance().getFlutterEngine().startFast(startCallBack);
  }

  • 3.接下來我們看代碼塊2署隘,這是一個例子宠能。可以看見 FlutterContainer 就是容器庫暴露出來的 api磁餐,用于初始化 Flutter 環(huán)境以及升級 Flutter Apk违崇。
  • 4.代碼塊2中調(diào)用了 init,所以我們來看看代碼塊3 FlutterContainer 中的 api诊霹。
    • 1.init:方法用于第一次需要初始化 Flutter apk 的時候調(diào)用一次羞延,有多個不同的 api。
    • 2.upgradeFlutterPackage:則是用于重新加載 Flutter apk脾还,比如我們需要發(fā)布新的 Flutter 版本伴箩,就可以使用這個 api 來重新加載一個新的 Flutter apk。
----代碼塊4鄙漏,本文發(fā)自簡書嗤谚、掘金:何時夕-----
public class FlutterManager {

  private static FlutterManager sInstance;

  private final FlutterEngine mFlutterEngine;
  private final FlutterContextWrapper mFlutterContextWrapper;
  private final Context mContext;

  FlutterManager(Application context) {
    sInstance = this; // 簡單單例, 線程并不安全, 邏輯保證
    mFlutterEngine = new FlutterEngine(context);
    mFlutterContextWrapper = new FlutterContextWrapper(context);
    mContext = context;
  }

  public static FlutterManager getInstance() {
    return sInstance;
  }

  public void registerChannel(BinaryMessenger messenger, String channel, BaseHandler handler) {
    new MethodChannel(messenger, channel + ".method").setMethodCallHandler(handler);
    if (handler.mEnableEventChannel) {
      new EventChannel(messenger, channel + ".event").setStreamHandler(handler);
    }
  }

  FlutterEngine getFlutterEngine() {
    return mFlutterEngine;
  }

  public FlutterContextWrapper getFlutterContextWrapper() {
    return mFlutterContextWrapper;
  }

  /**
   * 是否有 Flutter 包可用
   *
   * @return
   */
  public boolean isFlutterAvailable() {
    File activeApk = new File(FlutterContainer.getFlutterInstallPath());
    return activeApk.isFile();
  }

  /**
   * 如果要使用新的 Flutter 包,那么需要重置一下
   */
  void resetFlutterPackage() {
    mFlutterContextWrapper.reset();
  }
}

  • 5.FlutterContainer 相當(dāng)于初始化 Flutter apk 的入口怔蚌,那么 FlutterManager 就是具體做這件事情的類了巩步。我們看代碼塊4,可以了解到 FlutterManager 是一個單例桦踊,F(xiàn)lutterContainer.init 中有一個步驟就是初始化這個單例椅野。其中的 api 有下面這些功能:
    • 1.registerChannel:注冊 java 和 dart 之間的通信 channel,這個在后面會詳細(xì)講解钞钙。
    • 2.getFlutterEngine:獲取 FlutterEngine鳄橘,其內(nèi)部會調(diào)用 Flutter 真正加載 apk 的 api声离。
    • 3.getFlutterContextWrapper:一個 Context 的包裝類芒炼,主要是為了讓 Flutter 能順利解壓出 apk 里面的代碼和資源。
----代碼塊5术徊,本文發(fā)自簡書本刽、掘金:何時夕-----
public class FlutterContextWrapper extends ContextWrapper {

  private AssetManager sAssets;

  FlutterContextWrapper(Context base) {
    super(base);
  }

  public void reset() {
    sAssets = null; // 在每次安裝flutter包之后,需要重新創(chuàng)建新的assets
  }

  @Override
  public Resources getResources() {
    return new Resources(getAssets(), super.getResources().getDisplayMetrics(),
        super.getResources().getConfiguration());
  }

  @Override
  public AssetManager getAssets() {
    if (sAssets != null) {
      return sAssets;
    }

    File activeApk = new File(FlutterContainer.getFlutterInstallPath());
    if (!activeApk.isFile()) {
      return super.getAssets();
    }

    sAssets = ReflectionUtil.newInstance(AssetManager.class);
    ReflectionUtil.callMethod(sAssets, "addAssetPath", activeApk.getPath());
    return sAssets;
  }

  @Override
  public PackageManager getPackageManager() {
    return new FlutterPackageManager(super.getPackageManager());
  }
}

  • 6.因為 Flutter 在 build apk 的時候會將 Dart 代碼和資源都放在 asset 中赠涮,所以我們需要如代碼塊5中那樣子寓,創(chuàng)建一個 FlutterContextWrapper 來替換 AssetManager,使得 Flutter 加載 apk 時 asset 目錄指向我們創(chuàng)建的 Flutter apk 中笋除。
----代碼塊6斜友,本文發(fā)自簡書、掘金:何時夕-----
class FlutterEngine {

  private static boolean sInitialized; // 全局標(biāo)記引擎已經(jīng)啟動
  private final Context mContext;

  FlutterEngine(Context context) {
    mContext = context;
  }

  /**
   * 快速啟動模式垃它,表示已經(jīng)有包了
   */
  void startFast(@Nullable Callback callback) {
    if (sInitialized) {
      // 需要盡快啟動鲜屏,所以需要去重
      callback(callback, null);
      return;
    }
    if (FlutterManager.getInstance().isFlutterAvailable()) { // 當(dāng)前有可用包
      startFlutterInitialization();
      ensureInitializationComplete();
      callback(callback, null);
    } else {
      DebugUtil.logError(new RuntimeException("startFast but no available package"));
    }
  }

  /**
   * 慢速啟動模式, 表示沒有報烹看,需要準(zhǔn)備
   */
  void startSlow(@Nullable Callback callback, @NonNull PrepareFlutterPackage prepareFlutterPackage) {
    Single.fromCallable(() -> {
      // 此處不去重, 不管是否sInitialized都重新初始化, 保證使用最新flutter包.
      prepareFlutterPackage.prepareFlutterPackage();
      return new Object();
    }).subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(o -> {
          startFlutterInitialization();
          ensureInitializationComplete();
          callback(callback, null);
        }, throwable -> {
          throwable.printStackTrace();
          callback(callback, throwable);
        });
  }

  private static void callback(@Nullable Callback callback, Throwable t) {
    if (callback != null) {
      callback.onCompleted(t);
    }
  }

  private void startFlutterInitialization() { // 不阻塞UI
    // Flutter SDK的start方法可以多次調(diào)用, 他的主要作用是解壓資源, 因此不用做去重
    FlutterMain.startInitialization(FlutterManager.getInstance().getFlutterContextWrapper());
  }

  private void ensureInitializationComplete() {
    FlutterMain.ensureInitializationComplete(mContext, null);
    sInitialized = true; // 已經(jīng)初始化
  }

  // 啟動回調(diào)
  public interface Callback {

    /**
     * 初始化完成.
     *
     * @param e 成功為null,失敗不為null.
     */
    void onCompleted(Throwable e);
  }

  // 準(zhǔn)備 Flutter 包的回調(diào)
  public interface PrepareFlutterPackage {
    String prepareFlutterPackage();
  }
}

  • 7.順接 FlutterContainer 的調(diào)用繼續(xù)深入洛史,我們會來到代碼塊6的 FlutterEngine 中惯殊,這里主要有兩個 api:
    • 1.startFast:如方法名說的那樣,這個方法表示快速加載 flutter apk也殖。他只能被調(diào)用一次土思,多次調(diào)用會去重,一般來說我們?nèi)绻呀?jīng)準(zhǔn)備好了 flutter apk 的話忆嗜, 那么可以使用這個方法來加載 flutter apk己儒。可以看見其內(nèi)部最終會調(diào)用到 FlutterMain.startInitialization捆毫,這是 Flutter.jar 中的 api址愿,主要用于解壓和移動 Context 中的 Asset。因為我們前面創(chuàng)建了一個 FlutterContextWrapper冻璃,所以這里其實會解壓 flutter apk 中的 Dart 代碼和資源响谓。
    • 2.startSlow:這個方法能調(diào)用多次,主要用于升級 apk省艳,多次調(diào)用不會去重娘纷。如果我們沒有準(zhǔn)備好 apk,需要從網(wǎng)絡(luò)中下載跋炕,可以使用這個方法赖晶。但是最終的原理和 startFast 一樣,都是使用 FlutterMain.startInitialization 來解壓和移動 Flutter apk 中的資源辐烂。
  • 8.到這里成熟項目中無縫引入 Flutter 就完成了遏插。大家可以編譯Flutter容器項目然后將Flutter測試項目生成的 apk adb push 到手機的 /storage/emulated/0/flutter1.apk 中,就能體驗到動態(tài)加載 Flutter apk 的快感了纠修。
  • 9.另外你還可以使用 flutter attch 來對 debug 版的 Flutter apk 進行 hot reload胳嘲,享受到秒級代碼更新的快感。

二扣草、Flutter了牛、Native混合開發(fā)

前面完了在成熟項目中無縫引入 Flutter 的方式,這一章我們再來說說 Flutter 和 Native 混合開發(fā)的方式辰妙∮セ觯可能會混合開發(fā)不是很簡單嗎,直接嵌入一個 Flutter 的 Activity/Fragment 就能將其作為容器運行 Flutter 了密浑。其實這樣的想法太過理想化蛙婴,如果我的一個 Acitivity/Fragment 中 Flutter 和 Native 都需要有呢?這一章我我就是要來解決這個問題尔破,大家隨我一起往下看街图。

1.Flutter背传、Native混合開發(fā)場景以及閑魚的實踐

  • 1.我們先來聊聊在什么情況下在 Activity/Fragment 中會需要 Flutter、Native 一起使用
    • 1.比如我的一個界面上需要嵌入地圖 view台夺,此時如果我需要在這個界面上使用 Flutter 的話径玖,因為 Flutter 的組件遠沒有 Native 這么完善,像高德地圖颤介、百度地圖目前都只有 Native 的版本梳星,所以此時就需要 Flutter、Native 混合開發(fā)了滚朵。
    • 2.再拿目前比較火的短視頻 App 們來做例子冤灾,例如抖音 App 的視頻編輯功能,視頻編輯的大部分功能都是基于 Native 層的視頻編輯 sdk 來開發(fā)的辕近。如果這種界面要上 Flutter 的話韵吨,整個視頻編輯 sdk 需要提供一 Dart 的版本,這在短時間內(nèi)都是無法實現(xiàn)的移宅。
    • 3.有了上面兩個例子归粉,我們現(xiàn)在大概可以知道在什么場景下需要在一個界面上使用 Flutter、Native 進行混合開發(fā)了:Flutter 的控件還無法代替 Native 的控件時漏峰,如果某個界面需要上 Flutter 的話糠悼,就會出現(xiàn)這樣的場景。雖然隨著 Flutter 的慢慢發(fā)展浅乔,慢慢可能會有 Flutter 版的地圖倔喂、Flutter 版的視頻編輯 sdk,但是在最近一兩年內(nèi)靖苇,F(xiàn)lutter席噩、Native 混合開發(fā)還是一個非常常見的場景。
  • 2.那么我們再來聊聊目前已經(jīng)有的混合開發(fā)的實踐贤壁,目前閑魚有寫過博客分享自己的混合開發(fā)實踐:閑魚的混合開發(fā)實踐悼枢。
    • 1.使用 Flutter 提供的 api 將 Android 端的 View 交給 Flutter。
    • 2.因為 Flutter 渲染的方式是 SurfaceView 或者 TextureView芯砸,所以 Android 端的 View 會生成一個 Texture(OpenGL的紋理)萧芙,交給 Flutter 然后讓 Flutter 一起渲染在 Surface/TextureVIew 上给梅。
    • 3.相應(yīng)的手勢也由 Flutter 層傳遞給 Android 層假丧。
  • 3.閑魚的實踐方式當(dāng)然有它們的優(yōu)勢,例如是官方推薦的實踐方式动羽、通用性更好等等包帚。但是其有不可忽視的缺點就是Android View 的 Texture 傳遞到 Flutter 的流程是 GPU->CPU->GPU,這是一套昂貴的方案运吓。

2.我的實踐

為了解決數(shù)據(jù)傳遞的昂貴耗損渴邦,我想了另外一個辦法來繞過這個問題疯趟。本小結(jié)需要結(jié)合Flutter容器項目食用。

  • 1.我們首先得了解 Flutter 在 Android 端渲染的幾個前置知識:

    • 1.Flutter 在開始運行之后谋梭,畫面是渲染到 Android 端的 SurfaceView/TextureView 上面的信峻。
    • 2.要深入了解 SurfaceView 和 TextureView,可以看這篇文章:Android繪制機制以及Surface家族源碼全解析瓮床。
    • 3.Flutter 如果用 SurfaceView 渲染盹舞,底層默認(rèn)是黑的。
    • 4.Flutter 如果用 TextureView 渲染隘庄,底層默認(rèn)是透明的踢步。
    • 5.綜上所述,如果當(dāng)我們使用 TextureView 渲染 Flutter 的時候丑掺, 我們可以只將 Flutter 當(dāng)做 Android 視圖層級中的一個普通的 view获印,它可以在某些 View 的上面或者下面。這就是我們的解決方案:不再把 Flutter 當(dāng)做一個 Activity 的全部街州,它只是 View 層級中的一份子兼丰,這樣一來我們想對這個 View 做啥就做啥。
  • 2.在了解了混合開發(fā)的思想之后代碼上就非常簡單了唆缴。

    • 1.首先我們得知道除了 io.flutter.app.FlutterActivity地粪,這個一般我們使用的 Acitivty 外。Flutter 還提供了另一個 io.flutter.embedding.android.FlutterActivity Acitvity琐谤,這個 Activity 渲染 Flutter 的方式之一就是使用 TexutreView蟆技。
    • 2.當(dāng)然最后 io.flutter.embedding.android.FlutterAcitivity 還是通過 io.flutter.embedding.android.FlutterFragment 來將 TextureView 添加到 View 的層級中的。
----代碼塊7斗忌,本文發(fā)自簡書质礼、掘金:何時夕-----
public class FlutterTextureBaseFragment extends FlutterFragment {
  protected FlutterView mFlutterView;
  protected FlutterContextWrapper mFlutterContextWrapper;

  @Nullable
  @Override
  public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    View view = super.onCreateView(inflater, container, savedInstanceState);
    mFlutterView = ViewUtil.getFlutterView(view);
    mFlutterContextWrapper = new FlutterContextWrapper(getContext());
    return mFlutterView;
  }

  @Nullable
  public FlutterView getFlutterView() {
    return mFlutterView;
  }

  public static class TextureBuilder extends FlutterFragment.Builder {
    @NonNull
    public <T extends FlutterFragment> T build() {
      try {
        T frag = (T) FlutterTextureBaseFragment.class.newInstance();
        if (frag == null) {
          throw new RuntimeException("The FlutterFragment subclass sent in the constructor (" + FlutterTextureBaseFragment.class.getCanonicalName() + ") does not match the expected return type.");
        } else {
          Bundle args = this.createArgs();
          frag.setArguments(args);
          return frag;
        }
      } catch (Exception var3) {
        throw new RuntimeException("Could not instantiate FlutterFragment subclass (" + FlutterTextureBaseFragment.class.getName() + ")", var3);
      }
    }
  }

  @Override
  public Context getContext() {
    if (mFlutterContextWrapper == null) {
      return super.getContext();
    } else {
      return mFlutterContextWrapper;
    }
  }
}

  • 3.我們看代碼塊7,F(xiàn)lutterFragment.Builder 是構(gòu)建 io.flutter.embedding.android.FlutterFragment 的 Buidler 類织阳,我的 FlutterTextureBaseFragment 主要是為了提供 FlutterView 給外界使用眶蕉。
----代碼塊8,本文發(fā)自簡書唧躲、掘金:何時夕-----
public class FlutterTextureBaseActivity extends FlutterActivity {
  protected FlutterView mFlutterView;
  protected FlutterTextureBaseFragment mFlutterTextureBaseFragment;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
  }

  @Nullable
  public ViewGroup getFlutterViewParent() {
    getFlutterView();
    if (mFlutterView == null) {
      return null;
    } else {
      return (ViewGroup) mFlutterView.getParent();
    }
  }

  @Nullable
  public FlutterView getFlutterView() {
    if (mFlutterTextureBaseFragment == null) {
      return null;
    } else if (mFlutterTextureBaseFragment.getView() != null) {
      mFlutterView = mFlutterTextureBaseFragment.getFlutterView();
      return mFlutterView;
    } else {
      return null;
    }
  }

  @Nullable
  public FlutterTextureBaseFragment getFlutterTextureBaseFragment() {
    return mFlutterTextureBaseFragment;
  }

  @NonNull
  protected FlutterTextureBaseFragment createFlutterFragment() {
    mFlutterTextureBaseFragment = (new FlutterTextureBaseFragment.TextureBuilder())
        .dartEntrypoint(this.getDartEntrypoint())
        .initialRoute(this.getInitialRoute())
        .appBundlePath(this.getAppBundlePath())
        .flutterShellArgs(FlutterShellArgs.fromIntent(this.getIntent()))
        .renderMode(FlutterView.RenderMode.texture)
        .transparencyMode(FlutterView.TransparencyMode.opaque)
        .build();
    return mFlutterTextureBaseFragment;
  }
}

  • 4.在看代碼塊8造挽,F(xiàn)lutterTextureBaseActivity 繼承了 io.flutter.embedding.android.FlutterActivity,主要工作是創(chuàng)建一個以 TexutreVIew 作為渲染方式的 FlutterTextureBaseFragment弄痹,然后提供 FlutterView 的 ParentView饭入,以供外部使用。
  • 5.了解了上面的代碼之后肛真,大家要在一個 Activity 中進行混合開發(fā)也就非常簡單了谐丢。例如我需要用 Flutter 仿寫抖音 App 的視頻編輯頁,就可以有如下步驟:
    • 1.繼承 FlutterTextureBaseActivity 后,將視頻編輯 sdk 的 View 放在 FlutterView 的下面乾忱,此時 FlutterView 就會透出視頻編輯 View讥珍。
    • 2.在 Flutter 中開發(fā)業(yè)務(wù)邏輯
    • 3.使用 Channel 讓 Flutter 中的行為操作視頻編輯 View。
  • 6.我使用我司的視頻編輯 sdk 簡單的實踐了一下視頻播放和暫停的功能窄瘟,如下圖3
    • 1.下面的視頻播放器是 Android 端 Native 的代碼衷佃。
    • 2.上面的兩個 play 和 stop 的 button 是 Flutter 的代碼。
    • 3.因為是公司內(nèi)部代碼蹄葱,所以不能放在 github 上面纲酗,大家見諒。
image

作者:何時夕
鏈接:http://www.reibang.com/p/9f578d50ae94
來源:簡書
著作權(quán)歸作者所有新蟆。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán)觅赊,非商業(yè)轉(zhuǎn)載請注明出處。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末琼稻,一起剝皮案震驚了整個濱河市吮螺,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌帕翻,老刑警劉巖鸠补,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異嘀掸,居然都是意外死亡紫岩,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門睬塌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來泉蝌,“玉大人,你說我怎么就攤上這事揩晴⊙悖” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵硫兰,是天一觀的道長诅愚。 經(jīng)常有香客問我,道長劫映,這世上最難降的妖魔是什么违孝? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮泳赋,結(jié)果婚禮上雌桑,老公的妹妹穿的比我還像新娘。我一直安慰自己摹蘑,他們只是感情好筹燕,可當(dāng)我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著衅鹿,像睡著了一般撒踪。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上大渤,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天制妄,我揣著相機與錄音,去河邊找鬼泵三。 笑死耕捞,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的烫幕。 我是一名探鬼主播俺抽,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼较曼!你這毒婦竟也來了磷斧?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤捷犹,失蹤者是張志新(化名)和其女友劉穎弛饭,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體萍歉,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡侣颂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了枪孩。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片憔晒。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蔑舞,靈堂內(nèi)的尸體忽然破棺而出丛晌,到底是詐尸還是另有隱情,我是刑警寧澤斗幼,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布澎蛛,位于F島的核電站,受9級特大地震影響蜕窿,放射性物質(zhì)發(fā)生泄漏谋逻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一桐经、第九天 我趴在偏房一處隱蔽的房頂上張望毁兆。 院中可真熱鬧,春花似錦阴挣、人聲如沸气堕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽茎芭。三九已至揖膜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間梅桩,已是汗流浹背壹粟。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留宿百,地道東北人趁仙。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像垦页,于是被迫代替她去往敵國和親雀费。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,592評論 2 353

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