前言
前面两篇文章主要是对Flutter 项目 Android 端项目应用层大概启动流程源码的分析,这篇文章我们就重点来看下一个纯Android项目如何嵌入Flutter Module,以及如何通过FlutterActivity展示Flutter UI
可以按照上面那个链接手动配置一下依赖,下面我们先看下大致效果(下面的图都有点大,凑合看吧)
下面我们分析一下整个流程,配置混合开发环境就不说了,按照文档配置就行
正文
之前分析FlutterActivity的时候,有说到这个类有几个静态方法,现在我们来分析一下
@NonNull
public static Intent createDefaultIntent(@NonNull Context launchContext) {
return withNewEngine().build(launchContext);
}
@NonNull
public static NewEngineIntentBuilder withNewEngine() {
return new NewEngineIntentBuilder(FlutterActivity.class);
}
public static class NewEngineIntentBuilder {
private final Class<? extends FlutterActivity> activityClass;
private String initialRoute = DEFAULT_INITIAL_ROUTE;
private String backgroundMode = DEFAULT_BACKGROUND_MODE;
protected NewEngineIntentBuilder(@NonNull Class<? extends FlutterActivity> activityClass) {
this.activityClass = activityClass;
}
@NonNull
public NewEngineIntentBuilder initialRoute(@NonNull String initialRoute) {
this.initialRoute = initialRoute;
return this;
}
@NonNull
public NewEngineIntentBuilder backgroundMode(@NonNull BackgroundMode backgroundMode) {
this.backgroundMode = backgroundMode.name();
return this;
}
@NonNull
public Intent build(@NonNull Context context) {
return new Intent(context, activityClass)
.putExtra(EXTRA_INITIAL_ROUTE, initialRoute)
.putExtra(EXTRA_BACKGROUND_MODE, backgroundMode)
.putExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, true);
}
}
public static CachedEngineIntentBuilder withCachedEngine(@NonNull String cachedEngineId) {
return new CachedEngineIntentBuilder(FlutterActivity.class, cachedEngineId);
}
public static class CachedEngineIntentBuilder {
private final Class<? extends FlutterActivity> activityClass;
private final String cachedEngineId;
private boolean destroyEngineWithActivity = false;
private String backgroundMode = DEFAULT_BACKGROUND_MODE;
protected CachedEngineIntentBuilder(
@NonNull Class<? extends FlutterActivity> activityClass, @NonNull String engineId) {
this.activityClass = activityClass;
this.cachedEngineId = engineId;
}
public CachedEngineIntentBuilder destroyEngineWithActivity(boolean destroyEngineWithActivity) {
this.destroyEngineWithActivity = destroyEngineWithActivity;
return this;
}
@NonNull
public CachedEngineIntentBuilder backgroundMode(@NonNull BackgroundMode backgroundMode) {
this.backgroundMode = backgroundMode.name();
return this;
}
@NonNull
public Intent build(@NonNull Context context) {
return new Intent(context, activityClass)
.putExtra(EXTRA_CACHED_ENGINE_ID, cachedEngineId)
.putExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, destroyEngineWithActivity)
.putExtra(EXTRA_BACKGROUND_MODE, backgroundMode);
}
}
这几个方法就是为我们Android 跳转到一个FlutterActivity准备的
可以看到createDefaultIntent这个方法实际上是 实例化了一个NewEngineIntentBuilder,然后实例化一个Intent,目标class就是FlutterActivity.class,,然后我们传递值都是默认的
return new Intent(context, activityClass)
.putExtra(EXTRA_INITIAL_ROUTE, initialRoute)
.putExtra(EXTRA_BACKGROUND_MODE, backgroundMode)
.putExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, true);
private String initialRoute = DEFAULT_INITIAL_ROUTE;
private String backgroundMode = DEFAULT_BACKGROUND_MODE;
static final String EXTRA_DESTROY_ENGINE_WITH_ACTIVITY =
"destroy_engine_with_activity";
static final String DEFAULT_INITIAL_ROUTE = "/";
static final String DEFAULT_BACKGROUND_MODE = BackgroundMode.opaque.name();
/** The mode of the background of a Flutter {@code Activity}, either opaque or transparent. */
public enum BackgroundMode {
/** Indicates a FlutterActivity with an opaque background. This is the default. */
opaque,
/** Indicates a FlutterActivity with a transparent background. */
transparent
}
可以看到初始化路由默认是“/”
默认背景模式为BackgroundMode.opaque,选择BackgroundMod.ransparent的背景模式会将此{FlutterActivity的内部FlutterView配置为使用FlutterTextureView来支持透明性。此选择对性能有重要影响,仅在实现应用程序设计时,才应使用透明背景。
下面我们看一下这个值EXTRA_DESTROY_ENGINE_WITH_ACTIVITY
// FlutterActivity
@Override
public boolean shouldDestroyEngineWithHost() {
// 1
boolean explicitDestructionRequested =
getIntent().getBooleanExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, false);
if (getCachedEngineId() != null || delegate.isFlutterEngineFromHost()) {
// Only destroy a cached engine if explicitly requested by app developer.
return explicitDestructionRequested;
} else {
// If this Activity created the FlutterEngine, destroy it by default unless
// explicitly requested not to.
return getIntent().getBooleanExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, true);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
delegate.onDestroyView();
delegate.onDetach();
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
}
// FlutterActivityAndFragmentDelegate
void onDetach() {
// 省略部分源码
if (host.shouldDestroyEngineWithHost()) {
flutterEngine.destroy();
if (host.getCachedEngineId() != null) {
FlutterEngineCache.getInstance().remove(host.getCachedEngineId());
}
flutterEngine = null;
}
}
从上面源码我们看到无论是纯Flutter 项目还是NewEngineIntentBuilder.build方法跳转到的新FlutterActivity默认都是设置为true,即FlutterActivity销毁的时候会将此Engine一并销毁。
然后我们看下原生代码(Kotlin)
val button: Button = root.findViewById(R.id.btnGoFlutter)
button.setOnClickListener(fun(_: View) {
startActivity(
FlutterActivity
.withNewEngine()
.build(root.context)
)
})
其实很简单,就是一个startActivity方法,传递NewEngineIntentBuilder.build 实例化的Intent。
然后我们指定一个初始化路由路径
startActivity(
FlutterActivity
.withNewEngine()
.initialRoute("/second")
.backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.opaque)
.build(root.context)
)
然后在Flutter端配置这个路由
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
routes: {
"/second": (context) => new SecondPage(),
},
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'DartEntrypoint is Main'),
);
}
class SecondPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Page"),
),
body: Center(
child: Text(
"This is Second Page",
style: TextStyle(fontSize: 20, color: Colors.red),
),
),
);
}
}
然后看下启动效果
可以看到配置initialRoute就是这么一个效果
我们之前提到过DartEntrypointFunctionName这个方法,用来配置dart首次启动默认执行的方法名字,默认的是main方法。
接下来我们自己创建一个FlutterEngine,指定其DartEntrypoint,而我们的启动方法也要换成withCachedEngine进行构建
-
首先我们要创建自定义的applicaiton 继承android.app.Application
class MyApplication : Application() { private lateinit var flutterEngine :FlutterEngine override fun onCreate() { super.onCreate() flutterEngine = FlutterEngine(this,null,false) flutterEngine.dartExecutor.executeDartEntrypoint( DartExecutor.DartEntrypoint(FlutterMain.findAppBundlePath(), "main1") ) FlutterEngineCache.getInstance().put("my_engine_id",flutterEngine) }
然后指定
<application android:name=".MyApplication"
从上面代码可以看到,我们在onCreate里实例化一个新的FlutterEngine,同时指定flutterEngine.dartExecutor的默认执行方法名为main1,然后将其添加到FlutterEngineCache缓存池中。
-
接着我们要修改启动方式
startActivity( // FlutterActivity // .withNewEngine() // .initialRoute("/second") // .backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.opaque) // .build(root.context) FlutterActivity .withCachedEngine("my_engine_id") .destroyEngineWithActivity(true) .build(root.context) ) })
可以看到使用了Id为my_engine_id的缓存engine进行启动
-
接着我们配置Flutter端
void main1() => runApp(MyApp1()); class MyApp1 extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.red, ), home: MyHomePage(title: 'DartEntrypoint is Main1'), ); } }
可以看到我们创建了一个main1的方法,接下来看下效果
可以看到确实是通过main1入口进去的Flutter Module
关于初始化路由和启动方法源码介绍可以看Flutter Android启动源码分析(一)及后续文章
上面使用缓存Engine这里是有一个bug的,我们先看下效果和Log
可以看到报错提示我们这个缓存Id对应的Engine不存在...
那好吧,我们再来看下这个方法
// FlutterActivity
@Override
public boolean shouldDestroyEngineWithHost() {
// 1
boolean explicitDestructionRequested =
getIntent().getBooleanExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, false);
if (getCachedEngineId() != null || delegate.isFlutterEngineFromHost()) {
// Only destroy a cached engine if explicitly requested by app developer.
return explicitDestructionRequested;
} else {
// If this Activity created the FlutterEngine, destroy it by default unless
// explicitly requested not to.
return getIntent().getBooleanExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, true);
}
}
// FlutterActivityAndFragmentDelegate
void onDetach() {
// 省略部分源码
if (host.shouldDestroyEngineWithHost()) {
flutterEngine.destroy();
if (host.getCachedEngineId() != null) {
FlutterEngineCache.getInstance().remove(host.getCachedEngineId());
}
flutterEngine = null;
}
}
如果我们构造一个使用缓存Engine的时候,传递EXTRA_DESTROY_ENGINE_WITH_ACTIVITY值为true的时候,FlutterActivity和FlutterEngineCache会在onDestroy方法中一起销毁,所以再次Intent就会报错
将这个值改为false,或者删除缓存池中的缓存ID,重新操作engine并实例化,然后添加到缓存里就可以解决这个问题。
FlutterFragment的使用方式和FlutterActivity大同小异,这里就不做介绍了,可以看文档
最后我强烈建议大家看一下这篇文章,
总结
通过这篇文章我们可以学习到
- Android 嵌入Flutter module 的基本用法以及使用注意事项
- 目前比较成熟的混合开发的库,阿里的flutter_boost
笔者之前也没有做过混合开发,也是一头雾水。不过通过分析Flutter 在Android 应用层上的启动源码、然后结合官方提供的文档做一个demo以及博客的总结,这些流程下来的话基本掌握的差不多。
所以我们在平时开发过程中也应该养成多看源码,多思考的好习惯。确实能在源码里看到一些很好的编码规范和编程思想
下篇文章会讲一下我是如何解决这个Android启动Flutter后白屏和黑屏的问题。