http://blog.csdn.net/andyhuabing/article/details/7240713
JNI : Java Native Interface 即JAVA本地調用,爲何需要這種技術呢?原因有二:
1、運行JAVA程序的虛擬機是用Native語言編寫的,而虛擬機運行在具體的平臺上,所以虛擬機本身無法做到平臺無關,而利用JNI技術即可對JAVA層屏蔽不同操作系統平臺之間的差異,如file,socket等
2、在JAVA語言誕生前,很多程序使用Native語言編寫,JAVA直接利用JNI使用,避免造重複輪子的壞名聲。而且JNI的運行效率和速度會更高
第一大部分: JAVA如何調用Native函數
通過MediaScanner進行完整解釋其工作原理:
調用層次關係,圖示:
JAVA層代碼分析:MediaScanner.java
public class MediaScanner
{
static { //static 語句,前面的 JAVA學習系列(一) 有說明過這個問題
//加載對應用JNI庫,linux上即調用libmedai_jni.so,而windows平臺上調用libmedia_jni.dll
System.loadLibrary("media_jni");
native_init(); //調用native函數
}
//非native函數
public void scanDirectories(String[] directories, String volumeName)
//聲明native函數,native爲JAVA語言的關鍵字
private static native final void native_init();
private native final void native_setup();
private native final void native_finalize();
private native void processDirectory(String path, String extensions, MediaScannerClient client);
}
總結: 一個是加載jni動態庫,二是聲明java的native函數
JNI層 android_media_MediaScanner.cpp 分析:
首先來找下java層的native_init函數對應jni層的函數?
首先native_init函數位於android.media包中,它的全路徑:android.media.MediaScanner.native_init,由於
在native語言中,"."有特殊的意義,所以用"_"來替換:即上面路徑這爲:android_media_MediaScanner_native_init
ok,函數名就如此關聯起來了。而JNI函數註冊有兩種方式:
1、靜態註冊方法
a、編寫java代碼,然後編譯生成.class文件
b、使用javah -o output packagename.classname生成jni層頭文件
調用時先加載動態庫,然後查找native_init函數的jni函數:android_media_MediaScanner_native_init
如果找到則建立這兩個函數的關聯關係,即保存jni層函數的函數指針,這個由虛擬機完成。
缺點:javah生成的函數名特別長,不利於書寫,且第一次調用時需要根據函數名字搜索建立關聯關係。
2、動態註冊方法
利用JNINativeMethod結構保存其關係
typedef struct
{
//JAVA中native函數名字
const char *name;
//簽名信息,用字符串表示,參數類型及返回值類型的組合
const char *signature;
///JNI層函數函數指針,轉換爲void*類型
void *fnPtr;
};
static JNINativeMethod gMethods[] = {
{"processDirectory", "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V",
(void *)android_media_MediaScanner_processDirectory},
{"processFile", "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V",
(void *)android_media_MediaScanner_processFile},
{"setLocale", "(Ljava/lang/String;)V", (void *)android_media_MediaScanner_setLocale},
{"extractAlbumArt", "(Ljava/io/FileDescriptor;)[B", (void *)android_media_MediaScanner_extractAlbumArt},
{"native_init", "()V", (void *)android_media_MediaScanner_native_init},
{"native_setup", "()V", (void *)android_media_MediaScanner_native_setup},
{"native_finalize", "()V", (void *)android_media_MediaScanner_native_finalize},
};
這裏說明一下簽名信息:
因爲JAVA支持函數重載,可以定義同名但不同參數的函數,但直接根據函數名是沒法找到具體函數的,因此利用參數類型及返回類型
組成簽名信息。
常用類型標識符:
類型標識 JAVA類型 字長
Z boolean 8位
B byte 8位
C char 16位 -- 注意喲,定義爲unicode碼
S short 16位
I int 32位
J long 64位
F float 32位
D double 64位
L/java/languageString String
[I int[] int數組
[L/java/lang/object Object[] 對象數組
// This function only registers the native methods, and is called from
// JNI_OnLoad in android_media_MediaPlayer.cpp
int register_android_media_MediaScanner(JNIEnv *env)
{
//利用registerNativeMethods註冊JNI函數
return AndroidRuntime::registerNativeMethods(env,
"android/media/MediaScanner", gMethods, NELEM(gMethods));
}
// 加載jni庫,查找該庫中的JNI_OnLoad函數完成動態註冊工作
jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
...
if (register_android_media_MediaScanner(env) < 0) {
LOGE("ERROR: MediaScanner native registration failed\n");
goto bail;
}
}
static void
android_media_MediaScanner_processDirectory(JNIEnv *env, jobject thiz, jstring path, jstring extensions, jobject client)
{
MyMediaScannerClient myClient(env, client);
mp->processDirectory(pathStr, extensionsStr, myClient, ExceptionCheck, env);
...
}
NATIVE庫實現:MediaScanner.cpp (編譯成libmedia.so庫)
status_t MediaScanner::processDirectory(
const char *path, const char *extensions,
MediaScannerClient &client,
ExceptionCheck exceptionCheck, void *exceptionEnv) {
...
client.setLocale(locale());
status_t result =
doProcessDirectory(
pathBuffer, pathRemaining, extensions, client,
exceptionCheck, exceptionEnv);
free(pathBuffer);
...
}
如此整個流程調用邏輯就講完了。
第二大部分:JNIEnv 介紹
JNIEnv 是一個與線程相關的變量,由於線程相關,所以線程B中不能使用線程A中的JNIEnv函數。
那個多個線程由誰來保存並保證每個線程的JNIEnv結構體正確呢?
jint JNI_OnLoad(JavaVM* vm, void* reserved)
全進程只有一個JavaVM對象,可以保存並在任何地方使用沒有問題,獨此一份。
利用JavaVM中的 AttachCurrentThread函數,就可以得到這個線程的 JNIEnv結構體,利用用DetachCurrnetThread釋放相應資源
a、通過 JNIEnv 操作 jobject
jfieldID 操作成員變量
jmethodID 操作成員函數
/*
* Get a field ID (instance fields).
*/
static jfieldID GetFieldID(JNIEnv* env, jclass jclazz,
const char* name, const char* sig)
/*
* Get a method ID for an instance method.
*
* JNI defines <init> as an instance method, but Dalvik considers it a
* "direct" method, so we have to special-case it here.
*
* Dalvik also puts all private methods into the "direct" list, so we
* really need to just search both lists.
*/
static jmethodID GetMethodID(JNIEnv* env, jclass jclazz, const char* name,
const char* sig)
具體實現參考:dalvik\vm\jni.c的實現:重要的函數還有 JNI_CreateJavaVM [Create a new VM instance]
舉例說明JNI如何調用JAVA層函數:
JNIEnv *mEnv;
jmethodID mScanFileMethodID;
jmethodID mHandleStringTagMethodID;
MyMediaScannerClient(JNIEnv *env, jobject client)
: mEnv(env),
mClient(env->NewGlobalRef(client)),
mScanFileMethodID(0),
mHandleStringTagMethodID(0),
mSetMimeTypeMethodID(0)
{
...
// 找到 android.media.MediaScannerClient類在JNI層對應用用jclass實例
jclass mediaScannerClientInterface = env->FindClass("android/media/MediaScannerClient");
//依此取出 MediaScannerClient 類中的函數 scanFile/handleStringTag 的 jMethodID
mScanFileMethodID = env->GetMethodID(mediaScannerClientInterface, "scanFile",
"(Ljava/lang/String;JJ)V");
mHandleStringTagMethodID = env->GetMethodID(mediaScannerClientInterface, "handleStringTag",
"(Ljava/lang/String;Ljava/lang/String;)V");
}
// returns true if it succeeded, false if an exception occured in the Java code
virtual bool scanFile(const char* path, long long lastModified, long long fileSize)
{
// 調用JNIEnv的CallVoidMethod函數:
// 第一個參數代表 MediaScannerClient 的 jobject 對象,第二個參數代表 scanFile的 jMethodID
// 後面的是JAVA層 ScanFile 函數參數
mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, fileSize);
}
實現上JNIEnv提供了一系列類似的函數調用JAVA函數:
NativeType Call<type>Method(JNIEnv *env, jobject obj,jmethodID methodID,...)
同時也可以設定或獲取JAVA層的成員變量值,定義如下:
NativeType Get<type>Field(JNIEnv *env,jobject obj,jfieldID fieldID);
NativeTYpe Set<type>Field(JNIEnv *env,jobject obj,jfieldID fieldID,NativeType value);
補充說明:
如何生成靜態java的native jni頭文件?
1、先編寫java代碼,使用eclipse編譯生成.class文件
2、使用javah程序生成頭文件
javah -o output.h packagename.classname 舉例如下:
C:\Program Files\Java\jdk1.6.0_23\bin>javah -o output -classpath F:\JoinWSP\Json
Test\bin\classes test.android.json.DirectStruct
這裏有幾點注意下:
output 是生成的頭文件
-classpath <路徑> 用於裝入類的路徑
packagename.classname 完整的包名
===================================================================================================
問題的來源:
jni與java層之間互傳參數,有幾個問題需要解決:
1、需要傳遞的參數的函數非常多,達到100個左右
2、傳遞的參數有些比較複雜,涉及到結構的嵌套
3、參數有輸入參數與輸出參數兩種
舉函數例子如下:
/*-- 授權信息 --*/
typedef struct {
int m_dwProductID; /* 普通授權的節目ID */
int m_tBeginDate; /* 授權的起始時間 */
int m_tExpireDate; /* 授權的過期時間 */
char m_bCanTape; /* 用戶是否購買錄像:1-可以錄像;0-不可以錄像 */
char m_byReserved[3]; /* 保留 */
}CAEntitle;
結構嵌套結構,比較複雜
/*-- 授權信息集合 --*/
typedef struct {
int m_wProductCount;
char m_m_byReserved[2]; /* 保留 */
CAEntitle m_Entitles[300]; /* 授權列表 */
}SCDCAEntitles;
前面兩個是輸入參數,後面兩個是輸出參數
extern boolean STBCA_SCPBRun( const char* pbyCommand,
int wCommandLen,
char* pbyReply,
int* pwReplyLen );
問題解決方案1:
常用方案,
A、C++調用傳遞參數給java層
調用成員函數利用CallxxxMethodYYY(其中xxx表示返回數據類型,YYY表代碼V/A)
其參數可以直接利用函數傳,也可採用GetFieldID&SetObjectField配對使用
即如下方案:
struct CATestOffsets
{
jfieldID name;
jfieldID vendor;
jfieldID version;
jfieldID handle;
jfieldID m_tBeginDate;
jfieldID m_tExpireDate;
....
jmethodID STBCA_SCPBRunCallXX;
jmethodID ....
} gSensorOffsets;
利用jfieldID和jmethodID表示java類的成員變量及成員函數。。。。工作量相當大,調試也很麻煩
B、JAVA調用C++代碼
通過做法就是定義一堆的native函數,然後將參數傳遞一一定義傳遞下來
對於結構在java層定義類,在jni層進行轉換,首先可能傳遞的參數很多,而且每個函數都要小心寫,
對於需要既需要輸入參數又需要輸出參數的,很麻煩喲。。非常痛苦。
這種方案做了兩天,實在受不了,太多native函數,大多jmethodID與jfieldID,而且同時輸入及輸出。
確認讓人很頭痛噻!
問題解決方案2:
重點就是利用C++層分配共享內存空間,函數參數非常簡單,只需要返回值,所有參數傳遞利用共享內存搞定
這利方式對於參數的傳遞非常方便,只是需要注意地址及偏移值(addr:offset),代碼比較清晰。
但對於native函數及jmethodID與jfieldID的處理還是很麻煩呀!不對對於參數處理簡化了很多了。
基本代碼如下,我這裏搞了個最複雜的傳遞結構參數舉例:
JAVA 代碼編寫如下
- <span style="font-size:16px;"> /**
- * 按長度進行緩衝區分配空間
- * @param length: buffer length
- * @return start address
- */
- public int AllocStructMem(int length){
- if (length % 4 != 0) {
- length -= (length % 4);
- length += 4;
- }
- if ((address = native_alloc(length)) == 0)
- throw new RuntimeException();
- return address;
- }
- /**
- * 釋放緩衝區空間
- * @param ptr:start address
- */
- public void FreeStructMem(int ptr){
- if(ptr == 0x00){
- throw new NullPointerException("NULL pointer");
- }
- native_free(ptr);
- }
- public int DSSetInt(int ptr,int offset,int value) {
- if(ptr == 0x00){
- throw new NullPointerException("NULL pointer");
- }
- return native_setInt(ptr,offset,4,value);
- }
- public int DSSetBytes(int addr,int off,int len,byte[] b){
- return native_setBytes(addr,off, len, b);
- }
- public void DSSetUTFString(int addr,int off, int len, String s) {
- DSSetBytes(addr,off, len, s.getBytes());
- }
- public int DSGetInt(int addr,int off, int len) {
- if(off < 0 || len <= 0 || addr == 0x00 )
- throw new IllegalArgumentException();
- return native_getInt(addr, off, len);
- }
- public int DSGetBytes(int addr,int off, int len, byte[] b) {
- if (off < 0 || len <= 0 || addr == 0x00 ||b == null || b.length < len)
- throw new IllegalArgumentException();
- return native_getBytes(addr, off, len, b);
- }
- public String DSGetUTFString(int addr,int off, int len) {
- if (off < 0 || len <= 0 )
- throw new IllegalArgumentException();
- byte[] b = new byte[len];
- native_getBytes(addr, off, len, b);
- return new String(b);
- }
- private static native int native_alloc(int length);
- private static native void native_free(int ptr);
- private static native int native_setInt(int addr, int offset, int len,
- int value);
- private static native int native_setBytes(int addr, int offset, int len,
- byte[] b);
- private static native int native_getInt(int addr, int offset, int len);
- private static native int native_getBytes(int addr, int offset, int len,
- byte[] b);</span>
下面是測試代碼:
- <span style="font-size:16px;"> // C++ 定義結構
- // struct mmm_t {
- // int a;
- // int b[4];
- // struct {
- // int c1, c2;
- // } c;
- // struct {
- // int d1;
- // char*d2;
- // } d[2];
- // float e;
- // };
- static class cc{
- int c1;
- int c2;
- }
- static class dd{
- int d1;
- byte[] d2 = new byte[64];
- };
- static class mmm_t{
- int a;
- int[] b = new int[4];
- cc c[] = new cc[1];
- dd d[] = new dd[2];
- int e;
- dd darr[] = new dd[10];
- public void mmm_malloc(){
- c[0] = new cc();
- for(int i=0;i<2;i++){
- d[i] = new dd();
- }
- for(int i=0; i < 10; i++){
- darr[i] = new dd();
- }
- }
- }
- public int complexStructTest(){
- mmm_t m = new mmm_t();
- m.mmm_malloc();
- Log.i(TAG,"complexStructTest is test.....");
- /* 賦值 */
- m.a = 10;
- Log.i(TAG,"complexStructTest 000");
- m.b[0] = 0x22352352;
- m.b[1] = 0x31212362;
- m.b[2] = 0x31343521;
- m.b[3] = 0x33299552;
- Log.i(TAG,"complexStructTest 111");
- m.c[0].c1 = 0x2352539;
- Log.i(TAG,"complexStructTest 222");
- m.c[0].c2 = 0x9235265;
- Log.i(TAG,"complexStructTest 333");
- m.d[0].d1 = 0x983652;
- String s = "ksdf2035k8";
- Log.i(TAG,"length111:" + s.length());
- m.d[0].d2 = s.getBytes();
- m.d[1].d1 = 0x983652;
- String s1 = "ksdf2035k8";
- Log.i(TAG,"length222:" + s1.length());
- m.d[1].d2 = s1.getBytes();
- Log.i(TAG,"complexStructTest 444");
- m.e = 0x8923572;
- Log.i(TAG,"complexStructTest 555");
- /* 設定值到jni層 */
- int size = 0;
- size += 4 + 4*4;
- size += 4*2;
- size += 2 * (4+64);
- size += 4 ;
- Log.i("size","size=" + size);
- int ptr = AllocStructMem(size);
- Log.i(TAG,"alloc memory address=" + ptr);
- if(ptr !=0){
- DSSetInt(ptr,0,m.a);
- DSSetInt(ptr,1,m.b[0]);
- DSSetInt(ptr,2,m.b[1]);
- DSSetInt(ptr,3,m.b[2]);
- DSSetInt(ptr,4,m.b[3]);
- DSSetInt(ptr,5,m.c[0].c1);
- DSSetInt(ptr,6,m.c[0].c1);
- DSSetInt(ptr,7,m.d[0].d1);
- DSSetBytes(ptr,8,s.length(),s.getBytes());
- Log.e(TAG,"complexStructTest aaaa");
- DSSetInt(ptr,12,m.d[1].d1);
- Log.e(TAG,"complexStructTest bbbb");
- DSSetBytes(ptr,13,s1.length(),s1.getBytes());
- Log.e(TAG,"complexStructTest cccc");
- DSSetInt(ptr,17,m.e);
- }
- if(ptr != 0){
- FreeStructMem(ptr);
- ptr = 0;
- }
- return 0;
- }</span>
下面貼下jni實現代碼:
- <span style="font-size:16px;">static jint native_setInt(JNIEnv *e, jclass cls, jint addr,jint off,jint len,jint value) {
- int *ptr = (int*)addr;
- if(ptr == NULL){
- LOGE("setInt illegal memory address");
- return -1;
- }
- assert( len == 4);
- LOGI("ptr=0x%x,offset=%d native_setInt value = %d",ptr,off,value);
- (*(int*)(ptr + off)) = value;
- return 0;
- }
- static jint native_setBytes(JNIEnv *e, jclass cls, jint addr, jint off, jint len, jbyteArray byteValues) {
- jbyte *ptr = (jbyte*)addr;
- e->GetByteArrayRegion(byteValues,0,len,ptr+off*4);
- if(buf == NULL){
- LOGE("setBytes illegal memory address");
- return -1;
- }
- return 0;
- }
- static jint native_getInt(JNIEnv *e, jclass cls, jint addr, jint off, jint len) {
- int value = 0x00;
- int *ptr = (int*)addr;
- if(ptr == NULL){
- LOGE("getInt illegal memory address");
- return -1;
- }
- assert(len == 4);
- value = *((int*)(ptr + off));
- LOGI("ptr=0x%x,offset=%d native_getInt value = %d",ptr,off,value);
- return value;
- }
- static jint native_getBytes(JNIEnv *e, jclass cls, jint addr, jint off, jint len, jbyteArray byteValues) {
- jbyte *ptr = (jbyte*)addr;
- if(ptr == NULL){
- LOGE("getBytes illegal memory address");
- return -1;
- }
- e->SetByteArrayRegion(byteValues,0,len,ptr+off*4);
- return 0;
- }
- static jint native_alloc(JNIEnv *e, jobject thiz, jint len) {
- void *ptr = (void*)calloc(1,len);
- if(ptr == NULL){
- LOGE("alloc buffer out of memory(size=0x%x)",len);
- return -1;
- }
- return (jint)ptr;
- }
- static void native_free(JNIEnv *e, jobject thiz, jint ptr) {
- if(ptr != NULL){
- free((void*)ptr);
- ptr = NULL;
- }
- return ;
- }</span>
嘿嘿,還是夠複雜噻,不過比第一個方案前進了一大步。還有沒有更好的辦法呢?
請聽下會分解。。。哈哈哈!!!