阅读 896

Flutter插件开发---Android篇

上一篇文章我们一起来实现了iOS平台的插件开发,本节我们来看看Android平台的插件是如何实现的。

本文只会涉及到Android端的代码了,因为Flutter端代码是通用的,不需要修改了。

网络设置相关的修改

GoogleAndroid 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作为一个模块放到原生项目中进行混合开发。

个人感觉这个方案目前应该是一个比较稳妥的方案,接下来我将会介绍这方面的内容。

文章分类
Android
文章标签