JNI 基础教学
JNI是一种Java开发中使用到的技术手段,而NDK则是谷歌为Android提供的一种使用JNI的开发工具套件.
JNI的代码是个.c/.cpp文件,可以写c/cpp的函数,也可能用jni的函数,然后二者产生联系,jni又可以和java沟通
实际关系:c/c++ <--->jni<--->java
域描述符
域描述符是JNI中对Java数据类型的一种表示方法(就是对Java类中的变量,在JNI世界的定义),
即在JVM虚拟机中,存储数据类型的名称时,是使用指定的描述符来存储,而不是我们习惯的 int,float 等
/*常用引用数据类型对比
* All object jobject
* java.lang.Class jclass
* java.lang.String string
* int[] jintArray
* Object[] jobjectArray
*
* JNI 函数签名信息
* java支持函数重载,因此仅仅根据函数名是没法找到对应的JNI函数。
* 为了解决这个问题,JNI将参数类型和返回值类型作为函数的签名信息
*
* JNI规范定义的函数签名信息格式: (参数1类型字符…)返回值类型字符
*
* 例子:Java函数 对应的 函数签名
* String sayHello2(); "()Ljava/lang/String;"
* long fun(int i,Class c) "(ILjava/lang/Class;)J"
* void fun(byte[] bytes) "([B)V"
*
* JNI常用的数据类型及对应字符:
*
* Java类型 对应字符
* void V
* boolean Z
* int I
* long J
* double D
* float F
* byte B
* char C
* short S
* int[] [i
* String Ljava/lang/String;(后面一定要加;)
* Object[] [Ljava/lang/object;
*
多维数组则是 n个[ +该元素域描述符 , N代表的是几维数组。例如:
Java类型: int[][]
JNI域描述符:[[I
Java Method Method Description
String fun() ()Ljava/lang/String;
int fun(int i, Object object) (ILjava/lang/Object;)I
void fun(byte[ ] bytes) ([B)V
int fun(byte data1, byte data2) (BB)I
void fun() ()V
*
* JNIEnv 是个啥? 它是一个线程相关的结构体, 该结构体代表了 Java 在本线程的运行环境。通过JNIEnv可以调用到一系列JNI系统函数,
* 用于访问Java成员变量何成员方法,调用Java构造方法创建Java对象等
*
* JNIEnv线程相关性: 每个线程中都有一个 JNIEnv 指针。JNIEnv只在其所在线程有效, 它不能在线程之间进行传递
*
* */
void的处理比较特殊,如果返回值为void,那么方法描述符中必须使用V表示,当void作为参数的时候,忽略
static JNINativeMethod gMethods[] = {
{"getJavaBeanFromNative", "()Lcom/xxx/object2struct/JavaBean;",(void*)Java_com_xxx_object2struct_JniTransfer_getJavaBeanFromNative },
{"transferJavaBeanToNative", "(Lcom/xxx/object2struct/JavaBean;)V",(void*)Java_com_xxx_object2struct_JniTransfer_transferJavaBeanToNative },
};
jobject/jclass
jobject与jclass通常作为JNI函数的第二个参数,有何不同?
实例引用和java.lang.Object类或它的子类的实例对应。
类引用与java.lang.Class实例对应,它代表着类的类型。
//下述函数都是类引用有关系,所以参数都是jclass
jfieldID (*GetFieldID)(JNIEnv*, jclass, const char*, const char*);
jobject (*CallStaticObjectMethod)(JNIEnv*, jclass, jmethodID, ...);
jobject (*GetStaticObjectField)(JNIEnv*, jclass, jfieldID);
//下述函数都是和实例引用有关系,所以参数都是jobject
jobject (*GetObjectField)(JNIEnv*, jobject, jfieldID);
jobject (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...);
object (*GetObjectField)(JNIEnv*, jobject, jfieldID);
当Java中定义的native方法为静态方法时,则第二个参数为jclass,jclass代表native方法所属类的class本身
当Java中定义的native方法为非静态方法时,则第二个参数为jobject,jobject代表native方法所属类的实例对象
动态注册/静态注册
静态注册:优点非常简单,用Android studio 新建的ndk-jni demo 就是默认用的这种方式.
但是有一个最致命的缺点程序运行效率低,耗时.但是使用jni就是为了提供效率,所以不推荐.
动态注册:Android源码使用.
原理:直接通过 JNIEnv中提供的函数RegisterNatives方法手动完成 Java中Native 方法和JNI中相关函数的的绑定,
虚拟机可以通过这个函数映射表直接找到相应的方法
frameworks\base\services\core\jni\com_android_server_AlarmManagerService.cpp
static jint android_server_AlarmManagerService_waitForAlarm(JNIEnv*, jobject, jlong nativeData)
{
AlarmImpl *impl = reinterpret_cast<AlarmImpl *>(nativeData);
int result = 0;
do
{
result = impl->waitForAlarm();
} while (result < 0 && errno == EINTR);
if (result < 0)
{
ALOGE("Unable to wait on alarm: %s\n", strerror(errno));
return 0;
}
return result;
}
static const JNINativeMethod sMethods[] = {
/* name, signature, funcPtr */
...
{"waitForAlarm", "(J)I", (void*)android_server_AlarmManagerService_waitForAlarm},
...
};
int register_android_server_AlarmManagerService(JNIEnv* env)
{
return jniRegisterNativeMethods(env, "com/android/server/AlarmManagerService",
sMethods, NELEM(sMethods));
}
步骤:
1.利用结构体JNINativeMethod保存Java Native函数和JNI函数的对应关系;
2.在一个JNINativeMethod数组中保存所有native函数和JNI函数的对应关系;
3.在Java中通过System.loadLibrary加载完JNI动态库之后,调用JNI_OnLoad函数,开始动态注册;
4.JNI_OnLoad中调用通过调用JNIEnv中的函数RegisterNatives函数进行函数注册;
MainActivity.java
public class MainActivity extends Activity {
// Used to load the 'myapplication' library on application startup.
static {
System.loadLibrary("myapplication");
}
private ActivityMainBinding binding;
private int code = 8;
private String msg = "hello JNI";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
// Example of a call to a native method
TextView tv = binding.sampleText;
//java 调用 Native函数
tv.setText(stringFromJNI());
tv.setText(sayHello());
tv.setText(sayHello2());
tv.setText(sayAloveB(5,2)+"");
testCallJava(MainActivity.this);
}
public void cCallJava(String str){
Log.i("TAG","cCallJava:" +str);
Toast.makeText(this,str,Toast.LENGTH_SHORT).show();
}
public native void testCallJava(MainActivity activity);
/**
* A native method that is implemented by the 'myapplication' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
public native String sayHello();
public native String sayHello2();
public native int sayAloveB(int a,int b);
}
native-lib.cpp
#include <jni.h>
#include <string>
#include "android/log.h"
#define TAG "jni_t"
#define LOGE(TAG,...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,"tww" ,__VA_ARGS__)
extern "C" JNIEXPORT jstring JNICALL
Java_com_autochips_myapplication_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
LOGE(TAG,"ssssssfwefsfsfw");
return env->NewStringUTF(hello.c_str());
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_autochips_myapplication_MainActivity_sayAloveB(JNIEnv *env, jobject thiz, jint a, jint b) {
// TODO: implement sayAloveB()
jint ab = a + b;
return ab;
}
//静态注册
extern "C"
JNIEXPORT jstring JNICALL
Java_com_autochips_myapplication_MainActivity_sayHello(
JNIEnv *env, jobject /* this */) {
std::string hello = "tang say Hello JNI";
return env->NewStringUTF(hello.c_str());
}
//动态注册
jstring sayHello2(JNIEnv *env, jobject /* this */) {
std::string hello = "tang sayHello2 ----------";
//获得一维数组的类引用,即jintArray类型
jclass intArrayClass = env->FindClass("[I");
//构造一个指向jintArray类一维数组的对象数组,该对象数组初始大小为length,类型为 jsize
jobjectArray jobjectArray1 = env->NewObjectArray(6, intArrayClass, NULL);
return env->NewStringUTF(hello.c_str());
}
static const JNINativeMethod gMethods[] = {
{"sayHello2",//对应java交互类的名字
"()Ljava/lang/String;",//对应方法名的函数签名
(jstring *) sayHello2}//对应 cpp交互类的指针函数
};
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
__android_log_print(ANDROID_LOG_INFO, "native", "Jni_OnLoad");
JNIEnv *env = NULL;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK)//从JavaVM获取JNIEnv,一般使用1.4的版本
return -1;
//注意:这里 FindClass 必须要和交互类的 包名对应上,并换成[/]符号
jclass clazz = env->FindClass("com/autochips/myapplication/MainActivity");
if (!clazz) {
__android_log_print(ANDROID_LOG_INFO, "native", "cannot get class,"
"com/autochips/myapplication/MainActivity");
return -1;
}
if (env->RegisterNatives(clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0]))) {
__android_log_print(ANDROID_LOG_INFO, "native", "register native method failed!\n");
return -1;
}
return JNI_VERSION_1_4;
}
//native函数调用Java函数
extern "C"
JNIEXPORT void JNICALL
Java_com_autochips_myapplication_MainActivity_testCallJava(JNIEnv *env, jobject thiz,
jobject activity) {
// TODO: implement testCallJava()
//获取MainActivity
jclass cls = env->GetObjectClass(activity);
//拿成员变量ID
jfieldID codeId = env->GetFieldID(cls,"code","I");
jfieldID msgId = env->GetFieldID(cls,"msg","Ljava/lang/String;");
//通过id获取其值
jint code = env->GetIntField(activity,codeId);
jstring msg = (jstring)env->GetObjectField(activity,msgId);
//获取java.lang.String对象中的内容
const char *cMsg = env->GetStringUTFChars(msg,JNI_FALSE);//C++里面没有
LOGI("code = %d,msg = %s",code,cMsg);
env->ReleaseStringUTFChars(msg,cMsg);//用完String之后释放
//找到函数ID
jmethodID callJavaMethodId = env->GetMethodID(cls,"cCallJava","(Ljava/lang/String;)V");
jstring nativeMsg = env->NewStringUTF("java method cCallJava Go!!!");
//调用java中的cCallJava函数
env->CallVoidMethod(activity,callJavaMethodId,nativeMsg);
// 这里的DeleteLocalRef可以不执行,在函数执行完毕后LocalRef会自动释放,
// 但是在循环次数较多的循环中需要Delete,否则可能会溢出
env->DeleteLocalRef(msg);
env->DeleteLocalRef(nativeMsg);
env->DeleteLocalRef(cls);
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!