Android-jni(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. 使用類、字段名稱、字段簽名來定位到一個具體的字段
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章