Android JNI 实践基础(二) - 引用,线程,Bitmap

615 阅读5分钟

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

image.png

但其实我们只要加入这一段代码。即可实现 类似于try-catch的效果

jthrowable exc = env->ExceptionOccurred();
if (exc) {
    env->ExceptionDescribe();
    env->ExceptionClear();
}

image.png

但其实上面的方法还不够治本,我们发现异常以后 一定要抛出对应的异常 让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(&notifyHandle, 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 翻转一下。

image.png

先生成一个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;


}