当前位置:   article > 正文

Android JNI 篇 - JNI回调的三种方法(精华篇 ndk)_jni: unable to find method

jni: unable to find method

1、一般在JNI_OnLoad方法中保存JavaVM

2、之后新建一个初始化方法保存调用者jjobject,注意保存时要使用方法把局部变量“升级”为全局变量

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

自己的例子是:MyWebrtc

-----------------------------------------------------------------

https://www.jianshu.com/p/e576c7e1c403

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

device-2017-03-23-184023.gif

第一种方法

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

java 层代码:

  1. /**
  2. * Created by jiong103 on 2017/3/23.
  3. */
  4. public class Sdk {
  5. private Sdk() {
  6. }
  7. //单例
  8. private static class SdkHodler {
  9. static Sdk instance = new Sdk();
  10. }
  11. public static Sdk getInstance() {
  12. return SdkHodler.instance;
  13. }
  14. //调到C层的方法
  15. private native void nativeDownload();
  16. //c层回调上来的方法
  17. private int onProgressCallBack(long total, long already) {
  18. //自行执行回调后的操作
  19. System.out.println("total:"+total);
  20. System.out.println("already:"+already);
  21. return 1;
  22. }
  23. }

c层代码:

  1. JNIEXPORT void JNICALL Java_xxx_nativeDownload(JNIEnv *env, jobject thiz) {
  2. //直接用GetObjectClass找到Class, 也就是Sdk.class.
  3. jcalss jSdkClass =(*env)->GetObjectClass(env,thiz);
  4. if (jSdkClass == 0) {
  5. LOG("Unable to find class");
  6. return;
  7. }
  8. //找到需要调用的方法ID
  9. jmethodID javaCallback = (*env)->GetMethodID(env, jSdkClass,
  10. "onProgressCallBack", "(JJ)I");
  11. //进行回调,ret是java层的返回值(这个有些场景很好用)
  12. jint ret = (*env)->CallIntMethod(env, thiz, javaCallback,1,1);
  13. return ;
  14. }
  15. 或者是:
  16. JNIEXPORT void JNICALL Java_xxx_nativeDownload(JNIEnv *env, jobject thiz) {
  17. //直接用findClass找到Class, 也就是Sdk.class.
  18. jcalss jSdkClass =(*env)->FindClass(env,"your/package/name/Sdk");
  19. if (jSdkClass == 0) {
  20. LOG("Unable to find class");
  21. return;
  22. }
  23. //找到需要调用的方法ID
  24. jmethodID javaCallback = (*env)->GetMethodID(env, jSdkClass,
  25. "onProgressCallBack", "(JJ)I");
  26. //这时候要回调还没有jobject,那就new 一个
  27. jmethodID sdkInit = (*env)->GetMethodID(env, jSdkClass,"<init>","()V");
  28. jobject jSdkObject = (*env)->NewObject(env,jSdkClass,sdkInit);
  29. //进行回调,ret是java层的返回值(这个有些场景很好用)
  30. jint ret = (*env)->CallIntMethod(env, jSdkObject, javaCallback,1,1);
  31. return ;
  32. }

好了运行函数:

Sdk.getInstance().nativeDownload();

结果就出来了:

  1. total:1
  2. already:1

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

2.png

我再介绍一种你看看:

第二种

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

java层代码:

  1. /**
  2. * Created by jiong103 on 2017/3/23.
  3. */
  4. public class Sdk {
  5. private Sdk() {
  6. }
  7. //单例
  8. private static class SdkHodler {
  9. static Sdk instance = new Sdk();
  10. }
  11. public static Sdk getInstance() {
  12. return SdkHodler.instance;
  13. }
  14. //调到C层的方法
  15. private native void nativeDownload();
  16. //c层回调上来的方法
  17. private int onProgressCallBack(long total, long already) {
  18. //自行执行回调后的操作
  19. System.out.println("total:"+total);
  20. System.out.println("already:"+already);
  21. return 1;
  22. }
  23. }

