pollux's Dairy

NDK开发学习记录

字数统计: 1.7k阅读时长: 10 min
2019/12/09 Share

记录式学习,内容可能不全,用到知识就会记录下来,方便之后的查阅和复习。

JNIEnv的指针会被JNI传入到本地方法的实现函数中来对Java端的代码进行操作。

子进程获得虚拟机接口

1、使用ptrace创建子进程

1
2
3
pthread_t thread; 
void* run( void* );
pthread_create( &thread, NULL, run, NULL);

2、注册虚拟机接口

因为子线程没有注册到虚拟机,所以没有JNIEnv接口,需要自己生成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void* run( void* ) 
{
int status;
JNIEnv *env;
bool isAttached = false;
Thread_sleep(1);
status = gJavaVM->GetEnv((void **) &env, JNI_VERSION_1_4);
if(status < 0) {
LOGD("failed to get JNI environment");
status = gJavaVM->AttachCurrentThread(&env, NULL);
if(status < 0) {
LOGE("failed to attach");
return NULL;
}
isAttached = true;
}
callBack(env);
if(isAttached)
gJavaVM->DetachCurrentThread();
return NULL;
}

一、Native函数的注册

注册native函数的具体方法不同,会导致系统在运行时采用不同的方式来寻找这些native方法。

1、Native函数静态注册

静态注册就是根据函数名来遍历Java和JNI函数之间的关联,而且要求JNI层函数的名字必须遵循特定的格式。

1
2
3
4
在java层的native方法声明:
public static native int modifyBytecode();
javah生成头文件:
javah cn.pollux.modifydalvikbytecode.MainActivity

在头文件内会有native层的方法声明

1
2
JNIEXPORT jint JNICALL Java_cn_pollux_modifydalvikbytecode_MainActivity_modifyBytecode
(JNIEnv *, jclass);

JNIEXPORT:说明该函数时导出函数,在java层可以直接调用
JNICALL

总结静态注册的流程:
1、编写包含native方法的Java类
2、使用Javah命令生成.h头文件
3、编写代码实现头文件中的方法

2、Native函数动态注册

动态注册,即通过RegisterNatives方法把C/C++中的方法映射到Java中的native方法声明。

当用System.loadLibarary()方法加载so库的时候,虚拟机会首先调用JNI_OnLoad函数,这个函数的作用是告诉虚拟机此C库使用的是哪一个JNI版本,默认最初的JNI 1.1版本。所以可以在JNI_OnLoad函数中,注册JNI函数。

在Java层声明一个native函数

1
private native void testJava(String str);

在native层的JNI_OnLoad函数中将Java层的testJava函数与Native层的testNative函数绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
jint JNI_OnLoad(JavaVM* vm, void* reserved){
JNIEnv* env = NULL;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
LOGE("This jni version is not supported");
return JNI_VERSION_1_6;
}
if (!registerNativeMethod(env)) {
LOGE("Unable to register native method: maybe the java has not loaded");
// I don't want the process crash, just continue
env->ExceptionClear();
}
return JNI_VERSION_1_6;
}

通过GetEnv函数获取JNIEnv结构体指针,JNIEnv结构体指向了一个函数表,该函数表指向了对应的JNI函数,我们通过这些JNI函数实现JNI编程,比如在jni.h中

1
2
3
4
5
6
typedef const struct JNINativeInterface* JNIEnv;

