成熟專案的Flutter快速引入以及Flutter、Native混合開發探究

何時夕發表於2019-08-21

本文首發於微信公眾號——世界上有意思的事,搬運轉載請註明出處,否則將追究版權責任。

又有兩個月沒有發文了,最近我司逐漸開始在成熟的專案中引入 Flutter 作為一種新的開發方式。作為比較早吃螃蟹的人,我也在組內為三四十個同學做了一次 Flutter 相關的分享。因為涉及了一些內部資訊所以等我脫敏整理好之後會用專門的一篇文章進行相關的分享,大家可以開始期待了,哈哈。至於本篇文章,我會講一些有意思的東西——成熟專案的Flutter快速引入 與 Flutter、Native混合開發,希望大家能多多點贊關注。

閱讀須知:

  • 1.本篇文章基於 Android 平臺
  • 2.Flutter測試專案——測試、Flutter容器專案——容器

本文分為以下章節,讀者可按需閱讀:

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

Flutter測試專案Github

Flutter容器專案Github

一、成熟專案的Flutter快速引入

現在很多教程都停留在建立一個新的 Flutter 專案然後開始介紹如何使用這個專案開發 Flutter。但是其實我們目前大部分使用 Flutter 的場景都是基於已經成熟的專案。我們不可能因為使用 Flutter 而將原來的專案推到重來。這一節我就來介紹一種成熟專案無縫接入 Flutter 的方式。本章需要大家結合上面提到的 Github 專案程式碼食用。

1.閒魚以及美團的實踐

  • 1.目前很多廠商都有著自己的成熟專案的 Flutter 接入實踐,其中美團、閒魚的實踐應該已經執行的比較久了。他們的接入方式主要分下面幾步:
    • 1.理清楚 Flutter App 的構建和執行方式。
    • 2.修改 Flutter 專案的 Gradle 檔案,將 Flutter 專案打包成 AAR 檔案。
    • 3.將 AAR 檔案推送到 Maven 伺服器上。
    • 4.主工程引入 Flutter 的 AAR 檔案,和主工程一起編譯生成主 App。
  • 2.美團的實踐
  • 3.閒魚的實踐

2.我的實踐

從上面的介紹來看,閒魚、美團的實踐方式似乎有著一些不方便之處。比如說不能動態更新 Flutter 程式碼、Flutter 的 AAR 和主工程一起編譯太具有侵入性等等(這裡只是我自己淺薄的看法,有異議的同學可以在評論區提出)。所以我這一節要介紹一種侵入性非常小的接入 Flutter 的方式,簡單來說就一句話:動態載入 Flutter 生成的 Apk接下來我會結合前面提到的兩個 github 專案裡的程式碼進行講解,大家一定要把這兩個專案 clone 下來,當然能點個 star 就更好了。

(1).建立Flutter測試專案

圖1:Flutter-Test-專案.png

  • 1.建立一個 Flutter Project,這個很簡單,網上教程很多我就不復述了。
  • 2.建立好了之後如圖1所示,我們需要在 app 目錄下的 build.gradle 檔案中新增一些程式碼,如程式碼塊1所示。
----程式碼塊1,本文發自簡書、掘金:何時夕-----
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).建立Flutter容器專案

圖2:Flutter容器專案目錄.png

  • 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,我們可以通過動態下載來獲取。
----程式碼塊2,本文發自簡書、掘金:何時夕-----
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,本文發自簡書、掘金:何時夕-----
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 環境以及升級 Flutter Apk。
  • 4.程式碼塊2中呼叫了 init,所以我們來看看程式碼塊3 FlutterContainer 中的 api。
    • 1.init:方法用於第一次需要初始化 Flutter apk 的時候呼叫一次,有多個不同的 api。
    • 2.upgradeFlutterPackage:則是用於重新載入 Flutter apk,比如我們需要釋出新的 Flutter 版本,就可以使用這個 api 來重新載入一個新的 Flutter apk。
----程式碼塊4,本文發自簡書、掘金:何時夕-----
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 相當於初始化 Flutter apk 的入口,那麼 FlutterManager 就是具體做這件事情的類了。我們看程式碼塊4,可以瞭解到 FlutterManager 是一個單例,FlutterContainer.init 中有一個步驟就是初始化這個單例。其中的 api 有下面這些功能:
    • 1.registerChannel:註冊 java 和 dart 之間的通訊 channel,這個在後面會詳細講解。
    • 2.getFlutterEngine:獲取 FlutterEngine,其內部會呼叫 Flutter 真正載入 apk 的 api。
    • 3.getFlutterContextWrapper:一個 Context 的包裝類,主要是為了讓 Flutter 能順利解壓出 apk 裡面的程式碼和資源。
----程式碼塊5,本文發自簡書、掘金:何時夕-----
public class FlutterContextWrapper extends ContextWrapper {

  private AssetManager sAssets;

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

