Android培训
美国上市Android培训机构

400-111-8989

热门课程

详述Android JNI回调的三种方法

  • 发布:Overried
  • 来源:安卓巴士Android开发者门户
  • 时间:2018-05-18 15:27

今天我们Android培训班带来的分享是详述Android JNI回调的三种方法,由普通到高级,如果你知道第一种普通的方法,请你将Android JNI回调的方法写下,我们核对答案就行,剩下的就请你细细品阅。

开门见山, 不废话上效果, 上代码: c层回调进度

Android JNI回调的三种方法

Android JNI回调的三种方法

第一种方法:

在当前函数(同一个线程)里面回调,直接用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

这一个函数,我怎么区分数据是属于哪一个线程任务的?

怎么玩:

Picture

Picture

其实很简单:在Java层的Sdk.class类里面 创建一个Map, 通过一个long型的Uid作为key, 去区分线程任务, 回调接口存到value, 这样子key-value保存在Map里面。 当你调用C层方法的时候传相应的uid下去, 处理完毕后, 再把uid作为参数回调到java层的Sdk.class类的onProgressCallBack, 通过Map.get(uid),取出之前存好的对应回调接口, 进行分发回调。 搞定, 上代码:

java层代码:

/**

* Created by jiong103 on 2017/3/23.

*/

public class Sdk {

public Sdk() {

}

//单例

private static class SdkHodler {

static Sdk instance = new Sdk();

}

public static Sdk getInstance() {

return SdkHodler.instance;

}

//回调分发接口

public interface OnSubProgressListener {

public int onProgressChange(long total, long already);

};

private Map<Long, OnSubProgressListener> mMap = new HashMap<>();

//调到C层的方法

private native int nativeDownload(long uid,String downloadPath);

//回调的方法

private int onProgressCallBack(long uid, long total, long already) {

OnSubProgressListener listener = mMap.get(uid);

if(listener != null) {

if(already >= total) {

//下载完成,取消回调

mMap.remove(uid);

} else {

//回调到指定任务去,通过uid辨别

listener.onProgressChange(total,already);

}

}

return 0;

}

public void download(long uid,String downloadPath,OnSubProgressListener l) {

mMap.put(uid,l);

nativeDownload(uid,downloadPath);

}

}

C层代码:

带着uid 去执行任务,回调时候,把uid 回传到java层上面的

private int onProgressCallBack(long uid, long total, long already);

就可以区分是哪一个任务,并且取出Map里面存好的OnSubProgressListener接口进行回调

(这部分就不写了, 比较简单, 后面有读者要求我再补上)

好了运行函数:

开启两个下载任务

Sdk.getInstance().download(1,"xxx.jpg",new OnSubProgressListener(){

@Override

public int onProgressChange(long total, long already) {

return 0;

}

});

Sdk.getInstance().download(2,"xxx.png",new OnSubProgressListener(){

@Override

public int onProgressChange(long total, long already) {

return 0;

}

});

完毕!这样子就会回调到不同的接口中去了, 当然还有更牛逼的方法, 请看第三种。

第三种方法:

通过把接口jobject 传递到c层下面去,然后在c层里面进行回调 ( 和公司写c的同事共同研究出来的方法 ) :

java层代码:

public class Sdk {

public Sdk() {

}

//单例

private static class SdkHodler {

static Sdk instance = new Sdk();

}

public static Sdk getInstance() {

return SdkHodler.instance;

}

//回调到各个线程

public interface OnSubProgressListener {

public int onProgressChange(long total, long already);

};

//调到C层的方法

private native int nativeDownload(String downloadPath,OnSubProgressListener l);

}

c层代码:

JavaVM *g_VM;

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

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

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

//生成一个全局引用,回调的时候findclass才不会为null

jobject callback = (*env)->NewGlobalRef(env, jcallback)

// 把接口传进去,或者保存在一个结构体里面的属性, 进行传递也可以

pthread_create(xxx,xxx, download,callback);

return ;

}

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

void download(void *p) {

if(p == NULL) return ;

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;

}

//强转回来

jobject jcallback = (jobject)p;

//通过强转后的jcallback 获取到要回调的类

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

if (javaClass == 0) {

LOG("Unable to find class");

(*g_VM)->DetachCurrentThread(g_VM);

return;

}

//获取要回调的方法ID

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

"onProgressChange", "(JJ)I");

if (javaCallbackId == NULL) {

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

return;

}

//执行回调

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

//释放当前线程

if(mNeedDetach) {

(*g_VM)->DetachCurrentThread(g_VM);

}

env = NULL;

//释放你的全局引用的接口,生命周期自己把控

(*env)->DeleteGlobalRef(env, jcallback);

jcallback = NULL;

}

好了运行函数:

Sdk.getInstance().nativeDownload("xx.jpg",new OnSubProgressListener(){

@Override

public int onProgressChange(long total, long already) {

return 0;

}

});

Sdk.getInstance().nativeDownload("xx.png",new OnSubProgressListener(){

@Override

public int onProgressChange(long total, long already) {

return 0;

}

});

完毕!是不是少了uid这个参数, 而且少了map去保存你的接口, 优化了好多内存,啊哈哈! 这个是直接把接口传到jni层, 对应的类型是jobject, 在c层传递的这个接口的时候需(*env)->NewGlobalRef(env, jcallback) 生成全局引用进行传递,匹配C语言的void *类型, 那么在与c层交互人员联调的时候,如果使用到回调,需要在c开发人员那边程序代码 预留一个 void *变量进行存放回调接口。

恭喜你阅读完了本文,相信你已经了解了Android JNI回调的三种方法,现在就操作试试吧。如果你还有更多Android相关的问题,欢迎你来达内android培训机构进行咨询。

免责声明:内容和图片源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

预约申请免费试听课

上一篇:Android培训分享之Android基础知识讲解
下一篇:干货分享|iOS和Android 界面设计规范

android中的Rxjava、Retrofit返回json数据解析异常处理

android中webview控件的相关使用详解

android培训分享:android中自定义进度加载工具类的使用

android工程师分享android应用架构,通俗易懂!

选择城市和中心
贵州省

广西省

海南省