参考文章
初识JNI/NDK
NDK(Native Development Kit)是一个允许开发者使用C和C++编写Android应用程序的工具集。它提供了一系列的工具和库,可以帮助开发者将高性能的原生代码集成到Android应用中
NDK的主要目标是提供一种方式,让开发者能够在需要更高性能或更底层控制的情况下使用C和C++编写部分应用程序,而不仅仅依赖于Java。
JNI(Java Native Interface)是一种编程框架,用于在Java代码和原生代码(如C和C++)之间进行交互。通过JNI,开发者可以在Java代码中调用原生代码的函数,并且可以将Java对象传递给原生代码进行处理。
JNI 开发大致框架
android studio创建 Native项目
创建好了Natvie项目后,需要点开Tools→SDK Manager下载 NDK和CMake
静态注册Native方法
Java层注册/调用
在任意类的中,要想调用JNI .so
层的native方法需要存在如下定义
|
|
并且存在相应的Native函数声明
|
|
然后就能在该class中调用这些Native函数方法
c/c++层定义
上面导入的libc库文件learnjni,是在cpp/CMakeLists中定义的
|
|
其中的add_library用于添加生产库的源代码文件,比如这里的native-lib.cpp,然后再来看看native-lib.cpp中的c语言如何Java进行交互
初始化生成的代码如下
|
|
首先来看函数函数前一行声明
- extern “C"表示下面的函数通过c语言进行编译
- JNIEXPORT表示函数是JNI中的导出函数,能过在JAVA代码中调用
- JNICALL修饰符告诉编译器函数调用约定为JNI的规范
- jstring是该函数的返回类型,J开头代表说JAVA中的类型
再来看看函数命名,其规范如下
|
|
- 如果其中的名字包含
_
下划线,通过1_
来区分。
再来看看函数传递的参数,存在两个参数JNIEnv和jobject,其中JNIEnv定义如下
|
|
这里区分C和Cpp,先来看Cpp,其JNIEnv就是_JNIEnv的引用,_JNIEnv是一个结构体,包含了很多的函数指针,我们在c语言中处理Java中的类,对象,字段的时候需要用到这些方法。
由于c语言中环境和JAVA的环境完全不同,JAVA中存在类,类对象这些概念,因此存在如下定义
- jclass 表示对Java中的某个类引用
- jobject 表示对Java中某个类对象的引用
- jstring/jint这里表示Java中的string/int类型
假如要在native层修改Java层类中的一个普通字段String,那么其获取思路如下
普通字段属于类中的字段,首先需要获取到Java中的类,也就是jclass,存在两个方法
1 2
jclass GetObjectClass(jobject obj) jclass FindClass(const char* name)
对应的调用例子
1 2
jclass mainclass = env->GetObjectClass(thiz); jclass mainclass = env->FindClass("com/example/learnjni/MainActivity");
- 通过类的对象获取到相应类,这里的thiz就是函数定义的第二个参数jobject(类的对象),
- 直接通过包名/类名也可以获取到相应类
获取到Java中的类之后,就能过定位到类中相应字段
|
|
对应调用例子
|
|
- clazz表示字段处于到方法
- name表示字段名称
- sig表示字段的类型,也就是JNI类型
sig中最特殊的就是对象类型,其命名方式为L包名/对象名,这里以Java中的String为例子,JNI类型如下
|
|
- 获取到类中的字段后,就能修改其字段的值
|
|
要在c语言中修改的Java中的字符串,因此value需要jstring类型,通过NewStringUTF方法进行转换即可。
以上就是获取java中的类,对象,以及类中的字段,如果要修改的字段为静态的,和普通字段类似
|
|
唯一的区别在于静态static字段属于整个class类,而普通字段属于类的实例对象,因此在调用
setxxxObject的时候,static字段传入的类,普通字段传入的类的对象thiz。
在来看看c语言的情况
|
|
由于c语言没类机制,因此JNIEnv是一个指针,指向JNINativeInterface的结构题,该结构体和cpp中的_JNIEnv长得很像
其实本质上_JNIEnv就是JNINativeInterface类的封装,你会发现_JNIEnv中的函数,其实最终都调用JNINativeInterface类中的函数(Fuctions→xxx)。
而传入的参数为JNIEnv * evn,因此evn是一个二级指针,要想获取到JNINativeInterface结构体中的函数需要先解引用得到JNINativeInterface *的指针,c语言调用JNI的方法如下
|
|
理解了上面类,对象获取的方法,那么再来看看下面的代码就十分容易理解了
|
|
上面代码就是在cpp中调用了Java中的方法,这里需要注意获取方法ID的时候提供sig的目的在于区分重载函数
动态注册
除了让代码静态注册到.so库文件中,JNI还提供了动态注册native层代码方法,其实现方法就是在加载libc.so到库文件时,自动执行JNI_Onload函数,在该函数中进行代码的动态注册,具体实现思路如下
- java层和静态注册相同,直接声明该方法即可,比如这里的intNum
|
|
- 再cpp/c层不再更具JNI函数命名规则进行命名,而是直接以原名字编写
|
|
- 再定义JNI_OnLoad将intNum函数注册即可(注意JNI_OnLoad中的L是大写)
|
|
其注册的思路如下
注册需要用到evn->RegisterNatives,因此先通过vm→GetEnv获取JNIEnv,并判断是否获取成功
RegisterNatives函数需要包含JNINativeMethod结构题的数组参数,其定义如下
1 2 3 4 5
typedef struct { const char* name; //函数名称 const char* signature; // 函数签名 其参数类型和返回值类型 void* fnPtr; //函数指针,指向第一步定义的本地函数 } JNINativeMethod;
因此先注册methods数组当中RegisterNatives
然后获取到注册的Java类jclass,最后调用evn->RegisterNatives注册methods数组中的函数,第三个参数1代表methods数组中方法个数。
最后JNI_Onload函数需要返回本次注册函数的JNI版本
这里再记录一下安卓apk加载动态库.so文件的执行函数过程如下
|
|
code
最后附上代码
native-lib.cpp
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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
#include <jni.h> #include <string> extern "C" JNIEXPORT jstring JNICALL Java_com_example_learnjni_MainActivity_stringFromJNI( JNIEnv* env, jobject /* this */ ) { std::string hello = "Hello from C++"; return env->NewStringUTF(hello.c_str()); } extern "C" JNIEXPORT jstring JNICALL Java_com_example_learnjni_MainActivity_stringFromJAVA(JNIEnv * env,jobject thiz){ //modfty string from Java jclass mainclass = env->FindClass("com/example/learnjni/MainActivity"); jfieldID strId = env->GetFieldID(mainclass,"str","Ljava/lang/String;"); jstring modify = env->NewStringUTF("Hellow Java ,这是修改后的普通字段"); env->SetObjectField(thiz,strId,modify); return nullptr; } extern "C" JNIEXPORT jstring JNICALL Java_com_example_learnjni_MainActivity_staticFromC(JNIEnv * env,jobject thiz){ //modfty static string from Java // jclass mainclass = env->FindClass("com/example/learnjni/MainActivity"); jclass mainclass = env->GetObjectClass(thiz); jfieldID strId = env->GetStaticFieldID(mainclass,"static_str","Ljava/lang/String;"); jstring modify = env->NewStringUTF("Hellow Java ,这是修改后的静态字段"); env->SetStaticObjectField(mainclass,strId,modify); return nullptr; } extern "C" JNIEXPORT jstring JNICALL Java_com_example_learnjni_MainActivity_stringFromMethod(JNIEnv * env,jobject thiz){ jclass mainclass = env->GetObjectClass(thiz); jmethodID voidMethod = env->GetMethodID(mainclass,"str_method", "()V"); env->CallVoidMethod(thiz,voidMethod); return nullptr; } extern "C" JNIEXPORT jstring JNICALL Java_com_example_learnjni_MainActivity_staticFromMethod(JNIEnv * env,jobject thiz){ jclass mainclass = env->GetObjectClass(thiz); jmethodID voidMethod = env->GetStaticMethodID(mainclass,"static_method", "()V"); env->CallStaticVoidMethod(mainclass,voidMethod); return nullptr; } jint intNum(JNIEnv* env, jobject thiz, jint num) { return num + 123; } extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM * vm , void * reserved){ JNIEnv * evn = nullptr; jint result = vm->GetEnv((void **)&evn,JNI_VERSION_1_4); if(result != JNI_OK){ return -1; } jclass mainclass = evn->FindClass("com/example/learnjni/MainActivity"); JNINativeMethod methods[] = { {"intNum","(I)I",(void *)intNum}, }; evn->RegisterNatives(mainclass,methods,1); return JNI_VERSION_1_4; }
MainAvtivity
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 45 46 47 48 49 50 51 52 53 54
package com.example.learnjni; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.widget.TextView; import android.widget.Toast; import com.example.learnjni.databinding.ActivityMainBinding; public class MainActivity extends AppCompatActivity { public native int intNum(int num); public String str = "Helloc Java 我是普通字段"; public static AppCompatActivity your_this; public static String static_str = "Helloc Java 我是静态字段"; // Used to load the 'learnjni' library on application startup. static { System.loadLibrary("learnjni"); } public void str_method() { Toast.makeText(this, "普通方法", Toast.LENGTH_LONG).show(); } public static void static_method() { Toast.makeText(your_this, "静态方法", Toast.LENGTH_LONG).show(); } private ActivityMainBinding binding; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); your_this = this; binding = ActivityMainBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); // Example of a call to a native method TextView tv = binding.sampleText; tv.setText(stringFromJNI()); String ret = String.valueOf(intNum(123)); tv.setText(ret); } /** * A native method that is implemented by the 'learnjni' native library, * which is packaged with this application. */ public native String stringFromJNI(); public native String stringFromJAVA(); public native String staticFromC(); public native String stringFromMethod(); public native String staticFromMethod(); }