1. 程式人生 > >android 呼叫C/C++的互相呼叫,以及DLL的呼叫。

android 呼叫C/C++的互相呼叫,以及DLL的呼叫。

1.JNI
(1)Java call Native C
JNI的基本概念可以參考以下文獻:
http://blog.csdn.net/believefym/archive/2007/06/08/1644635.aspx
    這裡需要注意的是javah命令處理的是.class檔案,而不是.java檔案。你需要指定package的路徑和package名。javap命令也有類似的要求。
例子很簡單,可以從下面的網頁獲得,C++的程式碼沒有配工程,將之編譯成DLL即可。
http://www.namipan.com/d/21e857fd04fc086ce0b3ba90ba92f197e1b626ca55df0000

(2)Native C call Java
基本方法參考下列文獻:

http://icepeer.itpub.net/post/3982/19158
注意事項:
使用建構函式時,JDK1.1的寫法是:
    jmethodID mid = (*env)->GetMethodID(env,cls,"","(Ljava/lang/String;)V");
而JDK1.2的寫法是:
    jmethodID mid = (*env)->GetMethodID(env,cls,"<init>","(Ljava/lang/String;)V");
例子的功能,和上面的例子類似,可以從下面的網頁獲得
http://www.namipan.com/d/688b2aca53531d0aeb236015f95fd36b594005392d520000


(3)Java call Native C & Native C callback Java
Android由於提供的是Java介面,所以這裡最常見的情況是
1)Java啟動程式。
2)Java呼叫Native C。
3)Native C回撥Java。
有上面的程式設計經驗,這個也不是太難的事,例子如下:
http://www.namipan.com/d/54782eb9fc9480904e4152adff6e1df28f648976f83f0000
    這個例子和(2)中的區別在於:(2)中的例子由於程式是用C啟動的,所以在呼叫Java之前,需要啟動JVM,並例項化相關的Java物件,而(3)中例子由於程式是用Java啟動的,在C回撥Java時,JVM已經啟動,相關的Java物件例項也已存在,所以做法要簡單的多。
    使用javah生成的.h檔案中,函式的宣告在Java中宣告的引數之前,還有兩個引數JNIEnv和jobject。env是呼叫Java方法的介面,它的功能非常多。jobject傳入的是呼叫Native C的那個Java物件例項的引用。jobject指標不能在多個執行緒中共享. 就是說,不能直接在儲存一個執行緒中的jobject指標到全域性變數中,然後在另外一個執行緒中使用它.幸運的是,可以用
gs_object=env->NewGlobalRef(obj);
來將傳入的obj儲存到gs_object中,從而其他執行緒可以使用這個gs_object來操縱那個java物件了。

2.在Android上編譯C程式
(1)風臨左岸在這方面頗有建樹,以下是他的blog:
http://blog.sina.com.cn/flza

    最簡單的hello world程式可以參看下面這篇文章的做法。
Android原生(Native)C開發之一:環境搭建篇
http://blog.sina.com.cn/s/blog_4a0a39c30100auh9.html
    這裡有個概念要弄清楚,之前的例子都是針對x86-win32平臺的,而這裡我們要在arm-linux下程式設計。我這裡有個同事,曾將x86-win32下的dll放到Android模擬器中,然後抱怨無法使用JNI,這就是這類錯誤的一個典型的例子。
    還有一點需要注意的是,風臨左岸使用的交叉編譯工具,所編出的程式雖然可以在模擬器中執行,但卻是無法直接用於JNI的,需要使用一定的技巧,可參見以下網頁:
Shared library "Hello World!" for Android
http://honeypod.blogspot.com/2007/12/shared-library-hello-world-for-android.html
    從這篇文章可以看出,風臨左岸使用的交叉編譯工具的動態庫的預設格式,和Android平臺的動態庫的格式是不同的,這也是之前有人說Android無法使用JNI的原因。
    更好的辦法是直接使用google提供的交叉編譯工具。

(2)獲得Android的原始碼
    獲得原始碼的官方方法如下:
http://source.android.com/source/download.html
    雖然原始碼在http://source.android.com下,但用瀏覽器是無法獲得程式碼的,只能用上文提到的方法才行。