struct JNINativeInterface {
......
jclass (*FindClass)(JNIEnv*, const char*);
......

JNIEnv是指向JNINativeInterface结构体的指针,env是指向JNIEnv变量的指针,所以env是指向JNINativeInterface结构体指针的指针。

在看下registerNativeMethod函数内容

1
2
3
4
5
6
7
8
9
10
bool registerNativeMethod(JNIEnv *env){
auto clazz = env->FindClass("com/pollux/jnil/JNIDemo");
JNINativeMethod natives[] = {
{"testJava", "(Ljava/lang/String;)V", (void*)testNative}
};
if (env->RegisterNatives(clazz, natives,
sizeof(natives)/sizeof(JNINativeMethod))!= JNI_OK) {
env->ExceptionClear();
}
}

被动态注册的函数

1
2
3
static void testNative(JNIEnv *env, jobject, jlong handle) {
LOGI("JNI", "dynamic register");
}

RegisterNatives第二个参数为JNINativeMethod类型的数组,JNI允许通过一个函数映射表,注册给JVM,JVM通过函数映射表来查找调用相应的函数。这个函数映射表就是RegisterNatives数组,定义如下:

1
2
3
4
5
typedef struct { 
const char* name; //Java层函数名
const char* signature; //Java层函数签名
void* fnPtr; //Native层函数名(函数指针)
} JNINativeMethod;

因为Java支持重载,所以需要函数签名指定参数与返回值。

总结:

1、在Java层声明一个函数
2、在Native层的JNI_Onload函数中注册函数

二、 Java层调用Native层方法

三、Native层调用Java层代码

1、获取jclass

jclass是JNI中设定的对应于java层Class的变量,无论jclass、jmethod、jobject,出于安全的考虑,他们都是间接引用,这些变量定义在jni.h文件中

JNIEnv有三种方法获得jclass

1
2
3
jclass FindClass(const char* name)
jclass GetObjectClass(jobject obj)
jclass GetSuperclass(jclass clazz)

2、调用Java类的成员方法

关键API:

1
2
3
4
5
6
7
8
jclass FindClass(const char* name)
jobject NewObject(jclass clazz, jmethodID methodID, ...)
//获取方法ID
jmethodID GetStaticMethodID(jclass clazz, const char* name, const char* sig)
jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
//调用方法
NativeType CallStatic<type>Method(JNIEnv *env, jobject obj,jmethodID methodID, ...);
NativeType Call<type>Method(JNIEnv *env, jobject obj,jmethodID methodID, ...);

调用类的静态方法

1
2
3
jclass jclazz = env->FindClass("com/example/myapplication/JNITest");
jmethodID statictest_mid= env->GetStaticMethodID(jclazz,"static_test","()V");
env->CallStaticVoidMethod(jclazz,statictest_mid);

调用类的实例方法

1
2
3
4
5
6
7
//实例化对象
jclass jclazz = env->FindClass("com/example/myapplication/JNITest");
jmethodID construction_mid =env->GetMethodID(jclazz,"<init>", "()V");
jobject JNITest_obj = env->NewObject(jclazz,construction_mid);
//调用方法
jmethodID test_mid=env->GetMethodID(jclazz,"test", "(ILjava/lang/String;)Ljava/lang/String;");
jstring b =(jstring)env->CallObjectMethod(JNITest_obj,test_mid,4,env->NewStringUTF("bbbb"));

3、访问和设置Java类的成员变量

关键API:

1
2
3
4
jfieldID GetStaticFieldID(jclass clazz, const char* name, const char* sig)
void SetStaticIntField(jclass clazz, jfieldID fieldID, jint value)
jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
void SetIntField(jobject obj, jfieldID fieldID, jint value)

访问和设置类的静态变量

1
2
3
jfieldID fids = env->GetStaticFieldID(jclazz,"staticNum","I");
env->SetStaticIntField(jclazz,fids,34);
jint n2= env->GetStaticIntField(jclazz,fids);

访问和设置类的实例变量

1
2
3
jfieldID fid = env->GetFieldID(jclazz,"age","I");
env->SetIntField(JNITest_obj,fid,33);
jint n1 = env->GetIntField(JNITest_obj,fid);

CMakeLists.txt

1
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# 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.

add_library( # Sets the name of the library.
native-lib

# Sets the library as a shared library.
SHARED

# Provides a relative path to your source file(s).
native-lib.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.

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.
native-lib

# Links the target library to the log library
# included in the NDK.
${log-lib})

https://www.jianshu.com/p/7b8de8af39c3?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

CATALOG
  1. 1. 子进程获得虚拟机接口
  • 一、Native函数的注册
    1. 1. 1、Native函数静态注册
    2. 2. 2、Native函数动态注册
  • 二、 Java层调用Native层方法
  • 三、Native层调用Java层代码
    1. 1. 1、获取jclass
    2. 2. 2、调用Java类的成员方法
    3. 3. 3、访问和设置Java类的成员变量
  • CMakeLists.txt