开发环境配置
1. Android 开发环境
- Android Studio 安装
- Android SDK 配置
- Gradle 版本设置
2. 项目配置
// android/build.gradle
android {
compileSdkVersion 33
defaultConfig {
minSdkVersion 21
targetSdkVersion 33
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.9.0'
// 其他依赖
}
插件实现
1. Kotlin 实现
// android/src/main/kotlin/com/example/example_plugin/ExamplePlugin.kt
class ExamplePlugin: FlutterPlugin, MethodCallHandler {
private lateinit var channel: MethodChannel
private lateinit var context: Context
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
context = binding.applicationContext
channel = MethodChannel(binding.binaryMessenger, "example_plugin")
channel.setMethodCallHandler(this)
}
override fun onMethodCall(call: MethodCall, result: Result) {
when (call.method) {
"getPlatformVersion" -> {
result.success("Android ${android.os.Build.VERSION.RELEASE}")
}
else -> {
result.notImplemented()
}
}
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}
}
2. Java 实现
// android/src/main/java/com/example/example_plugin/ExamplePlugin.java
public class ExamplePlugin implements FlutterPlugin, MethodCallHandler {
private MethodChannel channel;
private Context context;
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
context = binding.getApplicationContext();
channel = new MethodChannel(binding.getBinaryMessenger(), "example_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);
}
}
生命周期管理
1. Activity 感知
class ExamplePlugin: FlutterPlugin, ActivityAware {
private var activity: Activity? = null
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
activity = binding.activity
}
override fun onDetachedFromActivity() {
activity = null
}
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
onAttachedToActivity(binding)
}
override fun onDetachedFromActivityForConfigChanges() {
onDetachedFromActivity()
}
}
2. 资源管理
class ExamplePlugin: FlutterPlugin {
private var resources: Resources? = null
private var disposables = CompositeDisposable()
override fun onAttachedToEngine(binding: FlutterPluginBinding) {
resources = binding.applicationContext.resources
}
override fun onDetachedFromEngine(binding: FlutterPluginBinding) {
resources = null
disposables.clear()
}
}
权限处理
1. 权限声明
<!-- android/src/main/AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.example_plugin">
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
</manifest>
2. 运行时权限
class ExamplePlugin: FlutterPlugin, ActivityAware {
private val PERMISSION_REQUEST_CODE = 100
private var permissionResult: Result? = null
private fun checkAndRequestPermission(permission: String, result: Result) {
if (activity == null) {
result.error("NO_ACTIVITY", "Plugin requires activity", null)
return
}
if (ContextCompat.checkSelfPermission(activity!!, permission)
== PackageManager.PERMISSION_GRANTED) {
result.success(true)
} else {
permissionResult = result
ActivityCompat.requestPermissions(
activity!!,
arrayOf(permission),
PERMISSION_REQUEST_CODE
)
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
): Boolean {
if (requestCode == PERMISSION_REQUEST_CODE && permissionResult != null) {
permissionResult?.success(
grantResults.isNotEmpty() &&
grantResults[0] == PackageManager.PERMISSION_GRANTED
)
permissionResult = null
return true
}
return false
}
}
性能优化
1. 异步处理
class ExamplePlugin: FlutterPlugin {
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
private fun performHeavyTask(result: Result) {
scope.launch {
try {
// 执行耗时操作
val data = processData()
// 切换到主线程返回结果
withContext(Dispatchers.Main) {
result.success(data)
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
result.error("ERROR", e.message, null)
}
}
}
}
override fun onDetachedFromEngine(binding: FlutterPluginBinding) {
scope.cancel()
}
}
2. 内存管理
class ExamplePlugin: FlutterPlugin {
private var cache: LruCache<String, Bitmap>? = null
override fun onAttachedToEngine(binding: FlutterPluginBinding) {
val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt()
val cacheSize = maxMemory / 8
cache = object : LruCache<String, Bitmap>(cacheSize) {
override fun sizeOf(key: String, bitmap: Bitmap): Int {
return bitmap.byteCount / 1024
}
}
}
override fun onDetachedFromEngine(binding: FlutterPluginBinding) {
cache?.evictAll()
cache = null
}
}
调试与测试
1. 日志记录
class ExamplePlugin: FlutterPlugin {
companion object {
private const val TAG = "ExamplePlugin"
}
private fun logDebug(message: String) {
if (BuildConfig.DEBUG) {
Log.d(TAG, message)
}
}
private fun logError(message: String, error: Throwable? = null) {
Log.e(TAG, message, error)
}
}
2. 单元测试
// android/src/test/kotlin/com/example/example_plugin/ExamplePluginTest.kt
class ExamplePluginTest {
@Test
fun testPlugin() {
val plugin = ExamplePlugin()
// 测试代码
}
}
最佳实践
1. 别抄 v1 embedding 的老教程
registerWith(Registrar) 在 Flutter 1.12 之后就废弃了,v1 embedding 的 shim 早已被移除。新插件一律用 FlutterPlugin + ActivityAware 结构;搜到带 registerWith 的教程直接关掉,抄来编译不过。
2. result 必须在主线程调用
onMethodCall 跑在主线程,但你的耗时操作在协程/线程池里——切记 result.success/result.error 要切回主线程再调,后台线程直接调会崩。上文协程示例里 withContext(Dispatchers.Main) 就是为这个,不是装饰。
3. 每个 MethodCall 必须 exactly once 回复
result.success、result.error、result.notImplemented 三选一,且只调一次。漏调,Dart 侧的 await 永远挂起;调两次,直接抛异常。用 try-finally 或协程的 invokeOnCompletion 兜底。
4. 盯住权限的版本差异
Android 6.0+ 要运行时申请(上文已讲),但更隐蔽的是 Android 12+:targetSdk 31+ 时蓝牙扫描/连接改用 BLUETOOTH_SCAN/BLUETOOTH_CONNECT,老教程里”申请位置权限连蓝牙”那套在新系统上直接拿不到设备。每个权限先查它是在哪个 API 级别引入的。
实战案例
flutter_aj_player 的 Android 端,说实话我没写多少原生代码——因为它底层用的是官方 video_player 插件,我的插件更多是 Dart 层的封装(加了 auto_orientation 全屏转屏、brightness_volume 手势调亮度音量)。真正的 Android 原生开发(Kotlin 写 MethodChannel)在我的项目里出场很少。唯一一次是调 Android 的 VideoView 行为,发现 video_player 的 ExoPlayer 封装已经够用了,就没往下挖。我的体会:先确认官方/社区插件能不能满足,别一上来就写原生。80% 的”要写插件”需求,其实是”没找对现有插件”。
总结
Android 平台插件开发的关键点:
- 合理的项目结构
- 完善的生命周期管理
- 规范的权限处理
- 优秀的性能表现
通过本文的学习,你应该已经掌握了 Flutter 插件在 Android 平台的开发要点。下一篇文章将介绍 iOS 平台插件的开发。
