细说JNI与NDK(二)基本操作

细说JNI与NDK专题目录:

细说JNI与NDK(一) 初体验
细说JNI与NDK(二)基本操作)
细说JNI与NDK(三)ndk 配置说明
细说JNI与NDK(四)动态和静态注册
细说JNI与NDK(五)JNI 线程
细说JNI与NDK(六)静态缓存,异常捕获,内置函数
细说JNI与NDK(七)Parcel底层JNI思想与OpenCV简单对比

要细说JNI与NDK 操作的细节点

  • 加载库的方式说明, System.load("xxx");& System.loadLibrary("xxx") 区别
 static {
        // System.load(); 这种是可以绝对路径的加载动态链接库文件
        System.loadLibrary("native-lib"); // 这种是从库目录遍历层级目录,去自动的寻找
    }
  • 操作数组,如:String引用类型等
  • Java 通过jni像C层传递对象,传递引用类型
  • C 层 操作Java,创建对象
  • 引用的相关操作和概念
  • 关于全局引用的释放

因为需要操作对象和对象数组,所以我们先准备好几个对象类, 为了方便,把每次都用到的c代码整理好

① Student

public class Student {

    private final static String TAG = Student.class.getSimpleName();

    public String name;
    public int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        Log.d(TAG, "Java setName name:" + name);
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        Log.d(TAG, "Java setAge age:" + age);
        this.age = age;
    }

    public static void showInfo(String info) {
        Log.d(TAG, "showInfo info:" + info);
    }

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

② Person ,参数Student

package top.zcwfeng.jni;
import android.util.Log;
public class Person {
    private static final String TAG = Person.class.getSimpleName();
    public Student student;
    public void setStudent(Student student) {
        Log.d(TAG, "call setStudent student:" + student.toString());
        this.student = student;
    }
    public static void putStudent(Student student) {
        Log.d(TAG, "call static putStudent student:" + student.toString());
    }
}

③ Dog,C层创建Java端对象用

public class Dog {
    public Dog() {
        Log.d("Dog", "Dog init...");
    }
}

④ Native 公用操作

#include 
#include 
#include 

// 日志输出
#include 

#define TAG "native_zcw"
// __VA_ARGS__ 代表 ...的可变参数
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG,  __VA_ARGS__);
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG,  __VA_ARGS__);
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG,  __VA_ARGS__);

using namespace std;

操作数组等基本数据

Java

// 操作数组 String引用类型,玩数组
    public native void testArrayAction(int count, String textInfo, int[] ints, String[] strs);

// 点击事件:操作testArrayAction函数
    public void test01ArrayAction(View view) {
        int[] ints = new int[]{1,2,3,4,5,6};
        String[] strs = new String[]{"李小龙","李连杰","李元霸"};
        testArrayAction(99, "你好", ints, strs);

        for (int anInt : ints) {
            Log.d(TAG, "test01: anInt:" + anInt);
        }
    }

Native

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

    // ① 基本数据类型  jint count,jint 就是可以直接用 int, jstring text_info
    int intCount = count;
    LOGI("param1:count->", intCount);

    // const char* GetStringUTFChars(jstring string, jboolean* isCopy)
    const char *textp = env->GetStringUTFChars(text_info, NULL);
    LOGI("param2 textInfo:%s\n", textp);

    // ② 把int[] 转成 int*
    // jint* GetIntArrayElements(jintArray array, jboolean* isCopy)
    int *j_intarrayp = env->GetIntArrayElements(ints, NULL);

    // Java层数组的长度
    // jsize GetArrayLength(jarray array) -- jintArray ints 可以放入到 jarray的参数中去
    jsize size = env->GetArrayLength(ints);
    // 此时C++的修改,影响不了Java层
    for (int i = 0; i < size; ++i) {
        *(j_intarrayp + i) += 100;
        LOGI("param3 int[]:%d\n", *j_intarrayp + i);
    }

    /**
    * 0:           刷新Java数组,并 释放C++层数组
    * JNI_COMMIT:  只提交 只刷新Java数组,不释放C++层数组
    * JNI_ABORT:   只释放C++层数组
    */
    env->ReleaseIntArrayElements(ints, j_intarrayp, NULL);

    // ③:jobjectArray 代表是Java的引用类型数组,不一样
    int strsize = env->GetArrayLength(strs);
    int i;
    for (i = 0; i < strsize; ++i) {
        jstring j_str = static_cast(env->GetObjectArrayElement(strs, i));
        // 想要打印必须转换成c的格式
        // const char* GetStringUTFChars(jstring string, jboolean* isCopy)
        const char *charp = env->GetStringUTFChars(j_str, NULL);
        LOGI("param4: 引用类型String 具体的:%s\n", charp);
        // 释放jstring
        env->ReleaseStringUTFChars(j_str,charp);
    }

}
  • jsize 最终还是封装int
  • 操控上下文,JNIEnv *env
  • 刷新底层操作数组到Java
    /**
     * 0:           刷新Java数组,并 释放C++层数组
     * JNI_COMMIT:  只提交 只刷新Java数组,不释放C++层数组
     * JNI_ABORT:   只释放C++层数组
     */
 env->ReleaseIntArrayElements(ints, jintArray, 0);
