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. 使用类、字段名称、字段签名来定位到一个具体的字段

你可能感兴趣的:(Android-NDK,Android,NDK,C调用Java静态方法,C修改Java静态字段)