首先创建一个 Plugin 的 flutter 工程。我们会得到一些生成的目录和代码。
例如:
public class MyFlutterPlugin implements FlutterPlugin, MethodCallHandler{
/// The MethodChannel that will the communication between Flutter and native Android
///
/// This local reference serves to register the plugin with the Flutter Engine and unregister it
/// when the Flutter Engine is detached from the Activity
private MethodChannel channel;
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "my_flutter_plugin");
channel.setMethodCallHandler(this);
}
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
if (call.method.equals("getPlatformVersion")) {
result.success("Android " + android.os.Build.VERSION.RELEASE);
} else {
result.notImplemented();
}
}
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
channel.setMethodCallHandler(null);
}
}
这个 MyFlutterPlugin ,工程会生成一个基础的。然后我们可以根据需求对这个类进行定制开发。比如我们需要在 flutter 的页面点击一个按钮,然后能在 MyFlutterPlugin 里得到对应的监听关联,就可以里用 MethodChannel 来实现。有点类似 Android 的 webview 里 h5 和 原生之间通信。
Flutter 提供了一套 PlatformChannel
机制用于 Flutter
和 Android
的通信,主要分为三种类型:
1、MethodChannel
:主要用于传递方法调用,Flutter
和 Native(Android)
之间进行方法调用时可以使用,是一种双向的通信方式
2、EventChannel
:主要用于用户数据流的通信,如:手机电量变化,网络连接变化等。这种方式只能 Native(Android)
向 Flutter
发送数据,是一种单向的通信方式
3、BaseicMessageChannel
:主要用于传递各种类型数据,它支持的类型有很多,如:String,半结构化信息等,是一种双向的通信方式
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
if (METHOD_INSTALL.equalsIgnoreCase(call.method)) {
...省略
}
}
以下是 openinstall.io 的实现代码:
public class OpeninstallFlutterPlugin implements FlutterPlugin, MethodCallHandler, ActivityAware, PluginRegistry.NewIntentListener {
private static final String TAG = "OpenInstallPlugin";
@Deprecated
private static final String METHOD_INIT_PERMISSION = "initWithPermission";
@Deprecated
private static final String METHOD_WAKEUP = "registerWakeup";
private static final String METHOD_CONFIG = "config";
private static final String METHOD_CLIPBOARD_ENABLED = "clipBoardEnabled";
private static final String METHOD_SERIAL_ENABLED = "serialEnabled";
private static final String METHOD_INIT = "init";
private static final String METHOD_INSTALL_RETRY = "getInstallCanRetry";
private static final String METHOD_INSTALL = "getInstall";
private static final String METHOD_REGISTER = "reportRegister";
private static final String METHOD_EFFECT_POINT = "reportEffectPoint";
private static final String METHOD_SHARE = "reportShare";
private static final String METHOD_OPID = "getOpid";
private static final String METHOD_WAKEUP_NOTIFICATION = "onWakeupNotification";
private static final String METHOD_INSTALL_NOTIFICATION = "onInstallNotification";
private MethodChannel channel = null;
private ActivityPluginBinding activityPluginBinding;
private FlutterPluginBinding flutterPluginBinding;
private Intent intentHolder = null;
private volatile boolean initialized = false;
private Configuration configuration = null;
private boolean alwaysCallback = false;
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
flutterPluginBinding = binding;
channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "openinstall_flutter_plugin");
channel.setMethodCallHandler(this);
}
@Override
public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) {
activityPluginBinding = binding;
binding.addOnNewIntentListener(this);
wakeup(binding.getActivity().getIntent());
}
@Override
public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) {
activityPluginBinding = binding;
binding.addOnNewIntentListener(this);
}
@Override
public void onMethodCall(MethodCall call, @NonNull final Result result) {
Log.d(TAG, "invoke " + call.method);
if (METHOD_CONFIG.equalsIgnoreCase(call.method)) {
config(call);
result.success("OK");
} else if (METHOD_CLIPBOARD_ENABLED.equalsIgnoreCase(call.method)) {
Boolean enabled = call.argument("enabled");
OpenInstall.clipBoardEnabled(enabled == null ? true : enabled);
result.success("OK");
} else if (METHOD_SERIAL_ENABLED.equalsIgnoreCase(call.method)) {
Boolean enabled = call.argument("enabled");
OpenInstall.serialEnabled(enabled == null ? true : enabled);
result.success("OK");
} else if (METHOD_INIT.equalsIgnoreCase(call.method)) {
Boolean box = call.argument("alwaysCallback");
alwaysCallback = box == null ? false : box;
init();
result.success("OK");
} else if (METHOD_INIT_PERMISSION.equalsIgnoreCase(call.method)) {
Boolean box = call.argument("alwaysCallback");
alwaysCallback = box == null ? false : box;
initWithPermission();
result.success("OK");
} else if (METHOD_WAKEUP.equalsIgnoreCase(call.method)) {
result.success("OK");
} else if (METHOD_INSTALL.equalsIgnoreCase(call.method)) {
Integer seconds = call.argument("seconds");
OpenInstall.getInstall(new AppInstallListener() {
@Override
public void onInstallFinish(AppData appData, Error error) {
Map<String, Object> data = data2Map(appData);
boolean shouldRetry = error!=null && error.shouldRetry();
data.put("shouldRetry", shouldRetry);
if(error != null) {
data.put("message", error.getErrorMsg());
}
channel.invokeMethod(METHOD_INSTALL_NOTIFICATION, data);
}
}, seconds == null ? 0 : seconds);
result.success("OK");
} else if (METHOD_INSTALL_RETRY.equalsIgnoreCase(call.method)) {
Integer seconds = call.argument("seconds");
OpenInstall.getInstallCanRetry(new AppInstallRetryAdapter() {
@Override
public void onInstall(AppData appData, boolean shouldRetry) {
Map<String, Object> data = data2Map(appData);
data.put("retry", String.valueOf(shouldRetry)); // 2.4.0 之前的版本返回
data.put("shouldRetry", shouldRetry); // 以后保存统一
channel.invokeMethod(METHOD_INSTALL_NOTIFICATION, data);
}
}, seconds == null ? 0 : seconds);
result.success("OK");
} else if (METHOD_REGISTER.equalsIgnoreCase(call.method)) {
OpenInstall.reportRegister();
result.success("OK");
} else if (METHOD_EFFECT_POINT.equalsIgnoreCase(call.method)) {
String pointId = call.argument("pointId");
Integer pointValue = call.argument("pointValue");
if(TextUtils.isEmpty(pointId) || pointValue == null){
Log.w(TAG, "pointId is empty or pointValue is null");
// result.error("ERROR", "pointId is empty or pointValue is null", null);
}else{
Map<String, String> extraMap = call.argument("extras");
OpenInstall.reportEffectPoint(pointId, pointValue, extraMap);
}
result.success("OK");
} else if (METHOD_SHARE.equalsIgnoreCase(call.method)) {
String shareCode = call.argument("shareCode");
String sharePlatform = call.argument("platform");
final Map<String, Object> data = new HashMap<>();
if(TextUtils.isEmpty(shareCode) || TextUtils.isEmpty(sharePlatform)){
data.put("message", "shareCode or platform is empty");
data.put("shouldRetry", false);
result.success(data);
}else {
OpenInstall.reportShare(shareCode, sharePlatform, new ResultCallback<Void>() {
@Override
public void onResult(@Nullable Void v, @Nullable Error error) {
boolean shouldRetry = error!=null && error.shouldRetry();
data.put("shouldRetry", shouldRetry);
if(error != null) {
data.put("message", error.getErrorMsg());
}
result.success(data);
}
});
}
} else if (METHOD_OPID.equalsIgnoreCase(call.method)) {
String opid = OpenInstall.getOpid();
result.success(opid);
} else {
result.notImplemented();
}
}
private void config(MethodCall call) {
Configuration.Builder builder = new Configuration.Builder();
if (call.hasArgument("androidId")) {
String androidId = call.argument("androidId");
builder.androidId(androidId);
}
if (call.hasArgument("serialNumber")) {
String serialNumber = call.argument("serialNumber");
builder.serialNumber(serialNumber);
}
if (call.hasArgument("adEnabled")) {
Boolean adEnabled = call.argument("adEnabled");
builder.adEnabled(checkBoolean(adEnabled));
}
if (call.hasArgument("oaid")) {
String oaid = call.argument("oaid");
builder.oaid(oaid);
}
if (call.hasArgument("gaid")) {
String gaid = call.argument("gaid");
builder.gaid(gaid);
}
if(call.hasArgument("imeiDisabled")){
Boolean imeiDisabled = call.argument("imeiDisabled");
if (checkBoolean(imeiDisabled)) {
builder.imeiDisabled();
}
}
if (call.hasArgument("imei")) {
String imei = call.argument("imei");
builder.imei(imei);
}
if(call.hasArgument("macDisabled")){
Boolean macDisabled = call.argument("macDisabled");
if (checkBoolean(macDisabled)) {
builder.macDisabled();
}
}
if (call.hasArgument("mac")) {
String macAddress = call.argument("mac");
builder.macAddress(macAddress);
}
configuration = builder.build();
// Log.d(TAG, String.format("Configuration: adEnabled=%s, oaid=%s, gaid=%s, macDisabled=%s, imeiDisabled=%s, "
// + "androidId=%s, serialNumber=%s, imei=%s, mac=%s",
// configuration.isAdEnabled(), configuration.getOaid(), configuration.getGaid(),
// configuration.isMacDisabled(), configuration.isImeiDisabled(),
// configuration.getAndroidId(), configuration.getSerialNumber(),
// configuration.getImei(), configuration.getMacAddress()));
}
private boolean checkBoolean(Boolean bool) {
if (bool == null) return false;
return bool;
}
private void init() {
Context context = flutterPluginBinding.getApplicationContext();
if (context != null) {
OpenInstall.init(context, configuration);
initialized = true;
if (intentHolder != null) {
wakeup(intentHolder);
intentHolder = null;
}
} else {
Log.d(TAG, "Context is null, can't init");
}
}
@Deprecated
private void initWithPermission() {
Activity activity = activityPluginBinding.getActivity();
if (activity == null) {
Log.d(TAG, "Activity is null, can't initWithPermission, replace with init");
init();
} else {
activityPluginBinding.addRequestPermissionsResultListener(permissionsResultListener);
OpenInstall.initWithPermission(activity, configuration, new Runnable() {
@Override
public void run() {
activityPluginBinding.removeRequestPermissionsResultListener(permissionsResultListener);
initialized = true;
if (intentHolder != null) {
wakeup(intentHolder);
intentHolder = null;
}
}
});
}
}
@Override
public boolean onNewIntent(Intent intent) {
wakeup(intent);
return false;
}
private void wakeup(Intent intent) {
if (initialized) {
Log.d(TAG, "getWakeUp : alwaysCallback=" + alwaysCallback);
if (alwaysCallback) {
OpenInstall.getWakeUpAlwaysCallback(intent, new AppWakeUpListener() {
@Override
public void onWakeUpFinish(AppData appData, Error error) {
if (error != null) { // 可忽略,仅调试使用
Log.d(TAG, "getWakeUpAlwaysCallback : " + error.getErrorMsg());
}
channel.invokeMethod(METHOD_WAKEUP_NOTIFICATION, data2Map(appData));
}
});
} else {
OpenInstall.getWakeUp(intent, new AppWakeUpAdapter() {
@Override
public void onWakeUp(AppData appData) {
channel.invokeMethod(METHOD_WAKEUP_NOTIFICATION, data2Map(appData));
}
});
}
} else {
intentHolder = intent;
}
}
@Deprecated
private final PluginRegistry.RequestPermissionsResultListener permissionsResultListener =
new PluginRegistry.RequestPermissionsResultListener() {
@Override
public boolean onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
OpenInstall.onRequestPermissionsResult(requestCode, permissions, grantResults);
return false;
}
};
private static Map<String, Object> data2Map(AppData data) {
Map<String, Object> result = new HashMap<>();
if (data != null) {
result.put("channelCode", data.getChannel());
result.put("bindData", data.getData());
}
return result;
}
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
}
@Override
public void onDetachedFromActivityForConfigChanges() {
}
@Override
public void onDetachedFromActivity() {
}
}
在 Flutter 里需要写一个 openinstall_flutter_plugin.dart,去处理和 Android 原生代码的交互
import 'dart:async';
import 'dart:io';
import 'package:flutter/services.dart';
typedef Future EventHandler(Map<String, Object> data);
class OpeninstallFlutterPlugin {
// 单例
static final OpeninstallFlutterPlugin _instance = new OpeninstallFlutterPlugin._internal();
factory OpeninstallFlutterPlugin() => _instance;
OpeninstallFlutterPlugin._internal();
Future defaultHandler() async {}
late EventHandler _wakeupHandler;
late EventHandler _installHandler;
static const MethodChannel _channel = const MethodChannel('openinstall_flutter_plugin');
// 已废弃
// 旧版本使用,保留一段时间,防止 npm 自动升级使用最新版本插件出现问题
void config(bool adEnabled, String? oaid, String? gaid) {
print("config(bool adEnabled, String? oaid, String? gaid) 后续版本将移除,请使用configAndroid(Map options)");
if (Platform.isAndroid) {
var args = new Map();
args["adEnabled"] = adEnabled;
args["oaid"] = oaid;
args['gaid'] = gaid;
_channel.invokeMethod('config', args);
} else {
// 仅使用于 Android 平台
}
}
// 广告平台配置,请参考文档
void configAndroid(Map options) {
if (Platform.isAndroid) {
_channel.invokeMethod('config', options);
} else {
// 仅使用于 Android 平台
}
}
// 关闭剪切板读取
void clipBoardEnabled(bool enabled){
if (Platform.isAndroid) {
var args = new Map();
args["enabled"] = enabled;
_channel.invokeMethod('clipBoardEnabled', args);
} else {
// 仅使用于 Android 平台
}
}
// 已废弃
// 关闭SerialNumber读取
void serialEnabled(bool enabled){
print("serialEnabled(bool enabled) 后续版本将移除,请使用configAndroid(Map options)");
if (Platform.isAndroid) {
var args = new Map();
args["enabled"] = enabled;
_channel.invokeMethod('serialEnabled', args);
} else {
// 仅使用于 Android 平台
}
}
//设置参数并初始化
//options可设置参数:
//AdPlatformEnable:必要,是否开启广告平台统计功能
//ASAEnable:必要,是否开启ASA功能
//ASADebug:可选,使用ASA功能时是否开启debug模式,正式环境中请关闭
//idfaStr:可选,用户可以自行传入idfa字符串,不传则插件内部会获取,通过其它插件获取的idfa字符串一般格式为xxxx-xxxx-xxxx-xxxx
void configIos(Map options) {
if (Platform.isAndroid) {
//仅使用于 iOS 平台
} else {
_channel.invokeMethod("config", options);
}
}
// wakeupHandler 拉起回调.
// alwaysCallback 是否总是有回调。当值为true时,只要触发了拉起方法调用,就会有回调
// permission 初始化时是否申请 READ_PHONE_STATE 权限,已废弃。请用户自行进行权限申请
void init(EventHandler wakeupHandler, {bool alwaysCallback = false, bool permission = false}) {
_wakeupHandler = wakeupHandler;
_channel.setMethodCallHandler(_handleMethod);
_channel.invokeMethod("registerWakeup");
if (Platform.isAndroid) {
var args = new Map();
args["alwaysCallback"] = alwaysCallback;
if (permission) {
print("initWithPermission 后续版本将移除,请自行进行权限申请");
_channel.invokeMethod("initWithPermission", args);
} else {
_channel.invokeMethod("init", args);
}
} else {
print("插件版本>=2.3.1后,由于整合了广告和ASA系统,iOS平台将通过用户手动调用init方法初始化SDK,需要广告平台或者ASA统计服务的请在init方法前调用configIos方法配置参数");
}
}
// SDK内部将会一直保存安装数据,每次调用install方法都会返回值。
// 如果调用install获取到数据并处理了自己的业务,后续不想再被触发,那么可以自己在业务调用成功时,设置一个标识,不再调用install方法
void install(EventHandler installHandler, [int seconds = 10]) {
var args = new Map();
args["seconds"] = seconds;
this._installHandler = installHandler;
_channel.invokeMethod('getInstall', args);
}
// 只有在用户进入应用后在较短时间内需要返回安装参数,但是又不想影响参数获取精度时使用。
// 在shouldRetry为true的情况下,后续再次通过install依然可以获取安装数据
// 通常情况下,请使用 install 方法获取安装参数
void getInstallCanRetry(EventHandler installHandler, [int seconds = 3]) {
if (Platform.isAndroid) {
var args = new Map();
args["seconds"] = seconds;
this._installHandler = installHandler;
_channel.invokeMethod('getInstallCanRetry', args);
} else {
// 仅使用于 Android 平台
}
}
void reportRegister() {
_channel.invokeMethod('reportRegister');
}
void reportEffectPoint(String pointId, int pointValue, [Map<String, String>? extraMap]) {
var args = new Map();
args["pointId"] = pointId;
args["pointValue"] = pointValue;
if(extraMap != null){
args["extras"] = extraMap;
}
_channel.invokeMethod('reportEffectPoint', args);
}
Future<Map<Object?, Object?>> reportShare(String shareCode, String platform) async {
var args = new Map();
args["shareCode"] = shareCode;
args["platform"] = platform;
Map<Object?, Object?> data = await _channel.invokeMethod('reportShare', args);
return data;
}
Future<String?> getOpid() async {
print("getOpid 当初始化未完成时,将返回空,请在业务需要时再获取,并且使用时做空判断");
String? opid = await _channel.invokeMethod('getOpid');
return opid;
}
Future _handleMethod(MethodCall call) async {
print(call.method);
switch (call.method) {
case "onWakeupNotification":
return _wakeupHandler(call.arguments.cast<String, Object>());
case "onInstallNotification":
return _installHandler(call.arguments.cast<String, Object>());
default:
throw new UnsupportedError("Unrecognized Event");
}
}
}
本质上通过 Flutter 实现 Plugin 的这个方案,里面还是用到了 Android 的 OpenInstall_v2.8.1.jar 的依赖库,然后通过 Plugin 的方式进行包装。
本文中的源码,可以在这里找到:https://github.com/OpenInstall/openinstall-flutter-plugin