上一篇文章我们一起来实现了iOS平台的插件开发,本节我们来看看Android平台的插件是如何实现的。
本文只会涉及到Android端的代码了,因为Flutter端代码是通用的,不需要修改了。
网络设置相关的修改
Google从Android P开始要求使用加密连接,如果应用使用的是非加密的明文流量的http
网络请求,则会导致该应用无法进行网络请求。
本项目中的图片等有使用到http
网络请求,需要适配下:
- 在
res
新建一个xml
目录; - 在
xml
目录中新建一个network_permission_config.xml
文件,内容如下:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>
复制代码
AndroidManifest.xml
中添加配置
<application
android:label="netmusic_flutter"
android:icon="@mipmap/ic_launcher"
// 添加的设置
android:networkSecurityConfig="@xml/network_permission_config"
>
</application>
复制代码
Flutter端向Android端发送消息
Flutter端的代码
省略,代码同上篇文章。
Android端的代码
- 新建播放器控制类
PlayerWrapper
class PlayerWrapper(engine: FlutterEngine, val context: Context) {
}
复制代码
构造函数传入了
FlutterEngine
: 因为FlutterEngine
中包含BinaryMessenger
,被用于创建MethodChannel
。
MainActivity
中初始化PlayerWrapper
class MainActivity: FlutterActivity() {
private var playerWrapper: PlayerWrapper? = null
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
// 初始化播放器
playerWrapper = PlayerWrapper(flutterEngine, this)
super.configureFlutterEngine(flutterEngine)
}
}
复制代码
从
configureFlutterEngine()
函数中获取到FlutterEngine
,然后创建PlayerWrapper
- 播放器控制类
PlayerWrapper
中建立MethodChannel
,然后注册回调函数
class PlayerWrapper(engine: FlutterEngine, private val context: Context) {
// 1. 新建MethodChannel
private var channel: MethodChannel = MethodChannel(engine.dartExecutor.binaryMessenger, "netmusic.com/audio_player").also {
// 2. 注册回调函数 handleMethodCall
it.setMethodCallHandler { call, result ->
try {
handleMethodCall(call, result)
result.success(1)
} catch (e: Exception) {
result.success(0)
}
}
}
}
复制代码
我们给
MethodChannel
注册了一个匿名函数,当Flutter调用原生代码时候能够收到对应的method
(方法名)和argument
(参数)。真正的处理方法在handleMethodCall
中。
handleMethodCall
中处理逻辑
private fun handleMethodCall(call: MethodCall, response: MethodChannel.Result) {
when (call.method) {
"play" -> {
// 1.1. 进行参数判断
// 1.2. 创建播放器然后进行播放
// 1.3. 注册音乐播放完成的回调函数, 播放完成后发送给Flutter
// 1.4. 注册音乐播放失败的回调函数,播放失败后发送给Flutter
// 1.5. 开始一个定时器获取当前的播放进度,把进度发送给Flutter
}
"resume" -> {
// 2.1. 开始播放
// 2.2. 开启定时器任务
}
"pause" -> {
// 3.1. 暂停播放
// 3.2. 取消定时器任务
}
"stop" -> {
// 4.1. 停止播放
// 4.2. 取消定时器任务
}
"seek" -> {
// 5.1. 判断位置参数
// 5.2. 跳转到某个地方进行播放
}
}
}
复制代码
我这里只写了逻辑,没写代码。接下来贴一下代码一对比就很清晰了。
Android端向Flutter端发送消息
Android端向Flutter端发送消息通过channel.invokeMethod
方法实现。
整个Android插件的全部代码如下:
MainActivity.kt
class MainActivity: FlutterActivity() {
private var playerWrapper: PlayerWrapper? = null
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
// 初始化播放器
playerWrapper = PlayerWrapper(flutterEngine, this)
super.configureFlutterEngine(flutterEngine)
}
}
复制代码
PlayerWrapper.kt
class PlayerWrapper(engine: FlutterEngine, private val context: Context) {
// 播放器
private var player: MediaPlayer? = null
// 当前的播放时间的定时器
private val positionTimer: Timer = Timer()
private var timerTask: PositionTimerTask? = null
// handler
private val handler: Handler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
when (msg?.what) {
1 -> {
val obj = (msg.obj as Int) / 1000
// 1.5. 开始一个定时器获取当前的播放进度,把进度发送给Flutter
channel.invokeMethod("onPosition", mapOf("value" to obj))
}
}
}
}
// MethodChannel
private var channel: MethodChannel = MethodChannel(engine.dartExecutor.binaryMessenger, "netmusic.com/audio_player").also {
it.setMethodCallHandler { call, result ->
try {
handleMethodCall(call, result)
result.success(1)
} catch (e: Exception) {
result.success(0)
}
}
}
private fun handleMethodCall(call: MethodCall, response: MethodChannel.Result) {
when (call.method) {
"play" -> {
// 1.1. 进行参数判断
val url = call.argument<String>("url") ?: throw error("播放地址错误")
player?.stop()
player?.release()
// 1.2. 创建播放器然后进行播放
player = MediaPlayer().also { player ->
player.setOnPreparedListener {
print("setOnPreparedListener")
// 回调音乐的时长
channel.invokeMethod("onDuration", mapOf("value" to player.duration / 1000))
player.start()
}
// 1.3. 注册音乐播放完成的回调函数, 播放完成后发送给Flutter
player.setOnCompletionListener {
// 回调音乐播放完成
channel.invokeMethod("onComplete", mapOf<String, Any>())
}
player.setOnSeekCompleteListener {
player.start()
}
// 1.4. 注册音乐播放失败的回调函数,播放失败后发送给Flutter
player.setOnErrorListener { mp, what, extra ->
print("$mp $what $extra")
// 回调音乐播放失败
channel.invokeMethod("onError", mapOf("value" to "play failed"))
true
}
player.setDataSource(this.context, Uri.parse(url))
player.prepareAsync()
}
// 1.5. 开始一个定时器获取当前的播放进度,把进度发送给Flutter
timerTask?.cancel()
timerTask = PositionTimerTask()
positionTimer.schedule(timerTask, 1000, 1000)
}
"resume" -> {
// 2.1. 开始播放
player?.start()
// 2.2. 开启定时器任务
timerTask?.cancel()
timerTask = PositionTimerTask()
positionTimer.schedule(timerTask, 1000, 1000)
}
"pause" -> {
// 3.1. 暂停播放
player?.pause()
// 3.2. 开启定时器任务
timerTask?.cancel()
timerTask = PositionTimerTask()
positionTimer.schedule(timerTask, 1000, 1000)
}
"stop" -> {
// 4.1. 停止播放
player?.stop()
// 4.2. 取消定时器任务
timerTask?.cancel()
}
"seek" -> {
// 5.1. 判断位置参数
val position = call.argument<Int>("position") ?: throw error("拖动播放出现错误")
// 5.2. 跳转到某个地方进行播放
player?.seekTo(position)
}
}
}
// 定时任务调用Handler发送Message到主线程
inner class PositionTimerTask: TimerTask() {
override fun run() {
if (player?.isPlaying == true) {
val message = Message().also {
it.what = 1
it.obj = player?.currentPosition ?: 0
}
handler.sendMessage(message)
}
}
}
}
复制代码
特别说明:这里使用Handler是因为
channel.invokeMethod
需要在主线程中调用。
总结
Google的理想是其他平台为Flutter项目提供插件实现跨平台的开发。但是由于各种原因,目前的很多的应用场景可能只是将Flutter作为一个模块放到原生项目中进行混合开发。
个人感觉这个方案目前应该是一个比较稳妥的方案,接下来我将会介绍这方面的内容。