调用图
说明:flutter通过MethonChannel方式调用android原生接口,借助android的jni开发能力调用fmod库
准备工作
- 下载FMOD库
FMOD官网 - 选择Android支持库下载,解压下载好的压缩文件, api/core/inc目录和api/core/lib目录下的文件就是之后项目中需要用到的c++的头文件和.so库,api/core/lib目录下包含了适用于不同ABI的.so文件
- Android Studio安装插件
这里有一个需要注意的点,勾选Show Package Details查看NDK下具体安装了那些版本的NDK,上图中是我所下载并安装的NDK的版本,我曾经尝试将所有版本的NDK都勾选,然后下载并安装所有版本的NDK,再运行程序,会报错,错误的内容是FMOD头文件中定义的一些属性跟全局定义的属性冲突了,我猜测是因为FMOD头文件中的一些属性,跟某个版本的NDK中的属性有冲突,所以如果出现定义冲突的问题,可以尝试更换NDK的版本来解决
配置SO库
- 在android/app目录下创建CMakeList.txt文件(该文件的配置说明将在之后说明)
- 在android/app目录下创建libs目录,将api/core/lib目录下的fmod.jar文件拷贝到libs目录下
- 在android/app/src/main目录下分别创建cpp、jniLibs目录。注意:jniLibs目录名称一定不能写错,jniLIbs也必须创建在android/app/src/main目录下,在打包APK的时候,默认会将jniLibs目录下的so文件打包到APK中,如果该文件夹名称错误,就会导致APP运行的时候找不到动态运行库(.so文件)
- 在cpp目录下创建include目录,将fmod的api/core/inc目录下的文件拷贝到include目录中
- 在cpp目录下创建native-lib.cpp文件(之后会详细说明该文件的用途)
到这里,所有需要创建目录和文件,以及需要拷贝到项目中的文件都已经准备好,下面开始进行配置
- 配置build.gradle(Module:android.app)文件
- 分别添加三处externalNativeBuild配置,在defaultConfig外面的是用来指定CMakeLists文件的路径,defaultConfig内部的是在执行CMake时的命令行参数,buildTypes内部的参数用来指定是用libfmod.so还是用libfmodL.so,fmod提供了调试环境和正式环境下对应的不同so文件
- dependencies增加依赖
- CMakeList.txt配置说明
cmake_minimum_required(VERSION 3.10.2)
# Declares and names the project.
project("audioplayerjni")
# 定义LIB_FMOD变量,值为FMOD的so文件在项目的绝对路径
# CMAKE_SOURCE_DIR内置变量,值为CMakeList.tx的路径
# ANDROID_ABI内置,当前android设备的ABI类型
# 在build.gradle的buildTypes中传递的参数
set( LIB_FMOD ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libfmod${FMOD_LIB_SUFFIX}.so )
# 定义CPP_DIR,值为cpp目录的绝对路径
set( CPP_DIR ${CMAKE_SOURCE_DIR}/src/main/cpp )
# 指定头文件查找目录
include_directories( ${CMAKE_SOURCE_DIR}/src/main/cpp/include )
# 添加共享类型的链接库,名称为fmod,类型为SHARED,IMPORTED告诉cmake,该库是导入的
add_library( fmod SHARED IMPORTED )
# 设置fmod链接库的属性
# IMPORTED_LOCATION:本地so文件路径
# INTERFACE_INCLUDE_DIRECTORIES:头文件路径
set_target_properties(
fmod PROPERTIES
IMPORTED_LOCATION ${LIB_FMOD}
INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_SOURCE_DIR}/src/main/cpp/include
)
# 添加一个共享类型的链接库,与IMPORTED不同的是,该库是项目目录中的一个cpp文件
add_library(
native-lib
SHARED
${CPP_DIR}/native-lib.cpp
)
# 指定需要链接的库
target_link_libraries(
native-lib
fmod
)
使用
- 在MyApplication中加载c++库
class MyApplication : FlutterApplication() {
init {
//native-lib是在CMakeLists.txt中定义的本地库的名称
System.loadLibrary("native-lib")
}
override fun onCreate() {
...
}
}
- 创建FmodAudioPlayer类
class FmodAudioPlayer {
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
* 定义方法,具体的实现在native-lib.cpp中
*/
external fun play(path: String, type: Int, uuid: String)
external fun stop(uuid: String)
external fun isPlaying(uuid: String):Boolean
}
- native-lib.cpp文件实现方法
/**
* Java_com_xiaomajiaoyu_baby_FmodAudioPlayer_play方法名说明
* Java固定开头
* com.xiaomajiaoyu.baby包名
* FmodAudioPlayer类名
* play方法名
* 都以_连接
*/
extern "C"
JNIEXPORT void JNICALL
Java_com_xiaomajiaoyu_baby_FmodAudioPlayer_play(JNIEnv *env, jobject thiz, jstring path_jstr,
jint type, jstring uuid_jstr) {
//调用fmod的播放音频的API
}
extern "C"
JNIEXPORT void JNICALL
Java_com_xiaomajiaoyu_baby_FmodAudioPlayer_stop(JNIEnv *env, jobject thiz, jstring uuid_jstr{
//调用fmod的停止播放的API
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_xiaomajiaoyu_baby_FmodAudioPlayer_isPlaying(JNIEnv *env, jobject thiz, jstring uuid_jstr) {
//调用fmod获取播放状态的API
}
至此,就已经完成了flutter项目集成fmod的库的工作,剩下的只需要在flutter端通过MethodChannel的通信方式,调用android远程的接口,再用远程接口调用FmodAudioPlayer中的native方法,就可完成fmod的使用。需要注意的一点,FmodAudioPlayer的接口调用都是在主线程,也就是UI线程上调用,为了不影响UI线程,需要将FmodAudioPlayer的接口调用放到子线程中,这样一来,如果native方法都是在不同线程中执行,就存在线程安全问题,这时候就需要用到IntentService,将FmodAudioPlayer接口的调用都放到IntentService中执行,IntentService可以即实现在子线程执行方法,同时也能保证方法的执行顺序。