混合式开发记录
这次的 JNI 方案,算是一次挺有趣的探索,折腾了一番,总算把 Capacitor 和安卓底层打通了。希望这篇文章能帮到同样在踩坑的朋友们!🚀
# 🏁 Capacitor 使用 JNI 的方式集成安卓底层 SDK
在 Capacitor 这类跨平台框架中,如何与安卓底层 SDK 交互?这是我在开发过程中遇到的一个现实问题。
这次的需求是:让地面端程序能够获取遥控器的遥感按键数据。刚开始,我尝试了各种方式,比如在网上查到的 joystick 方式,兴冲冲地试了一番,结果——行不通 😑。
后来,我反编译了遥控器的 APK(Jdax (opens new window) 大法好!),发现它是通过 UART 串口 读取通道值的。好,那就走 UART!🔍
研究了一下,发现 Google 以前的一个项目android-serialport-api (opens new window)可以作为参考,于是决定用 JNI + UART 来搞定这个问题。
# 🛠️ 实施过程
JNI 层:实现 UART 读取
- 使用
termios
配置串口参数 - 通过
open()
、read()
读取数据 - 提供
JNI_OnLoad()
方法,供 Java 层调用
- 使用
Java 层:封装 JNI 调用
- 通过
System.loadLibrary()
加载 so 库 - 用
native
方法封装 C 层的 UART 读取逻辑
- 通过
Capacitor Plugin:桥接到前端
- 在 Capacitor 中创建一个自定义插件
- 通过
@PluginMethod
提供前端调用 - 通过
bridge.getActivity()
获取上下文,调用 Java 层的 JNI 方法
# 🔥敲黑板
Android开发中使用.aar
文件和.so
库,以及JNI调用的相关知识点:
.aar文件:
.aar
文件是Android库项目的二进制分发格式。- 包含编译后的代码、资源文件、AndroidManifest.xml、ProGuard规则等。
- 用于模块化代码和资源,便于复用和管理。
- 可以通过Gradle依赖管理引入项目中。
.so文件:
.so
文件是动态链接库,包含C/C++编写的原生代码。- 用于性能提升、代码保护和复用现有库。
- 需要根据不同CPU架构提供对应的
.so
文件。
JNI(Java Native Interface):
- JNI是Java代码和原生代码之间的桥梁。
- 允许Java/Kotlin代码调用C/C++代码。
- 需要在Java/Kotlin中声明native方法,并在C/C++中实现这些方法。
引入.aar文件:
- 通过Gradle的
dependencies
块引入.aar
文件。 - 可以直接指定
.aar
文件的路径或使用Maven仓库。
- 通过Gradle的
引入.so文件:
- 将
.so
文件放入src/main/jniLibs/
目录下对应的架构子目录中。 - 配置
build.gradle
文件以确保.so
文件被正确打包到APK中。
- 将
打包.aar文件:
- 通过Gradle构建系统打包
.aar
文件。 - 配置
build.gradle
文件,使用com.android.library
插件。 - 构建完成后在
build/outputs/aar/
目录下找到.aar
文件。
- 通过Gradle构建系统打包
直接使用.so文件:
- 将构建好的
.so
文件复制到新项目的jniLibs
目录下。
- 将构建好的
实现JNI调用:
- 在Java/Kotlin中声明native方法。
- 在C/C++中实现native方法,使用JNI函数。
配置CMake或ndk-build:
- 配置
CMakeLists.txt
或Android.mk
文件以编译C/C++代码。
- 配置
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.4.1)
add_library( # Sets the name of the library.
serialport
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
src/main/jni/SerialPort.c)
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log)
target_link_libraries( # Specifies the target library.
serialport
# Links the target library to the log library
# included in the NDK.
${log-lib})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
- 调用Native方法:
- 在Java/Kotlin代码中加载
.so
库并调用native方法。
- 在Java/Kotlin代码中加载
# 🚀 拓展
明白了JNI对安卓底层调用的原理,再回头看 Capacitor 插件的实现,发现其实也是类似的思路。只不过 Capacitor 插件是通过 @PluginMethod
注解,将 Java 方法暴露给前端调用,而 JNI 是通过 native
关键字,将 C 方法暴露给 Java 调用。
后续FPV插件、RTK等插件的开发,也参考了这种思路,通过 JNI 调用安卓底层 SDK,实现更多功能。
# 🔥 遇到的难题 & 解决办法
问题 🤔 | 解决方案 ✅ |
---|---|
串口权限受限,无法访问 UART | 申请 android.permission.READ/WRITE_EXTERNAL_STORAGE ,并运行 chmod 666 放开权限 |
读取的数据乱码 | 设置正确的 波特率 ,确保串口参数匹配 |
JNI 层内存泄漏 | 使用 RAII 处理资源,close() 及时释放文件描述符 |
# 🎯 理解与感悟
别盲目尝试,先搞清楚原理再动手!
- joystick 方案就是个教训,得先弄明白遥控器的工作方式。
反编译有时候是个捷径!
- Jdax 让我快速找到了正确的 UART 方案。
Capacitor + JNI 是一个很好的组合
- 既能保证跨平台开发的灵活性,又能调用安卓底层能力。
# 🎭 结尾:送自己一句话
“所有的困难,都是为了让你学会解决它。” 😆