當然諸如www.androidin.com之類的網站也提供了非官方的原始碼下載。
    原始碼的大小有1G左右,解壓原始碼,在原始碼的prebuilt/linux-x86/toolchain/arm-eabi-4.2.1/bin資料夾下可以找到google提供的交叉編譯工具。
    使用prebuilt工具編譯C程式的方法可以參考以下文章:
http://www.j2medev.com/android/native/200901/20090119222445.html

3.Android JNI
    正統的做法可以參照以下網頁:
http://www.j2medev.com/android/native/200901/20090119222617.html
    當然簡單的做法也是有的,可以只解壓原始碼中的bionic、dalvik、prebuilt這三個資料夾。
需要注意的是,和第三方的交叉編譯工具不同,prebuilt下只有相關的標頭檔案而沒有相應的.o或.a檔案,所以用一般的makefile的話,沒有辦法編譯靜態可執行檔案,但是動態庫檔案是可以編的。
    這裡還有一個奇怪的地方,如果直接使用cc生成.so的話,會出錯。而先cc生成.o,再ld生成.so就沒問題。不知道這是什麼原因,尚需高手指點。
    這裡也提供一個例子,功能和例子1.(3)相同。使用時,將.apk和.so都放在/data/app下即可。
http://www.namipan.com/d/cb2c2eb8c1c6852eaf744e382b8d9805b44e5a2e7f660000
    這裡需要指出一點,普通的JNI呼叫使用System.loadLibrary載入動態庫,而Android平臺要使用System.load載入動態庫。這就是很多人套用PC上的語法,但卻在Android上失敗的原因。

4.關於一個語法現象
    AlertDialog dialog = new AlertDialog.Builder(SmartGuider.this)
           .setTitle("設定錯誤")
           .setMessage("GPS功能未開啟,請先開啟GPS後再使用本程式!/n/n點選Ok進入系統設定頁面,點選Cancel退出程式。")
           .create();

    (此處省略1xx個字)


    在C語言中,其實也有類似的情況,例如
    char *strcpy(char *strDestination,const char *strSource);
為什麼不寫成
    void strcpy(char *strDestination,const char *strSource);
或者
    char *strcpy(const char *strSource);
呢?
    不採用第一種,是因為這樣做可以使用諸如strlen(strcpy(...))之類的便捷寫法。
    不採用第二種,當然是和記憶體分配有關了,這裡就不多說了。

5.Activity生命週期初探
    Android SDK上關於Activity生命週期的圖,無疑是權威的,但相關文件中並沒有Android手機功能鍵對Activity生命週期所造成的改變的內容。
    我這裡通過過載Activity派生類的相關函式,並輸出日誌的方法,探討該問題。
    先說一下日誌,可以使用android.util.Log類來輸出日誌,eclipse在Debug狀態下可以看到輸出的日誌。輸出日誌的方法有很多種,他們並不是各自獨立的,而是一種資訊內容包含的關係,從最簡略到最詳細依次為ERROR, WARN, INFO, DEBUG, VERBOSE,也就是說ERROR中的日誌一定在VERBOSE中也能看到。還有Android執行時生成的日誌很多,可以自定義過濾器過濾無關的日誌。
    這裡僅對onCreate、onDestroy、onPause、onRestart、onResume、onStart、onStop進行過載,並輸出日誌。
1)啟動程式
onCreate->onStart->onResume
2)重啟動程式(按下左側的主頁鍵和左側的撥號按鈕,不會關閉當前的Activity)
onRestart->onStart->onResume
3)按下左側的主頁鍵和左側的撥號按鈕
onPause->onStop
4)按下右側的返回鍵
onPause->onStop->onDestroy
5)右側的掛機按鈕
onPause

6.新增新的View
    利用嚮導生成的程式碼中只有一個View,相應的UI佈局檔案在res/layout/main.xml,找了半天也沒找到如何快捷的新增View,於是只好自己手動新增相關的xml檔案,好在eclipse可以定製新增的xml檔案的模板,於是把main.xml複製一下,做成模板,以後新增就方便了。

