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

第一种方法

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

其实很简单:在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 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 *变量进行存放回调接口。

上面的gif图是用了第二种方案的map,jni回调所有的方法基本都在这里了。

更多相关文章

  1. Android单元测试,模拟http的get和post请求,服务器脚本使用php
  2. Android(安卓)xutil3.0完全解析
  3. Android线程中Handle的使用
  4. Android(安卓)---- WebView与JavaScript交互调用(2)
  5. Android——Fragment介绍及两种基本使用方法
  6. 基于Android中获取资源的id和url方法总结
  7. 使用handler更新UI
  8. Android(安卓)intent.setFlags方法中的参数值含义
  9. Android(安卓)AsyncTask实现

随机推荐

  1. Amazon 的平板能否威胁 Google
  2. Android(安卓)Handler 异步消息处理机制
  3. 在Android中实现service动态更新UI界面
  4. android Looper、Handler和MessageQueue
  5. CentOS 7下使用RPM安装mysql5.7.13
  6. MySQL5.7如何修改root密码
  7. Mac OS10.11下mysql5.7.12 安装配置方法
  8. Mac下mysql 5.7.17 安装配置方法图文教程
  9. MySQL 5.7.13 源码编译安装配置方法图文
  10. CentOS7下MySQL5.7安装配置方法图文教程(Y