Android的JNI、so与JAVA
Android程序分2层,java层与native层,Java层就是Java代码编译为dex文件的。而native层则是c++代码编译为so动态库。两者使用jni(java native interface)来进行链接。相比于java,native层安全性更加高,隐蔽性更好,某种情况下效率更高。国内的加密与检测一般都放在native层中进行。
Jni分为静态和动态注册两种方式,Android Studio默认的工程是静态注册。
环境搭建:
Android Studio 3.5.3
打开SdkManager下载LLDB、NDK、CMake。
Project Structure中配置NDK。
建立一个C++Native工程
[File] -> [New] ->[New Project…],选择Native C++模版。
点[Next],其余默认就行。完成后工程结构如下:
红色框中为自动生成。
蓝色框内为手动添加,目的是熟悉CMake工具构建过程,可以不添加。
CMakeLists.txt文件是CMake构建需要的文件,文件内容解析如下:
# 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)
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
# 创建和命名库,将其设置为静态或共享,并提供到其源代码的相对路径。
# 您可以定义多个库,然后CMake为您构建它们。Gradle自动将共享库打包到你的APK中。
add_library( # Sets the name of the library.
native-lib # 设置库的名字叫native-lib
# Sets the library as a shared library.
SHARED # 设置为共享库
# Provides a relative path to your source file(s).
# 源码文件的相对路径
native-lib.cpp
one-lib.cpp # 自己添加的CPP文件
two-lib.cpp) # 自己添加的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.
# 搜索指定的预构建库并将路径存储为变量。
# 由于默认情况下CMake的搜索路径中包含系统库,因此只需指定要添加的公共NDK库的名称。
# CMake在完成构建之前验证该库是否存在。
# 将log库存储为名叫log-lib的变量
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.
# 在上方定义好的native-lib库中引入log库
target_link_libraries( # Specifies the target library.
native-lib
# Links the target library to the log library
# included in the NDK.
${log-lib})
# 参数传入的是指定包含CMakeLists文件的目录的相对路径
add_subdirectory(one) # 添加子目录one, 为手动添加
add_subdirectory(two) # 添加子目录two, 为手动添加
静态注册:
因为Android Studio默认是静态注册,所以工程自动在native-lib.cpp中生成了如下代码:
#include "jni.h"
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_org_mu_myfirsthellonative_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
工程自动在MainActivity.java中生成如下方法与C++方法连接:
public native String stringFromJNI();
C++代码有JNIEXPORT和JNICALL,这两个宏定义的作用就是说明该函数为JNI函数,在Java虚拟机加载的时候会链接对应的native方法。
JNI函数名的构成是:以Java为前缀,并且用“_”下划线将包名、类名以及native方法名连接起来就是对应的JNI函数了。
静态注册的JNI方法的弊端:
- 名字过长,要遵循规则
- 运行时去找效率不高
动态注册:
动态注册在jni层实现,JAVA层不需要关心方法的命名。JAVA在System.loadLibrary("native-lib");
中自动调用JNI_OnLoad方法,在此方法中JNI层与JAVA层的方法进行关连。
动态注册的原理:JNI 允许我们提供一个函数映射表(一个结构体数组JNINativeMethod),注册给 JVM,这样 JVM 就可以用函数映射表来调用相应的函数。
动态注册过程
1.参数映射表
在JNI层添加函数映射表,Native方法都在这里添加。这是一个结构体数组,这个工程中因为只有一个Native方法,所以数组中只有一个成员。
static JNINativeMethod gMethods_MainActivity[] = {
{"stringFromJNI", "()Ljava/lang/String;", (void*)strFromJni}
};
参数说明:
参数1:就是java代码中用native关键字声明的函数名字符串
参数2:native方法的签名(参数类型和返回值类型)
参数3:C/C++中对应函数的函数名(地址)
2.注册native方法
将native方法关联到JAVA层的类中。
// JAVA层的类
static const char* gClassName = "org/mu/myfirsthellonative/MainActivity";
static int registerNatives(JNIEnv *env, const char* classname) {
jclass clazz;
clazz = env->FindClass(classname);
if(clazz == NULL) {
return JNI_FALSE;
}
if(env->RegisterNatives(clazz, gMethods_MainActivity, sizeof(gMethods_MainActivity) / sizeof(gMethods_MainActivity[0])) < 0) {
return JNI_FALSE;
}
return JNI_TRUE;
}
3.载入JNI方法
因为 JVM 执行到 System.loadLibrary() 函数时,会立即调用 JNI_OnLoad() 方法,因此在该方法中进行各种资源的初始化操作最为恰当。
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = NULL;
// jint result = -1;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
return -1;
}
assert(env != NULL);
//为了方便管理我们可以将native方法分别注册到java的不同类中
//这里只有一个方法
registerNatives(env, gClassName); //注册MainActivity类的native方法
return JNI_VERSION_1_6;
}
4.实现的Native方法
此处定义了C++的方法,以供JAVA调用。因为JAVA层的方法是实例方法,此处参数为jobject类型。若JAVA层的方法是静态方法,此处参数为jclass类型。
jstring strFromJni(JNIEnv *env, jobject /*0bj*/) {
std::string hello = "欢迎来到native的世界";
return env->NewStringUTF(hello.c_str());
}