我在部落格上發表一些我的NDK學習心得,希望對大家能有幫助。 這一篇我們講述如何動态注冊native方法
介紹
首先,之前寫的文章中通過一個簡單的例子來使用了一下NDK,實作了從Native中調用Java方法。
下面,我們要介紹的是實作動态綁定native方法來破除命名限制。
問題
在靜态注冊的情況,所有的方法都是有固定的方法名:Java_<包名> <類名> <方法名>,這種情況下,調用一個方法比較繁瑣,同時也有命名限制,是以使用動态綁定來解決這個問題。
實踐
首先,還是老樣子,加載so,定義native方法
public class MyJni {
static {
System.loadLibrary("myjni");
}
public native void nativePrint();
public native int nativeCount(int a, int b);
public MyJni() {
Log.d("123", "Load MyJni nativePrint & nativeCount");
nativePrint();
int a = nativeCount(,);
Log.d("123", a+"");
}
}
為了在程式初始化的時候進行方法的動态注冊,當so被加載時就會調用JNI_OnLoad函數,需要重寫JNI_OnLoad方法,在該方法中注冊
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv* env = NULL;
jint result = -;
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
LOGE("GetEnv failed!");
return -1;
}
//===========================================
assert(env != NULL);
if (!registerNatives(env)) // 注冊本地方法
{
return -1;
}
//===========================================
/* success -- return valid version number */
result = JNI_VERSION_1_4;
return result;
}
調用registerNatives進行注冊,其中需要指定注冊的類資訊,這樣才能夠擷取其jclass對象
/*
* 為所有類注冊本地方法
*/
static int registerNatives(JNIEnv* env) {
const char* kClassName = "com/example/qiuyu/testhellojni/MainActivity";//指定要注冊的類
return registerNativeMethods(env, kClassName, gMethods,
sizeof(gMethods) / sizeof(gMethods[]));
}
調用registerNativeMethods,使用RegisterNatives進行注冊
/*
* 為某一個類注冊本地方法
*/
static int registerNativeMethods(JNIEnv* env
, const char* className
, JNINativeMethod* gMethods, int numMethods) {
jclass clazz;
clazz = (*env)->FindClass(env, className);
if (clazz == NULL) {
return JNI_FALSE;
}
if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < ) {
return JNI_FALSE;
}
return JNI_TRUE;
}
需要定義gMethods JNINativeMethod數組,實際就是修改指針指向執行的函數,進而實作方法注冊
// 該結構表示一個Java本地方法與native層的c方法的映射
typedef struct {
char *name; // Java本地方法名稱
char *signature; // Java本地方法簽名
void *fnPtr; // c函數指針
} JNINativeMethod;
/**
* Table of methods associated with a single class.
*/
static JNINativeMethod gMethods[] =
{
{"nativePrint", "()V", (void*)native_Print },
{"nativeCount", "(II)I", (void*)native_Count },
};
// 實際執行函數
void native_Print(JNIEnv * env, jobject thiz) {
LOGE("native_Print");
}
jint native_Count(JNIEnv * env, jobject thiz, jint a, jint b) {
return a+b;
}
最終執行結果如下:
- :: -/com.example.qiuyu.testhellojni D/: Load MyJni nativeSetup
- :: -/com.example.qiuyu.testhellojni E/jni_thread: native_Print
- :: -/com.example.qiuyu.testhellojni D/:
注:classname千萬不能寫錯,寫錯之後會出現java.lang.NoClassDefFoundError錯誤
注:如果使用ProGuard進行混淆,很可能會找不到native方法,這個要注意
源碼
Github : https://github.com/QyMars/AndroidNativeCode
總結
通過動态注冊,可以避免寫死,更加靈活,下面一章學習Native線程使用,之後要完成如何Native中啟動一個線程來進行簽名校驗判斷。