1. 程式人生 > >Android-NDK學習記錄4-C呼叫Java靜態方法修改靜態欄位

Android-NDK學習記錄4-C呼叫Java靜態方法修改靜態欄位

一. jni互動相關-方法簽名

方法簽名在jni的使用中經常都會用到,在java中會有過載,那麼定位到一個方法的方式:類+方法名稱+方法簽名,那麼我們先學習下簽名規則:

  1. 基本型別簽名:

咱們基本型別有各自的簽名,如下表

型別名 簽名
boolean Z
byte B
char C
short S
int I
long J
float F
double D
void V

看錶就能知道,大多數基本型別的前面其實就是首字母的大寫,有兩個特殊的,boolean的簽名是‘Z’,long的簽名是‘J’

  1. 類型別簽名:

前面說到了基本型別,再來看看類的簽名,比如:

全路徑名 簽名
String java.lang.String Ljava/lang/String;
Bundle android.os.Bundle Landroid/os/Bundle;
Integer java.lang.Integer Ljava/lang/Integer;
List java.util.List Ljava/util/List;

我想看看上面,大家已經知道了他的規則,那就是:

L+全路徑名(.需要改為/)+;

PS:對於list,它的簽名依舊是list的類簽名,對於範型值沒有在簽名中,大家可以試試,兩個方法過載如果只改範型引數編譯器是會報錯的哦。

  1. 陣列型別簽名:

前面說了類的簽名,咱們陣列中的裝載物也是類,那它的簽名又是怎樣呢?,再看看:

