To Be An Artist Engineer.

0%

支持新的Flutter Android插件API(翻译)

原文链接:Supporting the new Android plugins APIs

随着1.12版本的发布,插件API也发生了变更。旧的API基于PluginRegistry.Registrar,这种方式不会立即被废弃,但是我们仍然鼓励开发者尽量使用基于FlutterPlugin的新的集成方式。

和旧的API相比,新的API更加依赖于组件的生命周期。比如说PluginRegistry.Registrar.activity()可能返回的是null,因为FlutterView可能并没有和任何Activity绑定。

换句话说,采用旧的API开发插件可能会产生不可预知的问题。目前,Flutter官方提供的插件均已完成迁移。

升级步骤

  1. 插件类实现FlutterPlugin接口,你也可以把FlutterPlugin和MethodCallHandler用两个类分别实现。插件需要保留registerWith方法来兼容使用老版本插件API的App。使用新插件的App将会调用FlutterPlugin的接口onAttachedToEngine来初始化插件,而未升级插件API的老版本的App调用registerWith方法实现注册。此外,对于所有公共非接口定义方法都应该补充文档。在混合开发的场景下,这些方法会对开发者完全开放。

  2. (可选)如果插件需要获取Activity引用,需要实现ActivityAware。

  3. (可选)如果需要持有后台服务实例,需要实现ServiceAware。

  4. 入口Flutter Activity需要继承v2 embedding FlutterActivity。具体可以看另一篇升级准1.12的Android项目,需要注意的是新版本的插件类最好提供一个公共构造方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package io.flutter.plugins.firebasecoreexample;

    import io.flutter.embedding.android.FlutterActivity;
    import io.flutter.embedding.engine.FlutterEngine;
    import io.flutter.plugins.firebase.core.FirebaseCorePlugin;

    public class MainActivity extends FlutterActivity {
    // You can keep this empty class or remove it. Plugins on the new embedding
    // now automatically registers plugins.
    }
  5. (可选)如果移除了MainActivity,需要在AndroidManifest.xml中使用io.flutter.embedding.android.FlutterActivity

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
      <activity android:name="io.flutter.embedding.android.FlutterActivity"
    android:theme="@style/LaunchTheme"
    android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale"
    android:hardwareAccelerated="true"
    android:windowSoftInputMode="adjustResize">
    <meta-data
    android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
    android:value="true" />
    <intent-filter>
    <action android:name="android.intent.action.MAIN"/>
    <category android:name="android.intent.category.LAUNCHER"/>
    </intent-filter>
    </activity>

  6. (可选)在MainActivity同级目录下创建继承v1 embedding API的EmbeddingV1Activity.java类来测试是否兼容v1 embedding的插件。需要注意的时候,必须要手动注册插件而不是使用GeneratedPluginRegistrant。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    package io.flutter.plugins.batteryexample;

    import android.os.Bundle;
    import dev.flutter.plugins.e2e.E2EPlugin;
    import io.flutter.app.FlutterActivity;
    import io.flutter.plugins.battery.BatteryPlugin;

    public class EmbeddingV1Activity extends FlutterActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    BatteryPlugin.registerWith(registrarFor("io.flutter.plugins.battery.BatteryPlugin"));
    E2EPlugin.registerWith(registrarFor("dev.flutter.plugins.e2e.E2EPlugin"));
    }
    }

  7. 添加<meta-data android:name="flutterEmbedding" android:value="2"/>到AndroidManifest.xml中,这样项目就会使用v2 embedding。

  8. (可选) 如果创建了EmbeddingV1Activity类,需要吧它加到AndroidManifest.xml文件中。

    1
    2
    3
    4
    5
    6
    7
    <activity
    android:name=".EmbeddingV1Activity"
    android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale"
    android:hardwareAccelerated="true"
    android:windowSoftInputMode="adjustResize">
    </activity>

测试插件

