JNI 引用类型处理
主要讲下,局部引用和全局引用吧,这两种很常用,而且一般很难用错
public native String errorCacheLocalReference();
public native String cacheWithGlobalReference();
extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_ndktest_JNIDynamicLoad_errorCacheLocalReference(JNIEnv *env, jobject thiz) {
// 局部引用 一般函数结束时会自动释放的 但是前提条件时局部引用不能太多
// 如果 你的函数体内 有 for循环创建引用的建议使用完毕以后 env->DeleteLocalRef(localRefs); 来释放
// 否则会有崩溃的
jclass localRefs = env->FindClass("java/lang/String");
jmethodID mid = env->GetMethodID(localRefs,"<init>","(Ljava/lang/String;)V");
jstring str = env->NewStringUTF("哈哈哈-局部引用");
return static_cast<jstring>(env->NewObject(localRefs, mid, str));
}
extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_ndktest_JNIDynamicLoad_cacheWithGlobalReference(JNIEnv *env, jobject thiz) {
// 全局引用 来做缓存
static jclass stringClass = nullptr;
if (stringClass== nullptr){
jclass cls = env->FindClass("java/lang/String");
stringClass = static_cast<jclass>(env->NewGlobalRef(cls));
env->DeleteLocalRef(cls);
}else{
LOGD("use cached");
}
jmethodID mid = env->GetMethodID(stringClass,"<init>","(Ljava/lang/String;)V");
jstring str = env->NewStringUTF("哈哈哈-全局引用");
return static_cast<jstring>(env->NewObject(stringClass, mid, str));
}
另外还有一个弱引用 就不演示了,有兴趣的可以自行搜索下。
异常处理
有时候我们需要在c++中 调用java的方法,但这个方法有可能抛出异常 那该怎么办呢?
public native void nativeInvokeJavaException();
private int operation(){
return 2/0;
}
例如 我直接这么写,模拟调用这个operation方法
extern "C"
JNIEXPORT void JNICALL
Java_com_example_ndktest_JNIDynamicLoad_nativeInvokeJavaException(JNIEnv *env, jobject thiz) {
jclass cls = env->FindClass("com/example/ndktest/JNIDynamicLoad");
jmethodID mid = env->GetMethodID(cls, "operation", "()I");
jmethodID cons = env->GetMethodID(cls, "<init>", "()V");
jobject obj = env->NewObject(cls, cons);
env->CallIntMethod(obj, mid);
}
程序显然会crash
但其实我们只要加入这一段代码。即可实现 类似于try-catch的效果
jthrowable exc = env->ExceptionOccurred();
if (exc) {
env->ExceptionDescribe();
env->ExceptionClear();
}
但其实上面的方法还不够治本,我们发现异常以后 一定要抛出对应的异常 让java层知道 到底是啥原因出了故障了,
public native void nativeThrowException() throws IllegalArgumentException;
抛出异常 即可
extern "C"
JNIEXPORT void JNICALL
Java_com_example_ndktest_JNIDynamicLoad_nativeThrowException(JNIEnv *env, jobject thiz) {
jclass cls = env->FindClass("java/lang/IllegalArgumentException");
env->ThrowNew(cls,"native throw exception");
}
JNI线程操作
先看一组最简单的 创建一个native线程
public native void createNativeThread();
void *printThreadHello(void *){
LOGD("hello thread");
return nullptr;
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_ndktest_JNIDynamicLoad_createNativeThread(JNIEnv *env, jobject thiz) {
pthread_t hanlder;
// 第二个参数 其实就是描述线程的,优先级啥的,我们一般不设置
// 第四个参数 是传递函数参数的
int result = pthread_create(&hanlder, nullptr,printThreadHello, nullptr);
if (result == 0){
LOGD("create thread success");
}else{
LOGD("create thread failed");
}
}
给线程传递参数的方式
public native void createNativeThreadWithArgs();
struct ThreadRunArgs {
int id;
int result;
};
void *printThreadArgs(void *arg) {
ThreadRunArgs *args = static_cast<ThreadRunArgs *>(arg);
LOGD("hello printThreadArgs id is %d", args->id);
LOGD("hello printThreadArgs result is %d", args->result);
return nullptr;
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_ndktest_JNIDynamicLoad_createNativeThreadWithArgs(JNIEnv *env, jobject thiz) {
pthread_t hanlder;
ThreadRunArgs *args = new ThreadRunArgs;
args->id = 2;
args->result = 100;
int result = pthread_create(&hanlder, nullptr, printThreadArgs, args);
if (result == 0) {
LOGD("create thread success");
} else {
LOGD("create thread failed");
}
}
另外线程退出还有如下方式
void *printThreadArgs(void *arg) {
ThreadRunArgs *args = static_cast<ThreadRunArgs *>(arg);
LOGD("hello printThreadArgs id is %d", args->id);
LOGD("hello printThreadArgs result is %d", args->result);
pthread_exit(0);
}
还有一种场景,我们希望 在native中 异步执行一段耗时操作 然后主线程把结果回调出去
这种我们其实用join就好
void *printThreadJoin(void *arg) {
ThreadRunArgs *args = static_cast<ThreadRunArgs *>(arg);
struct timeval begin;
gettimeofday(&begin, nullptr);
sleep(3);
struct timeval end;
gettimeofday(&end, nullptr);
LOGD("Time used %d", end.tv_sec-begin.tv_sec);
return reinterpret_cast<void *>(args->result);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_ndktest_JNIDynamicLoad_joinNativeThread(JNIEnv *env, jobject thiz) {
pthread_t hanlder;
ThreadRunArgs *args = new ThreadRunArgs;
args->id = 2;
args->result = 100;
int result = pthread_create(&hanlder, nullptr, printThreadJoin, args);
if (result == 0) {
LOGD("create thread success");
} else {
LOGD("create thread failed");
}
void *ret = nullptr;
pthread_join(hanlder,&ret);
LOGD("result is %d",ret);
}
线程的同步
严格来说,线程的同步关系哪个语言都有,如果你需要用到,再深入学习即可。 这里就简单实现2个函数, 一个线程唤醒另外一个线程的基本操作
pthread_mutex_t mutex;
pthread_cond_t cond;
pthread_t waitHandle;
pthread_t notifyHandle;
int flag = 0;
void *waitThread(void *) {
pthread_mutex_lock(&mutex);
while (flag == 0) {
LOGD("waiting");
// 这里wait调用以后 这个mutex锁 其实就暂时"释放"掉了
pthread_cond_wait(&cond, &mutex);
}
LOGD("wait thread unlock");
pthread_mutex_unlock(&mutex);
pthread_exit(0);
}
void *notifyThread(void *) {
pthread_mutex_lock(&mutex);
flag = 1;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
LOGD("notify thread unlock");
pthread_exit(0);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_ndktest_JNIDynamicLoad_waitNativeThread(JNIEnv *env, jobject thiz) {
pthread_mutex_init(&mutex, nullptr);
pthread_cond_init(&cond, nullptr);
pthread_create(&waitHandle, nullptr, waitThread, nullptr);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_ndktest_JNIDynamicLoad_notifyNativeThread(JNIEnv *env, jobject thiz) {
pthread_create(¬ifyHandle, nullptr, notifyThread, nullptr);
}
JNI bitmap操作
native层 操作bitmap 首先要修改一下cmake文件
target_link_libraries(dynamic-lib
# List libraries link to the target library
android
jnigraphics
log)
主要就是 引入 jnigraphics 这个库
可以实现下面一个效果 让这个马里奥的bitmap 翻转一下。
先生成一个bitmap对象,这里其实就是反向在native代码里面 调用java的 类方式 生成一个bitmap对象
jobject generateBitmap(JNIEnv *env, uint32_t width, uint32_t height) {
jclass bitmapCls = env->FindClass("android/graphics/Bitmap");
jmethodID createBitmapFunction = env->GetStaticMethodID(bitmapCls,
"createBitmap",
"(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
jstring configName = env->NewStringUTF("ARGB_8888");
jclass bitmapConfigClass = env->FindClass("android/graphics/Bitmap$Config");
jmethodID valueOfBitmapConfigFunction = env->GetStaticMethodID(
bitmapConfigClass, "valueOf",
"(Ljava/lang/String;)Landroid/graphics/Bitmap$Config;");
jobject bitmapConfig = env->CallStaticObjectMethod(bitmapConfigClass,
valueOfBitmapConfigFunction, configName);
jobject newBitmap = env->CallStaticObjectMethod(bitmapCls,
createBitmapFunction,
width,
height, bitmapConfig);
return newBitmap;
}
有了这个方法以后。就可以尝试 在native层,来反转bitmap了,其实本质上说 bitmap就是一个2维数组
我们反转的思路就是
extern "C"
JNIEXPORT jobject JNICALL
Java_com_example_ndktest_JNIDynamicLoad_callNativeMirrorBitmap(JNIEnv *env, jobject thiz,
jobject bitmap) {
AndroidBitmapInfo bitmapInfo;
int ret;
if ((ret = AndroidBitmap_getInfo(env, bitmap, &bitmapInfo)) < 0) {
LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
return NULL;
}
LOGD("Width is %d",bitmapInfo.width);
LOGD("height is %d",bitmapInfo.height);
// 读取 bitmap 的像素内容到 native 内存
void *bitmapPixels;
if ((ret = AndroidBitmap_lockPixels(env, bitmap, &bitmapPixels)) < 0) {
LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
return NULL;
}
uint32_t newWidth = bitmapInfo.width;
uint32_t newHeight = bitmapInfo.height;
uint32_t *newBitmapPixels = new uint32_t[newWidth * newHeight];
int whereToGet = 0;
// 弄明白 bitmapPixels 的排列,这里不同于二维数组了。
for (int y = 0; y < newHeight; ++y) {
for (int x = newWidth - 1; x >= 0; x--) {
uint32_t pixel = ((uint32_t *) bitmapPixels)[whereToGet++];
newBitmapPixels[newWidth * y + x] = pixel;
}
}
AndroidBitmap_unlockPixels(env,bitmap);
jobject newBitmap = generateBitmap(env, newWidth, newHeight);
void *resultBitmapPixels;
AndroidBitmap_lockPixels(env,newBitmap,&resultBitmapPixels);
int pixelsCount = newWidth * newHeight;
memcpy((uint32_t *)resultBitmapPixels,newBitmapPixels, sizeof(uint32_t) * pixelsCount);
AndroidBitmap_unlockPixels(env,newBitmap);
delete [] newBitmapPixels;
return newBitmap;
}