Flutter 将您的插件迁移到新的Android API

5,035 阅读5分钟

Migrating your plugin to the new Android APIs

翻译自 flutter.dev/docs/develo…

如果你不写或维护一个 Flutter 插件,您可以跳过这一页。

从 1.10.17 版本开始,新的插件 API 在 master 和 dev 通道上可用。 不会立即淘汰旧的 API,但我们建议您迁移到新的 API。 随着时间的流逝,将 Flutter 嵌入到 Android 应用中时,使用旧 API 的插件可能会产生奇怪的行为。 flutter.dev 提供的大多数 Flutter 插件已经被迁移。 (了解如何成为 pub.dev 的 verified publisher!)有关使用新 API 的插件的示例,请参阅 battery package

迁移步骤

以下说明概述了支持新 API 的步骤:

  1. 更新主插件类(* Plugin.java)以实现 FlutterPlugin []。 对于更复杂的插件,您可以将 将 FlutterPluginMethodCallHandler 分为两个类。 见下 Basic plugin [] 部分,以获取有关通过以下方式访问应用程序资源的更多详细信息: embedding 的最新版本(v2)。

    另外,请注意插件仍应包含静态的 registerWith() 方法, 与不使用 v2 embedding 的应用程序保持兼容。 最简单的操作(如果可能的话)是将逻辑从 registerWith() 中移出 进入一个私有方法,使 registerWith()onAttachedToEngine 都可以调用该方法。
    registerWith()AttachToEngine() 只有一个会被调用。

    如果你在 onAttachToEngine() 中创建 channels, 那么没必要在 onDetachFromEngine() 中清理这些创建,当 onAttachToEngine() 第二次被调用时再次创建他们是可以的。

    此外,您还应该在文档中记录所有 non-overridden 的公共成员。 在 add-to-app 场景中, these classes will be accessible to a developer and require documentation.

  2. (可选) 如果插件需要 Activity 的引用,则实现 ActivityAware.

  3. (可选) 如果您的插件预计将保存在后台服务,实现 ServiceAware.

  4. 更新 example app 的 MainActivity.java 来使用 v2 embedding 的 FlutterActivity。您可能需要一个 plugin 的公共构造函数(如果没有),例如:

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 {
  // TODO(<github-username>): Remove this once v2 of
  // GeneratedPluginRegistrant rolls to stable.
  // https://github.com/flutter/flutter/issues/42694
  @Override
  public void configureFlutterEngine(FlutterEngine flutterEngine) {
    flutterEngine.getPlugins().add(new FirebaseCorePlugin());
  }
}
  1. (可选) 使用 ShimPluginRegistry 还不支持 v2 embedding 的插件。例如:
ShimPluginRegistry shimPluginRegistry = new ShimPluginRegistry(flutterEngine);
PathProviderPlugin.registerWith(
        shimPluginRegistry.registrarFor("io.flutter.plugins.pathprovider.PathProviderPlugin"));
VideoPlayerPlugin.registerWith(
        shimPluginRegistry.registrarFor("io.flutter.plugins.videoplayer.VideoPlayerPlugin"));
  1. MainActivity 同文件夹下创建 EmbeddingV1Activity.java 文件使用 v1 embedding。例如:
package io.flutter.plugins.firebasecoreexample;

import android.os.Bundle;
import io.flutter.app.FlutterActivity;
import io.flutter.plugins.GeneratedPluginRegistrant;

public class EmbeddingV1Activity extends FlutterActivity {
 @Override
 protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   GeneratedPluginRegistrant.registerWith(this);
 }
}
  1. 添加 EmbeddingV1Activity 到 <plugin_name>/example/android/app/src/main/AndroidManifest.xml. 例如
<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. 为了使插件在分支 master 和稳定版上支持 Flutter, 将这个 gradle 脚本添加到 <plugin_name>/android/build.gradle。
// TODO(<github-username>): Remove this hack once androidx
// lifecycle is included on stable.
// https://github.com/flutter/flutter/issues/42348

afterEvaluate {
    def containsEmbeddingDependencies = false
    for (def configuration : configurations.all) {
        for (def dependency : configuration.dependencies) {
            if (dependency.group == 'io.flutter' &&
                    dependency.name.startsWith('flutter_embedding') &&
                    dependency.isTransitive())
            {
                containsEmbeddingDependencies = true
                break
            }
        }
    }
    if (!containsEmbeddingDependencies) {
        android {
            dependencies {
                def lifecycle_version = "1.1.1"
                compileOnly "android.arch.lifecycle:runtime:$lifecycle_version"
                compileOnly "android.arch.lifecycle:common:$lifecycle_version"
                compileOnly "android.arch.lifecycle:common-java8:$lifecycle_version"
            }
        }
    }
}

