1.JNI函数注册方式
在Android开发中,由于种种原因我们需要调用C/C++代码,在这个时候我们就需要使用jni了,
jni在使用时要对定义的函数进行注册,这样java才能通过native关键字定义的方法找到对应的C/C++函数
注册函数的方法有两种: 静态注册和动态注册。由于静态注册已经在上篇博客中介绍过了,这里重点介绍一下动态注册。
动态注册
由于静态注册每次添加新函数后要重新生成头文件,而且函数名又长,操作起来非常麻烦
我们可以用动态注册来避免这些麻烦 jni中提供了RegisterNatives方法来注册函数
并且我们在调用 System.loadLibrary的时候,会在C/C++文件中回调一个名为 JNI_OnLoad 的函数
在这个函数中一般是做一些初始化设定和指定jni版本 我们可以在这个方法里面注册函数
现在我们不需要头文件,只需要 C/C++ 源文件,.mk文件也和静态注册的一样
注册函数源码
// // Created by yuany on 6/5/18. // #include <jni.h> #include "android/log.h" #include <cassert> #define LOG_TAG "C_TAG" #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) //native method jstring myDynamicJNI (JNIEnv *env, jclass jobj) { LOGD("hello.length"); return env->NewStringUTF("This is my first dynamic JNI test"); } /*需要注册的函数列表,放在JNINativeMethod 类型的数组中, 以后如果需要增加函数,只需在这里添加就行了 参数: 1.java代码中用native关键字声明的函数名字符串 2.签名(传进来参数类型和返回值类型的说明) 3.C/C++中对应函数的函数名(地址) */ static JNINativeMethod getMethods[] = { {"sayHello","()Ljava/lang/String;",(void*)myDynamicJNI}, }; //此函数通过调用JNI中 RegisterNatives 方法来注册我们的函数 static int registerNativeMethods(JNIEnv* env, const char* className,JNINativeMethod* getMethods,int methodsNum){ LOGD("registerNativeMethods"); jclass clazz; //找到声明native方法的类 clazz = env->FindClass(className); if(clazz == NULL){ return JNI_FALSE; } LOGD("after Findclass"); //注册函数 参数:java类 所要注册的函数数组 注册函数的个数 if(env->RegisterNatives(clazz,getMethods,methodsNum) < 0){ return JNI_FALSE; } LOGD("after RegisterNatives"); return JNI_TRUE; } //回调函数 在这里面注册函数 JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved){ LOGD("JNI_OnLoad"); JNIEnv* env = NULL; //判断虚拟机状态是否有问题 if(vm->GetEnv((void**)&env,JNI_VERSION_1_6)!= JNI_OK){ return -1; } assert(env != NULL); //指定类的路径,通过FindClass 方法来找到对应的类 const char* className = "com/example/scarecrow/dynamicregisterjni/Demo"; //开始注册函数 registerNatives -》registerNativeMethods -》env->RegisterNatives if(!registerNativeMethods(env,className,getMethods, 1)){ return -1; } //返回jni 的版本 return JNI_VERSION_1_6; }
上面的代码就能实现动态注册jni了 以后要增加函数只需在java文件中声明native方法,在C/C++文件中实现,
并在getMethods数组添加一个元素并指明对应关系,通过ndk-build 生成so库就可以运行了。其中jni版本可以在jni.h头文件中去查看支持哪些版本,一般定义在文件最后几行。
其他文件代码
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> </android.support.constraint.ConstraintLayout>
MainActivity.java
package com.example.scarecrow.dynamicregisterjni; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.widget.TextView; public class MainActivity extends AppCompatActivity { private static final String TAG = MainActivity.class.getName(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView text = findViewById(R.id.text); Log.d(TAG, "Before"); text.setText(Demo.sayHello()); Log.d(TAG, "after"); } }
Demo.java
package com.example.scarecrow.dynamicregisterjni; import android.util.Log; public class Demo { public static final String TAG = "Demo"; static { System.loadLibrary("JniTest"); Log.d(TAG, "dynamic lib loaded"); } public static native String sayHello(); }
app/build.gradle
apply plugin: 'com.android.application' android { compileSdkVersion 27 defaultConfig { applicationId "com.example.scarecrow.dynamicregisterjni" minSdkVersion 24 targetSdkVersion 27 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" ndk { moduleName "JniTest" ldLibs "log", "z", "m" abiFilters "armeabi", "armeabi-v7a", "x86" } } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } externalNativeBuild { ndkBuild { path 'Android.mk' } } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'com.android.support:appcompat-v7:27.1.1' implementation 'com.android.support.constraint:constraint-layout:1.1.0' testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' }
app/Android.mk
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := JniTest LOCAL_LDFLAGS := -Wl,--build-id LOCAL_LDLIBS := \ -llog \ -lz \ -lm \ LOCAL_SRC_FILES := \ /home/yuany/workspace/DynamicRegisterJNI/app/src/main/jni/JniTest.cpp \ LOCAL_C_INCLUDES += /home/yuany/workspace/DynamicRegisterJNI/app/src/main/jni LOCAL_C_INCLUDES += /home/yuany/workspace/DynamicRegisterJNI/app/src/debug/jni include $(BUILD_SHARED_LIBRARY)
local.properties
## This file must *NOT* be checked into Version Control Systems, # as it contains information specific to your local configuration. # # Location of the SDK. This is only used by Gradle. # For customization when using a Version Control System, please read the # header note. #Thu Jun 07 11:01:17 CST 2018 ndk.dir=/home/yuany/Android/Sdk/ndk-bundle sdk.dir=/home/yuany/Android/Sdk
C/C++ - java - jni 对应参数
动态注册中 JNINativeMethod 结构体中第二个参数需注意
括号内代表传入参数的签名符号,如果为空括号内什么都不用写,括号外代表返回参数的签名符号,为空的话要写大写的V,对应关系见下表
签名符号 | JNI | java |
V | void | void |
Z | jboolean | boolean |
I | jint | int |
J | jlong | long |
D | jdouble | double |
F | jfloat | float |
B | jbyte | byte |
C | jchar | char |
S | jshort | short |
[Z | jbooleanArray | boolean[] |
[I | jintArray | int[] |
[J | jlongArray | long[] |
[D | jdoubleArray | double[] |
[F | jfloatArray | float[] |
[B | jbyteArray | byte[] |
[C | jcharArray | char[] |
[S | jshortArray | short[] |
L完整包名加类名; | jobject | class |
举个例子:
传入的java参数有两个 分别是 int 和 long[] 函数返回值为 String 即java函数的定义为:String getString(int a ,long[] b)
签名就应该是 "(I[J)Ljava/lang/String;"
如果有内部类 则用 $ 来分隔 如: Landroid/os/FileUtils$FileStatus; (这个我自己没有试过)
用静态注册方式注册函数时,会生成.h头文件,打开头文件,里面也可以看到对应的签名,它能够自动生成,如果实在
不知道怎么写签名,就生成头文件自己打开看一下就知道了。