一般情况用第一种,没有特殊需求不需要关心其他的。
  • 转换成c的String: env->GetStringUTFChars(j_str, NULL)
    const char* GetStringUTFChars(jstring string, jboolean* isCopy)
    参数isCopy :
    1,如果B是原始字符串java.lang.String的一份拷贝,则isCopy 被赋值为JNI_TRUE。
    2,如果B是和原始字符串指向的是JVM中的同一份数据,则isCopy 被赋值为JNI_FALSE。当isCopy 为JNI_FALSE时,本地代码绝不能修改字符串的内容,否则JVM中的原始字符串也会被修改,这会打破Java语言中字符串不可变的规则。
    3,通常,我们不必关心JVM是否会返回原始字符串的拷贝,只需要为isCopy传递NULL作为参数。

  • 释放jstring
    env->ReleaseStringUTFChars

  • jarray ,jintArray 等Array都是封装_jarray 都是_jobject

操作对象,传递引用类型,传递对象

Java

// 传递引用类型,传递对象
    public native void putObject(Student student, String str);
// 点击事件:操作putObject函数
    public void test02putObject(View view) {
        Student student = new Student();
        student.name = "史泰龙";
        student.age = 88;
        putObject(student, "九阳神功");
    }

Native

extern "C"
JNIEXPORT void JNICALL
Java_top_zcwfeng_jni_JavaJNIActivity_putObject(JNIEnv *env, jobject thiz, jobject student,
                                               jstring str) {
    const char * strChar = env->GetStringUTFChars(str, NULL);
    LOGI("strChar:%s\n", strChar);
    env->ReleaseStringUTFChars(str, strChar);
    // --------------
    // 1.寻找类 Student
//    env->FindClass("top/zcwfeng/jni/Student");
    jclass student_class = env->GetObjectClass(student);
    // 2.Student类里面的函数规则  签名
    jmethodID setName = env->GetMethodID(student_class,"setName", "(Ljava/lang/String;)V");
    jmethodID getName = env->GetMethodID(student_class,"getName", "()Ljava/lang/String;");
    jmethodID showInfo = env->GetStaticMethodID(student_class,"showInfo", "(Ljava/lang/String;)V");
    // 3.调用 setName
    jstring value = env->NewStringUTF("我是Student测试");
    env->CallVoidMethod(student,setName,value);
    // 4.调用 getName
    jstring nameResult = static_cast(env->CallObjectMethod(student, getName));
    const char * getNameResult = env->GetStringUTFChars(nameResult,NULL);
    LOGE("调用到getName方法,值是:%s\n", getNameResult);
    // 5.调用静态showInfo
    jstring info = env->NewStringUTF("静态方法Student:我是C++");
    env->CallStaticVoidMethod(student_class,showInfo,info);
}
  • 好习惯,用完及时释放,不会因为一直在方法结束前占用空间,导致可能的崩溃。
    GetStringUTFChars<------>ReleaseStringUTFChars 一般都是成对出现
  • jclass 查找两种方式
    ① 全局: env->FindClass("top/zcwfeng/jni/Student")
    ② 通过传递的对象:env->GetObjectClass(student)
  • GetMethodID,GetStaticMethodID,CallStaticVoidMethod,CallVoidMethod 使用

操作对象 C++ 创建实例

Java

    // 没传如对象,直接创建Java对象
    public native void insertObject();

// 点击事件:操作insertObject函数
    public void test03insertObject(View view) {
        insertObject();
    }

Native

extern "C"
JNIEXPORT void JNICALL
Java_top_zcwfeng_jni_JavaJNIActivity_insertObject(JNIEnv *env, jobject thiz) {
    // 1.通过包名+类名的方式 拿到 Student class  凭空拿class
    jclass student_class = env->FindClass("top/zcwfeng/jni/Student");
    // 2.通过student的class  实例化此Student对象   C++ new Student,
    // AllocObject 只实例化对象,不会调用对象的构造函数
    //    env->NewObject()  实例化对象,会调用对象的构造函数
    jobject student_obj = env->AllocObject(student_class);
    // 3.方法签名的规则
    jmethodID setName = env->GetMethodID(student_class, "setName", "(Ljava/lang/String;)V");
    jmethodID setAge = env->GetMethodID(student_class, "setAge", "(I)V");
    // 调用方法
    jstring strvalue = env->NewStringUTF("David,zcwfeng");
    env->CallVoidMethod(student_obj, setName, strvalue);
    env->CallVoidMethod(student_obj, setAge, 100);

    // ====================  下面是 Person对象  调用person对象的  setStudent 函数等
    // 4.通过包名+类名的方式 拿到 Student class  凭空拿class
    const char *personp = "top/zcwfeng/jni/Person";
    jclass person_class = env->FindClass(personp);
    // AllocObject 只实例化对象,不会调用对象的构造函数
    jobject jobj = env->AllocObject(person_class);
    // setStudent 此函数的 签名 规则
    jmethodID jmethodId = env->GetMethodID(person_class, "setStudent",
                                           "(Ltop/zcwfeng/jni/Student;)V");
    env->CallVoidMethod(jobj,jmethodId,student_obj);
}
  • 在C层创建Java对象,并且赋值