测试插件

剩下的步骤涉及测试您的插件,我们鼓励,但不是必需的。

  1. 更新 <plugin_name>/example/android/app/build.gradleandroid.support.test 替换为 androidx.test:
defaultConfig {
   ...
  testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
  ...
}
dependencies {
...
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test:rules:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
...
}
  1. <plugin_name>/example/android/app/src/androidTest/java/<plugin_path>/ 中为 MainActivityEmbeddingV1Activity 添加测试文件。例如:
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 {
  @Rule public ActivityTestRule<MainActivity> rule = new ActivityTestRule<>(MainActivity.class);
}
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);
}
  1. 添加 e2eflutter_driver dev_dependencies 到 <plugin_name>/pubspec.yaml<plugin_name>/example/pubspec.yaml.
e2e: ^0.2.1
flutter_driver:
  sdk: flutter
  1. MainActivity.java 中手动注册 E2E 插件 以及示例应用程序使用的任何其他插件。
package io.flutter.plugins.packageinfoexample;

import dev.flutter.plugins.e2e.E2EPlugin;
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugins.packageinfo.PackageInfoPlugin;

public class MainActivity extends FlutterActivity {
  // TODO(jackson): Remove this once v2 of GeneratedPluginRegistrant
  // rolls to stable.
  // https://github.com/flutter/flutter/issues/42694
  @Override
  public void configureFlutterEngine(FlutterEngine flutterEngine) {
    flutterEngine.getPlugins().add(new PackageInfoPlugin());
    flutterEngine.getPlugins().add(new E2EPlugin());
  }
}
  1. <plugin_name> /pubspec.yaml 更新环境中的最低 Flutter 版本 所有插件都在目前 forward 会将最低版本设置为 1.9.1 + hotfix.4,这是我们可以保证支持的最低版本。例如:
environment:
  sdk: ">=2.0.0-dev.28.0 <3.0.0"
  flutter: ">=1.9.1+hotfix.4 <2.0.0"

  1. <plugin_name>/test/<plugin_name>_e2e.dart 创建一个简单测试。 为了测试添加了 v2 嵌入支持的 PR,我们正在尝试测试该插件的一些非常基本的功能。这是一个冒烟测试,以确保插件正确注册新的嵌入器。 例如:
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);
  });
}
  1. 在本地测试运行 e2e 测试,在 terminal 执行下列操作:
cd <plugin_name>/example
flutter build apk
cd android
./gradlew app:connectedAndroidTest -Ptarget=`pwd`/../../test/<plugin_name>_e2e.dart

基本插件

要开始实现 Flutter Android 插件,首先实现FlutterPlugin.

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.
  }
}

如上所示,您的插件可能会或可能不会与 在任何给定时间的特定 Flutter 体验。 您应该注意初始化插件的行为 在 onAttachedToEngine() 中,然后清除插件的引用 在 onDetachedFromEngine() 中。

FlutterPluginBinding 给你的插件提供了几个重要的引用:

binding.getFlutterEngine() : Returns the FlutterEngine that your plugin is attached to, providing access to components like the DartExecutor, FlutterRenderer, and more.

binding.getApplicationContext() : Returns the Android application's Context for the running app.

binding.getLifecycle() : Returns a reference that can be used to obtain a Lifecycle object. If you need to use this lifecycle reference then you need add a project dependency on Flutter's Android lifecycle package.

UI/Activity 插件

如果您的插件需要与用户界面进行交互, 例如请求权限或更改 Android UI 镶边, 那么您需要采取其他步骤来定义您的插件。 您必须实现 ActivityAware 接口。

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 个阶段实施适当的行为。 首先你的插件被加载到 Activity. 可通过提供的 ActivityPluginBinding 接触到 Activity 和一堆 callbacks.

由于可以在配置更改期间销毁 Activity, 在 onDetachedFromActivityForConfigChanges() 您必须清除对指定给定 Activity 的所有引用, 然后在 onReattachedToActivityForConfigChanges() 中重建这些引用。

最后,在onDetachedFromActivity() 中,您的插件应清理 与 Activity 行为相关的所有参考,然后返回 非 UI 配置。

Service plugin

TODO

ContentProvider plugin

TODO