1. 程式人生 > >EasyDarwin安卓直播之EasyPusher NDK開發:JNI回撥函式的實現

EasyDarwin安卓直播之EasyPusher NDK開發:JNI回撥函式的實現

最近在做EasyDarwin的EasyPusher手機直播專案開發時涉及到JNI回撥,今日便研究了一下,跟native呼叫Java層的程式碼不同,此文說的是直接通過setCallback的方式去實現回撥:

先看一下載入so庫的程式碼,我就直接在Activity中使用了:

package org.easydarwin.hellojni;

import android.app.Activity;
import android.util.Log;
import android.os.Bundle;

public class HelloJni extends Activity {

    @Override
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setValueSetCallback(new OnValueSetCallback() { @Override public void onValueSet(String value) { Log.i("callback__", "callback__" + value); } }); setValue("1234"
); setValue("12345"); setValue("123456"); } /** * 回撥介面 */ public interface OnValueSetCallback { void onValueSet(String value); } /** * 設定回撥函式 */ public native void setValueSetCallback(OnValueSetCallback callback); public native void
setValue(String value); static { System.loadLibrary("hello-jni"); } }

上面的檔案是直接在ndk的hello-jni demo的基礎上修改的。OnValueSetCallback就是事件的回撥介面,通過setValueSetCallback設定回撥函式,onValueSet為具體回撥處理方法,這裡測試的時候是將通過setValue方法set的value在callback中打印出來,來證明回撥事件呼叫成功了。

下面看具體的native層程式碼:

#include <pthread.h>
#include <jni.h>
#include <stdio.h>

#define _JNI_VERSION JNI_VERSION_1_4
#define THREAD_NAME "lib_hello_jni"

JNIEnv *jni_get_env(const char *name);

/*
 * Pointer to the Java virtual machine
 * Note: It's okay to use a static variable for the VM pointer since there
 * can only be one instance of this shared library in a single VM
 */
static JavaVM *myVm;

static pthread_key_t jni_env_key;

static jobject callback_obj = NULL;

/*
 * This function is called when a thread attached to the Java VM is canceled or
 * exited
 */
static void jni_detach_thread(void *data) {
    (*myVm)->DetachCurrentThread(myVm);
}

JNIEnv *jni_get_env(const char *name) {
    JNIEnv *env;

    env = pthread_getspecific(jni_env_key);
    if (env == NULL) {
        if ((*myVm)->GetEnv(myVm, (void **) &env, _JNI_VERSION) != JNI_OK) {
            /* attach the thread to the Java VM */
            JavaVMAttachArgs args;
            jint result;

            args.version = _JNI_VERSION;
            args.name = name;
            args.group = NULL;

            if ((*myVm)->AttachCurrentThread(myVm, &env, &args) != JNI_OK)
                return NULL;

            /* Set the attached env to the thread-specific data area (TSD) */
            if (pthread_setspecific(jni_env_key, env) != 0) {
                (*myVm)->DetachCurrentThread(myVm);
                return NULL;
            }
        }
    }

    return env;
}

/**
 * java檔案在執行System.loadLibrary("hello-jni"); 的時候會呼叫此方法
 */
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env = NULL;
    // Keep a reference on the Java VM.
    myVm = vm;

    if ((*vm)->GetEnv(vm, (void **) &env, _JNI_VERSION) != JNI_OK)
        return -1;

    /* Create a TSD area and setup a destroy callback when a thread that
     * previously set the jni_env_key is canceled or exited */
    if (pthread_key_create(&jni_env_key, jni_detach_thread) != 0)
        return -1;

    return _JNI_VERSION;
}


void Java_com_example_hellojni_HelloJni_setValueSetCallback(JNIEnv *env, jobject thiz,jobject callback) {
    if (callback_obj != NULL)
        (*env)->DeleteGlobalRef(env, callback_obj);
    //callback就是java層傳進來的回撥事件,在這裡賦值給一個全域性變數
    callback_obj = callback ? (*env)->NewGlobalRef(env, callback) : NULL;
}

//內部回撥處理函式
void jni_callback(jstring value) {
    JNIEnv *env;
    if (!(env = jni_get_env(THREAD_NAME)))
        return;
    if (callback_obj == NULL) {
        return;
    }
    //GetObjectClass是通過傳入jni中的一個java的引用來獲取該引用的型別。
    //FindClass是通過傳java中完整的類名來查詢java的class,
    jclass cls = (*env)->GetObjectClass(env, callback_obj);
    //根據class以及方法名、引數資訊獲取medthodId
    //onValueSet就是方法名稱,(Ljava/lang/String;)V中的Ljava/lang/String;代表該方法的引數型別,V代表Void,是方法返回型別
    //如果一個方法名稱為abc,有兩個int引數,返回值為String則應該寫作:(*env)->GetMethodID(env, cls, "abc", "(II)Ljava/lang/String")
    jmethodID methodId = (*env)->GetMethodID(env, cls, "onValueSet", "(Ljava/lang/String;)V");
    //根據methodId執行這個方法,最後的value是引數,注意這個value必須為jstring型別的,否則報錯
    (*env)->CallVoidMethod(env, callback_obj, methodId, value);
    //清除cls引用
    (*env)->DeleteLocalRef(env, cls);
}

void Java_com_example_hellojni_HelloJni_setValue(JNIEnv *env, jobject thiz, jstring value) {
//    char *tmp = (*env)->GetStringUTFChars(env, value, NULL);
    jni_callback(value);
}

在這個例子中不設定JavaVM *myVm這個全域性變數也可以,直接將setValue中的env變數傳給jni_callback也可以,這裡全域性變數是應對非同步操作的情況。

java型別對應的型別簽名列表:

java型別 型別簽名
boolean Z
byte B
char C
short S
int I
long L
float F
double B
L全限定名;,比如String, 其簽名為Ljava/lang/util/String;
陣列 [型別簽名, 比如 [B