Android NDK 开发之JNI常规操作

image.png

1. JNI函数详解

java中native方法在C++代码中一般如下:

extern "C" JNIEXPORT jstring JNICALL
Java_com_lucky_jnidemo_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

1. extern "C" 说明

表示下面的代码,都采用C的编译方式。

之所以用C的编译方式,是因为 JNIEnv 是C语言代码写的,避免一些函数重载等C语言中不支持的

查看JNIEnv源码,进入到jni.h,可以看到:

#if defined(__cplusplus)
typedef _JNIEnv JNIEnv; // 如果是C++代码,用这个宏定义
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;  //如果是C代码,用这个宏定义
typedef const struct JNIInvokeInterface* JavaVM;
#endif

继续跟踪 _JNIEnv,可以看到:

struct _JNIEnv {
    /* do not rename this; it does not seem to be entirely opaque */
    const struct JNINativeInterface* functions;

因此,env的调用方式如下:

// C++的情况如下: 
Java_com_lucky_jnidemo_MainActivity_stringFromJNI(JNIEnv * env, ...) 
{
   env->NewStringUTF();  //详细看源码中的结构体定义
}

C的情况如下: 
Java_com_lucky_jnidemo_MainActivity_stringFromJNI(JNIEnv * env, ...) 
{ 
  (*env)->NewStringUTF(); //二级指针
}

2. JNIEXPORT 宏定义说明

针对Linux平台:该声明的作用是保证在本动态库中声明的方法 , 能够在其他项目中可以被调用 ;

3. JNICALL 宏定义说明

此宏定义为空,用来表示函数的调用规范。

4. jobjectjclass

jobject // java传递下来的对象,就是本项目中 MainActivity 对象 
jclass // java传递下来的 class 对象,就是本项目中的 MainActivity class

2. JNI中函数操作

1. 在C层中修改Java/Kotlin层String变量(引用类型遍历)

Kotlin代码:

//即将要修改的代码熟悉
private var mName: String = "Lucky" 

//native 修改String name
external fun changeStringName()

//点击按钮进行修改
binding.btn1.setOnClickListener {
    Log.i("MainActivity", "修改前:$mName")
    changeStringName()
    Log.i("MainActivity", "修改后:$mName")
}

C层中的代码

extern "C"
JNIEXPORT void JNICALL
Java_com_lucky_jnidemo_MainActivity_changeStringName(JNIEnv *env, jobject thiz) {

    //获取Class方式一
    jclass mainActivityCls = env->FindClass("com/lucky/jnidemo/MainActivity");

    //获取Class方式二
    //jclass mainActivityCls = env->GetObjectClass(thiz);

    // jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
    jfieldID nameFid = env->GetFieldID(mainActivityCls, "mName", "Ljava/lang/String;");

    // void SetObjectField(jobject obj, jfieldID fieldID, jobject value)
    jstring value = env->NewStringUTF("Update Lucky");

    //void SetObjectField(jobject obj, jfieldID fieldID, jobject value)
    env->SetObjectField(thiz, nameFid, value); //修改值
}

打印结果:

com.lucky.jnidemo I/MainActivity: 修改前:Lucky
com.lucky.jnidemo I/MainActivity: 修改后:Update Lucky

2. 在C层中修改Kotlin层静态Int变量

通过apk查看编译后的MainActivity 类,可以看到静态mAege是在MainActivity 中

image.png

因此,这里跟修改MainActivity中的变量一样的操作

Kotlin代码:

companion object {
    private var mAge: Int = 666
    init {
        System.loadLibrary("jnidemo")
    }
    external fun changeIntAge() //修改Int age
}


binding.btn2.setOnClickListener {
    Log.i("MainActivity", "修改前:${mAge}")
    changeIntAge()
    Log.i("MainActivity", "修改后:${mAge}")
}

C层中的代码

extern "C"
JNIEXPORT void JNICALL
Java_com_lucky_jnidemo_MainActivity_00024Companion_changeIntAge(JNIEnv *env, jobject thiz) {

    // 这里要注意用FindClass方式获取,不然拿到的jcalss会是 "com/lucky/jnidemo/MainActivity$Companion"
    jclass mainActivityCls = env->FindClass("com/lucky/jnidemo/MainActivity");

    //jfieldID GetStaticFieldID(jclass clazz, const char* name, const char* sig)
    jfieldID ageFid = env->GetStaticFieldID(mainActivityCls, "mAge", "I");
    if (ageFid == NULL) {
        LOGI("%s", "no field  mAge ");
        return;
    }
    // jint 背后就是int,所以可以直接用,  但是String 必须用 jstring
    int age = env->GetStaticIntField(mainActivityCls, ageFid); // 获取之前的age
    //  void SetStaticIntField(jclass clazz, jfieldID fieldID, jint value)
    env->SetStaticIntField(mainActivityCls, ageFid, age + 10); //修改值+10

}

打印结果:

com.lucky.jnidemo I/MainActivity: 修改前:666
com.lucky.jnidemo I/MainActivity: 修改后:676

3. 在C层中修改Kotlin层double变量

kotlin层代码:

private var mHeight: Double = 175.0

binding.btn3.setOnClickListener {
    Log.i("MainActivity", "修改前:${mHeight}")
    changeDoubleHeight()
    Log.i("MainActivity", "修改后:${mHeight}")
}

external fun changeDoubleHeight() //修改Double height

C层中的代码

extern "C"
JNIEXPORT void JNICALL
Java_com_lucky_jnidemo_MainActivity_changeDoubleHeight(JNIEnv *env, jobject thiz) {

    jclass mainActivityCls = env->GetObjectClass(thiz);

    // jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
    jfieldID numberFid  = env->GetFieldID(mainActivityCls, "mHeight", "D");

    // void SetDoubleField(jobject obj, jfieldID fieldID, jdouble value)
    env->SetDoubleField(thiz, numberFid, 178.55);

}

打印结果:

com.lucky.jnidemo I/MainActivity: 修改前:175.0
com.lucky.jnidemo I/MainActivity: 修改后:178.55

4. 在C层中调用Kotlin层方法

kotlin层代码:

/***
 * "(II)I"
 * 被C层调用的方法
 */
fun add(number1: Int, number2: Int): Int {
    return number1 + number2
}

/***
 * 被C调用的方法 (Ljava/lang/String;I) Ljava/lang/String;
 */
fun showString(str: String, num: Int): String {
    Log.i("MainActivity", "Value from C++ str: $str  num: $num")
    return "【$str】"
}

external fun callKotlinMethod() //在C层中调用Kotlin代码

binding.btn4.setOnClickListener {
    callKotlinMethod() //点击按钮
}

C层中的代码

extern "C"
JNIEXPORT void JNICALL
Java_com_lucky_jnidemo_MainActivity_callKotlinMethod(JNIEnv *env, jobject thiz) {

    jclass mainActivitCls = env->GetObjectClass(thiz);

    // jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
    jmethodID addMid = env->GetMethodID(mainActivitCls, "add", "(II)I"); //kotlin层 fun add(number1: Int, number2: Int): Int

    // jint CallIntMethod(jobject obj, jmethodID methodID, ...)
    int result = env->CallIntMethod(thiz, addMid, 10, 10); //调用方法
    LOGD("result:%d\n", result);

    // ++++++++++++++++++++++ C调用 fun showString(str: String, num: Int): String 函数
    jmethodID showStringMid = env->GetMethodID(mainActivitCls, "showString", "(Ljava/lang/String;I)Ljava/lang/String;");

    // jobject     (*CallObjectMethod)(jobject, jmethodID, ...);
    jstring value = env->NewStringUTF("C语言调用Kotlin");
    jstring resultStrJ = (jstring) env->CallObjectMethod(thiz, showStringMid, value, 10086);
    const char *resultStr = env->GetStringUTFChars(resultStrJ, NULL);
    LOGD("result2:%s\n", resultStr);
}

打印结果:

//C中的打印
com.lucky.jnidemo D/Lucky: result:20
com.lucky.jnidemo D/Lucky: result2:【C语言调用Kotlin】

//Kotlin中的打印
com.lucky.jnidemo I/MainActivity: Value from C++ str: C语言调用Kotlin  num: 10086

5. 在C层中修改Kotlin层数组

kotlin层代码:

binding.btn5.setOnClickListener {
    val ints = intArrayOf(1, 2, 3, 4, 5, 6)
    val strs = arrayOf("李小龙", "李连杰", "李元霸")
    testArrayAction(99, "你好", ints, strs)

    for (anInt in ints) {
        Log.d("MainActivity", "(Kotlin)修改后: ints:$anInt")
    }

    for (str in strs) {
        Log.e("MainActivity", "(Kotlin)修改后: strs:$str")
    }
}

//在C层中修改数组
external fun testArrayAction(count: Int, textInfo: String, ints: IntArray, strs: Array<String>) 

C层中的代码

extern "C"
JNIEXPORT void JNICALL
Java_com_lucky_jnidemo_MainActivity_testArrayAction(JNIEnv *env, jobject thiz, jint count, jstring text_info, jintArray ints, jobjectArray strs) {

    //++++++++++++++++++++++++++ 操作Int数组
    LOGD("count:%d\n", count);
    const char *_textInfoStr = env->GetStringUTFChars(text_info, NULL);
    LOGD("text_info:%s\n", _textInfoStr);
    env->ReleaseStringUTFChars(text_info, _textInfoStr); //释放

    int intsLen = env->GetArrayLength(ints); //获取数组个数

    for (int i = 0; i < intsLen; ++i) {
        jint *_ints = env->GetIntArrayElements(ints, NULL);
        *(_ints + i) = (i + 1000001); //修改数组的值
        LOGD("C++ _ints item:%d\n", *(_ints + i));
        // JNI_OK 0 == 代表 先用操纵杆刷新到JVM,JVM会更新上层代码。再释放C++层数组
        // JNI_COMMIT 1 == 代表 用操纵杆刷新到JVM,JVM会更新上层代码
        // JNI_ABORT 2 == 代表 释放C++层数组
        env->ReleaseIntArrayElements(ints, _ints, JNI_OK); //在数组循环中,一定要记得释放
    }

    //++++++++++++++++++++++++++ 操作String数组
    int strsLen = env->GetArrayLength(strs);
    for (int i = 0; i < strsLen; ++i) {
        jobject item = env->GetObjectArrayElement(strs, i); //获取每一个值
        jstring itemStr = (jstring) item;
        const char *itemStr_1 = env->GetStringUTFChars(itemStr, NULL);
        LOGI("C++ 修改前itemStr_:%s\n", itemStr_1);
        env->ReleaseStringUTFChars(itemStr, itemStr_1);

        jstring value = env->NewStringUTF("AAAAAAA");
        env->SetObjectArrayElement(strs, i, value); //修改数组值
        jobject item2 = env->GetObjectArrayElement(strs, i);
        jstring itemStr2 = (jstring) item2;
        const char *itemStr_2 = env->GetStringUTFChars(itemStr2, NULL);
        LOGI("C++ 修改后itemStr_2:%s\n", itemStr_2);
        env->ReleaseStringUTFChars(itemStr2, itemStr_2);
    }
}

打印日志:

com.lucky.jnidemo D/Lucky: count:99
com.lucky.jnidemo D/Lucky: text_info:你好
com.lucky.jnidemo D/Lucky: C++ _ints item:1000001
com.lucky.jnidemo D/Lucky: C++ _ints item:1000002
com.lucky.jnidemo D/Lucky: C++ _ints item:1000003
com.lucky.jnidemo D/Lucky: C++ _ints item:1000004
com.lucky.jnidemo D/Lucky: C++ _ints item:1000005
com.lucky.jnidemo D/Lucky: C++ _ints item:1000006
com.lucky.jnidemo I/Lucky: C++ 修改前itemStr_:李小龙
com.lucky.jnidemo I/Lucky: C++ 修改后itemStr_2:AAAAAAA
com.lucky.jnidemo I/Lucky: C++ 修改前itemStr_:李连杰
com.lucky.jnidemo I/Lucky: C++ 修改后itemStr_2:AAAAAAA
com.lucky.jnidemo I/Lucky: C++ 修改前itemStr_:李元霸
com.lucky.jnidemo I/Lucky: C++ 修改后itemStr_2:AAAAAAA
com.lucky.jnidemo D/MainActivity: (Kotlin)修改后: ints:1000001
com.lucky.jnidemo D/MainActivity: (Kotlin)修改后: ints:1000002
com.lucky.jnidemo D/MainActivity: (Kotlin)修改后: ints:1000003
com.lucky.jnidemo D/MainActivity: (Kotlin)修改后: ints:1000004
com.lucky.jnidemo D/MainActivity: (Kotlin)修改后: ints:1000005
com.lucky.jnidemo D/MainActivity: (Kotlin)修改后: ints:1000006
com.lucky.jnidemo E/MainActivity: (Kotlin)修改后: strs:AAAAAAA
com.lucky.jnidemo E/MainActivity: (Kotlin)修改后: strs:AAAAAAA

6. 在C层中修改Kotlin层类

修改实体类,我们这里用Java类,这样修改int 变量不需要kotlin中这么麻烦,需要转换Integer

kotlin层代码:

Person类

public class Person {

    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
binding.btn6.setOnClickListener {
    val person = Person()
    person.name = "张三"
    person.age = 18
    putObject(person, "Kotlin文本")
    Log.i("MainActivity", "(Kotlin)修改后, person:$person")
}

// 只玩Student对象里面的成员
external fun putObject(person: Person, str: String) // 传递引用类型,传递对象

C层中的代码

extern "C"
JNIEXPORT void JNICALL
Java_com_lucky_jnidemo_MainActivity_putObject(JNIEnv *env, jobject thiz, jobject person, jstring str) {

    const char *_str = env->GetStringUTFChars(str, NULL);
    LOGD("_str:%s\n", _str);
    env->ReleaseStringUTFChars(str, _str);
    jclass mStudentClass = env->GetObjectClass(person);
    // toString
    jmethodID toStringMethod = env->GetMethodID(mStudentClass, "toString", "()Ljava/lang/String;");
    jstring results = (jstring) env->CallObjectMethod(person, toStringMethod);
    const char *result = env->GetStringUTFChars(results, NULL);
    LOGD("C++ toString:%s\n", result);
    env->ReleaseStringUTFChars(results, result);

    // setName
    jmethodID setNameMethod = env->GetMethodID(mStudentClass, "setName", "(Ljava/lang/String;)V");
    jstring nameS = env->NewStringUTF("李四");
    env->CallVoidMethod(person, setNameMethod, nameS);

    // getName
    jmethodID getNameMethod = env->GetMethodID(mStudentClass, "getName", "()Ljava/lang/String;");
    jstring nameS2 = (jstring) env->CallObjectMethod(person, getNameMethod);
    const char *nameS3 = env->GetStringUTFChars(nameS2, NULL);
    LOGD("C++ getName:%s\n", nameS3);
    env->ReleaseStringUTFChars(nameS2, nameS3);

    // setAge
    jmethodID setAgeMethod = env->GetMethodID(mStudentClass, "setAge", "(I)V");
    env->CallVoidMethod(person, setAgeMethod, 99);

    // getAge
    jmethodID getAgeMethod = env->GetMethodID(mStudentClass, "getAge", "()I");
    int age = env->CallIntMethod(person, getAgeMethod);
    LOGD("C++ getAge:%d\n", age);

    env->DeleteLocalRef(mStudentClass);
}

7. 在C层中创建类

Student类

public class Student {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

Person 类

public class Person {

