1、參看博客:http://www.jianshu.com/p/e576c7e1c403
Android JNI 篇 - JNI回調的三種方法(精華篇)
2、參看博客:?
JNI層線程回調Java函數關鍵點及示例
http://blog.csdn.net/fu_shuwu/article/details/41121741
?3?http://blog.csdn.net/u010402982/article/details/48199487
核心的關鍵點:
三、本地線程中調用java對象
問題1:
JNIEnv是一個線程相關的變量
JNIEnv 對于每個 thread 而言是唯一的
JNIEnv *env指針不可以為多個線程共用
解決辦法:
但是java虛擬機的JavaVM指針是整個jvm公用的,我們可以通過JavaVM來得到當前線程的JNIEnv指針.
可以使用javaAttachThread保證取得當前線程的Jni環境變量
static JavaVM *gs_jvm=NULL;
gs_jvm->AttachCurrentThread((void **)&env, NULL);//附加當前線程到一個Java虛擬機
jclass cls = env->GetObjectClass(gs_object);
jfieldID fieldPtr = env->GetFieldID(cls,"value","I");
問題2:
不能直接保存一個線程中的jobject指針到全局變量中,然后在另外一個線程中使用它。
解決辦法:
用env->NewGlobalRef創建一個全局變量,將傳入的obj(局部變量)保存到全局變量中,其他線程可以使用這個全局變量來操縱這個java對象
注意:若不是一個 jobject,則不需要這么做。如:
jclass 是由 jobject public 繼承而來的子類,所以它當然是一個 jobject,需要創建一個 global reference 以便日后使用。
而 jmethodID/jfieldID 與 jobject 沒有繼承關系,它不是一個 jobject,只是個整數,所以不存在被釋放與否的問題,可保存后直接使用。
?
總結:創建兩個全局的變量一個是JavaVM 虛擬機環境jvm
另外一個全局變量是jobj對象
然后創建一個線程,使用全局的jvm獲得與該線程一一對應的env,通過env和全局的jobj對象,創建java層的對象,調用java層的方法,最近將線程環境關閉。
?
我們來看下程序的框架:
我們來看下程序的代碼:
package im.weiyuan.com.jni;public class Sdk {static {System.loadLibrary("hello");}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層回調上來的方法public int onProgressCallBack(long total, long already) {//自行執行回調后的操作System.out.println("total:"+total);System.out.println("already:"+already);return 1;}//調到C層的方法public native void nativeDownload();}
然后
(一)?? 第二步:make project一下,目的就是編譯成對應的class文件。然后根據生成的class文件,利用javah生成對應的 .h頭文件。
?
(一)?? 第三步:
Cmd終端進入到你新建的android工程的src/main目錄下:我的目錄是:
F:\JNI\app\src\main
執行命令:
Javah -d jni -classpath D:\android_sdk_ndk\sdk\platforms\android-21\android.jar;..\..\build\intermediates\classes\debug im.weiyuan.com.jni.Sdk
其中:?D:\android_sdk_ndk\sdk\是你sdk的路徑?
?im.weiyuan.com.jni.Sdk是你對應的
就是你聲明的native函數所在的包名加上類名。
就會發現在main目錄下多了一個jni文件夾,里面有生成好的頭文件:
?
在這個頭文件中就自動幫助我們生成了函數的聲明
第五步:
在jni目錄下新建一個 .c文件。來實現頭文件里面聲明的方法。我的叫im_weiyuan_com_jni_Sdk.c
我們來看下程序的代碼:
// // Created by wei.yuan on 2017/6/13. // #include <jni.h> #include <string.h> #include <pthread.h> #include "im_weiyuan_com_jni_Sdk.h" #include "im_weiyuan_com_jni_Sdk_OnSubProgressListener.h" #include "im_weiyuan_com_jni_Sdk_SdkHodler.h" JavaVM *g_VM; jobject g_obj; #include <jni.h> #include <string.h> #include <android/log.h> #include <time.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <dlfcn.h> #include <assert.h> #include <android\log.h> #include <errno.h> #include <pthread.h>static void* native_thread_exec(void* arg){JNIEnv *env;int mNeedDetach = -1;//獲取當前native線程是否有沒有被附加到jvm環境中int getEnvStat = (*g_VM)->GetEnv(g_VM, (void **) &env,JNI_VERSION_1_6);if (getEnvStat == JNI_EDETACHED) {//如果沒有, 主動附加到jvm環境中,獲取到envif ((*g_VM)->AttachCurrentThread(g_VM, &env, NULL) != 0) {return;}mNeedDetach = JNI_TRUE;}//通過全局變量g_obj 獲取到要回調的類jclass javaClass = (*env)->GetObjectClass(env, g_obj);if (javaClass == 0) {// LOGI("Unable to find class");(*g_VM)->DetachCurrentThread(g_VM);return;}//獲取要回調的方法IDjmethodID javaCallbackId = (*env)->GetMethodID(env, javaClass,"onProgressCallBack", "(JJ)I");if (javaCallbackId == NULL) {//LOGI("Unable to find method:onProgressCallBack");return;}//執行回調(*env)->CallIntMethod(env, g_obj, javaCallbackId,100,100);//釋放當前線程if(mNeedDetach) {(*g_VM)->DetachCurrentThread(g_VM);}
//釋放你的全局引用的接口,生命周期自己把控
(*env)->DeleteGlobalRef(env, g_obj);
g_obj = NULL;
env = NULL;
} JNIEXPORT void JNICALL Java_im_weiyuan_com_jni_Sdk_nativeDownload (JNIEnv * env , jobject thiz){//JavaVM是虛擬機在JNI中的表示,等下再其他線程回調java層需要用到(*env)->GetJavaVM(env, &g_VM);// 生成一個全局引用保留下來,以便回調g_obj = (*env)->NewGlobalRef(env, thiz);// 此處使用c語言開啟一個線程,進行回調,這時候java層就不會阻塞,只是在等待回調 pthread_t thread_id;if(( pthread_create(&thread_id,NULL, native_thread_exec,NULL))!=0){return ;}return; }
其中:
JNIEXPORT void JNICALL Java_im_weiyuan_com_jni_Sdk_nativeDownload (JNIEnv * env , jobject thiz)就是在im_weiyuan_com_jni_Sdk.h頭文件中系統自動生成的
(一)?? 第五步:配置ndk的路徑
在 local.properties 文件中設置ndk的路徑:
?
sdk.dir=D\:\\android_sdk_ndk\\sdk
ndk.dir=D\:\\android_sdk_ndk\\android-ndk-r10e
?
(一)?? 在gradle.propertes中添加
android.useDeprecatedNdk=true
http://stackoverflow.com/questions/31979965/after-updating-android-studio-to-version-1-3-0-i-am-getting-ndk-integration-is
?
?
在app目錄下的 build.gradle中設置庫文件名(生成的so文件名):
?
找到 defaultConfig 這項,在里面添加如下內容:
??????? ndk{
?????????? ?moduleName "hello"? //設置庫(so)文件名稱
??????????? abiFilters "armeabi", "armeabi-v7a", "x86"?
??????? }
這里??? hello必須和?
?static {
??????? System.loadLibrary("hello");
}中的名字是對應的,abiFilters是指定生成那種平臺下的so庫,對應于eclipse中的Aplication.mk文件中的內容。編譯,并運行。界面上就會顯示從native方法傳過來的值。
在c代碼中
"onProgressCallBack", "(JJ)I"
這里調用上層java函數的時候,使用到了函數的簽名:
如何得到函數的簽名了:
如何查看函數的簽名:
如果當前的工程存放的目錄在F盤下的JNI目錄:
進入到工程的F:\JNI\app\build\intermediates\classes\debug 目錄下
執行命令:
Javap? -s? im.weiyuan.com.jni.Sdk
?
其中im.weiyuan.com.jni是包名,Sdk是類名
F:\JNI\app\build\intermediates\classes\debug> javap -s im.weiyuan.com.jni.Sdk
Compiled from "Sdk.java"
public class im.weiyuan.com.jni.Sdk {
? public im.weiyuan.com.jni.Sdk();
??? descriptor: ()V
?
? public static im.weiyuan.com.jni.Sdk getInstance();
??? descriptor: ()Lim/weiyuan/com/jni/Sdk;
?
? public int onProgressCallBack(long, long);
??? descriptor: (JJ)I
?
? public native void nativeDownload();
??? descriptor: ()V
?
? static {};
??? descriptor: ()V
}
?
在activity中我們可以調用native函數的代碼:
package im.weiyuan.com.jni;import android.app.Activity; import android.support.v7.app.AppCompatActivity; import android.os.Bundle;public class MainActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//調用native的方法 Sdk.getInstance().nativeDownload();} }
我們來看下程序的運行結果:
06-13 15:38:40.070 14935-15032/? I/System.out: total:100
android studio 的代碼地址:
?http://download.csdn.net/detail/jksfkdjksdfjkjk/9869331
生成的so的目錄如下所示:
?