Android学习笔记--JNI的使用方法
1、JNI是什么
JNI是Java Native Interface的縮寫,它提供若干的API實(shí)現(xiàn)Java與其他語言之間的通信。而Android Framework由基于Java語言的的Java層與基于C/C++語言的C/C++層組成,每個(gè)層中的功能模塊都是以有相應(yīng)的語言編寫,并且兩層中的大部分模塊有著千絲萬縷的聯(lián)系。而在兩層之間充當(dāng)連接橋梁這一角色的就是JNI,它允許Java代碼和C/C++編寫的應(yīng)用程序與庫之間進(jìn)行交互;通常在以下幾種情況下使用JNI
1、注重處理速度,C/C++的處理速度要優(yōu)于Java語言
2、硬件控制,硬件驅(qū)動(dòng)程序通常使用C語言編寫,而要是Java層能夠控制硬件,需要用到JNI
3、C/C++代碼的復(fù)用,一些好的C/C++模塊可以被多處復(fù)用
2、在Java中調(diào)用C庫函數(shù)
?
下面以一個(gè)例子來說明在Java代碼中調(diào)用C庫函數(shù)的流程
1、編寫Java代碼
class HelloJNI { /*聲明本地方法,該函數(shù)在C庫中實(shí)現(xiàn)*/ native void printHello(); native void printString(String str); /*在靜態(tài)塊中加載C庫,可以保證在main方法前加載完成*/ static { System.loadLibrary("./hellojni"); } public static void main(String args[]) { HelloJNI myJNI = new HelloJNI(); /*調(diào)用C庫中實(shí)現(xiàn)的函數(shù)*/ myJNI.printHello(); myJNI.printString("Hello world from printstring func"); } }在上述代碼中使用native關(guān)鍵字聲明本地方法,告訴Java編譯器,此函數(shù)由其他語言編寫;在靜態(tài)塊中加載hellojni庫,該庫由C語言實(shí)現(xiàn),
如果是在Linux系統(tǒng)下則會加載libhellojni.so,如果在Windows系統(tǒng)下則會加載hellojni.dll;(本文以Linux系統(tǒng)為測試環(huán)境)
2、編譯Java代碼
javac HelloJNI.java編譯Java代碼很簡單,只要配置好JDK就可以完成編譯,需要注意的是此時(shí)編譯通過,但如果運(yùn)行的話,由于沒有實(shí)現(xiàn)本地函數(shù),所以會拋出找不到函數(shù)的異常
3、生成C頭文件
當(dāng)Java調(diào)用本地函數(shù)printHello或者printString時(shí)并非直接映射到C語言的printHello或者printString函數(shù),而是有一套自己的映射方法,使用如下命令即可生成C函數(shù)的頭文件 javap HelloJni 執(zhí)行完成后生成HelloJni.h如下 /* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class HelloJNI */ #ifndef _Included_HelloJNI #define _Included_HelloJNI #ifdef __cplusplus extern "C" { #endif /* * Class: HelloJNI * Method: printHello * Signature: ()V */ JNIEXPORT void JNICALL Java_HelloJNI_printHello(JNIEnv *, jobject); /* * Class: HelloJNI * Method: printString * Signature: (Ljava/lang/String;)V */ JNIEXPORT void JNICALL Java_HelloJNI_printString(JNIEnv *, jobject, jstring); #ifdef __cplusplus } #endif #endif可以看到生成的函數(shù)原型并非與Java代碼調(diào)用的函數(shù)一致,函數(shù)有JNIEXPORT和JNICALL兩個(gè)關(guān)鍵字聲明,這兩個(gè)關(guān)鍵詞是必須的,有了他們JNI才能正常調(diào)用函數(shù);而通過觀察函數(shù)名稱,我們可以知道其命名方式是"Java_類名_本地方法名"; 再看參數(shù),可知JNIEnv*和jobject是本地函數(shù)的共同參數(shù),第一個(gè)參數(shù)是JNI接口的直接,用來調(diào)用JNI提供的基本函數(shù)集;第二個(gè)參數(shù)中保存著調(diào)用本地方法的對象的一個(gè)引用,上例中的jobject中保存的對象myJNI的引用,其他的參數(shù)根據(jù)Java代碼的本地方法的調(diào)用生成的
4、編寫C/C++代碼
把上一步驟生成的HelloJni.h頭文件include進(jìn)來,實(shí)現(xiàn)其聲明的函數(shù)即可,編寫hellojni.c如下
#include "HelloJNI.h" #include <stdio.h> /* * Class: HelloJNI * Method: printHello * Signature: ()V */ JNIEXPORT void JNICALL Java_HelloJNI_printHello(JNIEnv *env, jobject obj) { printf("Hello World!\n"); return; } /* * Class: HelloJNI * Method: printString * Signature: (Ljava/lang/String;)V */ JNIEXPORT void JNICALL Java_HelloJNI_printString(JNIEnv *env, jobject obj, jstring string) { /*JNI提供的基本函數(shù)集,將jstring轉(zhuǎn)化成char *類型*/ const char *str = (*env)->GetStringUTFChars(env, string, 0); printf("%s! \n" ,str); return ; }?
5、生成C動(dòng)態(tài)鏈接庫
gcc -fPIC -shared -o libhellojni.so hellojni.c -I$JAVA_HOME/include其中JAVA_HOME已經(jīng)配置到環(huán)境變量中,表示JDK安裝的目錄,需要指定其中的include目錄使用jni.h頭文件
6、運(yùn)行Java程序
此時(shí)執(zhí)行java HelloJni會提供找不到hellojni庫,這是由于在加載C庫的時(shí)候在默認(rèn)目錄中沒有找到libhellojni.so庫,只需將該庫復(fù)制到/usr/lib/下再次執(zhí)行
xlzh@cmos:~/code/jni/simpleJNI$ java HelloJNI Hello World! Hello world from printstring func!?
3、調(diào)用JNI函數(shù)
上圖來自<Android框架揭秘>
由上圖可知此示例程序有JniFuncMain類、JniTest類和libjnifunc.so(linux系統(tǒng))組成,此示例有Java和C代碼混合而成。
JniFuncMain類:
public class JniFuncMain { private static int staticIntField = 300; /*加載libjnifunc.so庫*/ static { System.loadLibrary("jnifunc"); } /*使用static關(guān)鍵字聲明本地方法,再C庫中實(shí)現(xiàn)*/ public static native JniTest createJniObject(); public static void main(String[] args) { System.out.println("[Java] createJniObject() call native method"); /*調(diào)用C庫的createJniObject,得到JniTest對象,注意不是用new*/ JniTest jniObj = createJniObject(); /*利用JniTest對象調(diào)用JniTest中的方法*/ jniObj.callTest(); } }此例中與上例不同的是本地方法返回了一個(gè)JniTest類的對象的引用,這樣就可以在JniFuncMain類中調(diào)用JniTest類的方法。
JniTest類
class JniTest { private int intField; public JniTest(int num) { intField = num; System.out.println("[Java] call JniTest: intFiled" + intField); } public int callByNative(int num) { System.out.println("[Java] JniTest 對象的 callByNative(" + num + ")調(diào)用"); return num; } public void callTest() { System.out.println("[Java] JniTest對象的callTest() 方法調(diào)用: intField = " + intField); } }JniTest類提供兩個(gè)方法供JniFuncMain類和C庫函數(shù)調(diào)用
JniFuncMain.h
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class JniFuncMain */ #ifndef _Included_JniFuncMain #define _Included_JniFuncMain #ifdef __cplusplus extern "C" { #endif /* * Class: JniFuncMain * Method: createJniObject * Signature: ()LJniTest; */ JNIEXPORT jobject JNICALL Java_JniFuncMain_createJniObject (JNIEnv *, jclass); #ifdef __cplusplus } #endif #endif使用javah JniFuncMain生成頭文件,需要注意的是第二個(gè)參數(shù)是jclass,而不是jobject,這是由于該本地方法在JniFuncMain類中聲明的是static方法,所以第二個(gè)參數(shù)表示的該類的應(yīng)用,而不需要對象的引用
jnifunc.cpp
#include "JniFuncMain.h" JNIEXPORT jobject JNICALL Java_JniFuncMain_createJniObject(JNIEnv *env, jclass clazz) { jclass targetClass; jmethodID mid; jobject newObject; jstring hellostr; jfieldID fid; jint staticIntField; jint result; /*獲取JniFuncMain類的staticField變量值*/ fid = env->GetStaticFieldID(clazz, "staticIntField", "I"); staticIntField = env->GetStaticIntField(clazz, fid); printf("[CPP] 獲取 JniFuncMain類的staticIntField 值\n"); printf(" JniFuncMain.staticIntField = %d\n", staticIntField); /*查找生成對象的類*/ targetClass = env->FindClass("JniTest"); /*查找構(gòu)造方法*/ mid = env->GetMethodID(targetClass, "<init>", "(I)V"); /*生成JniTest對象*/ printf("[CPP] JniTest 對象生成 \n"); newObject = env->NewObject(targetClass, mid, 100); /*調(diào)用對象的方法*/ mid = env->GetMethodID(targetClass, "callByNative", "(I)I"); result = env->CallIntMethod(newObject, mid, 200); /*設(shè)置JniObject對象的intField值*/ fid = env->GetFieldID(targetClass, "intField", "I"); printf("[CPP] 設(shè)置JniTest對象的intField值為200\n"); env->SetIntField(newObject, fid, result); /*返回對象引用*/ return newObject; }如果想在C代碼中訪問Java中的成員變量,就需要獲取相應(yīng)成員變量的ID值,成員變量的ID值保存在jfieldID類型的變量中;獲取成員變量ID的JNI本地方法有兩個(gè),分別是
/* 獲取Java中的靜態(tài)成員變量ID * env : JNI接口指針 * clazz : 包含成員變量的類的jclass * name : 成員變量名稱 * signature: 成員變量簽名 */ jfield GetStaticFieldID(JNIEnv *env, jclass clazz, const char *name, const char *signature); /*獲取Java中的普通成員變量ID*/ jfield GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *signature);上述示例中要訪問Java類中的靜態(tài)成員變量,所以需要使用GetStaticFieldID方法,其他參數(shù)很簡單,直接使用即可,而對于變量的簽名,則需要借助Java反編譯器javap命令,如下所示
xlzh@cmos:~/code/jni/middleJNI$ javap -s -p JniFuncMain Compiled from "JniFuncMain.java" public class JniFuncMain { private static int staticIntField; Signature: I public JniFuncMain(); Signature: ()V public static native JniTest createJniObject(); Signature: ()LJniTest; public static void main(java.lang.String[]); Signature: ([Ljava/lang/String;)V static {}; Signature: ()V }可以看到,staticIntField的簽名是I,將I傳入第四個(gè)參數(shù)即可,其他函數(shù)中用到簽名的時(shí)候可用同樣的方法獲取
OK,我們得到了成員變量的ID,那么如何通過成員變量的ID來獲取或者設(shè)置成員變量的值呢?就需要用到以下幾個(gè)JNI函數(shù)
/* * 獲取Java類中靜態(tài)成員變量的值 * <jnitype> jobject,jboolean,jbyte,jchar, jshort, jint, jlong, jfloat, jdouble * <type> Object ,Boolean ,Byte,Char , Short , Int, Long , Float , Double * env: JNI接口指針 * jcalss: 包含成員變量的類 * jfieldID: 成員變量ID */ <jnitype> GetStatic<type>Field(JNIEnv *env, jcalss jclazz, jfieldID fieldID) /* * 獲取Java類的對象中普通成員變量的值 */ <jnitype> Get<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID) /* * 設(shè)置Java類中靜態(tài)成員變量的值 */ <jnitype> SetStatic<type>Field(JNIEnv *env, jcalss clazz, jfieldID fieldID, <type> value) /* * 設(shè)置Java類的對象中普通成員變量的值 */ <jnitype> Set<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID, <type> value) ? 與成員變量類似, 獲取和調(diào)用類中方法的JNI函數(shù)原型如下 /*獲取Java類靜態(tài)方法的ID*/ jmethod GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *signature) /*獲取Java類的對象中普通方法的ID*/ jmethod GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *signature) /*調(diào)用Java類中的靜態(tài)方法*/ <jnitype> CallStatic<type>Method(JNIEnv *env, jclass clazz, jmethod methodID, ...) /*調(diào)用Java類的對象中的普通方法*/ <jnitype> Call<type>Method(JNIEnv *env, jobject obj, jmethod methodID, ...) 如何獲取Java類的對象呢?以示例中獲取JniTest類的對象代碼例,分為三步 1、獲取JniTest的類 2、獲取JniTest類的構(gòu)造方法ID 3、通過構(gòu)造方法的ID調(diào)用和JniTest類使用NeoObject方法生成對象 對比上例中獲取JniTest的對象流程,可以很清楚的進(jìn)行對照?
4、在C代碼中運(yùn)行Java類
Java類編譯的字節(jié)碼需要在Java虛擬機(jī)上運(yùn)行,那么在C/C++中運(yùn)行Java類自然也需要加載Java虛擬機(jī);JNI為我們提供了一套Invocation API,它允許本地代碼在自身內(nèi)存區(qū)域內(nèi)加載Java虛擬機(jī),同樣我們以實(shí)例的方式進(jìn)行講解
InvocationApiTest.java
public class InvocationApiTest { public static void main(String[] args) { System.out.println(args[]0); } }invocationApi.c
#include <jni.h> int main(void) { JNIEnv *env; JavaVM *vm; JavaVMInitArgs vm_args; JavaVMOption options[1]; jint res; jclass cls; jmethodID mid; jstring jstr; jclass stringClass; jobjectArray args; /*加載虛擬機(jī)選項(xiàng)*/ options[0].optionString = "-Djava.class.path=."; vm_args.version = JNI_VERSION_1_6; vm_args.options = options; vm_args.nOptions = 1; vm_args.ignoreUnrecognized = JNI_TRUE; /*生成虛擬機(jī)*/ res = JNI_CreateJavaVM(&vm, (void**)&env, &vm_args); /*查找并加載類*/ cls = (*env)->FindClass(env, "InvocationApiTest"); /*獲取main()方法的ID*/ mid = (*env)->GetStaticMethodID(env, cls, "main", "([Ljava/lang/String;)V"); /*生成字符串對象*/ jstr = (*env)->NewStringUTF(env, "Hello Invocation API!!"); stringClass = (*env)->FindClass(env, "java/lang/String"); args = (*env)->NewObjectArray(env, 1, stringClass, jstr); /*調(diào)用main()方法*/ (*env)->CallStaticVoidMethod(env, cls, mid, args); /*銷毀虛擬機(jī)*/ (*vm)->DestroyJavaVM(vm); } 編譯允許結(jié)果如下 xlzh@cmos:~/code/jni/superJNI$ javac InvocationApiTest.java xlzh@cmos:~/code/jni/superJNI$ sudo echo "/usr/lib/jvm/java-1.7.0-openjdk-amd64/jre/lib/amd64/jamvm" >> /etc/ld.so.conf xlzh@cmos:~/code/jni/superJNI$ sudo ldconfig xlzh@cmos:~/code/jni/superJNI$ gcc -o a.out invocationApi.c -I$JAVA_HOME/include -L$JAVA_HOME/jre/lib/amd64/jamvm/ -ljvm xlzh@cmos:~/code/jni/superJNI$ ./a.out Hello Invocation API!! xlzh@cmos:~/code/jni/superJNI$ ?轉(zhuǎn)載于:https://www.cnblogs.com/gordon0918/p/5128034.html
總結(jié)
以上是生活随笔為你收集整理的Android学习笔记--JNI的使用方法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 页面判断用户是否登录
- 下一篇: nefu 753 n!末尾有多少个0