    private String name;
    private int age;
    private static Student student;

    public static void putStudent(Student student) {
        Person.student = student;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Student getStudent() {
        return student;
    }

    public void setStudent(Student student) {
        Person.student = student;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", student=" + student +
                '}';
    }
}

kotlin层中的代码

binding.btn7.setOnClickListener {
    insertObject()
}
external fun insertObject()

C层中的代码

extern "C"
JNIEXPORT void JNICALL
Java_com_lucky_jnidemo_MainActivity_insertObject(JNIEnv *env, jobject thiz) {

    // @Student对象
    jclass studentClass = env->FindClass("com/lucky/jnidemo/Student");
    jobject student = env->AllocObject(studentClass);

    // setName
    jmethodID setNameMethod = env->GetMethodID(studentClass, "setName", "(Ljava/lang/String;)V");
    jstring value1 = env->NewStringUTF("张三");
    env->CallVoidMethod(student, setNameMethod, value1);

    // setAge
    jmethodID setAgeMethod = env->GetMethodID(studentClass, "setAge", "(I)V");
    env->CallVoidMethod(student, setAgeMethod, 99);

    // @Person对象
    jclass personClass = env->FindClass("com/lucky/jnidemo/Person");
    jobject person = env->AllocObject(personClass); // C++ 分配一个对象出来,不会调用此对象的构造函数
    // env->NewObject();  // C++ 实例化一个对象出来,会调用此对象的构造函数,相当于: new XXX();

    // void CallVoidMethod(person obj, jmethodID methodID, ...)
    jmethodID setStudent = env->GetMethodID(personClass, "setStudent", "(Lcom/lucky/jnidemo/Student;)V");
    env->CallVoidMethod(person, setStudent, student);

    // static void putStudent
    jmethodID putStudent = env->GetStaticMethodID(personClass, "putStudent", "(Lcom/lucky/jnidemo/Student;)V");
    env->CallStaticVoidMethod(personClass, putStudent, student);

    // TODO 释放工作
    // 释放方式一
    env->DeleteLocalRef(personClass);
    env->DeleteLocalRef(student);
    env->DeleteLocalRef(value1);
    env->DeleteLocalRef(personClass);
    env->DeleteLocalRef(person);
    // 释放方式二
    /*env->GetStringUTFChars()
    env->ReleaseStringUTFChars()*/

    // 释放方式三
    /*new StudentCPP对象
    delete StudentCPP对象*/
}

GitHub源码:MyJniDemo

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容