杭州Android培训
达内杭州Android培训中心

13732203138

热门课程

JNI回调的三种方法

  • 时间:2018-05-25 15:03
  • 发布:杭州android中心
  • 来源:疑难解答

第一种方法:

在当前函数(同一个线程)里面回调,直接用findClass或者GetObjectClass,进行回调(国内各大博客介绍的普遍方法):

java 层代码:

/**

* Created by jiong103 on 2017/3/23.

*/

public class Sdk {

private Sdk() {

}

//单例

private static class SdkHodler {

static Sdk instance = new Sdk();

}

public static Sdk getInstance() {

return SdkHodler.instance;

}

//调到C层的方法

private native void nativeDownload();

//c层回调上来的方法

private int onProgressCallBack(long total, long already) {

//自行执行回调后的操作

System.out.println("total:"+total);

System.out.println("already:"+already);

return 1;

}

}

c层代码:

JNIEXPORT void JNICALL Java_xxx_nativeDownload(JNIEnv *env, jobject thiz) {

//直接用GetObjectClass找到Class, 也就是Sdk.class.

jcalss jSdkClass =(*env)->GetObjectClass(env,thiz);

if (jSdkClass == 0) {

LOG("Unable to find class");

return;

}

//找到需要调用的方法ID

jmethodID javaCallback = (*env)->GetMethodID(env, jSdkClass,

"onProgressCallBack", "(JJ)I");

//进行回调,ret是java层的返回值(这个有些场景很好用)

jint ret = (*env)->CallIntMethod(env, thiz, javaCallback,1,1);

return ;

}

或者是:

JNIEXPORT void JNICALL Java_xxx_nativeDownload(JNIEnv *env, jobject thiz) {

//直接用findClass找到Class, 也就是Sdk.class.

jcalss jSdkClass =(*env)->FindClass(env,"your/package/name/Sdk");

if (jSdkClass == 0) {

LOG("Unable to find class");

return;

}

//找到需要调用的方法ID

jmethodID javaCallback = (*env)->GetMethodID(env, jSdkClass,

"onProgressCallBack", "(JJ)I");

//这时候要回调还没有jobject,那就new 一个

jmethodID sdkInit = (*env)->GetMethodID(env, jSdkClass,"<init>","()V");

jobject jSdkObject = (*env)->NewObject(env,jSdkClass,sdkInit);

//进行回调,ret是java层的返回值(这个有些场景很好用)

jint ret = (*env)->CallIntMethod(env, jSdkObject, javaCallback,1,1);

return ;

}

好了运行函数:

Sdk.getInstance().nativeDownload();

结果就出来了:

total:1

already:1

好了第一种讲述完毕,有些人肯定会说,这尼玛坑爹, 写了一大堆东西就实现一个这么鸡肋的功能, 还在当前的函数回调。 那我还不如直接return一个值更加方便, 是的没错, 这就是网上最普遍的一种回调方法, 压根没法投入项目用。

好了兄弟别激动

我再介绍一种你看看:

第二种方法:

在其他线程里面回调到java层,通过NewGlobalRef,保存全局变量(Stack Overflow 介绍的方法):

java层代码:

/**

* Created by jiong103 on 2017/3/23.

*/

public class Sdk {

private Sdk() {

}

//单例

private static class SdkHodler {

static Sdk instance = new Sdk();

}

public static Sdk getInstance() {

return SdkHodler.instance;

}

//调到C层的方法

private native void nativeDownload();

//c层回调上来的方法

private int onProgressCallBack(long total, long already) {

//自行执行回调后的操作

System.out.println("total:"+total);

System.out.println("already:"+already);

return 1;

}

}

c层代码:

JavaVM *g_VM;

jobject g_obj;

JNIEXPORT void JNICALL Java_xxx_nativeDownload(JNIEnv *env, jobject thiz) {

//JavaVM是虚拟机在JNI中的表示,等下再其他线程回调java层需要用到

(*env)->GetJavaVM(env, &g_VM);

// 生成一个全局引用保留下来,以便回调

g_obj = (*env)->NewGlobalRef(env, thiz);

// 此处使用c语言开启一个线程,进行回调,这时候java层就不会阻塞,只是在等待回调

pthread_create(xxx,xxx, download,NULL);

return ;

}

//在此处跑在子线程中,并回调到java层

void download(void *p) {

JNIEnv *env;

//获取当前native线程是否有没有被附加到jvm环境中

int getEnvStat = (*g_VM)->GetEnv(g_VM, (void **) &env,JNI_VERSION_1_6);

if (getEnvStat == JNI_EDETACHED) {

//如果没有, 主动附加到jvm环境中,获取到env

if ((*g_VM)->AttachCurrentThread(g_VM, &env, NULL) != 0) {

return;

}

mNeedDetach = JNI_TRUE;

}

//通过全局变量g_obj 获取到要回调的类

jclass javaClass = (*env)->GetObjectClass(env, g_obj);

if (javaClass == 0) {

LOG("Unable to find class");

(*g_VM)->DetachCurrentThread(g_VM);

return;

}

//获取要回调的方法ID

jmethodID javaCallbackId = (*env)->GetMethodID(env, jSdkClass,

"onProgressCallBack", "(JJ)I");

if (javaCallbackId == NULL) {

LOGD("Unable to find method:onProgressCallBack");

return;

}

//执行回调

(*env)->CallIntMethod(env, g_obj, javaCallbackId,1,1);

//释放当前线程

if(mNeedDetach) {

(*g_VM)->DetachCurrentThread(g_VM);

}

env = NULL;

}

好了运行函数:

Sdk.getInstance().nativeDownload();

结果又出来了:

total:1

already:1

好了第二种讲述完毕, 是不是感觉第二种还真有点靠谱了,在和C语言同事开发的时候这东西,还真能派上用场。

那么有同学问了我在新线程里的void download(void *p), 直接用findClass,直接找到类进行回调不就行了吗,干嘛要保存为一个全局变量。 我只能说jni不允许这么干, 你这么干是find到的class直接为空, 从而无法回调!

可是又有同学问了,如果我的需求场景是这样子呢:

多线程任务下载,然后需要回调进度,那么多的线程都一并回调到

onProgressCallBack

这一个函数,我