一、底層實(shí)現(xiàn):
網(wǎng)站建設(shè)哪家好,找創(chuàng)新互聯(lián)建站!專注于網(wǎng)頁(yè)設(shè)計(jì)、網(wǎng)站建設(shè)、微信開發(fā)、微信小程序開發(fā)、集團(tuán)企業(yè)網(wǎng)站建設(shè)等服務(wù)項(xiàng)目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了新華免費(fèi)建站歡迎大家使用!
c文件:hardware/libhardware_legacy/power/power.c
以其中set_screen_state(int)函數(shù)為例
其Android.mk中添加:
LOCAL_MODULE:= libpower 編譯成lib
LOCAL_SRC_FILES += power.c
hardware/libhardware_legacy/power/power.c
1: int
2: set_screen_state(int on)
3: {
4: QEMU_FALLBACK(set_screen_state(on));
5:
6: LOGI("*** set_screen_state %d", on);
7:
8: initialize_fds();
9:
10: //LOGI("go_to_sleep eventTime=%lld now=%lld g_error=%s\n", eventTime,
11: // systemTime(), strerror(g_error));
12:
13: if (g_error)
14: goto failure;
15:
16: char buf[32];
17: int len;
18: if(on)
19: len = snprintf(buf, sizeof(buf), "%s", on_state);
20: else
21: len = snprintf(buf, sizeof(buf), "%s", off_state);
22:
23: buf[sizeof(buf) - 1] = '\0';
24: len = write(g_fds[REQUEST_STATE], buf, len);
25: if(len 0) {
26: failure:
27: LOGE("Failed setting last user activity: g_error=%d\n", g_error);
28: }
29: return 0;
30: }
其頭文件power.h中:
1: #if__cplusplus
2: extern "C" { //注1
3: #endif
4: enum {
5: PARTIAL_WAKE_LOCK = 1, // the cpu stays on, but the screen is off
6: FULL_WAKE_LOCK = 2 // the screen is also on
7: };
8:
9: // while you have a lock held, the device will stay on at least at the
10: // level you request.
11: int acquire_wake_lock(int lock, const char* id);
12: int release_wake_lock(const char* id);
13:
14: // true if you want the screen on, false if you want it off
15: int set_screen_state(int on);
16:
17: // set how long to stay awake after the last user activity in seconds
18: int set_last_user_activity_timeout(int64_t delay);
19:
20:
21: #if __cplusplus
22: } // extern "C"
23: #endif
注1:
注1:extern表示其他的類已經(jīng)定義了這段代碼里面的內(nèi)容,這里只是做聲明。
"C”表示的一種編譯和連接規(guī)約,這里為下一步c++調(diào)用其做準(zhǔn)備.
比如void foo(int,int);該函數(shù)被C編譯器編譯后在庫(kù)中的名字為_foo,
而C++編譯器則會(huì)產(chǎn)生像_foo_int_int之類的名字用來(lái)支持函數(shù)重載和類型安全連接。
由于編譯后的名字不同,C++程序不能直接調(diào)用C函數(shù)。
因此C++提供了一個(gè)C連接交換指定符號(hào)extern“C”來(lái)解決這個(gè)問(wèn)題而不是一種語(yǔ)言。
C表示這段代碼可以是符合C語(yǔ)言的編譯和連接規(guī)約的任何語(yǔ)言,如Fortran、assembler等。
二、cpp構(gòu)成jni橋梁
一個(gè)CPP文件調(diào)用之,則需添加其頭文件,比如frameworks/base/core/jni/android_os_Power.cpp.
1: #include "JNIHelp.h"
2: #include "jni.h"
3: #include "android_runtime/AndroidRuntime.h"
4: #include hardware_legacy/power.h
5: namespace android{
6: ....
7:
8: //定義函數(shù):
9: static int setScreenState(JNIEnv *env, jobject clazz, jboolean on)
10: {
11: return set_screen_state(on);//以此實(shí)現(xiàn)cpp到c的調(diào)用
12: }
13:
14: static JNINativeMethod method_table[] = {//此處實(shí)現(xiàn)java對(duì)cpp的調(diào)用轉(zhuǎn)化 注2
15: { "acquireWakeLock", "(ILjava/lang/String;)V", (void*)acquireWakeLock },
16: { "releaseWakeLock", "(Ljava/lang/String;)V", (void*)releaseWakeLock },
17: { "setLastUserActivityTimeout", "(J)I", (void*)setLastUserActivityTimeout },
18: { "setScreenState", "(Z)I", (void*)setScreenState },
19: { "shutdown", "()V", (void*)android_os_Power_shutdown },
20: { "rebootNative", "(Ljava/lang/String;)V", (void*)android_os_Power_reboot },
21: };
22: int register_android_os_Power(JNIEnv *env) //此處注冊(cè)jni
23: { //向VM(即AndroidRuntime)登記 gMethods[]表格所含的本地函數(shù)
24: return AndroidRuntime::registerNativeMethods(
25: env, "android/os/Power",
26: method_table, NELEM(method_table));
27: }
28: };
注2:
typedef struct {
const char* name; //Java中函數(shù)的名字
const char* signature; //用字符串是描述了函數(shù)的參數(shù)和返回值
void* fnPtr; //函數(shù)指針,指向C函數(shù)
} JNINativeMethod;
其中比較難以理解的是第二個(gè)參數(shù),例如
"()V"
"(II)V"
"(Ljava/lang/String;Ljava/lang/String;)V"
實(shí)際上這些字符是與函數(shù)的參數(shù)類型一一對(duì)應(yīng)的。
"()" 中的字符表示參數(shù),后面的則代表返回值。例如"()V" 就表示void Func();
"(II)V" 表示 void Func(int, int);
具體的每一個(gè)字符的對(duì)應(yīng)關(guān)系如下
字符 Java類型 C類型
V void void
Z jboolean boolean
I jint int
J jlong long
D jdouble double
F jfloat float
B jbyte byte
C jchar char
S jshort short
數(shù)組則以"["開始,用兩個(gè)字符表示
[I jintArray int[]
[F jfloatArray float[]
[B jbyteArray byte[]
[C jcharArray char[]
[S jshortArray short[]
[D jdoubleArray double[]
[J jlongArray long[]
[Z jbooleanArray boolean[]
上面的都是基本類型。如果Java函數(shù)的參數(shù)是class,則以"L"開頭,以";"結(jié)尾中間是用"/" 隔開的包及類名。而其對(duì)應(yīng)的C函數(shù)名的參數(shù)則為jobject. 一個(gè)例外是String類,其對(duì)應(yīng)的類為jstring
Ljava/lang/String; String jstring
Ljava/net/Socket; Socket jobject
如果JAVA函數(shù)位于一個(gè)嵌入類,則用$作為類名間的分隔符。
例如 "(Ljava/lang/String;Landroid/os/FileUtils$FileStatus;)Z"
三、java的封裝實(shí)現(xiàn)
frameworks/base/core/java/android/os/Power.java //此處路徑跟cpp中注冊(cè)jni處的路徑是一致的.待細(xì)研究是否有關(guān)系
1: package android.os;
2: public class Power
3: {
4: ...
5: public static native int setScreenState(boolean on); //被native修飾的表示調(diào)用了非java語(yǔ)言的本地方法
6: ...
7: }
四、java中對(duì)其調(diào)用
frameworks/base/services/java/com/android/server/PowerManagerService.java
import android.os.Power;
public class PowerManagerService extends IPowerManager.Stub
implements LocalPowerManager, Watchdog.Monitor {
...
int err = Power.setScreenState(on);
...
}
安裝NDK,在eclipse里設(shè)置關(guān)聯(lián)
首先你在類里面寫native方法,如native String printHello();
設(shè)置靜態(tài)方法塊?調(diào)用so文件,如static {System.loadLibrary("jnitest");}
右鍵工程-android tools-add native support
編寫C/C++文件,對(duì)應(yīng).c/.cpp,如
JNIEXPORT jstring JNICALL Java_com_example_jnit_MainActivity_printHello
(JNIEnv *env, jobject obj){
return env-NewStringUTF("Hello world !");
}
編譯運(yùn)行
android的jni可以使用c/c++來(lái)開發(fā),相比java而言,運(yùn)行的效率提高了很多,特別是在做一些圖像算法,或者游戲邏輯的時(shí)候,使用jni將大大的提高效率。比如某個(gè)游戲要采用opengl,同樣加載一個(gè)由1000個(gè)多邊形組成的3d模型,jni要比java運(yùn)算快好幾倍,這樣就保證了游戲運(yùn)行的fps不會(huì)太低。
另外一個(gè)好處就是內(nèi)存管理上面,java的內(nèi)存管理全部由虛擬機(jī)來(lái)管理,C++由程序員來(lái)管理,利用率上面就好多了。
等等其他優(yōu)點(diǎn)。
既然這么多的優(yōu)點(diǎn),為什么一個(gè)android程序不采用純c來(lái)開發(fā)呢?因?yàn)槭莂ndroid的 UI framework采用java,所以,在UI上面還是采用java來(lái)開發(fā)。
1.引言
我們知道,Android系統(tǒng)的底層庫(kù)由c/c++編寫,上層Android應(yīng)用程序通過(guò)Java虛擬機(jī)調(diào)用底層接口,銜接底層c/c++庫(kù)與Java應(yīng)用程序間的接口正是JNI(JavaNative Interface)。本文描述了如何在ubuntu下配置AndroidJNI的開發(fā)環(huán)境,以及如何編寫一個(gè)簡(jiǎn)單的c函數(shù)庫(kù)和JNI接口,并通過(guò)編寫Java程序調(diào)用這些接口,最終運(yùn)行在模擬器上的過(guò)程。
2.環(huán)境配置
2.1.安裝jdk1.6
(1)從jdk官方網(wǎng)站下載jdk-6u29-linux-i586.bin文件。
(2)執(zhí)行jdk安裝文件
[html] view plaincopyprint?
01.$chmod a+x jdk-6u29-linux-i586.bin
02.$jdk-6u29-linux-i586.bin
$chmod a+x jdk-6u29-linux-i586.bin
$jdk-6u29-linux-i586.bin
(3)配置jdk環(huán)境變量
[html] view plaincopyprint?
01.$sudo vim /etc/profile
02.#JAVAEVIRENMENT
03.exportJAVA_HOME=/usr/lib/java/jdk1.6.0_29
04.exportJRE_HOME=$JAVA_HOME/jre
05.exportCLASSPATH=$JAVA_HOME/lib:$JRE_HOME/lib:$CLASSPATH
06.exportPATH=$JAVA_HOME/bin:$JRE_HOME/bin:$PATH
$sudo vim /etc/profile
#JAVAEVIRENMENT
exportJAVA_HOME=/usr/lib/java/jdk1.6.0_29
exportJRE_HOME=$JAVA_HOME/jre
exportCLASSPATH=$JAVA_HOME/lib:$JRE_HOME/lib:$CLASSPATH
exportPATH=$JAVA_HOME/bin:$JRE_HOME/bin:$PATH
保存后退出編輯,并重啟系統(tǒng)。
(4)驗(yàn)證安裝
[html] view plaincopyprint?
01.$java -version
02.javaversion "1.6.0_29"
03.Java(TM)SE Runtime Environment (build 1.6.0_29-b11)
04.JavaHotSpot(TM) Server VM (build 20.4-b02, mixed mode)
05.$javah
06.用法:javah[選項(xiàng)]類
07.其中[選項(xiàng)]包括:
08.-help輸出此幫助消息并退出
09.-classpath路徑用于裝入類的路徑
10.-bootclasspath路徑用于裝入引導(dǎo)類的路徑
11.-d目錄輸出目錄
12.-o文件輸出文件(只能使用-d或-o中的一個(gè))
13.-jni生成JNI樣式的頭文件(默認(rèn))
14.-version輸出版本信息
15.-verbose啟用詳細(xì)輸出
16.-force始終寫入輸出文件
17.使用全限定名稱指定類(例
18.如,java.lang.Object)。
$java -version
javaversion "1.6.0_29"
Java(TM)SE Runtime Environment (build 1.6.0_29-b11)
JavaHotSpot(TM) Server VM (build 20.4-b02, mixed mode)
$javah
用法:javah[選項(xiàng)]類
其中[選項(xiàng)]包括:
-help輸出此幫助消息并退出
-classpath路徑用于裝入類的路徑
-bootclasspath路徑用于裝入引導(dǎo)類的路徑
-d目錄輸出目錄
-o文件輸出文件(只能使用-d或-o中的一個(gè))
-jni生成JNI樣式的頭文件(默認(rèn))
-version輸出版本信息
-verbose啟用詳細(xì)輸出
-force始終寫入輸出文件
使用全限定名稱指定類(例
如,java.lang.Object)。2.2.安裝android應(yīng)用程序開發(fā)環(huán)境
ubuntu下安裝android應(yīng)用程序開發(fā)環(huán)境與windows類似,依次安裝好以下軟件即可:
(1)Eclipse
(2)ADT
(3)AndroidSDK
與windows下安裝唯一不同的一點(diǎn)是,下載這些軟件的時(shí)候要下載Linux版本的安裝包。
安裝好以上android應(yīng)用程序的開發(fā)環(huán)境后,還可以選擇是否需要配置emulator和adb工具的環(huán)境變量,以方便在進(jìn)行JNI開發(fā)的時(shí)候使用。配置步驟如下:
把emulator所在目錄android-sdk-linux/tools以及adb所在目錄android-sdk-linux/platform-tools添加到環(huán)境變量中,android-sdk-linux指androidsdk安裝包android-sdk_rxx-linux的解壓目錄。
[plain] view plaincopyprint?
01.$sudo vim /etc/profile
02.exportPATH=~/software/android/android-sdk-linux/tools:$PATH
03. exportPATH=~/software/android/android-sdk-linux/platform-tools:$PATH
$sudo vim /etc/profile
exportPATH=~/software/android/android-sdk-linux/tools:$PATH
exportPATH=~/software/android/android-sdk-linux/platform-tools:$PATH
編輯完畢后退出,并重啟生效。
2.3.安裝NDK
NDK是由android提供的編譯android本地代碼的一個(gè)工具。
(1)從androidndk官網(wǎng)下載ndk,目前最新版本為android-ndk-r6b-linux-x86.tar.bz2.
(2)解壓ndk到工作目錄:
[plain] view plaincopyprint?
01.$tar -xvf android-ndk-r6b-linux-x86.tar.bz2
02.$sudo mv android-ndk-r6b /usr/local/ndk
$tar -xvf android-ndk-r6b-linux-x86.tar.bz2
$sudo mv android-ndk-r6b /usr/local/ndk
(3)設(shè)置ndk環(huán)境變量
[plain] view plaincopyprint?
01.$sudo vim /etc/profile
02.exportPATH=/usr/local/ndk:$PATH
$sudo vim /etc/profile
exportPATH=/usr/local/ndk:$PATH
編輯完畢后保存退出,并重啟生效
(4)驗(yàn)證安裝
[plain] view plaincopyprint?
01.$ cd/usr/local/ndk/samples/hello-jni/
02.$ ndk-build
03.Gdbserver : [arm-linux-androideabi-4.4.3] libs/armeabi/gdbserver
04.Gdbsetup : libs/armeabi/gdb.setup
05.Install : libhello-jni.so = libs/armeabi/libhello-jni.so
$ cd/usr/local/ndk/samples/hello-jni/
$ ndk-build
Gdbserver : [arm-linux-androideabi-4.4.3] libs/armeabi/gdbserver
Gdbsetup : libs/armeabi/gdb.setup
Install : libhello-jni.so = libs/armeabi/libhello-jni.so
3.JNI實(shí)現(xiàn)
我們需要定義一個(gè)符合JNI接口規(guī)范的c/c++接口,這個(gè)接口不用太復(fù)雜,例如輸出一個(gè)字符串。接下來(lái),則需要把c/c++接口的代碼文件編譯成共享庫(kù)(動(dòng)態(tài)庫(kù)).so文件,并放到模擬器的相關(guān)目錄下。最后,啟動(dòng)Java應(yīng)用程序,就可以看到最終效果了。
3.1.編寫Java應(yīng)用程序代碼
(1)啟動(dòng)Eclipse,新建android工程
Project:JNITest
Package:org.tonny.jni
Activity:JNITest
(2)編輯資源文件
編輯res/values/strings.xml文件如下:
編輯res/layout/main.xml文件
我們?cè)谥鹘缑嫔咸砑恿艘粋€(gè)EditText控件和一個(gè)Button控件。
(3)編輯JNITest.java文件
static表示在系統(tǒng)第一次加載類的時(shí)候,先執(zhí)行這一段代碼,在這里表示加載動(dòng)態(tài)庫(kù)libJNITest.so文件。
再看這一段:
[java] view plaincopyprint?
01.privatenativeString GetReply();
privatenativeString GetReply();
native表示這個(gè)方法由本地代碼定義,需要通過(guò)jni接口調(diào)用本地c/c++代碼。
[java] view plaincopyprint?
01.publicvoidonClick(View arg0) {
02.edtName.setText(reply);
03.}
publicvoidonClick(View arg0) {
edtName.setText(reply);
}
這段代碼表示點(diǎn)擊按鈕后,把native方法的返回的字符串顯示到EditText控件。
(4)編譯工程,生成.class文件。
3.2.用javah工具生成符合JNI規(guī)范的c語(yǔ)言頭文件
在終端中,進(jìn)入android工程所在的bin目錄
[plain] view plaincopyprint?
01.$cd ~/project/Android/JNITest/bin
$cd ~/project/Android/JNITest/bin
我們用ls命令查看,可以看到bin目錄下有個(gè)classes目錄,其目錄結(jié)構(gòu)為classes/org/tonny/jni,即classes的子目錄結(jié)構(gòu)是android工程的包名org.tonny.jni。請(qǐng)注意,下面我們準(zhǔn)備執(zhí)行javah命令的時(shí)候,必須進(jìn)入到org/tonny/jni的上級(jí)目錄,即classes目錄,否則javah會(huì)提示找不到相關(guān)的java類。
下面繼續(xù):
[plain] view plaincopyprint?
01.$cd classes
02.$javah org.tonny.jni.JNITest
03.$ls
04.org org_tonny_jni_JNITest.h
$cd classes
$javah org.tonny.jni.JNITest
$ls
org org_tonny_jni_JNITest.h
執(zhí)行javahorg.tonny.jni.JNITest命令,在classes目錄下會(huì)生成org_tonny_jni_JNITest.h頭文件。如果不進(jìn)入到classes目錄下的話,也可以這樣:
[plain] view plaincopyprint?
01.$javah -classpath ~/project/Android/JNITest/bin/classesorg.tonny.jni.JNITest
$javah -classpath ~/project/Android/JNITest/bin/classesorg.tonny.jni.JNITest
-classpath 參數(shù)表示裝載類的目錄。
3.3.編寫c/c++代碼
生成org_tonny_jni_JNITest.h頭文件后,我們就可以編寫相應(yīng)的函數(shù)代碼了。下面在android工程目錄下新建jni目錄,即~/project/Android/JNITest/jni,把org_tonny_jni_JNITest.h頭文件拷貝到j(luò)ni目錄下,并在jni目錄下新建org_tonny_jni_JNITest.c文件,編輯代碼如下:
[cpp] view plaincopyprint?
01.#includejni.h
02.#includestring.h
03.#include"org_tonny_jni_JNITest.h"
04.
05.
06.JNIEXPORTjstring JNICALLJava_org_tonny_jni_JNITest_GetReply
07.(JNIEnv *env, jobject obj){
08.return(*env)-NewStringUTF(env,(char*)"Hello,JNITest");
09.}
#includejni.h
#includestring.h
#include"org_tonny_jni_JNITest.h"
JNIEXPORTjstring JNICALLJava_org_tonny_jni_JNITest_GetReply
(JNIEnv *env, jobject obj){
return(*env)-NewStringUTF(env,(char*)"Hello,JNITest");
}
我們可以看到,該函數(shù)的實(shí)現(xiàn)相當(dāng)簡(jiǎn)單,返回一個(gè)字符串為:"Hello,JNITest"
3.4.編寫Android.mk文件
在~/project/Android/JNITest/jni目錄下新建Android.mk文件,android可以根據(jù)這個(gè)文件的編譯參數(shù)編譯模塊。編輯Android.mk文件如下:
[plain] view plaincopyprint?
01.LOCAL_PATH:= $(call my-dir)
02.include$(CLEAR_VARS)
03.LOCAL_MODULE := libJNITest
04.LOCAL_SRC_FILES:= org_tonny_jni_JNITest.c
05.include$(BUILD_SHARED_LIBRARY)
LOCAL_PATH:= $(call my-dir)
include$(CLEAR_VARS)
LOCAL_MODULE := libJNITest
LOCAL_SRC_FILES:= org_tonny_jni_JNITest.c
include$(BUILD_SHARED_LIBRARY)
LOCAL_MODULE表示編譯的動(dòng)態(tài)庫(kù)名稱
LOCAL_SRC_FILES 表示源代碼文件
3.5.用ndk工具編譯并生成.so文件
進(jìn)入到JNITest的工程目錄,執(zhí)行ndk-build命令即可生成libJNITest.so文件。
[plain] view plaincopyprint?
01.$cd ~/project/Android/JNITest/
02.$ndk-build
03.Invalidattribute name:
04.package
05.Install : libJNITest.so = libs/armeabi/libJNITest.so
$cd ~/project/Android/JNITest/
$ndk-build
Invalidattribute name:
package
Install : libJNITest.so = libs/armeabi/libJNITest.so
可以看到,在工程目錄的libs/armeabi目錄下生成了libJNITest.so文件。
3.6.在模擬器上運(yùn)行
(1)首先,我們把a(bǔ)ndroid模擬器啟動(dòng)起來(lái)。進(jìn)入到emulator所在目錄,執(zhí)行emulator命令:
[plain] view plaincopyprint?
01.$cd ~/software/android/android-sdk-linux/tools
02.$./emulator @AVD-2.3.3-V10 -partition-size 512
$cd ~/software/android/android-sdk-linux/tools
$./emulator @AVD-2.3.3-V10 -partition-size 512
AVD-2.3.3-V10表示你的模擬器名稱,與在Eclipse-AVDManager下的AVDName對(duì)應(yīng),-partition-size表示模擬器的存儲(chǔ)設(shè)備容量。
(2)接下來(lái),我們需要把libJNITest.so文件拷貝到模擬器的/system/lib目錄下,執(zhí)行以下命令:
[plain] view plaincopyprint?
01.$cd ~/project/Android/JNITest/libs/armeabi/
02.$adb remount
03.$adb push libJNITest.so /system/lib
04.80 KB/s (10084 bytes in 0.121s)
$cd ~/project/Android/JNITest/libs/armeabi/
$adb remount
$adb push libJNITest.so /system/lib
80 KB/s (10084 bytes in 0.121s)
當(dāng)在終端上看到有80 KB/s (10084 bytes in 0.121s)傳輸速度等信息的時(shí)候,說(shuō)明拷貝成功。
(3)在終端上執(zhí)行JNITest程序,這個(gè)我們可以在Eclipse下,右鍵點(diǎn)擊JNITest工程,RunAs-Android Application,即可在模擬器上啟動(dòng)程序