全路徑名 簽名
String[] java.lang.String [Ljava/lang/String;
Integer[] java.lang.Integer [Ljava/lang/Integer;
Integer[][] java.lang.Integer [[Ljava/lang/Integer;

看看上面表格,我想大家也看出來它的規則了:

[+類路徑名。//對於前面的[,是幾維陣列就有幾個[
  1. 檢視方法簽名:javap -s命令
ndkdemo xin$ javap -s ./MainActivity.class 
Compiled from "MainActivity.java"
public class shixin.ndkdemo.MainActivity extends android.support.v7.app.AppCompatActivity {
  public shixin.ndkdemo.MainActivity();
    descriptor: ()V

  protected void onCreate(android.os.Bundle);
    descriptor: (Landroid/os/Bundle;)V

  public native java.lang.String stringFromJNI();
    descriptor: ()Ljava/lang/String;

  public native int intFromJni();
    descriptor: ()I

  static {};
    descriptor: ()V
}

看了這個輸出,我想大家知道了方法簽名的表述了

名稱不說了,對於描述,可以看到,包含了引數與返回。規則:

(引數簽名)返回簽名

二. jni呼叫靜態方法與修改靜態欄位值

  • jni呼叫靜態方法主要步驟,比如:
  1. 找到靜態類:jclass = env->FindClass
  2. 通過靜態類找到要使用的靜態方法id:jmethodID = env->GetStaticMethodID
  3. 呼叫該方法:env->CallStaticVoidMethod
  • 修改靜態欄位類似,比如:
  1. 找到靜態類:jclass = env->FindClass
  2. 通過靜態類找到要使用的靜態方法id:jfieldID = env->GetStaticFieldID
  3. 呼叫該方法:env->SetStaticIntField

知道了步驟,那麼試試實現:

  1. 準備的靜態類:提供一個無引數無返回值的方法 和 一個帶引數帶返回值的方法
/**
 * 靜態方法測試類
 */
public class StaticClassTest {
    private static final String TAG = StaticClassTest.class.getSimpleName();
    /**
    * 名稱,用於測試被jni修改
    */
    private static int age = 5;

    /**
     * 呼叫靜態方法測試
     */
    public static void staticMethodTest() {
        Log.d(TAG, "1 呼叫到靜態方法了");
        Log.d(TAG, "2 呼叫到靜態方法了,age:" + age);
    }

    /**
     * 呼叫帶引數靜態方法測試
     */
    public static int staticMethodTest(String name) {
        Log.d(TAG, "3 呼叫到帶參靜態方法了,name:" + name);
        Log.d(TAG, "4 呼叫到帶參靜態方法了,修改後age:" + age);
        return 1;
    }
}

在前面基本步驟的第二步呢,我們會使用到方法的簽名,前面說了,用於區分方法引數等,可以看作是java中的區分過載的體現。那麼先來獲取下方法的簽名:

  • 檢視靜態類的簽名:
MBP:static_test shixin$ javap -s StaticClassTest.class 
Compiled from "StaticClassTest.java"
public class shixin.ndkdemo.static_test.StaticClassTest {
  public shixin.ndkdemo.static_test.StaticClassTest();
    descriptor: ()V

  public static void staticMethodTest();
    descriptor: ()V

  public static int staticMethodTest(java.lang.String);
    descriptor: (Ljava/lang/String;)I

  static {};
    descriptor: ()V
}

這樣就得到了簽名分別是:

  • 無引數無返回:()V
  • 帶引數帶返回:(Ljava/lang/String;)I
  1. 建立jni方法來呼叫咱們的靜態類方法與修改靜態欄位:
/**
 * 測試靜態方法呼叫
 */
extern "C"
JNIEXPORT void JNICALL
Java_shixin_ndkdemo_MainActivity_testStaticMethodUse(JNIEnv *env, jobject instance) {

    //1. 使用FindClass方法找到類,引數:全路徑
    jclass class_static = env->FindClass("shixin/ndkdemo/static_test/StaticClassTest");

    if (class_static == NULL) {
        LOGE("沒找到StaticClassTest");
        return;
    }

    //2. 找到方法,無引數,無返回。引數:class、方法名稱、方法簽名
    jmethodID jme_staticMethodTest = env->GetStaticMethodID(class_static, "staticMethodTest",
                                                            "()V");
    if (jme_staticMethodTest == NULL) {
        LOGE("沒找到jme_staticMethodTest");
        return;
    }

    //3. 開始呼叫,因為是返回void的,所以呼叫CallStaticVoidMethod
    env->CallStaticVoidMethod(class_static, jme_staticMethodTest);


    //帶引數和返回值的靜態方法
    jmethodID jme_staticMethodTest_Have_Back = env->GetStaticMethodID(class_static,
                                                                      "staticMethodTest",
                                                                      "(Ljava/lang/String;)I");
    if (jme_staticMethodTest_Have_Back == NULL) {
        LOGE("沒找到jme_staticMethodTest_Have_Back");
        return;
    }


    //4. 在呼叫帶引數方法前,我們試著修改下fild,即靜態類中的age
    jfieldID jfieldID_age = env->GetStaticFieldID(class_static, "age", "I");
    if(jfieldID_age==NULL){
        LOGE("沒找到age欄位");
        return;
    }

    //給age欄位賦值為7
    env->SetStaticIntField(class_static,jfieldID_age,7);

    jstring string_para = env->NewStringUTF("北鼻");
    int a = env->CallStaticIntMethod(class_static, jme_staticMethodTest_Have_Back, string_para);
    LOGE("呼叫後返回: %d", a);

    //使用完,釋放本地變數,這裡有兩個,class和後面用到的string字串
    env->DeleteLocalRef(class_static);
    env->DeleteLocalRef(string_para);
}

檢視註釋就清楚步驟了。

  • 輸出:
D/StaticClassTest: 1 呼叫到靜態方法了
D/StaticClassTest: 2 呼叫到靜態方法了,age:5
D/StaticClassTest: 3 呼叫到帶參靜態方法了,name:北鼻
D/StaticClassTest: 4 呼叫到帶參靜態方法了,修改後age:7
I/Native-lib: 呼叫後返回: 1

三. 總結:

  1. 呼叫方法的時候有多種呼叫方法:CallStaticXXMethod,其中XX為返回值型別
  2. 設定靜態欄位值的時候:SetStaticXXField,其中XX為要設定欄位的型別
  3. 使用類、方法名稱、(引數簽名)返回簽名來定位到一個具體的方法
  4. 使用類、欄位名稱、欄位簽名來定位到一個具體的欄位