Java 类型和JNI符号对比

对应基础类型字段的转换:

Java 类型 JNI符号
Boolean Z
Byte B
Char C
Short S
Int I
Long J
Float F
Double D

对于引用类型的字段签名转换,是大写字母 L 开头,然后是类的签名转换,最后以 ; 结尾。

Java 类型 JNI符号
String Ljava/lang/String;
Class Ljava/lang/Class;
Throwable Ljava/lang/Throwable;
int[] [I
Object[] [Ljava/lang/Object;

对于方法签名描述的转换,首先是将方法内所有参数转换成对应的字段描述,并全部写在小括号内,然后在小括号外再紧跟方法的返回值类型描述。

Java 类型 JNI 对应的描述转换
String f(); ()Ljava/lang/String;
long f(int i, Class c); (ILjava/lang/Class;)J
String(byte[] bytes); ([B)V

这里要注意的是在 JNI 对应的描述转换中不要出现空格。

JNI 访问属性

创建 Util.java 类

/**
 * @author : zhaoyanjun
 * @time : 2021/7/29
 * @desc :
 */
public class Util {

    String usernName = "zhaoyanjun";
    int age = 10;
    static float key = 2f;

    //修改类string属性
    native void changeNameValue();

    //修改类int属性
    native void changeAgeValue();

    //改变静态变量值
    native void changeStaticValue();
}

访问 String 属性、int 属性

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        val util = Util()
        util.changeNameValue()  //修改string
        util.changeAgeValue()   //修改int
        util.changeStaticValue()  //修改静态变量
        binding.sampleText.text = "value:${util.usernName} ${util.age} ${Util.key}"

    }

    companion object {

        // Used to load the 'native-lib' library on application startup.
        init {
            System.loadLibrary("native-lib")
        }
    }
}

native-lib.cpp

//修改字符串属性
extern "C"
JNIEXPORT void JNICALL
Java_com_example_myapplication_Util_changeNameValue(JNIEnv *env, jobject thiz) {
    //获取class
    jclass cls = env->GetObjectClass(thiz);
    //获取字段
    jfieldID fid = env->GetFieldID(cls, "usernName", "Ljava/lang/String;");
    //创建新值
    jstring str = env->NewStringUTF("niu b");
    //给字段赋值
    env->SetObjectField(thiz, fid, str);
}

//修改int属性
extern "C"
JNIEXPORT void JNICALL
Java_com_example_myapplication_Util_changeAgeValue(JNIEnv *env, jobject thiz) {
    //获取class
    jclass cls = env->GetObjectClass(thiz);
    //获取字段
    jfieldID fid = env->GetFieldID(cls, "age", "I");
    //给字段赋值
    env->SetIntField(thiz, fid, 100);
}

//修改静态属性
extern "C"
JNIEXPORT void JNICALL
Java_com_example_myapplication_Util_changeStaticValue(JNIEnv *env, jobject thiz) {
    //获取class
    jclass cls = env->GetObjectClass(thiz);
    //获取字段
    jfieldID fid = env->GetStaticFieldID(cls, "key", "F");
    //获取静态字段值
    jfloat value = env->GetStaticFloatField(cls, fid);

    //给字段赋值
    env->SetStaticFloatField(cls, fid, value + 100);
}

运行起来,看看效果
Android NDK学习笔记3:JNI访问Java属性、方法_Android JNI
值已经改变。

native方法,非晶态和静态区别

Util.java

public class Util {

    native void fun1();

    native static void fun2();
}

native-lib.cpp

//非静态方法
extern "C"
JNIEXPORT void JNICALL
Java_com_example_myapplication_Util_fun1(JNIEnv *env, jobject thiz) {

}

//静态方法
extern "C"
JNIEXPORT void JNICALL
Java_com_example_myapplication_Util_fun2(JNIEnv *env, jclass clazz) {
    
}

可以看到,非静态方法,参数是 jobject; 静态方法,参数是jclass

多种方式获得 jclass
extern "C"
JNIEXPORT void JNICALL
Java_com_example_myapplication_Util_fun1(JNIEnv *env, jobject thiz) {
    //方式一:FindClass,参数传入类全路径
    jclass cls = env->FindClass("com/example/myapplication/Util");
    
    //方式二:GetObjectClass
    jclass cls2 = env->GetObjectClass(thiz);
}

JNI 方法Java方法

无参无返回值

/**
 * @author : zhaoyanjun
 * @time : 2021/7/29
 * @desc :
 */
public class Util {

    void run() {
        Log.d("util-", "runing");
    }

    String speak(String message) {
        Log.d("util-", "speak " + message);
        return message + " java";
    }

    //无参无返回值
    native void callRun();
}

native-lib.cpp

extern "C"
JNIEXPORT void JNICALL
Java_com_example_myapplication_Util_callRun(JNIEnv *env, jobject thiz) {
    //获取jclass
    jclass cls = env->GetObjectClass(thiz);
    //获取方法
    jmethodID method = env->GetMethodID(cls, "run", "()V");
    //调用方法
    env->CallVoidMethod(thiz, method);
}

无参符号:() , 无返回值符号:V , 结合起来就是 ()V

有参数有返回值

native String callSpeak();

native-lib.cpp

//调用带有
extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_myapplication_Util_callSpeak(JNIEnv *env, jobject thiz) {
    //获取jclass
    jclass cls = env->GetObjectClass(thiz);
    //获取方法
    jmethodID method = env->GetMethodID(cls, "speak", "(Ljava/lang/String;)Ljava/lang/String;");
    
    jstring str = env->NewStringUTF("jni hello");
    //方法调用,并接收返回值
    jobject result = env->CallObjectMethod(thiz, method, str);
    //返回结果
    return static_cast<jstring>(result);
}

静态方法

方式1:非静态 native 方法

public class Util {

    static void run() {
        Log.d("util-", "runing");
    }

    native void callRun();
}

native-lib.cpp

extern "C"
JNIEXPORT void JNICALL
Java_com_example_myapplication_Util_callRun(JNIEnv *env, jobject thiz) {
    jclass cls = env->GetObjectClass(thiz);
    jmethodID method = env->GetStaticMethodID(cls, "run", "()V");
    env->CallStaticVoidMethod(cls, method);
}

调用:

val util = Util()
util.callRun()

需要注意的是

  • 获取静态方法用 GetStaticMethodID
  • 调用静态方法用 CallStaticVoidMethod

方式2:静态 native 方法

public class Util {

    static void run() {
        Log.d("util-", "runing");
    }

    //静态的
    native static void callRun();
}

native-lib.cpp

extern "C"
JNIEXPORT void JNICALL
Java_com_example_myapplication_Util_callRun(JNIEnv *env, jclass clazz) {
    jmethodID method = env->GetStaticMethodID(clazz, "run", "()V");
    env->CallStaticVoidMethod(clazz, method);
}

调用:

 Util.callRun()

需要注意的是:静态的 native 方法,必须用 类名调用。非静态的 nativie方法,需要用对象调用。

实战演练: 调用Android Log

我们首先来看系统Log类
Android NDK学习笔记3:JNI访问Java属性、方法_Android NDK_02
要确认两点信息
第一:类的包名是:android.util
第二:d() 方法,两个参数,都是 String 类型。返回值是 int

下面我们就可以写代码了。

首先创建 native 方法,有两个参数,一个是 tag , 一个是 message

public class Util {

    //静态的
    native static void logd(String tag, String message);

}

native-lib.cpp

extern "C"
JNIEXPORT void JNICALL
Java_com_example_myapplication_Util_logd(JNIEnv *env, jclass clazz, jstring tag, jstring message) {

    //获取Log class
    jclass cls = env->FindClass("android/util/Log");

    //获取 d() 方法
    jmethodID method = env->GetStaticMethodID(cls, "d",
                                              "(Ljava/lang/String;Ljava/lang/String;)I");
    
    //调用方法
    env->CallStaticIntMethod(cls, method, tag, message);
}

具体使用:

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        //调用c++输出日志
        Util.logd("china--", "good")
    }

    companion object {

        // Used to load the 'native-lib' library on application startup.
        init {
            System.loadLibrary("native-lib")
        }
    }
}
实战演练: 弹 Toast

