Android JNI 篇 - JNI回調(diào)的三種方法(精華篇)

開門見山, 不廢話上效果, 上代碼: c層回調(diào)進(jìn)度
device-2017-03-23-184023.gif

第一種方法

在當(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)方法, 壓根沒法投入項目用。
好了兄弟別激動

2.png

我再介紹一種你看看:

第二種

在其他線程里面回調(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ù)的?

怎么玩:
3.gif

其實很簡單:在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ā)請注明鏈接地址。謝幕!

推薦閱讀:Android 編譯速度優(yōu)化黑科技 - RocketX

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容