成熟项目的 Flutter 快速引入以及 Flutter、Native 混合开发探究
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
的版本,这在短时间内都是无法实现的。
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();
评论