写点什么

成熟项目的 Flutter 快速引入以及 Flutter、Native 混合开发探究

用户头像
Android架构
关注
发布于: 9 小时前

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}


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


@Overridepublic 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;}


@Overridepublic 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


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


的版本,这在短时间内都是无法实现的。


  • 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.闲鱼的实践方式当然有它们的优势,例如是官方推荐的实践方式、通用性更好等等。

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@Overridepublic 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;}


@Nullablepublic FlutterView getFlutterView() {return mFlutterView;}


public static class TextureBuilder extends FlutterFragment.Builder {@NonNullpublic <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);}}}


@Overridepublic 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;


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


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

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
成熟项目的Flutter快速引入以及Flutter、Native混合开发探究