最近由于项目需要,需要把flutter升级到stable版本,目前的stable版本是1.12.13的hotfix,而我们项目目前的版本是1.7.3。Google在发布flutter 1.12对Android做了不少改动,只能说官方的指南都是一些非常基础的,很多使用细节都不完整。这里总结一下我升级遇到的一些问题。
相关参考链接
1.去除FlutterApplication
1.12版本整个flutter的engine,已经不在FlutterApplication中去初始化了,所以只需要把项目的FlutterApplication改回原生的Application即可。
官方介绍:
If you invoke FlutterMain.startInitialization(...) or
FlutterMain.ensureInitializationComplete(...) anywhere in your code, you should
remove those calls. Flutter now initializes itself at the appropriate time.
也就是说如果你代码中有调用FlutterMain.startInitialization(...)方法,需要去除。FlutterApplication中的onCreate方法实际上也调了这个方法。
2. io.flutter.facade包已移除,flutterView不再建议使用
//官方介绍
The deprecated io.flutter.facade.Flutter class has a factory method called
createView(...). This method is deprecated, along with all other code in the
io.flutter.facade package.
Flutter does not currently provide convenient APIs for utilizing Flutter at the View
level, so the use of a FlutterView should be avoided, if possible. However, it is
technically feasible to display a FlutterView, if required. Be sure to use
io.flutter.embedding.android.FlutterView instead of io.flutter.view.FlutterView.
You can instantiate the new FlutterView just like any other Android View. Then,
follow instructions in the associated Javadocs to display Flutter via a
FlutterView.
大概意思就是说io.flutter.facade这个包没啦,要避免使用FlutterView。这个点倒是慢慢和ios靠近了。
3.FlutterActivity的相关修改
3.1 FlutterActivity包路径修改
//import io.flutter.app.FlutterActivity;
import io.flutter.embedding.android.FlutterActivity;
3.2 修改启动FlutterActivity方法
根据官方的介绍
startActivity(
FlutterActivity
.withNewEngine()
.initialRoute("/my_route")
.build(currentActivity)
);
如果你的项目没有继承FlutterActivity,那只需要按照官方新的启动方法启动即可。但是一般项目都需要埋点啥的,肯定会继承FlutterActivity。按照官方的方式,你永远启动的都是FlutterActiivty,而不是自己写的子类。咋办呢,只好从源码入手。
//FlutterActivity部分源码
.....
@NonNull
public static NewEngineIntentBuilder withNewEngine() {
return new NewEngineIntentBuilder(FlutterActivity.class);
}
.....
protected NewEngineIntentBuilder(@NonNull Class<? extends FlutterActivity> activityClass) {
this.activityClass = activityClass;
}
.....
@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);
}
可以看出这个withNewEngine方法传的一直都是FlutterActivity.class,然后new一个NewEngineIntentBuilder,那我自己new一个NewEngineIntentBuilder传入子类不就好了吗?
成功报错:
原因是NewEngineIntentBuilder的构造函数是protected,不是子类没法直接new。那只好继承NewEngineIntentBuilder这个类,重写它的构造函数了。最终代码如下:
public class MyFlutterActivity extends FlutterActivity {
public static NewMyEngineIntentBuilder withNewEngine(Class<? extends FlutterActivity> activityClass) {
return new NewMyEngineIntentBuilder(activityClass);
}
//重写创建引擎方法
public static class NewMyEngineIntentBuilder extends NewEngineIntentBuilder{
protected NewMyEngineIntentBuilder(Class<? extends FlutterActivity> activityClass) {
super(activityClass);
}
调用:
Intent intent = MyFlutterActivity
.withNewEngine(MyFlutterActivity.class)
.initialRoute("/my_route")
.build(context);
context.startActivity(intent);
3.3 启动transparency透明FlutterActivity的超级大坑
项目中之前有需求要把FlutterActivity弄成透明的,之前的做法是activity设置透明,再拿到flutterView设置透明。现在拿不到flutterView咋办,官方倒是贴心,有直接设置FlutterActivity。如下:
<!-- 设置activity透明属性 -->
<style name="MyTheme" parent="@style/MyParentTheme">
<item name="android:windowIsTranslucent">true</item>
</style>
startActivity(
FlutterActivity
.withNewEngine()
.backgroundMode(FlutterActivity.BackgroundMode.transparent)//设置backgroundMode
.build(context)
);
这也太方便了吧,可以一用发现FlutterActivity并没有这个属性,一用就报错。但是在FlutterActivityLaunchConfigs发现了这个属性,可惜类声明不是public,压根调不到啊,坑爹。
package io.flutter.embedding.android;
class FlutterActivityLaunchConfigs {
......
/**
* 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
}
private FlutterActivityLaunchConfigs() {}
}
查看官方的issue显示这个已经fix了,但是好像并没有合到stable分支上。官方提交
最后参考了issue下面的回答解决了这个问题。查看源码其实FlutterActivity会接受Intent中的参数background_mode,只需要传入一样的“ transparent”,也能达到效果。
最终代码:
Intent intent = MyFlutterActivity
.withNewEngine(MyFlutterActivity.class)
.initialRoute("/my_route")
.build(context);
//主要加入这句话
intent.putExtra("background_mode","transparent");
context.startActivity(intent);
3.4 新增启动FlutterActivity过渡的图片
这步是为了启动了FlutteActivity后加载flutter过程中显示的图片,没设置一般是白屏或者黑屏。
如果你之前设置了android:name="io.flutter.app.android.SplashScreenUntilFirstFrame".需要移除掉
新设置如下:
<!-- 在所在的activity中加入 -->
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@mipmap/normal_background" />
4.MethodChannel注册改动
首先在application下加入:
<meta-data
android:name="flutterEmbedding"
android:value="2" />
声明完后插件的注册就使用FlutterEngine而不是以前的PluginRegistry.Registrar。
4.1 注册第三方插件的修改
之前注册方式:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//升级后这句话会报错
GeneratedPluginRegistrant.registerWith(this);
}
升级后registerWith的入参已经改为FlutterEngine,只需要做如下修改即可:
@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
}
4.2 修改自定义的MethodChannel注册
官方介绍的写法:
@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
.setMethodCallHandler(
(call, result) -> {
// Note: this method is invoked on the main thread.
// TODO
}
);
}
但是我的项目已经分了模块化,每个channel都写好了静态方法registerWith(PluginRegistry registry)。于是乎参考了GeneratedPluginRegistrant.registerWith(flutterEngine);里面的方法:
public final class GeneratedPluginRegistrant {
public static void registerWith(@NonNull FlutterEngine flutterEngine) {
//关键在这句,把flutterEngine,转为了shimPluginRegistry,而shimPluginRegistry是PluginRegistry的子类
ShimPluginRegistry shimPluginRegistry = new ShimPluginRegistry(flutterEngine);
com.example.flutterimagecompress.FlutterImageCompressPlugin.registerWith(shimPluginRegistry.registrarFor("com.example.flutterimagecompress.FlutterImageCompressPlugin"));
com.example.flutterautotext.FlutterautotextPlugin.registerWith(shimPluginRegistry.registrarFor("com.example.flutterautotext.FlutterautotextPlugin"));
com.github.adee42.keyboardvisibility.KeyboardVisibilityPlugin.registerWith(shimPluginRegistry.registrarFor("com.github.adee42.keyboardvisibility.KeyboardVisibilityPlugin"));
io.flutter.plugins.localauth.LocalAuthPlugin.registerWith(shimPluginRegistry.registrarFor("io.flutter.plugins.localauth.LocalAuthPlugin"));
io.flutter.plugins.packageinfo.PackageInfoPlugin.registerWith(shimPluginRegistry.registrarFor("io.flutter.plugins.packageinfo.PackageInfoPlugin"));
io.flutter.plugins.pathprovider.PathProviderPlugin.registerWith(shimPluginRegistry.registrarFor("io.flutter.plugins.pathprovider.PathProviderPlugin"));
io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin.registerWith(shimPluginRegistry.registrarFor("io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin"));
com.tekartik.sqflite.SqflitePlugin.registerWith(shimPluginRegistry.registrarFor("com.tekartik.sqflite.SqflitePlugin"));
io.flutter.plugins.urllauncher.UrlLauncherPlugin.registerWith(shimPluginRegistry.registrarFor("io.flutter.plugins.urllauncher.UrlLauncherPlugin"));
}
}
所以也参考着生成一个ShimPluginRegistry,传到我的每个registerWith方法中,目前使用没啥问题,但感觉不是最佳方案。
4.3 FlutterPlugin和ActivityAware
按照官方这次更新的方法,新的插件除了需要继承MethodCallHandler接口,还是需要继承FlutterPlugin接口,而ActivityAware是用于 Activity 的生命周期管理和获取,这个优势在于为插件所依赖的生命周期提供了一套更解耦的使用方法,只有Flutter插件Attach到引擎时才初始化,所以需要实现下面两个方法:
public class MyPlugin implements FlutterPlugin {
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
// TODO: your plugin is now attached to a Flutter experience.
}
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
// TODO: your plugin is no longer attached to a Flutter experience.
}
}
但是我研究了下,MyPlugin这个类在哪里初始化呢?怎么让这个插件和某个FlutterActivity关联呢?有知道的小伙伴麻烦告诉我一下~
5.FlutterFragment修改
5.1FlutterFragment包路径修改
我们项目混合了原生Fragment和FlutterFragment,首先要保证你的FlutterFragment包路径是对的:
import io.flutter.embedding.android.FlutterFragment;
5.2 如何创建FlutterFragment
官方使用:
FlutterFragment flutterFragment = FlutterFragment.withNewEngine().build();
依然很简单,但是不可能满足我们的需要,和FlutterActivity一样,我们还是会写一个子类去继承FlutterFragment,同样的直接调用withNewEngine()启动的永远都是FlutterFragment,继续查看源码:
public NewEngineFragmentBuilder() {
fragmentClass = FlutterFragment.class;
}
/**
* Constructs a {@code NewEngineFragmentBuilder} that is configured to construct an instance of
* {@code subclass}, which extends {@code FlutterFragment}.
*/
public NewEngineFragmentBuilder(@NonNull Class<? extends FlutterFragment> subclass) {
fragmentClass = subclass;
}
在FlutterFragment里面竟然是public的,真的是让人摸不着头脑,那这样就很好修改了,最终代码:
NewEngineFragmentBuilder newEngineFragmentBuilder = new NewEngineFragmentBuilder(MyFlutterFragment.class);
newEngineFragmentBuilder.initialRoute("/myRoute");
MyFlutterFragment myFlutterFragment = newEngineFragmentBuilder.build();
5.3 FlutterFragment两种渲染模式
之前的FlutterView都是SurfaceView的子类,虽然说性能高,但是偶尔会出现一些视图层级的bug。升级后FlutterFragment支持另一种渲染方式,TextureView。FlutterFragment默认是创建SurfaceView,如果你想改为TextureView只需要如下操作:
FlutterFragment flutterFragment = FlutterFragment.withNewEngine()
.renderMode(FlutterView.RenderMode.texture)
.build();
5.4 FlutterFragment显示过渡图片
和FlutterActivity一样,FlutterFragment在加载flutter时也支持增加过渡图片显示
public class MyFlutterFragment extends FlutterFragment {
@Override
protected SplashScreen provideSplashScreen() {
// Load the splash Drawable.
Drawable splash = getResources().getDrawable(R.drawable.my_splash);
// Construct a DrawableSplashScreen with the loaded splash Drawable and
// return it.
return new DrawableSplashScreen(splash);
}
}
6.flutter出现黑屏无法显示
如果改完之后发现flutter页面是黑屏,没有任何显示,则需要在flutter的main函数中,runApp前调用如下方法即可:
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
7.其他坑
7.1建议升级到AndroidX
一开始没有升级androidX,升级到Flutter后编译出现support包冲突,咬咬牙把整个项目都升级到AndroidX,竟然解决了。而且官方也建议使用AndroidX,长痛不如短痛,建议都升到AndroidX,具体怎么升这里就不详细介绍了。
7.2 flutter中.android还是使用support包的问题
在升级完AndroidX后,发现flutter下的.android中GeneratedPluginRegistrant.java还是使用support包,这个文件是通过指令生成的,导致一开始需要修改包的引用为AndroidX:
//这个两个类需要修改为androidX
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
其实官方文档有相关介绍,只需要在pubspec.yaml中增加:
module:
...
androidX: true // Add this line.
然后执行flutter clean,重新build一下就OK了