Flutter升级到1.12填坑指南

最近由于项目需要,需要把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的相关修改

官方新版启动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修改

官方新版添加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了

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345