注意,例子使用的各版本信息如下:
AS 3.3
Gradle Tool:3.3.0
Gradle Version:4.10.1
最近在阅读Android 源码的过程中发现大量的Native方法,在没有系统掌握JNI与NDK知识的情况下寸步难行,所以有必要系统地了解相关知识。
在学习之前,我常常有有如下几个疑问:
NDK的开发流程?
C/C++ 与 Java如何进行通信的?
如何阅读Android Native 源码?
希望经过一系列的学习和总结,以上的问题能得到解决。 今天首先解决“如何进行NDK开发?”
NDK:
作用:
在NDK的开发过程中,有两种形式——ndk-build和CMake,其实两种方式最终的目的都是一样,将C 或 C++(“原生代码”)嵌入到 Android 应用。只是CMake使用起来更加方便,同时Android官方也推荐在Android Studio 2.2及以上使用CMake。工具的本质就是方便人们进行日常活动,对比一下两者流程,方便开发过程进行技术选型。
下载和配置NDK:
ndk-build只是NDK工具包里面的一个脚本工具,帮忙调用NDK里面正确的构建脚本,路径在sdk/ndk-bundle目录下。
需要在Studio中下载与配置NDK:Settings–Appearance & Behavior–System Settings–Android SDK–SDK Tools。
Java中创建本地方法
在Java类HelloWorldJNI中创建本地方法:
public class HelloWorldJNI {
// 加载Native动态库(so库),动态库的名称后面在mk文件中会使用到
static {
System.loadLibrary("helloworld_jni");
}
// 定义Native方法
public native String getString();
public native String setString(String text);
}
创建JNI文件目录并链接项目
在src\main 目录下创建 jni文件夹,JNI相关的编码都在讲在该文件下进行,有两种创建方式:
a. Android Studio UI下右击main文件下创建:
b. 手动创建jni文件:
在main文件夹手动创建jni文件夹,然后在项目的gradle中配置
android {
......
externalNativeBuild {
ndkBuild {
path 'src/main/jni/Android.mk'
}
}
}
生成本地方法头文件
在命令终端Terminal中进入到项目的java文件目录中,如本例中的 DemoProjects\JNITest\src\main\java,执行命令:
javah -jni -encoding UTF-8 -d <你的工程目录>\DemoProjects\JNITest\src\main\jni com.wuzl.jnitest.HelloWorldJNI
-jni:表示生成jni格式的头文件;
-encoding:编码格式;
-d:头文件的输出路径;
javah 命令导出HelloWorldJNI的头文件(com_wuzl_jnitest_HelloWorldJNI.h)至jni文件目录中,重点来了!
头文件的命名规则为:
包名(.变成下划线)
其函数命名遵循如下规则:
Java_包名_类名_方法名
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class com_wuzl_jnitest_HelloWorldJNI */
#ifndef _Included_com_wuzl_jnitest_HelloWorldJNI
#define _Included_com_wuzl_jnitest_HelloWorldJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_wuzl_jnitest_HelloWorldJNI
* Method: getString
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_wuzl_jnitest_HelloWorldJNI_getString
(JNIEnv *, jobject);
/*
* Class: com_wuzl_jnitest_HelloWorldJNI
* Method: setString
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_wuzl_jnitest_HelloWorldJNI_setString
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
创建C/C++ 代码文件:
在jni目录下创建helloworld.cpp,然后导入上面生成的头文件,并实现头文件声明的两个本地方法
# include
# include
#include
# include "com_wuzl_jnitest_HelloWorldJNI.h"
# ifdef _cplusplus
extern "C"
{
# endif
JNIEXPORT jstring JNICALL Java_com_wuzl_jnitest_HelloWorldJNI_getString(JNIEnv *env, jobject obj){
return env -> NewStringUTF("Get Hello world JNI!");
}
JNIEXPORT jstring JNICALL Java_com_wuzl_jnitest_HelloWorldJNI_setString(JNIEnv *env, jobject obj, jstring str){
char* jnistr = (char *) env->GetStringUTFChars(str, NULL);
strcat(jnistr,": I am JNI");
return env -> NewStringUTF(jnistr);
}
# ifdef _cplusplus
}
# endif
创建配置文件:
在jni文件目录下配置Android.mk和Application.mk配置文件
Android.mk
# 当前模块路径,即Android.mk所在的文件目录
LOCAL_PATH := $(call my-dir)
# 开始清除LOCAL_XXX变量
include $(CLEAR_VARS)
# 动态库模块名称,Java代码中中加载动态库中使用System.loadLibrary("helloworld_jni");
LOCAL_MODULE := helloworld_jni
# 编译的C/C++源文件,可多个,以空格隔开
LOCAL_SRC_FILES := helloworld.cpp
# 开始动态编译生成动态库
include $(BUILD_SHARED_LIBRARY)
Application.mk
# 需要编译的ABI
APP_ABI :=armeabi-v7a x86 x86_64
配置文件的详细选项可查看Google官网:
Android.mk, Application.mk
可利用ndk-build命令工具生成so库:
在命令终端Terminal中进入到jni目录的父目录,直接执行ndk-build命令(需要将ndk目录添加到环境变量中)将会在当前目录下生成libs和obj文件目录,其中libs就是我们想要so库
小技巧:
ndk-build NDK开发流程讲得差不多了,但是有个小技巧可以分享给大家,在上述流程中,每次都需要输入命令来生成头文件和so库,而且有时候忘记命令了,其实AS提供了相关技巧来优化开发流程:
Settings–External Tools–“+”
其中 Program:javah所在的路径;
Arguments: 命令参数,此处为-jni -encoding UTF-8 -d M o d u l e F i l e D i r ModuleFileDir ModuleFileDir\src\main\jni F i l e C l a s s FileClass FileClass;
Wroking directory: 工作目录,此处为 M o d u l e F i l e D i r ModuleFileDir ModuleFileDir\src\main\java;
使用(右击定义Native方法的Java文件,选择External Tools-javah-jni,然后在jni文件下直接生成对应的头文件):
CMake并不是什么新鲜的内容,脱离Android来说,CMake是一个跨平台的安装编译工具,通过简单的脚本语句描述不同平台的编译过程,已经运用在UNIX和WindowW系统中。
Google在AS2.2版本上开始支持CMake,并提倡在AS 2.2及以上版本使用CMake进行JNI开发,在上述使用ndk-build开发过程中发现,需要的步骤特别多,比如生成头文件、配置Android.mk, Application.mk文件,并需要了解其中mk各种繁琐的配置项,而使用CMake进行开发可以省略这些步骤。
官网已经给出非常详细的使用过程:向您的项目添加 C 和 C++ 代码
如果是新建项目使用CMake进行JNI开发,非常简单,在创建新项目的时向导面板中选中 Include C++ Support 复选框即可,AS会创建一个简单的DEMO供使用参考。
但是我们大多数都是需要在已存在的项目中进行JNI的开发,下面将以此作为讲解。
创建CMakeLists.txt配置文件
在项目根目录下创建CMakeLists.txt配置文件
# CMake 最小工具版本要求,可通过SDK Manager 查看CMake的本地版本
# 该版本对Gradle有要求,比如3.10 版本需要Gragle 4.10+version才行
cmake_minimum_required(VERSION 3.6)
add_library( # Sets the name of the library.
# 动态库的名称,与ndk-build中Android.mk 的模块名一样
# 最终编译出来的动态库名称为:lib+library name
helloworld
# Sets the library as a shared library.
# 编译库的类型,有静态库、动态库、和模块库
SHARED
# Provides a relative path to your source file(s).
# C/C++ 源文件
src/main/cpp/helloworld.cpp)
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
# 找到依赖库,比如下面将系统log-lib链接到helloworld动态库
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 )
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
# 配置库的链接
target_link_libraries( # Specifies the target library.
helloworld
# Links the target library to the log library
# included in the NDK.
${log-lib} )
这个是最最基本的CMake的配置,CMake还可以做很多强大的配置选项,详情可查看CMake的官方文档(https://cmake.org/documentation/)
创建C/C++源文件
在main目录(当然目录可以自己选择)下创建cpp文件夹,然后在该文件夹下创建helloworld.cpp源文件,与ndk-build一致:
# include
# include
# include
extern "C" {
JNIEXPORT jstring JNICALL Java_com_wuzl_cmake_HelloWorldJNI_getString(JNIEnv *env, jobject obj){
return env -> NewStringUTF("Get Hello world JNI");
}
JNIEXPORT jstring JNICALL Java_com_wuzl_cmake_HelloWorldJNI_setString(JNIEnv *env, jobject obj, jstring str){
char* jnistr = (char *) env->GetStringUTFChars(str, NULL);
strcat(jnistr,": I am JNI by CMake building");
return env -> NewStringUTF(jnistr);
}
}
链接项目
右击项目,选择Lick C++ Project with Gradle,然后在弹出来的选项里选择CMake并选择上面配置好的CMakeLists.txt
NDK开发的两种流程:
两者只是构建的脚本和命令不同而已,但是个人感觉CMake更加方便和简洁
Google推荐使用CMake进行JNI的开发,而且CMake的配置更加强大,其官方文档可查阅使用的细节(https://cmake.org/documentation/)。
最后上DEMO:https://github.com/PlepleLiang/JNIDemo
参考:
https://blog.csdn.net/quwei3930921/article/details/78820991
https://juejin.im/post/5a67dcdb518825732c53b338
https://developer.android.com/studio/projects/add-native-code?hl=zh-cn