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了


鏈接:https://www.jianshu.com/p/24b9b3e702a4
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章