  public void reset() {
    sAssets = null; // 在每次安裝flutter包之後,需要重新建立新的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中那樣,建立一個 FlutterContextWrapper 來替換 AssetManager,使得 Flutter 載入 apk 時 asset 目錄指向我們建立的 Flutter apk 中。
----程式碼塊6,本文發自簡書、掘金:何時夕-----
class FlutterEngine {
  
  private static boolean sInitialized; // 全域性標記引擎已經啟動
  private final Context mContext;
  
  FlutterEngine(Context context) {
    mContext = context;
  }
  
  /**
   * 快速啟動模式,表示已經有包了
   */
  void startFast(@Nullable Callback callback) {
    if (sInitialized) {
      // 需要儘快啟動,所以需要去重
      callback(callback, null);
      return;
    }
    if (FlutterManager.getInstance().isFlutterAvailable()) { // 當前有可用包
      startFlutterInitialization();
      ensureInitializationComplete();
      callback(callback, null);
    } else {
      DebugUtil.logError(new RuntimeException("startFast but no available package"));
    }
  }
  
  /**
   * 慢速啟動模式, 表示沒有報,需要準備
   */
  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方法可以多次呼叫, 他的主要作用是解壓資源, 因此不用做去重
    FlutterMain.startInitialization(FlutterManager.getInstance().getFlutterContextWrapper());
  }
  
  private void ensureInitializationComplete() {
    FlutterMain.ensureInitializationComplete(mContext, null);
    sInitialized = true; // 已經初始化
  }
  
  // 啟動回撥
  public interface Callback {
    
    /**
     * 初始化完成.
     *
     * @param e 成功為null,失敗不為null.
     */
    void onCompleted(Throwable e);
  }
 
  // 準備 Flutter 包的回撥
  public interface PrepareFlutterPackage {
    String prepareFlutterPackage();
  }
}
複製程式碼
  • 7.順接 FlutterContainer 的呼叫繼續深入,我們會來到程式碼塊6的 FlutterEngine 中,這裡主要有兩個 api:
    • 1.startFast:如方法名說的那樣,這個方法表示快速載入 flutter apk。他只能被呼叫一次,多次呼叫會去重,一般來說我們如果已經準備好了 flutter apk 的話, 那麼可以使用這個方法來載入 flutter apk。可以看見其內部最終會呼叫到 FlutterMain.startInitialization,這是 Flutter.jar 中的 api,主要用於解壓和移動 Context 中的 Asset。因為我們前面建立了一個 FlutterContextWrapper,所以這裡其實會解壓 flutter apk 中的 Dart 程式碼和資源。
    • 2.startSlow:這個方法能呼叫多次,主要用於升級 apk,多次呼叫不會去重。如果我們沒有準備好 apk,需要從網路中下載,可以使用這個方法。但是最終的原理和 startFast 一樣,都是使用 FlutterMain.startInitialization 來解壓和移動 Flutter apk 中的資源。
  • 8.到這裡成熟專案中無縫引入 Flutter 就完成了。大家可以編譯Flutter容器專案然後將Flutter測試專案生成的 apk adb push 到手機的 /storage/emulated/0/flutter1.apk 中,就能體驗到動態載入 Flutter apk 的快感了。
  • 9.另外你還可以使用 flutter attch 來對 debug 版的 Flutter apk 進行 hot reload,享受到秒級程式碼更新的快感。

二、Flutter、Native混合開發

前面完了在成熟專案中無縫引入 Flutter 的方式,這一章我們再來說說 Flutter 和 Native 混合開發的方式。可能會混合開發不是很簡單嗎,直接嵌入一個 Flutter 的 Activity/Fragment 就能將其作為容器執行 Flutter 了。其實這樣的想法太過理想化,如果我的一個 Acitivity/Fragment 中 Flutter 和 Native 都需要有呢?這一章我我就是要來解決這個問題,大家隨我一起往下看。

1.Flutter、Native混合開發場景以及閒魚的實踐

