Android JNI反射呼叫Java構造方法、成員方法和靜態方法
阿新 • • 發佈:2018-12-26
Android開發中一般講Java介面呼叫放在APP層,但是如果想對外隱藏Java介面呼叫,應該怎麼辦呢?我們可以將介面呼叫放在JNI層,通過反射呼叫所需介面,之後打包成.so庫,這樣既可對外隱藏所有呼叫細節。下面開始講解JNI怎麼呼叫Java方法。
首先模擬實現一個類,代表想隱藏的介面
程式碼如下:
package com.lb6905.jnidemo; import android.util.Log; public class TestClass { private final static String TAG = "TestClass"; public TestClass(){ Log.i(TAG, "TestClass"); } public void test(int index) { Log.i(TAG, "test : " + index); } public static void testStatic(String str) { Log.i(TAG, "testStatic : " + str); } public static class InnerClass { private int num; public InnerClass() { Log.i(TAG, "InnerClass"); } public void setInt(int n) { num = n; Log.i(TAG, "setInt: num = " + num); } } }
這個類包一個構造方法、一個成員方法,一個靜態方法,一個內部類,大多數的類都是由這三種方法組成的。下面要做的就是怎麼在JNI呼叫這些方法。
檢視方法簽名
這裡首先Make Project,否則不會生成Java檔案對應的class檔案
進入到classpath目錄下:
命令: cd app/build/intermediates/classes/debug
檢視外部類的簽名
javap -s -p com.lb6905.jnidemo.TestClass
檢視內部類的簽名
javap -s -p com.lb6905.jnidemo.TestClass$InnerClass
結果如下:
F:\Apps\jniDemo\JNIDemo\app\build\intermediates\classes\debug>javap -s -p com.lb6905.jnidemo.TestClass Compiled from "TestClass.java" public class com.lb6905.jnidemo.TestClass { private static final java.lang.String TAG; descriptor: Ljava/lang/String; public com.lb6905.jnidemo.TestClass(); descriptor: ()V public void test(int); descriptor: (I)V public static void testStatic(java.lang.String); descriptor: (Ljava/lang/String;)V } F:\Apps\jniDemo\JNIDemo\app\build\intermediates\classes\debug>javap -s -p com.lb6905.jnidemo.TestClass$InnerClass Compiled from "TestClass.java" public class com.lb6905.jnidemo.TestClass$InnerClass { private int num; descriptor: I public com.lb6905.jnidemo.TestClass$InnerClass(); descriptor: ()V public void setInt(int); descriptor: (I)V }
在JNI中反射呼叫上述方法
JNIEXPORT void JNICALL Java_com_lb6905_jnidemo_MainActivity_JNIReflect
(JNIEnv *env, jobject thiz)
{
//例項化Test類
jclass testclass = (*env)->FindClass(env, "com/lb6905/jnidemo/TestClass");
//建構函式的方法名為<init>
jmethodID testcontruct = (*env)->GetMethodID(env, testclass, "<init>", "()V");
//根據建構函式例項化物件
jobject testobject = (*env)->NewObject(env, testclass, testcontruct);
//呼叫成員方法,需使用jobject物件
jmethodID test = (*env)->GetMethodID(env, testclass, "test", "(I)V");
(*env)->CallVoidMethod(env, testobject, test, 1);
//呼叫靜態方法
jmethodID testStatic = (*env)->GetStaticMethodID(env, testclass, "testStatic", "(Ljava/lang/String;)V");
//建立字串,不能在CallStaticVoidMethod中直接使用"hello world!",會報錯的
jstring str = (*env)->NewStringUTF(env, "hello world!");
//呼叫靜態方法使用的是jclass,而不是jobject
(*env)->CallStaticVoidMethod(env, testclass, testStatic, str);
//例項化InnerClass子類
jclass innerclass = (*env)->FindClass(env, "com/lb6905/jnidemo/TestClass$InnerClass");
jmethodID innercontruct = (*env)->GetMethodID(env, innerclass, "<init>", "()V");
jobject innerobject = (*env)->NewObject(env, innerclass, innercontruct);
//呼叫子類的成員方法
jmethodID setInt = (*env)->GetMethodID(env, innerclass, "setInt", "(I)V");
(*env)->CallVoidMethod(env, innerobject, setInt, 2);
}
生成.so檔案
ndk-build打包成.so庫,之後只需要在Android中使用就可以了
static {
System.loadLibrary("hello-jni");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
......
JNIReflect();
}
結果如下,達到預期效果
10-17 11:00:29.685 26729-26729/com.lb6905.jnidemo I/TestClass: TestClass
10-17 11:00:29.685 26729-26729/com.lb6905.jnidemo I/TestClass: test : 1
10-17 11:00:29.685 26729-26729/com.lb6905.jnidemo I/TestClass: testStatic : hello world!
10-17 11:00:29.686 26729-26729/com.lb6905.jnidemo I/TestClass: InnerClass
10-17 11:00:29.686 26729-26729/com.lb6905.jnidemo I/TestClass: setInt: num = 2
這種方法也可以呼叫Android的自帶的介面,可以很大程度提高安全性,因為.so檔案比較難破解。
關於env,在C和C++語言中呼叫方式是不同的,如下,本例使用的C語言
在C中:
(*env)->方法名(env,引數列表)
在C++中:
env->方法名(引數列表)
程式碼地址(順手給個Star啊):點選檢視原始碼