今天在进行插件开发的时候,遇到了各种问题,有 AndroidStudio 版本不对的问题,有插件的 build.gradle 文件配置不对的问题,还有插件中的 kotlin 插件没有配置,导致找不到类的问题,头疼,所以记录下开发的完整流程,以便后期再遇到此种问题的时候,有个解决方案。
一、Flutter插件
Flutter插件是由于 Flutter 自身不具备访问平台特性的能力,所以需要借助插件使用原生API的能力才能具有相应的功能,比如吐司,拍照,位置信息等,所以需要一个通道能够使得Flutter和原生平台之间能够顺利的沟通,这就是 channel, channel有好几种,但是我们一般都是用的 MethodChannel, 后面有时间再补上这块的内容。
二、Flutter插件开发流程
- 先在 Flutter 项目中定义好有关 MethodChannel 相应的工具类,并提供静态方法供Flutter项目中其他地方使用
- 然后在 Flutter 项目中 进行 插件开发,并添加依赖到宿主工程中
- 在宿主工程中开发PluginRegistraint类,并将插件添加到 FlutterEngine的插件列表 plugins 中
- 在MainActivity中调用 PluginRegistrant的 registerWith() 方法,最终将插件注册到 FlutterEngine 中
- 在Flutter 项目中对第1步创建好的方法进行调用和测试
三、Flutter中工具类
plugin_toast.dart
import 'package:flutter/services.dart';
class ToastPlugin {
static MethodChannel _channel = new MethodChannel('lucky_toast_plugin');
static Future<void> showToast() async {
await _channel.invokeMethod('showToast', {'msg': 'hello world'});
return;
}
}
四、插件开发
1. 首先把Flutter项目中用 AndroidStudio 打开,这一步很重要,否则不能进行插件开发。
2. 在 AndroidStudio 打开项目之后,搜索 MainActivity, 然后点击右上角的的 Open for Editing in Android Studio。
3. 单独打开之后,在File中选择创建 Module, 并选择 Android Library,设置好项目名称 plugin_toast,此时注意检查 根目录下 settings.gradle 文件是否配置正确。
4. 查看项目根目录下的build.gradle文件是否正确, 可如下参考:
buildscript {
ext.kotlin_version = '1.3.72'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
jcenter()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
delete rootProject.buildDir
}
5. 查看并确定plugin_toast 插件的 build.gradle 文件是否正确,可参考如下,因为这个配置问题,导致项目编译好了,但是运行是找不到 MainActivity.class 的问题。
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
//
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.library'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 30
buildToolsVersion "30.0.1"
defaultConfig {
minSdkVersion 22
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
//implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
//implementation 'androidx.core:core-ktx:1.2.0'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'com.google.android.material:material:1.1.0'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
flutter {
source '../..'
}
6. 在宿主 app 的build.gradle 文件中添加插件库
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 30
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.flutter_widgets"
minSdkVersion 22
targetSdkVersion 30
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
}
flutter {
source '../..'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
//添加插件库
implementation project(path:':plugin_toast')
}
7. 确定好以上没有问题,就可以正常开发插件了,代码如下
package com.luckyboy.plugin_toast;
import android.content.Context;
import android.widget.Toast;
import androidx.annotation.NonNull;
import java.util.Map;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
public class ToastPlugin implements FlutterPlugin, MethodChannel.MethodCallHandler {
private MethodChannel channel;
private Context context;
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
context = binding.getApplicationContext();
channel = new MethodChannel(binding.getBinaryMessenger(), "lucky_toast_plugin");
channel.setMethodCallHandler(this);
}
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
channel.setMethodCallHandler(null);
}
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
if (call.method.equals("showToast")){
Map<String, String> map = call.arguments();
String message = map.get("msg");
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
}
}
}
- 创建 ToastPlugin 类,并实现 FlutterPlugin, MethodChannel.MethodCallHandler 接口,总共有三个方法 :onAttachedToEngine, onDetachedFromEngine, onMethodCall 。
- 首先在 onAttachedToEngine 中 创建了 MethodChannel 对象 channel,并设置插件的标记 "lucky_toast_plguin",这个字符串就是在Flutter中已经定义好的,方便在Flutter中对插件的查找 ,最后调用 channel.setMethodCallHandler() 设置回调 。
- 其次在 onDetachedFromEngine 中将回调设置为 null 。
- 在 onMethodCall 方法中 进行业务处理,call.method 获取的是 Flutter 中 invoke 方法设置的调函函数名,call.arguments() 方法返回的是invoke 方法设置的参数。
- 可以在result.success("success")。
- 插件开发完毕。
五、添加插件到宿主
1. 创建 ToastPluginRegistrant 类,用于将插件添加到 FlutterEngine 的插件列表中
package io.flutter.plugins;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import com.luckyboy.plugin_toast.ToastPlugin;
import io.flutter.embedding.engine.FlutterEngine;
@Keep
public final class ToastPluginRegistrant {
public static void registerWith(@NonNull FlutterEngine flutterEngine){
flutterEngine.getPlugins().add(new ToastPlugin());
}
}
2. 在 MainActivity 中进行 registerWith 方法调用,注册插件到插件中
package com.example.flutter_widgets
import android.os.Bundle
import io.flutter.embedding.android.FlutterActivity
import io.flutter.plugins.GeneratedPluginRegistrant
import io.flutter.plugins.ToastPluginRegistrant
class MainActivity: FlutterActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GeneratedPluginRegistrant.registerWith(flutterEngine!!);
ToastPluginRegistrant.registerWith(flutterEngine!!);
}
}
GeneratedPluginRegistrant 是另外一个系统帮我们自动生成的,可以不用管。至此,Android 端的插件开发部分已经完成,剩下的就是Flutter的调用插件部分代码。
六、Flutter 插件调用测试
import 'package:flutter/material.dart';
import 'package:flutter_widgets/plugin/lib/plugin_toast.dart';
class PluginPage extends StatefulWidget {
@override
_PluginPageState createState() => _PluginPageState();
}
class _PluginPageState extends State<PluginPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Plugin页面')),
body: Column(
children: [
MaterialButton(
minWidth: double.infinity,
height: 48,
onPressed: () {
showToast();
},
child: Text('点击Toast'),
)
],
),
);
}
Future<void> showToast() async {
await ToastPlugin.showToast();
return;
}
}