Flutter 插件 从启动到插件开始执行

6,849 阅读4分钟

2020-05-14 0:00:26
Flutter 插件,FlutterPlugin,FlutterEngine

Android Plugin API 2.0

在 Flutter 1.12 ,插件 API 升 级到了 2.0 。
API 2.0 之前。
开发者通常要在 MainActivityonCreate() 方法中手动调用 GeneratedPluginRegistrant.registerWith() 来执行插件类中的一个静态方法初始化插件。

API 2.0 之后,出现了 FlutterPlugin 接口。
开发者只要让创建的插件类实现它,并把初始化代码放到一个重写的方法中就好了。
甚至都不需要创建 MainActivity

从 Flutter 启动到开始执行插件

随 FlutterPlugin 一同出现的还有新的 FlutterActivity 和 FlutterEngine 。

FlutterActivity 是什么

启动一个 Android 应用,启动的通常是一个 Activity 。
如果这个应用是 Flutter 应用,那么这个 Activity 是 FlutterActivity

Activity 创建后会先执行 onCreate() 方法。
在 FlutterActivity 的 onCreate() 方法中,创建了一个 FlutterEngine

FlutterEngine 是什么

官方文档对 FlutterEngine 的解释:

A single Flutter execution environment.
The FlutterEngine is the container through which Dart code can be run in an Android application.

是一个单独的 Flutter 执行环境,通过它,可以让一个 Android 应用运行 Dart 代码。

FlutterEngine 提供了

  • FlutterRenderer 负责渲染视图。

  • DartExecutor 作为 BinaryMessenger 用来和 Flutter 端通信。我们创建 MethodChannel 就要用到这个。

  • FlutterEnginePluginRegistry 作为 PluginRegistry 来登记开发者创建的 FlutterPlugin 。当我们在项目的 pubspec.yaml 文件中添加了插件,运行 flutter pub get 后,会在安卓平台生成一个 GeneratedPluginRegistrant 类,该类中就用到了 PluginRegistry 来注册 FlutterPlugin 。

  • PlatformViewsController 用来管理提供给 Flutter 端的原生平台视图。当我们想让 Flutter 应用中显示安卓原生视图的时候,例如百度地图,就会用到。

创建完 FlutterEngine ,FlutterActivity 的 onCreate() 方法还没有结束。

登记插件

接下来会执行 FlutterActivity 中的 configureFlutterEngine() 方法。
并把 FlutterEngine 作为参数传递给它。

@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
  registerPlugins(flutterEngine);
}
  private static void registerPlugins(@NonNull FlutterEngine flutterEngine) {
    try {
      Class<?> generatedPluginRegistrant =
          Class.forName("io.flutter.plugins.GeneratedPluginRegistrant");
      Method registrationMethod =
          generatedPluginRegistrant.getDeclaredMethod("registerWith", FlutterEngine.class);
      registrationMethod.invoke(null, flutterEngine);
    } catch (Exception e) {
      Log.w(
          TAG,
          "Tried to automatically register plugins with FlutterEngine ("
              + flutterEngine
              + ") but could not find and invoke the GeneratedPluginRegistrant.");
    }
  }

这里调用了 GeneratedPluginRegistrant 中的 registerWith() 来登记插件。
并且给了 FlutterEngine 作为参数。
下面的内容就和我们写的插件相关了。

GeneratedPluginRegistrant

这个类是根据 pubspec.yaml 中的依赖生成的。
新建一个插件后,示例中的 pubspec.yaml

# <plugin_name>/example/pubspec.yaml
dependencies:
  flutter:
    sdk: flutter

  flutter_plugin_demo: # 示例依赖了新建的插件
    path: ../

插件的 pubspec.yaml

# <plugin_name>/pubspec.yaml
name: flutter_plugin_demo
flutter:
  plugin:
    platforms:
      android:
        package: xyz.waixingjiandie.flutter_plugin_demo
        pluginClass: FlutterPluginDemoPlugin # 指定了 Android 插件的类名字
      ios:
        pluginClass: FlutterPluginDemoPlugin