我们鼓励对插件进行充分测试,虽然不是必要的,但是我们鼓励开发者这么做。


  1. 把build.gradle文件中的android.support.test替换成androidx.test。

    1
    2
    3
    4
    5
    defaultConfig {
    ...
    testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    ...
    }
    1
    2
    3
    4
    5
    6
    7
    dependencies {
    ...
    androidTestImplementation 'androidx.test:runner:1.2.0'
    androidTestImplementation 'androidx.test:rules:1.2.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
    ...
    }
  2. <plugin_name>/example/android/app/src/androidTest/java/<plugin_path>目录为MainActivityEmbeddingV1Activity添加测试文件。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    package io.flutter.plugins.firebase.core;

    import androidx.test.rule.ActivityTestRule;
    import dev.flutter.plugins.e2e.FlutterRunner;
    import io.flutter.plugins.firebasecoreexample.MainActivity;
    import org.junit.Rule;
    import org.junit.runner.RunWith;

    @RunWith(FlutterRunner.class)
    public class MainActivityTest {
    // Replace `MainActivity` with `io.flutter.embedding.android.FlutterActivity` if you removed `MainActivity`.
    @Rule
    public ActivityTestRule<MainActivity> rule = new
    ActivityTestRule<>(MainActivity.class);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    package io.flutter.plugins.firebase.core;

    import androidx.test.rule.ActivityTestRule;
    import dev.flutter.plugins.e2e.FlutterRunner;
    import io.flutter.plugins.firebasecoreexample.EmbeddingV1Activity;
    import org.junit.Rule;
    import org.junit.runner.RunWith;

    @RunWith(FlutterRunner.class)
    public class EmbeddingV1ActivityTest {
    @Rule
    public ActivityTestRule<EmbeddingV1Activity> rule =
    new ActivityTestRule<>(EmbeddingV1Activity.class);
    }
  3. 添加e2eflutter_driverdev_dependencies到/pubspec.yaml/example/pubspec.yaml

    1
    2
    3
    e2e: ^0.2.1
    flutter_driver:
    sdk: flutter
  4. 更新Flutter的最低支持版本,目前所有新v2 embedding插件最小支持版本均已升级至1.12.13+hotfix.6版本。

    1
    2
    3
    4
    environment:
    sdk: ">=2.0.0-dev.28.0 <3.0.0"
    flutter: ">=1.12.13+hotfix.6 <2.0.0"

  5. <plugin_name>/test目录下创建测试文件<plugin_name>_e2e.dart。下面的测试针对的是插件是否成功注册到v2 embedder。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import 'package:flutter_test/flutter_test.dart';
    import 'package:battery/battery.dart';
    import 'package:e2e/e2e.dart';

    void main() {
    E2EWidgetsFlutterBinding.ensureInitialized();

    testWidgets('Can get battery level', (WidgetTester tester) async {
    final Battery battery = Battery();
    final int batteryLevel = await battery.batteryLevel;
    expect(batteryLevel, isNotNull);
    });
    }
  6. 也可以在Native运行测试:

    1
    2
    3
    4
    5
    cd <plugin_name>/example
    flutter build apk
    cd android
    ./gradlew app:connectedAndroidTest -Ptarget=`pwd`/../../test/<plugin_name>_e2e.dart

基础插件

只实现FlutterPlugin接口

1
2
3
4
5
6
7
8
9
10
11
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.
}
}

在onAttachedToEngine方法内初始化插件,在onDetachedFromEngine方法内清理插件引用。

FlutterPluginBinding有两个很重要的方法:

  • binding.getFlutterEngine(),返回的是绑定的FlutterEngine对象,可以通过它继而拿到DartExecutor,FlutterRenderer等。
  • bingding.getApplicationContext(),返回的是Applicaton‘s Context。

UI/Activity 插件

如果需要与UI交互,比如申请权限或者改变Android UI,那么你需要实现ActivityAware接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class MyPlugin implements FlutterPlugin, ActivityAware {
//...normal plugin behavior is hidden...

@Override
public void onAttachedToActivity(ActivityPluginBinding activityPluginBinding) {
// TODO: your plugin is now attached to an Activity
}

@Override
public void onDetachedFromActivityForConfigChanges() {
// TODO: the Activity your plugin was attached to was
// destroyed to change configuration.
// This call will be followed by onReattachedToActivityForConfigChanges().
}

@Override
public void onReattachedToActivityForConfigChanges(ActivityPluginBinding activityPluginBinding) {
// TODO: your plugin is now attached to a new Activity
// after a configuration change.
}

@Override
public void onDetachedFromActivity() {
// TODO: your plugin is no longer associated with an Activity.
// Clean up references.
}
}

为了和Activity交互,你的ActivityAware插件需要经历4个阶段。

  • 首先是onAttachedToActivity,此时可以通过ActivityPluginBinding获取Activity实例和一系列的回调。
  • 当发生configuration change时,你必须在onDetachedFromActivityConfigChanges回调中做一些清理工作。
  • 在onReattachedToActivityForConfigChanges回调中重新做一些初始化工作。
  • 在onDetachedFromActivity中必须把所有Activity引用都清理掉,返回到无UI配置中。