1. 程式人生 > >【轉】Android ROM研究---Android build system增加模組

【轉】Android ROM研究---Android build system增加模組

Android build system就是編譯系統的意思

在我們需要向自己編譯的原始碼中增加模組的時候,需要一些規則,當然這個規則都是類似的。

Android.mk檔案解析

讓我們來看一個 Android.mk 檔案的樣子

Java程式碼  
  1. LOCAL_PATH := $(call my-dir)  
  2. include $(CLEAR_VARS)  
  3. LOCAL_MODULE :=Hello  
  4. LOCAL_SRC_FILES := hello.c  
  5. include $(BUILD_SHARED_LIBRARY)  

①     LOCAL_PATH :=$(call my-dir)

固定寫法, LOCAL_PATH 表示此時位於工程目錄的根目錄中, (call my-dir) 的功能由編譯器提供,被用來返回當前目錄的地址(包含 Android.mk 本身)

②     include $(CLEAR_VARS)

固定寫法, CLEAR_VARS 這個變數由編譯系統提供,並且要執行一個 GNU makefile 檔案,這個功能會清理掉所有以 LOCAL_ 開頭的內容(比如 LOCAL_MODULE 、 LOCAL_SRC_FILES 等),除了 LOCAL_PATH。這句話也是必須的,因為如果所有變數都是全域性變數的話,所有的可控的編譯檔案都需要在一個單獨的GNU 中被解析並執行

③     LOCAL_MODULE :=Hello

LOCAL_MODLE 變數必須被定義,用來區分 Android.mk 中的每一個模組。檔名必須是唯一的,不能有空格。注意,編譯器會為你自動加上一些字首和字尾,來保證檔案是一致的。比如:這裡表明一個動態連結庫模組被命名為“ Hello ”,但是最後會生成“ libHello.so ”檔案。但是在 java 中裝載這個庫的時候還要使用“ Hello ”名稱。

④     LOCAL_SRC_FILES :=hello.c

LOCAL_SRC_FILES 變數必須包含一個 C 和 C++ 原始檔的列表,這些會被編譯並聚合到一個模組中

注意:這裡並不需要列標頭檔案和被包含的檔案,因為編譯系統會自動為你計算相關的屬性,原始碼的列表會直接傳遞給編譯器

⑤     include $(BUILD_SHARED_LIBRARY)

BUILD_SHARED_LIBRARY 這個變數由系統提供,並且指定給 GNU makefile 的指令碼,它可以收集所有你定義的 ”include $(CLEAR_VARS)” 中以 LOCAL_ 開頭的變數,並且決定哪些要編譯,哪些應該做的更加準確。我們同樣可以使用 BUILD_STATIC_LIBRARY 來生成一個靜態庫,如果使用 BUILD_STATIC_LIBRARY 編譯系統便會生成一個以“ lib$(LOCAL_MODULE).a ”為檔名的檔案來提供動態庫的呼叫

⑥     TARGET_ARCH

TARGET_ARCH 是指架構中 CPU 的名字已經被 Android 開原始碼明確指出了,這裡的 arm 包含了任何ARM- 獨立結構的架構,以及每個獨立的 CPU 版本

⑦    TARGET_PLATFORM

Android 平臺的名字在 Android.mk 檔案中被解析,比如 ”android-2.3”

⑧     TARGET_ROOT_OUT :表示根檔案系統

用法: CAL_MODULE_PATH:=$(TARGET_ROOT_OUT)

⑨     TARGET_OUT: 表示 system 檔案系統

⑩    TARGET_OUT_DATA: 表示 data 檔案系統

⑪         TARGET_ABI

TARGET_ABI 平臺目標板和 abi 的連結,這裡要定義 $(TARGET_PLATFORM)-$(TARGET_ARCH_ABI) ,它們都非常有用,特變是當你想測試一個具體的系統映象在幾個真實裝置的時候

下面是 GNU 編譯出來的巨集,並且它們都必須使用“ $(call <function>) ”才能返回文字化的資訊。

all-subdir-makefiles :返回一個 Android.mk 檔案所在位置的列表,以及當前 my-dir 的路徑。