7.編譯Froyo(部分轉貼)
--------------------------------------------------------------------------------------------------------------------------
    說點題外話,這個Blog本來打算完全寫些自己的原創內容,對於轉貼只給連結,但連結這東西,有利有弊。常常過上幾個月,原來的連結就不好使了。所以只好有選擇的貼上一些了。
--------------------------------------------------------------------------------------------------------------------------
    Froyo出來有一陣子了,一時興起,從官網上git了程式碼,打算編譯。不料根據出錯資訊得知,Froyo及其以後的版本需要64-bit的OS才能編譯。所以只好重新安裝64-bit的Ubuntu。

    按照官網上的步驟一步一步的做,然後卡在apt-get install sun-java5-jdk上了。出錯資訊告訴我,找不到這個包。Google了一下,找到以下解決方法:
9.10/10.04 add ubuntu 9.04 line to you /etc/apt/sources.list

deb http://us.archive.ubuntu.com/ubuntu/ jaunty multiverse
deb http://us.archive.ubuntu.com/ubuntu/ jaunty-updates multiverse

#sudo apt-get update
#sudo apt-get install sun-java5-jdk
(注意安裝會一直停留在閱讀sun的同意書上,使勁按確定都沒反應的(確定是文字不是按鈕),後來按鍵盤Tab解決。)

更改預設jdk的方法如下:同理,更改 預設的javac,方法為
#update-alternatives --config java
#update-alternatives --config javac

顯示如下,然後鍵入java-1.5.0-sun的 編號:
有 2 個選項可用於替換項 java (提供 /usr/bin/java)。

  選擇       路徑                                    優先順序  狀態
------------------------------------------------------------
* 0            /usr/lib/jvm/java-6-openjdk/jre/bin/java   1061      自動模式
  1            /usr/lib/jvm/java-1.5.0-sun/jre/bin/java   53        手動模式
  2            /usr/lib/jvm/java-6-openjdk/jre/bin/java   1061      手動模式

  Selection    Path                                   優先順序  Status
------------------------------------------------------------
  0            /usr/lib/jvm/java-6-openjdk/bin/javac   1061      auto mode
  1            /usr/bin/ecj                            143       manual mode
  2            /usr/bin/gcj-wrapper-4.4                1044      manual mode
* 3            /usr/lib/jvm/java-1.5.0-sun/bin/javac   53        manual mode
  4            /usr/lib/jvm/java-6-openjdk/bin/javac   1061      manual mode

檢視當前的java版本:
#java -version

java version "1.5.0_22"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_22-b03)
Java HotSpot(TM) Client VM (build 1.5.0_22-b03, mixed mode, sharing)
最鬱悶的是,在安裝好sun-java5-jdk之後,make的時候,出錯資訊告訴我,Froyo已經改用Java 1.6了。 大哭 

make一切順利,之後在out/host/linux-x86/bin下找到emulator,但卻無法執行,提示缺少AVD device,使用export ANDROID_PRODUCT_OUT=~/android/out/target/product/generic解決之。

3.Android JNI
    正統的做法可以參照以下網頁:
http://www.j2medev.com/android/native/200901/20090119222617.html
    當然簡單的做法也是有的,可以只解壓原始碼中的bionic、dalvik、prebuilt這三個資料夾。
需要注意的是,和第三方的交叉編譯工具不同,prebuilt下只有相關的標頭檔案而沒有相應的.o或.a檔案,所以用一般的makefile的話,沒有辦法編譯靜態可執行檔案,但是動態庫檔案是可以編的。
    這裡還有一個奇怪的地方,如果直接使用cc生成.so的話,會出錯。而先cc生成.o,再ld生成.so就沒問題。不知道這是什麼原因,尚需高手指點。
    這裡也提供一個例子,功能和例子1.(3)相同。使用時,將.apk和.so都放在/data/app下即可。
http://www.namipan.com/d/cb2c2eb8c1c6852eaf744e382b8d9805b44e5a2e7f660000
    這裡需要指出一點,普通的JNI呼叫使用System.loadLibrary載入動態庫,而Android平臺要使用System.load載入動態庫。這就是很多人套用PC上的語法,但卻在Android上失敗的原因。