自动生成的 GeneratedPluginRegistrant :

package io.flutter.plugins;

import androidx.annotation.Keep;
import androidx.annotation.NonNull;

import io.flutter.embedding.engine.FlutterEngine;

/**
 * Generated file. Do not edit.
 * This file is generated by the Flutter tool based on the
 * plugins that support the Android platform.
 */
@Keep
public final class GeneratedPluginRegistrant {
  public static void registerWith(@NonNull FlutterEngine flutterEngine) {
    flutterEngine.getPlugins().add(new xyz.waixingjiandie.flutter_plugin_demo.FlutterPluginDemoPlugin());
  }
}

通过 FlutterEngine 的 getPlugins() 方法返回一个 PluginRegistry
这个 PluginRegistry 是新版,没有 hasPlugin() 方法。
通过它的 add() 方法登记插件,会自动检查是否已经登记过。
插件的登记,其实就是把插件添加到一个 Map 里。
这样就算把 FlutterPlugin 附加到了 FlutterEngine 。

插件登记后,会执行插件的 onAttachedToEngine() 方法。
并传递一个 FlutterPluginBinding 作为参数。

然后程序的运行就进入了 FlutterPlugin 。

FlutterPlugin

插件的代码位于 <plugin_name>/android/ 目录下。

插件有2个方法:

  • onAttachedToEngine()
  • onDetachedFromEngine()

根据名字就能看出来,
一个是插件被关联到 FlutterEngine 时调用,可以做一些初始化工作。
另一个是插件从 FlutterEngine 移除时调用,可以做一些清理工作。

他们都接收一个 FlutterPluginBinding 作为参数。
这个 FlutterPluginBinding 是 FlutterEngine 创建 PluginRegistry 时,
由 PluginRegistry 的实现 FlutterEnginePluginRegistry 创建的。

通过 FlutterPluginBinding 可以得到

  • Application context
  • BinaryMessenger
  • PlatformViewRegistry

等常用对象。

自带的示例在这里创建了一个 MethodChannel 用来和 Flutter 通信。
创建 MethodChannel 就要用到 BinaryMessenger 。
可以通过 FlutterPluginBinding 的 getBinaryMessenger() 方法得到。

如果插件需要用到 Activity ,可以让插件再实现一个 ActivityAware 接口。
如果涉及后台服务,还有个 ServiceAware 接口。

用法可以参考官方插件
目前我只写了一个百度地图插件,参考了谷歌地图,需要用到 ActivityAware 。

以上就是从 app 启动到插件开始执行的过程了。
新的 API 还提出了 Federated plugins 的概念。

Federated plugins

将一个插件包,分离成了3类。

  • 插件原生平台端提供的功能声明成接口放在一个包中
  • 特定平台实现接口的包,可以有多个
  • 插件为 app 提供各种功能的包,插件使用者调用这个包中的方法。
    接口为这个包提供一个平台的实现,这个包调用实现中的方法。

见另一篇 Federated plugins

可以简化的东西

这是新建项目后示例的清单文件(有省略):

<!-- <plugin_name>/example/android/app/src/main/AndroidManifest.xml -->

<application
    android:name="io.flutter.app.FlutterApplication"
    <activity
        android:name=".MainActivity"
    </activity>
</application>

MainActivity.java

public class MainActivity extends FlutterActivity {
}

MainActivity 我并没有省略,就是空的。

参考官方的升级指南

我们可以在清单文件中把 .MainActivity 改成 io.flutter.embedding.android.FlutterActivity
然后删掉 MainActivity.java 文件。
清单文件中的 FlutterApplication 也可以去掉。于是变成了这样子:

<application
    <activity
        android:name="io.flutter.embedding.android.FlutterActivity"
    </activity>
</application>

相关文章

参考资料

源代码

文章:

API文档: