细说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->ReleaseStringUTFCharsjarray ,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);
}