又有两个月没有发文了,最近我司逐渐开始在成熟的项目中引入 Flutter 作为一种新的开发方式。作为比较早吃螃蟹的人,我也在组内为三四十个同学做了一次 Flutter 相关的分享。因为涉及了一些内部信息所以等我脱敏整理好之后会用专门的一篇文章进行相关的分享,大家可以开始期待了,哈哈。至于本篇文章,我会讲一些有意思的东西——成熟项目的Flutter快速引入 与 Flutter、Native混合开发,希望大家能多多点赞关注。
阅读须知:
本文分为以下章节,读者可按需阅读:
现在很多教程都停留在创建一个新的 Flutter 项目然后开始介绍如何使用这个项目开发 Flutter。但是其实我们目前大部分使用 Flutter 的场景都是基于已经成熟的项目。我们不可能因为使用 Flutter 而将原来的项目推到重来。这一节我就来介绍一种成熟项目无缝接入 Flutter 的方式。本章需要大家结合上面提到的 Github 项目代码食用。
从上面的介绍来看,闲鱼、美团的实践方式似乎有着一些不方便之处。比如说不能动态更新 Flutter 代码、Flutter 的 AAR 和主工程一起编译太具有侵入性等等(这里只是我自己浅薄的看法,有异议的同学可以在评论区提出)。所以我这一节要介绍一种侵入性非常小的接入 Flutter 的方式,简单来说就一句话:动态加载 Flutter 生成的 Apk。接下来我会结合前面提到的两个 github 项目里的代码进行讲解,大家一定要把这两个项目 clone 下来,当然能点个 star 就更好了。
图1:Flutter-Test-项目.png
----代码块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}"
}
}
}
}
}
图2:Flutter容器项目目录.png
----代码块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);
}
----代码块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,本文发自简书、掘金:何时夕-----
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,本文发自简书、掘金:何时夕-----
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();
}
}
前面完了在成熟项目中无缝引入 Flutter 的方式,这一章我们再来说说 Flutter 和 Native 混合开发的方式。可能会混合开发不是很简单吗,直接嵌入一个 Flutter 的 Activity/Fragment 就能将其作为容器运行 Flutter 了。其实这样的想法太过理想化,如果我的一个 Acitivity/Fragment 中 Flutter 和 Native 都需要有呢?这一章我我就是要来解决这个问题,大家随我一起往下看。
为了解决数据传递的昂贵耗损,我想了另外一个办法来绕过这个问题。本小结需要结合Flutter容器项目食用。
----代码块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;
}
}
}
----代码块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;
}
}
图3:demo.gif