  • 1.我們先來聊聊在什麼情況下在 Activity/Fragment 中會需要 Flutter、Native 一起使用
    • 1.比如我的一個介面上需要嵌入地圖 view,此時如果我需要在這個介面上使用 Flutter 的話,因為 Flutter 的元件遠沒有 Native 這麼完善,像高德地圖、百度地圖目前都只有 Native 的版本,所以此時就需要 Flutter、Native 混合開發了。
    • 2.再拿目前比較火的短視訊 App 們來做例子,例如抖音 App 的視訊編輯功能,視訊編輯的大部分功能都是基於 Native 層的視訊編輯 sdk 來開發的。如果這種介面要上 Flutter 的話,整個視訊編輯 sdk 需要提供一 Dart 的版本,這在短時間內都是無法實現的。
    • 3.有了上面兩個例子,我們現在大概可以知道在什麼場景下需要在一個介面上使用 Flutter、Native 進行混合開發了:**Flutter 的控制元件還無法代替 Native 的控制元件時,如果某個介面需要上 Flutter 的話,就會出現這樣的場景。**雖然隨著 Flutter 的慢慢發展,慢慢可能會有 Flutter 版的地圖、Flutter 版的視訊編輯 sdk,但是在最近一兩年內,Flutter、Native 混合開發還是一個非常常見的場景。
  • 2.那麼我們再來聊聊目前已經有的混合開發的實踐,目前閒魚有寫過部落格分享自己的混合開發實踐:閒魚的混合開發實踐
    • 1.使用 Flutter 提供的 api 將 Android 端的 View 交給 Flutter。
    • 2.因為 Flutter 渲染的方式是 SurfaceView 或者 TextureView,所以 Android 端的 View 會生成一個 Texture(OpenGL的紋理),交給 Flutter 然後讓 Flutter 一起渲染在 Surface/TextureVIew 上。
    • 3.相應的手勢也由 Flutter 層傳遞給 Android 層。
  • 3.閒魚的實踐方式當然有它們的優勢,例如是官方推薦的實踐方式、通用性更好等等。但是其有不可忽視的缺點就是Android View 的 Texture 傳遞到 Flutter 的流程是 GPU->CPU->GPU,這是一套昂貴的方案。

2.我的實踐

為了解決資料傳遞的昂貴耗損,我想了另外一個辦法來繞過這個問題。本小結需要結合Flutter容器專案食用。

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

    • 1.Flutter 在開始執行之後,畫面是渲染到 Android 端的 SurfaceView/TextureView 上面的。
    • 2.要深入瞭解 SurfaceView 和 TextureView,可以看這篇文章:Android繪製機制以及Surface家族原始碼全解析
    • 3.Flutter 如果用 SurfaceView 渲染,底層預設是黑的。
    • 4.Flutter 如果用 TextureView 渲染,底層預設是透明的。
    • 5.綜上所述,如果當我們使用 TextureView 渲染 Flutter 的時候, 我們可以只將 Flutter 當做 Android 檢視層級中的一個普通的 view,它可以在某些 View 的上面或者下面。這就是我們的解決方案:不再把 Flutter 當做一個 Activity 的全部,它只是 View 層級中的一份子,這樣一來我們想對這個 View 做啥就做啥。
  • 2.在瞭解了混合開發的思想之後程式碼上就非常簡單了。

    • 1.首先我們得知道除了 io.flutter.app.FlutterActivity,這個一般我們使用的 Acitivty 外。Flutter 還提供了另一個 io.flutter.embedding.android.FlutterActivity Acitvity,這個 Activity 渲染 Flutter 的方式之一就是使用 TexutreView。
    • 2.當然最後 io.flutter.embedding.android.FlutterAcitivity 還是通過 io.flutter.embedding.android.FlutterFragment 來將 TextureView 新增到 View 的層級中的。
    ----程式碼塊7,本文發自簡書、掘金:何時夕-----
    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,FlutterFragment.Builder 是構建 io.flutter.embedding.android.FlutterFragment 的 Buidler 類,我的 FlutterTextureBaseFragment 主要是為了提供 FlutterView 給外界使用。
    ----程式碼塊8,本文發自簡書、掘金:何時夕-----
    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,FlutterTextureBaseActivity 繼承了 io.flutter.embedding.android.FlutterActivity,主要工作是建立一個以 TexutreVIew 作為渲染方式的 FlutterTextureBaseFragment,然後提供 FlutterView 的 ParentView,以供外部使用。
    • 5.瞭解了上面的程式碼之後,大家要在一個 Activity 中進行混合開發也就非常簡單了。例如我需要用 Flutter 仿寫抖音 App 的視訊編輯頁,就可以有如下步驟:
      • 1.繼承 FlutterTextureBaseActivity 後,將視訊編輯 sdk 的 View 放在 FlutterView 的下面,此時 FlutterView 就會透出視訊編輯 View。
      • 2.在 Flutter 中開發業務邏輯
      • 3.使用 Channel 讓 Flutter 中的行為操作視訊編輯 View。
    • 6.我使用我司的視訊編輯 sdk 簡單的實踐了一下視訊播放和暫停的功能,如下圖3
      • 1.下面的視訊播放器是 Android 端 Native 的程式碼。
      • 2.上面的兩個 play 和 stop 的 button 是 Flutter 的程式碼。
      • 3.因為是公司內部程式碼,所以不能放在 github 上面,大家見諒。

圖3:demo.gif

四、尾巴

最近更新文章的頻率變長了,一個是因為工作比較忙,另一個原因則是我希望能寫出更多的乾貨給讀者。所以希望大家能夠持續關注我的掘金、簡書、微信公眾號,我也會一直分享原創精品文章給大家。

不販賣焦慮,也不標題黨。分享一些這個世界上有意思的事情。題材包括且不限於:科幻、科學、科技、網際網路、程式設計師、計算機程式設計。下面是我的微信公眾號:世界上有意思的事,乾貨多多等你來看。

世界上有意思的事

相關文章