1. 程式人生 > >Android JNI反射呼叫Java構造方法、成員方法和靜態方法

Android JNI反射呼叫Java構造方法、成員方法和靜態方法

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啊):點選檢視原始碼