c层代码:

  1. JavaVM *g_VM;
  2. jobject g_obj;
  3. JNIEXPORT void JNICALL Java_xxx_nativeDownload(JNIEnv *env, jobject thiz) {
  4. //JavaVM是虚拟机在JNI中的表示,等下再其他线程回调java层需要用到
  5. (*env)->GetJavaVM(env, &g_VM);
  6. // 生成一个全局引用保留下来,以便回调
  7. g_obj = (*env)->NewGlobalRef(env, thiz);
  8. // 此处使用c语言开启一个线程,进行回调,这时候java层就不会阻塞,只是在等待回调
  9. pthread_create(xxx,xxx, download,NULL);
  10. return ;
  11. }
  12. //在此处跑在子线程中,并回调到java层
  13. void download(void *p) {
  14. JNIEnv *env;
  15. //获取当前native线程是否有没有被附加到jvm环境中
  16. int getEnvStat = (*g_VM)->GetEnv(g_VM, (void **) &env,JNI_VERSION_1_6);
  17. if (getEnvStat == JNI_EDETACHED) {
  18. //如果没有, 主动附加到jvm环境中,获取到env
  19. if ((*g_VM)->AttachCurrentThread(g_VM, &env, NULL) != 0) {
  20. return;
  21. }
  22. mNeedDetach = JNI_TRUE;
  23. }
  24. //通过全局变量g_obj 获取到要回调的类
  25. jclass javaClass = (*env)->GetObjectClass(env, g_obj);
  26. if (javaClass == 0) {
  27. LOG("Unable to find class");
  28. (*g_VM)->DetachCurrentThread(g_VM);
  29. return;
  30. }
  31. //获取要回调的方法ID
  32. jmethodID javaCallbackId = (*env)->GetMethodID(env, jSdkClass,
  33. "onProgressCallBack", "(JJ)I");
  34. if (javaCallbackId == NULL) {
  35. LOGD("Unable to find method:onProgressCallBack");
  36. return;
  37. }
  38. //执行回调
  39. (*env)->CallIntMethod(env, g_obj, javaCallbackId,1,1);
  40. //释放当前线程
  41. if(mNeedDetach) {
  42. (*g_VM)->DetachCurrentThread(g_VM);
  43. }
  44. env = NULL;
  45. }

好了运行函数:

Sdk.getInstance().nativeDownload();

结果又出来了:

  1. total:1
  2. already:1

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

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

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

多线程任务下载,然后需要回调进度,那么多的线程都一并回调到
onProgressCallBack
这一个函数,我怎么区分数据是属于哪一个线程任务的?

怎么玩:

3.gif

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

java层代码:

  1. /**
  2. * Created by jiong103 on 2017/3/23.
  3. */
  4. public class Sdk {
  5. public Sdk() {
  6. }
  7. //单例
  8. private static class SdkHodler {
  9. static Sdk instance = new Sdk();
  10. }
  11. public static Sdk getInstance() {
  12. return SdkHodler.instance;
  13. }
  14. //回调分发接口
  15. public interface OnSubProgressListener {
  16. public int onProgressChange(long total, long already);
  17. };
  18. private Map<Long, OnSubProgressListener> mMap = new HashMap<>();
  19. //调到C层的方法
  20. private native int nativeDownload(long uid,String downloadPath);
  21. //回调的方法
  22. private int onProgressCallBack(long uid, long total, long already) {
  23. OnSubProgressListener listener = mMap.get(uid);
  24. if(listener != null) {
  25. if(already >= total) {
  26. //下载完成,取消回调
  27. mMap.remove(uid);
  28. } else {
  29. //回调到指定任务去,通过uid辨别
  30. listener.onProgressChange(total,already);
  31. }
  32. }
  33. return 0;
  34. }
  35. public void download(long uid,String downloadPath,OnSubProgressListener l) {
  36. mMap.put(uid,l);
  37. nativeDownload(uid,downloadPath);
  38. }
  39. }

C层代码:

  1. 带着uid 去执行任务,回调时候,把uid 回传到java层上面的
  2. private int onProgressCallBack(long uid, long total, long already);
  3. 就可以区分是哪一个任务,并且取出Map里面存好的OnSubProgressListener接口进行回调
  4. (这部分就不写了, 比较简单, 后面有读者要求我再补上)

好了运行函数:

