開門見山, 不廢話上效果, 上代碼: c層回調(diào)進(jìn)度

第一種方法
在當(dāng)前函數(shù)(同一個線程)里面回調(diào),直接用findClass或者GetObjectClass,進(jìn)行回調(diào)(國內(nèi)各大博客介紹的普遍方法):
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;
}
//調(diào)到C層的方法
private native void nativeDownload();
//c層回調(diào)上來的方法
private int onProgressCallBack(long total, long already) {
//自行執(zhí)行回調(diào)后的操作
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;
}
//找到需要調(diào)用的方法ID
jmethodID javaCallback = (*env)->GetMethodID(env, jSdkClass,
"onProgressCallBack", "(JJ)I");
//進(jìn)行回調(diào),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;
}
//找到需要調(diào)用的方法ID
jmethodID javaCallback = (*env)->GetMethodID(env, jSdkClass,
"onProgressCallBack", "(JJ)I");
//這時候要回調(diào)還沒有jobject,那就new 一個
jmethodID sdkInit = (*env)->GetMethodID(env, jSdkClass,"<init>","()V");
jobject jSdkObject = (*env)->NewObject(env,jSdkClass,sdkInit);
//進(jìn)行回調(diào),ret是java層的返回值(這個有些場景很好用)
jint ret = (*env)->CallIntMethod(env, jSdkObject, javaCallback,1,1);
return ;
}
好了運行函數(shù):
Sdk.getInstance().nativeDownload();
結(jié)果就出來了:
total:1
already:1
好了第一種講述完畢,有些人肯定會說,這尼瑪坑爹, 寫了一大堆東西就實現(xiàn)一個這么雞肋的功能, 還在當(dāng)前的函數(shù)回調(diào)。 那我還不如直接return一個值更加方便, 是的沒錯, 這就是網(wǎng)上最普遍的一種回調(diào)方法, 壓根沒法投入項目用。
好了兄弟別激動

我再介紹一種你看看:
第二種
在其他線程里面回調(diào)到j(luò)ava層,通過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;
}
//調(diào)到C層的方法
private native void nativeDownload();
//c層回調(diào)上來的方法
private int onProgressCallBack(long total, long already) {
//自行執(zhí)行回調(diào)后的操作
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中的表示,等下再其他線程回調(diào)java層需要用到
(*env)->GetJavaVM(env, &g_VM);
// 生成一個全局引用保留下來,以便回調(diào)
g_obj = (*env)->NewGlobalRef(env, thiz);
// 此處使用c語言開啟一個線程,進(jìn)行回調(diào),這時候java層就不會阻塞,只是在等待回調(diào)
pthread_create(xxx,xxx, download,NULL);
return ;
}
//在此處跑在子線程中,并回調(diào)到j(luò)ava層
void download(void *p) {
JNIEnv *env;
//獲取當(dāng)前native線程是否有沒有被附加到j(luò)vm環(huán)境中
int getEnvStat = (*g_VM)->GetEnv(g_VM, (void **) &env,JNI_VERSION_1_6);
if (getEnvStat == JNI_EDETACHED) {
//如果沒有, 主動附加到j(luò)vm環(huán)境中,獲取到env
if ((*g_VM)->AttachCurrentThread(g_VM, &env, NULL) != 0) {
return;
}
mNeedDetach = JNI_TRUE;
}
//通過全局變量g_obj 獲取到要回調(diào)的類
jclass javaClass = (*env)->GetObjectClass(env, g_obj);
if (javaClass == 0) {
LOG("Unable to find class");
(*g_VM)->DetachCurrentThread(g_VM);
return;
}
//獲取要回調(diào)的方法ID
jmethodID javaCallbackId = (*env)->GetMethodID(env, jSdkClass,
"onProgressCallBack", "(JJ)I");
if (javaCallbackId == NULL) {
LOGD("Unable to find method:onProgressCallBack");
return;
}
//執(zhí)行回調(diào)
(*env)->CallIntMethod(env, g_obj, javaCallbackId,1,1);
//釋放當(dāng)前線程
if(mNeedDetach) {
(*g_VM)->DetachCurrentThread(g_VM);
}
env = NULL;
}
好了運行函數(shù):
Sdk.getInstance().nativeDownload();
結(jié)果又出來了:
total:1
already:1
好了第二種講述完畢, 是不是感覺第二種還真有點靠譜了,在和C語言同事開發(fā)的時候這東西,還真能派上用場。
那么有同學(xué)問了我在新線程里的void download(void *p), 直接用findClass,直接找到類進(jìn)行回調(diào)不就行了嗎,干嘛要保存為一個全局變量。 我只能說jni不允許這么干, 你這么干是find到的class直接為空, 從而無法回調(diào)!??!
可是又有同學(xué)問了,如果我的需求場景是這樣子呢:
多線程任務(wù)下載,然后需要回調(diào)進(jìn)度,那么多的線程都一并回調(diào)到
onProgressCallBack
這一個函數(shù),我怎么區(qū)分?jǐn)?shù)據(jù)是屬于哪一個線程任務(wù)的?
怎么玩:

