前言:想借助JNI和NDK的知识开发Android的串口通信,但是之前对这一部分没有了解过,以至于第一步so文件的生成和使用,就花费了两天,这里记录下配置过程。(网上有些资料也不完全对,走了很多弯路。) 借鉴博客如下:

so文件的生成及其使用

  • Android Studio软件的配置说明
  • NDK/JNI介绍
  • 什么是NDK?
  • 为什么使用NDK?
  • 什么是JNI?
  • 为什么使用JNI?
  • 安卓中的so文件是什么?
  • NDK的安装及其配置
  • NDK的安装
  • NDK的配置
  • so库开发
  • 新建本地方法
  • 编译该类得到对应的.h文件
  • 创建jni文件夹,添加.c文件
  • 编写Android.mk文件
  • 使用so文件
  • 相关配置
  • MainActivity中引用libdemo.so和使用本地函数
  • 注意

Android Studio软件的配置说明

  • Android Studio Arctic Fox | 2020.3.1 Patch 2
  • distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
  • classpath "com.android.tools.build:gradle:7.0.2"
  • 以下工程就是正常新建工程得到的
  • 之所以提到版本号,是因为不同版本号操作步骤略有不同,这里只是给大家做参考。

NDK/JNI介绍

什么是NDK?

  • NDK全称是Native Development Kit,NDK提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk。NDK继承了交叉编译器(交叉编译器需要UNIX或LINUX系统环境),提供了响应的mk文件隔离CPU、平台、ABI等差异,开发人员只需要简单地修改mk文件(指出”哪些文件需要编译“、“编译特性要求等”),就可以创建出so。

为什么使用NDK?

  • 代码的保护,由于apk的java代码层很容易被反编译,而C/C++库反汇难度较大。
  • 可以方便地使用现存的开源库。大部分现存的开源库都是用C/C++代码编写的。
  • 提高程序的执行效率,将要求高性能的应用逻辑使用C开发,从而提高程序的执行效率。
  • 便于移植,用C/C++写的库可以方便地在其他的嵌入式平台上再次使用。

什么是JNI?

  • JNI的全称是Java Native Interface ,它提供了若干的API实现了Java和其他语言的通信(主要是C/C++)。

为什么使用JNI?

  • JNI的目的是使java方法能够调用c实现的 一些函数。

安卓中的so文件是什么?

  • android中用到的so文件是一个c++的函数库。在android的JNI中,要先将相应的C语言打包成so库,然后导入到lib文件夹中供java调用。

NDK的安装及其配置

NDK的安装

  • 可以直接从Android Studio软件中的SDK Manager中:Settings — Appearance & Behavior — Android SDK
  • 选中SDK Tools
  • android so 库压缩 android so文件使用_ndk

  • 下载NDK和CMake,只需要勾选前面,点击OK即可下载。
  • 这里注意可以点击右下角的 Show Package Details,可以看到安装包的版本。
  • android so 库压缩 android so文件使用_android so 库压缩_02

  • 开始NDK和CMake我都选择了最新版本:NDK – 24.0.8215888 , CMake – 3.18.1
  • android so 库压缩 android so文件使用_Android_03

  • 但是出现问题NDK does not contain any platforms,后来查阅资料知道,是因为NDK版本过高,不适合当前版本的Android Studio,因此又下载了较低版本的NDK和CMake,具体版本如上图所示。
  • 之前如果设置过SDK的下载路径,则NDK的下载路径会默认下载到SDK的下载路径里。等待10-15分钟左右,时间稍微有些长。
  • 但这里要注意下,Android Studio中的路径不要设置带空格和带中文的 ,我就是因为之前SDK的下载路径是带空格的,之前一直没出现问题,结果NDK的默认下载路径还是这个,但因为带空格,所以后面测试NDK是否配置成功时,一直不反应,无奈只能复制NDK和CMake的资料到另外新建的文件夹中(路径名不带空格)。
  • 原本下载路径是:D:\JAVA\tools\Android Studio\Android_Sdk
  • 将NDK和CMake单独复制出来的路径是:D:\JAVA\tools\AndroidSdk(其实由于我也不知道NDK和CMake的资料是哪几部分,索性把Android_Sdk中的内容全部复制到AndroidSdk文件夹中)
  • 大家如果路径不带空格和中文,就不用看我上面的废话,我只是做个笔记。