比如: include $(call all-subdir-makefiles)

this-makefile :返回當前 makefile 的路徑(就是哪個功能被呼叫了)

parent-makefile :返回 makefile 的包含樹,也就是包含 makefile 的當前檔案

Application.mk

要講 C\C++ 編譯為 so 檔案,光有 Android.mk 檔案是不行的,還需要 Application.mk 檔案。

Application.mk 檔案存放的位置是 NDK 工程的根目錄, Application.mk 檔案就是用來描述應用程式中所需要的原生的模組(也就是靜態庫和動態庫),每一個 Application.mk 檔案都必須放在應用目錄下的子目錄,例如$NDK/apps/HelloNdk/Application.mk ,作為 GNU makefile 的一部分, Application.mk 檔案必須要定義以下部分

1、  APP_MODULES

APP_MODULES 變數是強制性的,並且會列出所有你所需要的模組(通過 Android.mk )

2、  APP_PROJECT_PATH

APP_PROJECT_PATH 變數也是強制性的,並且會給出應用程式工程的根目錄一個絕對路徑。這是用來複制或者安裝一個沒有任何版本限制的 JNI 庫,從而給 APK 生成工具一個詳細的路徑。

例如: \HelloNDK\Application.mk

APP_PROJECT_PATH := $(call my-dir)/project

APP_MODULES := HelloNdk

這裡定義了工程路徑為 $(call my-dir)/project ,而要編譯的模組則是 HelloNdk ,這樣編譯系統才會找到我們要編譯的庫和原檔案

3、  APP_CFLAGS 則是當要編譯模組中有任何 C 檔案的時候, C 編譯器的訊號就會被髮出

4、  APP_OPTIM

這個變數是可選的,可以定義為釋出版或者測試版

------------------------------------------------------------------------------------------------------------------------

在Android.mk中:

include $(BUILD_EXECUTABLE)表示生成二進位制可執行檔案

include $(BUILD_STATIC_LIBRARY)表示生成靜態庫.a檔案,名字叫做lib<工程名>.a

include $(BUILD_SHARED_LIBRARY)表示生成動態庫.so檔案,名字叫做lib<工程名>.so

另外需要注意的是,生成的檔案需要放在手機的data/local目錄下,才可以有執行的許可權(未root),如果root了,則會有些目錄是可以執行,但是某些目錄依然不能執行,當然可以用umount命令解決。SD卡是沒有執行許可權的,所以當你生成的比如二進位制可執行檔案放到sdcard中時,是無法執行的。

可以這樣測試一下哪些是有執行許可權,哪些是沒有的:

*將手機插入電腦,並開啟USB除錯

*在終端輸入adb shell進入

*su(root了的手機)

*mount:可以看到一大堆的列表,如果對應的目錄的資訊中有noexec,說明這個目錄就沒有執行許可權,剩下的都可以執行二進位制等檔案。

Android.mk檔案的具體語法參見我的部落格:http://hualang.iteye.com/blog/1140414

向Android原始碼中增加模組主要有如下幾種:

1、增加可執行檔案

增加可執行檔案,這些可執行檔案一般都是由C/C++生成,下面簡單的介紹一些如何向原始碼中生成可執行檔案

假設我的原始碼在~/mydroid目錄下

在external/loulijun/test目錄下,建立兩個檔案,一個C,一個Android.mk

hello.c

C程式碼  
  1. #include <stdio.h>  
  2. int main(void)  
  3. {  
  4.     printf("Hello world!\n");  
  5.     return 0;  
  6. }  

 Android.mk

Java程式碼  
  1. LOCAL_PATH := $(call my-dir)  
  2. include $(CLEAR_VARS)  
  3. LOCAL_SRC_FILES :=hello.c  
  4. LOCAL_MODULE_TAGS :=optional  
  5. LOCAL_MODULE :=test  
  6. include $(BUILD_EXECUTABLE)  

首先退出到mydroid目錄下,執行

Java程式碼  
  1. . build/envsetup.sh  
  2. 或者  
  3. source build/envsetup.sh  

 進行環境變數的配置

