目录
禁止转载,侵权必究! update 2021.9.28
更新了Null Safety兼容
前言
之前的教程是教大家如何利用别人开发好的flutter的库来快速完成需求。但是客户需求并不总是能用第三方的库来满足,我们需要编写自己的plugin。从本教程开始,我们进入Java和Objective-C本地flutter plugin的开发学习。
创建plugin项目
命令行执行以下命令:
flutter create --org cn.abilitygame -t plugin -i objc -a java
--platforms android,ios ability_device_info_plugin
在这里我们用参数 -t plugin来标识我们创建的是flutter的插件。
build一下项目:
cd ability_device_info_plugin/example
flutter build apk --target-platform android-arm,android-arm64 --split-per-abi
可以看到以下build结果
注: /Users/ouyang/app/flutter/abilitygame_projects/ability_device_info_plugin/android/src/main/java/cn/abilitygame/ability_device_info_plugin/AbilityDeviceInfoPlugin.java使用或覆盖了已过时的 API。
注: 有关详细信息, 请使用 -Xlint:deprecation 重新编译。
Removed unused resources: Binary resource data reduced from 46KB to 36KB: Removed 20%
Removed unused resources: Binary resource data reduced from 46KB to 36KB: Removed 20%
Running Gradle task 'assembleRelease'...
Running Gradle task 'assembleRelease'... Done 34.0s
✓ Built build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk (4.9MB).
打开Android Studio,选择import project,一路选到项目中的example的android目录下,点击Open。
注意:是example目录下。
按这个流程,Android Studio帮我们配置好了flutter plugin的Android开发环境。非常轻松。
扩展阅读:如果按上述步骤,你的项目还报AndroidX错误,需要升级到flutter v1.12.13以上版本。如果你用的第三方的库是旧版本。那你只能选其他库或者帮它升级版本了。学完这个系列教程,你应该具备升级flutter plugin的能力了。
编写plugin-Android部分
Android Studio应该给你展示的是Android项目结构,如下图:
1. Java的类定义
public class AbilityDeviceInfoPlugin implements FlutterPlugin, MethodCallHandler
它实现了官方的FlutterPlugin接口。为了兼容旧版,它包含了flutter旧版静态方法:
public static void registerWith(Registrar registrar){}
它还包含了flutter新的API方法:
public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {}
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {}
以上都是约定写法,我们把重点关注到它实现的第二个接口MethodCallHandler:
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
if (call.method.equals("getAndroidDeviceInfo")) {
Map<String, Object> build = new HashMap<>();
build.put("model", Build.MODEL);
Map<String, Object> version = new HashMap<>();
version.put("release", Build.VERSION.RELEASE);
version.put("sdkInt", Build.VERSION.SDK_INT);
build.put("version", version);
result.success(build);
} else {
result.notImplemented();
}
}
重点就是这个方法。我们也可以用单独的Java类来实现onMethodCall方法。它就是flutter App调用Java本地方法的入口函数。它首先判断了方法名是getAndroidDeviceInfo,然后从Android系统自带的Build类中获取了相关的系统参数并以Map<String, Object>形式返回给Flutter。
编写plugin-Flutter部分
关闭Android Studio。重新打开Android Studio,选择Open an exist Android Studio Project。
注意:打开根目录,不是example目录,跟本教程速成篇一样。
我们用dart来定义plugin,如下图:
注意:因为Java返回的是Map,这里要使用invokeMapMethod方法。
1. 定义两个类AndroidDeviceInfo & AndroidBuildVersion
对应Java插件中的两个Map。
class AndroidDeviceInfo {
final String model;
final AndroidBuildVersion version;
AndroidDeviceInfo._({
this.model,
this.version,
});
static AndroidDeviceInfo _fromMap(Map<String, dynamic> map) {
return AndroidDeviceInfo._(
version: AndroidBuildVersion._fromMap(map['version']?.cast<String, dynamic>()),
model: map['model'],
);
}
}
注意:它的_fromMap静态私有方法,从Java Map中转换KV为类属性。
class AndroidBuildVersion {
final String release;
final int sdkInt;
AndroidBuildVersion._({
this.release,
this.sdkInt,
});
static AndroidBuildVersion _fromMap(Map<String, dynamic> map) {
return AndroidBuildVersion._(
release: map['release'],
sdkInt: map['sdkInt'],
);
}
}
注意:它的_fromMap静态私有方法,从Java Map中转换KV为类属性。
定义dart的plugin的私有类变量,缓存device info
AndroidDeviceInfo _cachedAndroidDeviceInfo;
2. 定义MethodChannel
MethodChannel是Java和dart沟通的桥梁,它用name属性来唯一标识。
static const MethodChannel _channel =
const MethodChannel('abilitygame.cn/device_info');
3. 定义dart调用java的方法deviceInfo
Future<AndroidDeviceInfo> get deviceInfo async =>
_cachedAndroidDeviceInfo ??= AndroidDeviceInfo._fromMap(await _channel.invokeMapMethod<String, dynamic>('getAndroidDeviceInfo'));
它是一个异步方法。使用第2步的命名MethodChannel,它告诉java约定的方法名是:getAndroidDeviceInfo。我们可以回头看看,java侧代码有判断此方法名的逻辑。
测试自定义插件
打开example/lib/main.dart,如下图:
1. import依赖包
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:ability_device_info_plugin/ability_device_info_plugin.dart';
2. 定义类变量
Map<String, dynamic> _deviceData = <String, dynamic>{};
3. 定义initDeviceState方法
Future<void> initDeviceState() async {
Map<String, dynamic> deviceInfo;
try {
final AbilityDeviceInfoPlugin plugin = AbilityDeviceInfoPlugin();
deviceInfo = _readAndroidBuildData(await plugin.deviceInfo);
} on PlatformException {
print('failed to get device info.');
}
if (!mounted) return;
setState(() {
_deviceData = deviceInfo;
});
}
4. 把AndroidDeviceInfo重新转换回Map对象
Map<String, dynamic> _readAndroidBuildData(AndroidDeviceInfo build) {
return <String, dynamic>{
'version.sdkInt': build.version.sdkInt,
'version.release': build.version.release,
'model': build.model,
};
}
5. 定义Flutter的UI
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: ListView(
children: _deviceData.keys.map((String property){
return Row(
children: <Widget>[
Container(
padding: const EdgeInsets.all(10),
child: Text(property),
),
Expanded(child: Text('${_deviceData[property]}'))
],
);
}).toList(),
),
),
);
}
真机调试
用数据线连接上Android手机,点击运行。
可以看到我的手机型号是NX563J;Android 9; sdk 28。
机型适配问题
适配所有Android机型