其實很簡單:在Java層的Sdk.class類里面 創(chuàng)建一個Map, 通過一個long型的Uid作為key, 去區(qū)分線程任務(wù), 回調(diào)接口存到value, 這樣子key-value保存在Map里面。 當(dāng)你調(diào)用C層方法的時候傳相應(yīng)的uid下去, 處理完畢后, 再把uid作為參數(shù)回調(diào)到j(luò)ava層的Sdk.class類的onProgressCallBack, 通過Map.get(uid),取出之前存好的對應(yīng)回調(diào)接口, 進(jìn)行分發(fā)回調(diào)。 搞定, 上代碼:
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;
}
//回調(diào)分發(fā)接口
public interface OnSubProgressListener {
public int onProgressChange(long total, long already);
};
private Map<Long, OnSubProgressListener> mMap = new HashMap<>();
//調(diào)到C層的方法
private native int nativeDownload(long uid,String downloadPath);
//回調(diào)的方法
private int onProgressCallBack(long uid, long total, long already) {
OnSubProgressListener listener = mMap.get(uid);
if(listener != null) {
if(already >= total) {
//下載完成,取消回調(diào)
mMap.remove(uid);
} else {
//回調(diào)到指定任務(wù)去,通過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 去執(zhí)行任務(wù),回調(diào)時候,把uid 回傳到j(luò)ava層上面的
private int onProgressCallBack(long uid, long total, long already);
就可以區(qū)分是哪一個任務(wù),并且取出Map里面存好的OnSubProgressListener接口進(jìn)行回調(diào)
(這部分就不寫了, 比較簡單, 后面有讀者要求我再補上)
好了運行函數(shù):
開啟兩個下載任務(wù)
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;
}
});
完畢!這樣子就會回調(diào)到不同的接口中去了, 當(dāng)然還有更牛逼的方法, 請看第三種。
第三種方法:
通過把接口jobject 傳遞到c層下面去,然后在c層里面進(jìn)行回調(diào) ( 和公司寫c的同事共同研究出來的方法 ) :
java層代碼:
public class Sdk {
public Sdk() {
}
//單例
private static class SdkHodler {
static Sdk instance = new Sdk();
}
public static Sdk getInstance() {
return SdkHodler.instance;
}
//回調(diào)到各個線程
public interface OnSubProgressListener {
public int onProgressChange(long total, long already);
};
//調(diào)到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中的表示,等下再其他線程回調(diào)java層需要用到
(*env)->GetJavaVM(env, &g_VM);
//生成一個全局引用,回調(diào)的時候findclass才不會為null
jobject callback = (*env)->NewGlobalRef(env, jcallback)
// 把接口傳進(jìn)去,或者保存在一個結(jié)構(gòu)體里面的屬性, 進(jìn)行傳遞也可以
pthread_create(xxx,xxx, download,callback);
return ;
}
//在此處跑在子線程中,并回調(diào)到j(luò)ava層
void download(void *p) {
if(p == NULL) return ;
JNIEnv *env;
//獲取當(dāng)前native線程是否有沒有被附加到j(luò)vm環(huán)境中
int getEnvStat = (*g_VM)->GetEnv(g_VM, (void **) &env,JNI_VERSION_1_6);
if (getEnvStat == JNI_EDETACHED) {
//如果沒有, 主動附加到j(luò)vm環(huán)境中,獲取到env
if ((*g_VM)->AttachCurrentThread(g_VM, &env, NULL) != 0) {
return;
}
mNeedDetach = JNI_TRUE;
}
//強轉(zhuǎn)回來
jobject jcallback = (jobject)p;
//通過強轉(zhuǎn)后的jcallback 獲取到要回調(diào)的類
jclass javaClass = (*env)->GetObjectClass(env, jcallback);
if (javaClass == 0) {
LOG("Unable to find class");
(*g_VM)->DetachCurrentThread(g_VM);
return;
}
//獲取要回調(diào)的方法ID
jmethodID javaCallbackId = (*env)->GetMethodID(env, javaClass,
"onProgressChange", "(JJ)I");
if (javaCallbackId == NULL) {
LOGD("Unable to find method:onProgressCallBack");
return;
}
//執(zhí)行回調(diào)
(*env)->CallIntMethod(env, jcallback, javaCallbackId,1,1);
//釋放當(dāng)前線程
if(mNeedDetach) {
(*g_VM)->DetachCurrentThread(g_VM);
}
env = NULL;
//釋放你的全局引用的接口,生命周期自己把控
(*env)->DeleteGlobalRef(env, jcallback);
jcallback = NULL;
}
好了運行函數(shù):
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這個參數(shù), 而且少了map去保存你的接口, 優(yōu)化了好多內(nèi)存,啊哈哈! 這個是直接把接口傳到j(luò)ni層, 對應(yīng)的類型是jobject, 在c層傳遞的這個接口的時候需(*env)->NewGlobalRef(env, jcallback) 生成全局引用進(jìn)行傳遞,匹配C語言的void *類型, 那么在與c層交互人員聯(lián)調(diào)的時候,如果使用到回調(diào),需要在c開發(fā)人員那邊程序代碼 預(yù)留一個 void *變量進(jìn)行存放回調(diào)接口。
上面的gif圖是用了第二種方案的map,jni回調(diào)所有的方法基本都在這里了。
轉(zhuǎn)發(fā)請注明鏈接地址。謝幕!