然後進入到test目錄下,執行“mm”(mm表示編譯當前專案),如果想重新執行,可以"mm -B"

這樣,會在out/target/product/generic/obj/EXECUTABLES/test_intermediates/LINKED/目錄下生成可執行檔案test

然後將test檔案用adb push test /data/local 到data/local目錄下。

下面開始執行,你可以在手機中用terminal emulator來執行,也可以以adb shell後,執行

Java程式碼  
  1. ./test  
  2. 顯示:Hello world!  

2、增加靜態庫(.a)

Android.mk檔案

Java程式碼  
  1. LOCAL_PATH :=$(call my-dir)  
  2. include $(CLEAR_VARS)  
  3. LOCAL_SRC_FILES := \  
  4. hello.c  
  5. LOCAL_MODULE :=test  
  6. include $(BUILD_STATIC_LIBRARY)  

編譯:

mydroid#. build/envsetup.sh

test#mm

生成的結果在out/target/product/generic/obj/STATIC_LIBRARY

目標資料夾{XXX}_static_intermediates下,XXX為你定義的模組名稱test

假如這個靜態庫是由hello.c生成的,但是生成的靜態庫是不能直接使用的,而是由動態庫呼叫它

3、增加動態庫(.so)

編譯動態庫其實可以用NDK的,那樣生成非常方便,但是有時候還是需要掌握其他的方法的

Android.mk

Java程式碼  
  1. LOCAL_PATH :=$(call my-dir)  
  2. include $(CLEAR_VARS)  
  3. LOCAL_SRC_FILES := \  
  4. hello.c  
  5. LOCAL_MODULE :=test  
  6. include $(BUILD_SHARED_LIBRARY)  

 編譯的放法都差不多,只不過Android.mk不同的是最後一句,如果比較一下就會發現那句話決定了生成的是什麼

不過要想生成動態庫,絕非是這麼簡單的,有時候只需要Android.mk和原始檔即可,但是有時候還需要Application.mk檔案。Application.mk檔案的具體語法很快會在部落格中更新

下面是使用 NDK 來生成 .so 檔案的,環境是在 ubuntu11.04 下面

1、 下載 Android-NDK-r6 ,將其解壓到 ~/android/android-ndk-r6 目錄下

2、  配置 .bash_profile 檔案,加入

NDK=~/android/android-ndk-r6

export NDK

3、  cd $NDK 後,進入 ndk 的目錄,我以它自帶的專案為例,進入 samples/hello-jni

在終端輸入 $NDK/ndk-build

系統會自動編譯這個檔案,並將生成的 libhello-jni.so 檔案存放在當前目錄的 libs/armeabi 目錄下

4、  我們可以將這個生成的 libhello-jni.so 放在 Android 原始碼的適當的位置即可使用了

下面是相應的檔案

hello-jni.c

Java程式碼  
  1. #include <string.h>  
  2. #include <jni.h>  
  3. jstring  
  4. Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env,  
  5.                                                   jobject thiz )  
  6. {  
  7.     return (*env)->NewStringUTF(env, "Hello from JNI !");  
  8. }  

Android.mk 檔案

Java程式碼  
  1. LOCAL_PATH := $(call my-dir)  
  2. include $(CLEAR_VARS)  
  3. LOCAL_MODULE    := hello-jni  
  4. LOCAL_SRC_FILES := hello-jni.c  
  5. include $(BUILD_SHARED_LIBRARY)  

由於這裡只是使用了一個簡單的 C 檔案,所以沒用用到 Application.mk 檔案

然後將 hello-jni 匯入到 eclipse 中,執行模擬器,即可顯示

下面再看一個例子 native-activity

1、  進入目錄後執行 $NDK/ndk-build

2、  生成 libnative_activity.so 並存於當前目錄的 lib/armeabi 目錄下,另外由原始碼生成的還有靜態庫,存放於obj/local/armeabi/ libandroid_native_app_glue.a

這裡,由於 main.c 比較大,就不貼上了

Android.mk