开启两个下载任务

  1. Sdk.getInstance().download(1,"xxx.jpg",new OnSubProgressListener(){
  2. @Override
  3. public int onProgressChange(long total, long already) {
  4. return 0;
  5. }
  6. });
  7. Sdk.getInstance().download(2,"xxx.png",new OnSubProgressListener(){
  8. @Override
  9. public int onProgressChange(long total, long already) {
  10. return 0;
  11. }
  12. });

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

第三种方法:

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

java层代码:

  1. public class Sdk {
  2. public Sdk() {
  3. }
  4. //单例
  5. private static class SdkHodler {
  6. static Sdk instance = new Sdk();
  7. }
  8. public static Sdk getInstance() {
  9. return SdkHodler.instance;
  10. }
  11. //回调到各个线程
  12. public interface OnSubProgressListener {
  13. public int onProgressChange(long total, long already);
  14. };
  15. //调到C层的方法
  16. private native int nativeDownload(String downloadPath,OnSubProgressListener l);
  17. }

c层代码:

  1. JavaVM *g_VM;
  2. JNIEXPORT void JNICALL Java_xxx_nativeDownload(JNIEnv *env, jobject thiz,jstring jpath,jobject jcallback) {
  3. //JavaVM是虚拟机在JNI中的表示,等下再其他线程回调java层需要用到
  4. (*env)->GetJavaVM(env, &g_VM);
  5. //生成一个全局引用,回调的时候findclass才不会为null
  6. jobject callback = (*env)->NewGlobalRef(env, jcallback)
  7. // 把接口传进去,或者保存在一个结构体里面的属性, 进行传递也可以
  8. pthread_create(xxx,xxx, download,callback);
  9. return ;
  10. }
  11. //在此处跑在子线程中,并回调到java层
  12. void download(void *p) {
  13. if(p == NULL) return ;
  14. JNIEnv *env;
  15. //获取当前native线程是否有没有被附加到jvm环境中
  16. int getEnvStat = (*g_VM)->GetEnv(g_VM, (void **) &env,JNI_VERSION_1_6);
  17. if (getEnvStat == JNI_EDETACHED) {
  18. //如果没有, 主动附加到jvm环境中,获取到env
  19. if ((*g_VM)->AttachCurrentThread(g_VM, &env, NULL) != 0) {
  20. return;
  21. }
  22. mNeedDetach = JNI_TRUE;
  23. }
  24. //强转回来
  25. jobject jcallback = (jobject)p;
  26. //通过强转后的jcallback 获取到要回调的类
  27. jclass javaClass = (*env)->GetObjectClass(env, jcallback);
  28. if (javaClass == 0) {
  29. LOG("Unable to find class");
  30. (*g_VM)->DetachCurrentThread(g_VM);
  31. return;
  32. }
  33. //获取要回调的方法ID
  34. jmethodID javaCallbackId = (*env)->GetMethodID(env, javaClass,
  35. "onProgressChange", "(JJ)I");
  36. if (javaCallbackId == NULL) {
  37. LOGD("Unable to find method:onProgressCallBack");
  38. return;
  39. }
  40. //执行回调
  41. (*env)->CallIntMethod(env, jcallback, javaCallbackId,1,1);
  42. //释放当前线程
  43. if(mNeedDetach) {
  44. (*g_VM)->DetachCurrentThread(g_VM);
  45. }
  46. env = NULL;
  47. //释放你的全局引用的接口,生命周期自己把控
  48. (*env)->DeleteGlobalRef(env, jcallback);
  49. jcallback = NULL;
  50. }

好了运行函数:

  1. Sdk.getInstance().nativeDownload("xx.jpg",new OnSubProgressListener(){
  2. @Override
  3. public int onProgressChange(long total, long already) {
  4. return 0;
  5. }
  6. });
  7. Sdk.getInstance().nativeDownload("xx.png",new OnSubProgressListener(){
  8. @Override
  9. public int onProgressChange(long total, long already) {
  10. return 0;
  11. }
  12. });

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

上面的gif图是用了第二种方案的map,jni回调所有的方法基本都在这里了。
转发请注明链接地址。谢幕!



作者:Overried
链接:https://www.jianshu.com/p/e576c7e1c403
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/133195
推荐阅读
相关标签
  

闽ICP备14008679号