全局引用 局部引用理解

Java

// 测试引用
    public native void testQuote();
    // 释放全局引用
    public native void delQuote();


 // 点击事件:两个函数是一起的,操作引用 与 释放引用
    public void test04Quote(View view) {
        testQuote();
    }
    public void test05DelQuote(View view) {
        delQuote();
    }
  
  // 正常在声明周期destroy 释放
    @Override
    protected void onDestroy() {
        super.onDestroy();
        delQuote();
    }

Native

// 在java中这是全局引用,在c中仍然是局部引用
jclass dogClass;
extern "C"
JNIEXPORT void JNICALL
Java_top_zcwfeng_jni_JavaJNIActivity_testQuote(JNIEnv *env, jobject thiz) {
    if (dogClass == NULL) {
//        dogClass = env->FindClass("top/zcwfeng/jni/Dog");

        //!!!!!!
        // 升级全局引用: JNI函数结束也不释放,必须手动释放
        // 相当于: C++ 对象 new、手动delete
        jclass temp = env->FindClass("top/zcwfeng/jni/Dog");
        dogClass = static_cast(env->NewGlobalRef(temp));
        // 注意!!!:用完了,如果不用了,马上释放
        env->DeleteGlobalRef(temp);
    }

    jmethodID init = env->GetMethodID(dogClass, "", "()V");
    jobject dog = env->NewObject(dogClass,init);

    init = env->GetMethodID(dogClass, "", "(I)V");
    dog = env->NewObject(dogClass,init,101);

    init = env->GetMethodID(dogClass, "", "(II)V");
    dog = env->NewObject(dogClass,init,100,200);

    init = env->GetMethodID(dogClass, "", "(III)V");
    dog = env->NewObject(dogClass,init,301,401,501);

    env->DeleteLocalRef(dog);
}
  • 局部引用 DeleteLocalRef
jclass dogClass;

Java_top_zcwfeng_jni_JavaJNIActivity_testQuote(JNIEnv *env, jobject thiz) {
...
 if (dogClass == NULL) {
 dogClass = env->FindClass("top/zcwfeng/jni/Dog");
}
...
 env->DeleteLocalRef(dog);
...
}


// 手动释放全局引用
extern "C"
JNIEXPORT void JNICALL
Java_top_zcwfeng_jni_JavaJNIActivity_delQuote(JNIEnv *env, jobject thiz) {
    if (dogClass != NULL) {
        LOGE("全局引用释放完毕,上面的按钮已经失去全局引用,再次点击会报错");
        env->DeleteGlobalRef(dogClass);
        // 最好给一个NULL,指向NULL的地址,避免悬空或者野指针
        dogClass = NULL;
    }
}

因为局部引用释放掉,再次调用的时候dogClass不是NULL,是个悬空指针,所以会出问题。
解决方法就是升级为全局

  • 全局引用----》NewGlobalRef----》DeleteGlobalRef
      
        // 升级全局引用: JNI函数结束也不释放,必须手动释放
        // 相当于: C++ 对象 new、手动delete
        jclass temp = env->FindClass("top/zcwfeng/jni/Dog");
        dogClass = static_cast(env->NewGlobalRef(temp));
        env->DeleteGlobalRef(temp);
  • 全局引用需要我们手动释放,保持及时释放的习惯。

external 的用法

a.cpp

// 非常方便,可以使用了
extern int age; // 声明age
extern void show(); // 声明show函数

在a.cpp 方法里直接调用show(). 实现卸载b.cpp 中

相当于头文件功能,但是比头文件方便了很多。但是有时候不建议这么做。看设计的透明性。
b.cpp

#include 

// 日志输出
#include 

#define TAG "native_zcw_test"
// __VA_ARGS__ 代表 ...的可变参数
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG,  __VA_ARGS__);
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG,  __VA_ARGS__);
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG,  __VA_ARGS__);

int age = 999; // 实现

void show() { // 实现

    LOGI("show run age:%d\n", age);
}

你可能感兴趣的:(细说JNI与NDK(二)基本操作)