Java程式碼  
  1. LOCAL_PATH := $(call my-dir)  
  2. include $(CLEAR_VARS)  
  3. LOCAL_MODULE    := native-activity  
  4. LOCAL_SRC_FILES := main.c  
  5. LOCAL_LDLIBS    := -llog -landroid -lEGL -lGLESv1_CM  
  6. LOCAL_STATIC_LIBRARIES := android_native_app_glue  
  7. include $(BUILD_SHARED_LIBRARY)  
  8. $(call import-module,android/native_app_glue)  

Application.mk

Java程式碼  
  1. APP_PLATFORM := android-10<span style="white-space: normal;"> </span>  

執行結果:顏色會逐漸變淺,然後再次從這個顏色變淺

4、增加apk檔案(有原始碼)

如果將 android 程式的原始碼加入到 build system 中呢

(1)       在 eclipse 開發環境中建立你的 android 工程,比如叫做 Success

(2)       將工程拷貝到原始碼的 package/apps 目錄下

(3)       進入 Success 目錄下,建立一個 Android.mk 檔案,內容如下

Java程式碼  
  1. LOCAL_PATH :=$(call my-dir)  
  2. include $(CLEAR_VARS)  
  3. LOCAL_MODULE_TAGS :=optional  
  4. LOCAL_SRC_FILES :=$(call all-java-files-under, src)  
  5. LOCAL_PACKAGE_NAME :=(工程名字)  
  6. LOCAL_CERTIFICATE :=platform  
  7. include $(BUILD_PACKAGE)  

(4)       退回到 android 原始碼目錄下,執行

#. build/envsetup.sh

#mmm packages/apps/Success

編譯成功之後,會在 out/target/product/generic/system/app/Success.apk

(5)       如果要在真機上測試, system 的目錄是在 out/target/product/crespo 目錄下,編譯的時候需要設定一些引數。為了測試,將 crespo 中的 system 記其他核心等檔案放入到一個叫做 samsung 的資料夾中,再將Success.apk 放到 system/app 中

(6)       用 #zip –r update.zip . 命令將其打包為 zip ,也可以用 zip 直接壓縮

(7)      用 #java –jar testsign.jar Samsung/update.zip update.zip 將 zip 包簽名

(8)       開啟手機的 usb 除錯,連線到電腦上,在終端輸入 #adb push update.zip /sdcard/update.zip ,將 zip包上傳到裝置的 sdcard 目錄下

(9)       輸入 #adb reboot bootloader 進入 bootloader 介面

(10)   輸入 #fastboot flash recovery recovery.img 刷 recovery, 我刷的是 Recovery 3.0

(11)   進入 Recovery 選項,刷機,重啟後就可以見到 Success.apk 程式了

注意:修改 AndroidManifest.xml ,在 manifest 標籤中加入 android:sharedUserId=”media” ,當然這個 media只是個 id ,它的名字隨便一般類似包名。我們知道,在不同的 apk 包中預設是不能相互訪問資料的,但是如果在 AndroidManifest.xml 中設定 sharedUserId 並且相同的話,那麼這兩個包就可以相互訪問資料。由於我寫的只是個測試程式,所以沒有加入這條

5、增加apk檔案(無原始碼)

(1)      這種方式最簡單,就是將 ***.apk 檔案拷貝到編譯 android 原始碼時候生成的out/target/product/crespo/system/app 中,執行 make snod 後就可以把 apk 新增到 system.img 中了,然後將system 目錄及其他的幾個檔案打包成 zip 並簽名後即可,刷機後可以看到這個內建的系統程式。

(2)       上一種方式在 make clean 之後,再次 make 以後才能執行上述的操作,比較麻煩

①     新建一個目錄,在 packages/apps 下面,專門用來存放 apk 檔案

#mkdir packages/apps/apks

#cd packages/apps/apks

在這個目錄中新建一個 Android.mk 檔案

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_POST_PROCESS_COMMAND := (shell cp –r $(LOCAL_PATH)/*.apk $(TARGET_OUT)/app/)

儲存退出

②     把需要編譯的 apk 拷貝到 apks 目錄中,執行 make ,在 apks 中的 apk 就會被拷貝到out/target/product/generic/system/app 中

③    執行 make snod 即可

這樣,在執行 make clean 之後,再次 make ,只需要 make snod 即可了