NDK的配置

  • 第一步:环境变量的配置
  • 电脑右键属性 – 高级系统设置 – 环境变量 – 系统变量(非用户变量)
  • 第一种方式:
  • 首先新建NDK_HOME 变量名,变量值是D:\JAVA\tools\AndroidSdk\ndk-bundle(不带空格) ,因为 ndk-bundle 文件夹中有 ndk-build 命令,因此需要设置。
  • 编辑Path 环境变量,添加进 ndk-bundle 的路径,%NDK_HOME%
  • 第二种方式:就是直接把 ndk-bundle 的路径添加进Path环境变量中,不需要在建立中间变量NDK_HOME。
  • 但我还是建议用第一种,因为NDK_HOME这个变量我们在使用终端命令时,是用得到的,即和JAVA_HOEM变量一样。
  • 第二步:Android Studio软件中配置 ndk-bundle 的路径,这里我写进的是带空格的路径,也不影响,但上面提到的Path环境变量里需要是不带空格的路径。
  • 我们一般是在SDK Location里直接选择 ndk-bundle 的路径即可,但我们这里显示灰色,无法选择,查阅资料,直接在 local.properties文件 中写进路径:
  • 第三步:验证NDK配置是否成功:
  • 打开Android Studio的终端窗口:输入 ndk-build 命令行
  • 出现以下信息便证明配置成功

so库开发

新建本地方法

  • 在MainActivity.java中建立了一个方法public native String getStrFromJNI();
  • 可以看到这个方法的声明中有 native 关键字,这个关键字表示这个方法是本地方法,也就是说这个方法getStrFromJNI()是通过本地代码(C/C++)实现的,在java代码中仅仅是声明。

编译该类得到对应的.h文件

  • 进入终端命令窗口
  • 切换到java目录下:cd app\src\main\java
  • 再输入: javah -jni 包名.类名
  • 这里输入:javah -jni com.example.testndk2.MainActivity
  • 编译成功后,等待会可以看到编译出的.h文件。
  • 打开.h文件,其中最重要的代码是:
JNIEXPORT jstring JNICALL Java_com_example_testndk2_MainActivity_getStrFromJNI
  (JNIEnv *, jobject);
  • 其中遵循 Java_包名_类名_本地方法名 来定义。

创建jni文件夹,添加.c文件

  • 右键Moudle,菜单中选择 New -> Folder -> JNI Folder,弹出的对话框,直接点击完成。
  • 将前面生成 .h文件 移到 jni 文件夹中
  • 再右键jni文件夹,弹出菜单选择New -> C/C++ Source File,创建demo.c文件。
  • 在demo.c文件中编写测试代码:
#include <string.h>
#include <jni.h>

jstring
Java_com_example_testndk2_MainActivity_getStrFromJNI(JNIEnv *env,
                                                          jobject thiz) {
    return  (*env)->NewStringUTF(env, "I`m Str !");
}
  • 注意demo.c代码中的格式和.h文件中的相同:Java_包名_类名_本地方法名

编写Android.mk文件

  • 右键 jni文件夹,弹出菜单 New -> File
  • 在 Android.mk 文件中编写代码
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE      :=   demo
LOCAL_SRC_FILES   :=   demo.c

include $(BUILD_SHARED_LIBRARY)
  • LOCAL_MODULE:=demo 是要生成的so库的名称,但实际为libdemo.so
  • LOCAL_SRC_FILES:=demo.c 是要使用的文件,刚才编写的demo.c文件
  • 注意这两句后面不要跟着注释,否则会报错Android NDK: LOCAL_MODULE definition in D:/JAVA/tools/AndroidSerialPort/TestNdk2/app/src/main/jni/Android.mk must not contain space
  • 接着在终端命令窗口中,在java目录下,输入指令:ndk-build
  • 接着窗口会打印出一系列信息:说明编译成功,生成.so文件。
  • 等待会或者刷新工程,便看到工程目录中多出 libs 和 obj 两个文件夹。
  • libs文件夹中有四个子文件夹,每一个文件夹都有 libdemo.so文件 ,便是生成成功。

使用so文件

相关配置

  • 在app的 build.gradle 的 android节点 下设置:
  • 注意路径是libs文件夹的相对路径

MainActivity中引用libdemo.so和使用本地函数

android so 库压缩 android so文件使用_android so 库压缩_04

注意

如果改变demo.c文件中代码内容,都需要重新ndk-build,重新编译生成.so文件。