c 代码

/**
 * 显示一个 Toast
 * Toast.makeText(this,"message",Toast.LENGTH_SHORT).show();
 */
extern "C"
JNIEXPORT void JNICALL
Java_com_example_myapplication_Util_showToast(JNIEnv *env, jclass clazz, jobject context,
                                              jstring message) {
    //获取Toast
    jclass cls = env->FindClass("android/widget/Toast");

    //获取 makeText 方法
    jmethodID makeText = env->GetStaticMethodID(cls, "makeText",
                                                "(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;");

    //调用makeText方法, Toast.LENGTH_SHORT=0
    jobject toast = env->CallStaticObjectMethod(cls, makeText, context, message, 0);

    //获取 show 方法
    jmethodID show = env->GetMethodID(cls, "show", "()V");

    //调用 show 方法
    env->CallVoidMethod(toast, show);
}

java 方法声明


public class Util {

    native static void showToast(Context context, String message);
}

方法调用:

//显示一个toast
Util.showToast(this, "今天是周一")
实战演练:数组传值

Util.java

public class Util {

    native void run();

    //这个方法时c调用
    public void show(String[] array) {
        for (int i = 0; i < array.length; i++) {
            Log.d("show-", "" + i + "  " + array[i]);
        }
    }
}

可以看到 Util 有两个方法,一个是 run , 一个是 show 方法,参数是一个 String 数组。

我们现在来做,java 调用 run , 然后 c 实现的 run 再调用 show方法,并且传值 。

c 代码如下:

extern "C"
JNIEXPORT void JNICALL
Java_com_example_myapplication_Util_run(JNIEnv *env, jobject thiz) {
    jclass cls = env->GetObjectClass(thiz);

    //获取show反复
    jmethodID show = env->GetMethodID(cls, "show", "([Ljava/lang/String;)V");

    //定义string数组
    int size = 3;
    jclass strClass = env->FindClass("java/lang/String");
    jobjectArray resultArray = env->NewObjectArray(size, strClass, nullptr);

    //string数组赋值
    jstring strItem;
    for (int i = 0; i < size; ++i) {
        strItem = env->NewStringUTF("string in native");
        env->SetObjectArrayElement(resultArray, i, strItem);
    }

    //调用 show 方法
    env->CallVoidMethod(thiz, show, resultArray);
}

java 调用 run , 代码如下:

 Util().run()

输出结果如下:

D/show-: 0  string in native
D/show-: 1  string in native
D/show-: 2  string in native