android系統原始碼中新增app原始碼(原始碼部署移植)
涉及到系統定製,需要在系統中加入自己的apk工程,但是上網找了很多資料都是不夠全面的,或者看了還是沒搞懂,我自己也是一點點摸索過來的,花了不少的時間,也是踩了不少的坑,因此特開一文,幫助大家渡河。
申明,本文親測有效,如果有疑問,歡迎在下方留言。人人為我,我為人人
如需要深入瞭解make檔案的編寫和build系統,可以先看看這篇文章《Android系統原始碼Build入門詳解》,本篇文章不對make檔案進行深入的解釋,但是會在make檔案上做相當部分的註釋。如果你是剛接觸系統原始碼移植,強烈建議按照以下閱讀順序一個一個Demo自己寫,然後測試執行,在確保理解的前提下提高自己的編碼水平和原始碼認知水平。
文章思路大致為:
- 最簡單的android原始碼移植HelloWorld
- 含有jni原生代碼的原始碼移植
- 含有第三方jar包的原始碼移植
- 往系統原始碼中新增自己的純java原始碼並打包成jar
- 含有第三方so檔案的原始碼移植
- 既有so又有第三方jar的原始碼移植
- 自有java原始碼包+so+三方jar+本地原始碼的綜合專案
- 新增公用so共享庫並打包進system.img
- 注意和總結
移植HelloWorld
相信我們接觸Android第一個練手專案就是HelloWorld,不過那是在IDE上開發的,這次我們需要把這個HelloWorld工程移植到系統原始碼中,並編譯成目標Apk。關於不帶jni的專案也可以參考原始碼樹中/development/samples/HelloActivity工程,可以說,google為開發者考慮的還是很周到的。
(一)修改mk檔案。
1.在Android核心原始碼中選擇一個目錄來存放HelloWorld應用的原始碼,比如放到/packages/apps目錄下。
2.在HelloWorld目錄下新建Android.mk檔案,示例如下:
LOCAL_PATH:= $(call my-dir)
#清理快取變數
include $(CLEAR_VARS)
#表示目標模式
LOCAL_MODULE_TAGS := samples
#表示原始檔編譯路徑 這個應用裡面只有java原始檔
LOCAL_SRC_FILES := $(call all-java-files-under, src)
#表示專案包名也就是模組名,在專案中唯一
LOCAL_PACKAGE_NAME := HelloWorld
#指定編譯sdk版本為當前版本
LOCAL_SDK_VERSION := current
# 使用該指令編譯目標Apk.
include $(BUILD_PACKAGE)
#搜尋編譯該原始碼目錄下所有的mk檔案,如果沒有可以不寫
include $(call all-makefiles-under,$(LOCAL_PATH))
注:LOCAL_MODULE_TAGS的備選值有user,eng,tests,optional,本示例中使用的TAGS值為eng(工程模式,自帶root),因此,僅當用戶指定的編譯選項為eng時才會編譯該工程。否則會停止編譯,預設值為optional。具體可以參考文章《Android系統原始碼Build入門詳解》
(二).配置makefile,新增新的專案
注意,這是很關鍵的一步,如果忽略了你是沒辦法在編譯成的system.img裡面找到這個工程的。
選擇 /device// /xxxx.mk,或者從 build/target/product/ 目錄下選擇一個被“引用”的.mk,比如我用的是build/target/product/core.mk ,如果是在/devices/下指定產品下修改新增,則只會在指定目標產品生效,如果你的工程對所有產品有效,建議新增在/build/target/product/core.mk中。
最重要的是在其中的PRODUCT_PACKAGES引數列表中新增本工程:如果不新增的話整編出來的競相里面是沒有新增到原始碼樹的應用的。下面要提到的新增公用so檔案也是需要在這個地方新增,新增的方式則是把mk檔案中的標識名填上(LOCAL_PACKAGE_NAME或者LOCAL_MODULE)。
PRODUCT_PACKAGES := \
DeskClock \
Calculator \
Calendar \
Camera2 \
Email \
HelloWorld//這個是我的工程
(三)編譯
在HelloWorld目錄下輸入mm命令,或者切換到Android原始碼根目錄下執行下面任意一條命令即可:
make HelloWorld
mmm package/apps/HelloWorld
編譯生成的apk會放到在out/target/product/《your product》/system/app/目錄下。
含有第三方jar
Android開發中經常會用到一些開源庫,使用Eclipse作為開發工具的時代,第三方庫基本都是jar包的形式存在的。現在我們討論一下怎麼在原始碼中使用第三方庫。
使用第三方庫其實也是很簡單,只需要修改mk檔案,按照如下方式修改即可,有一些公用的庫可能在你的系統遠嗎工程需要多次引用,你可以把這些庫單獨做一個Android.mk並對每一個要使用的庫新增標識。
第三方庫包括兩個部分,一個是引用,一個是模組定義。正如上面所說,這兩個是可以放在不同的mk檔案中的,假設我們的apk程式名為SayhelloFromJar,在這個apk裡面引用了一個叫testlib.jar的第三方庫。我們這麼寫:
Android.mk
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_STATIC_JAVA_LIBRARIES := testlib
# Only compile source java files in this apk.
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := SayhelloFromJar
LOCAL_PRIVILEGED_MODULE := true
#LOCAL_SDK_VERSION := current
include $(BUILD_PACKAGE)
#############################################
##上面這部分和前面的HelloWorld基本是沒區別的,唯一的區別就是添加了一句
##LOCAL_STATIC_JAVA_LIBRARIES := testlib
##下面則是對於這個jar的定義,假設這個jar在libs/testlib.jar
##################### add third part library #######################
include $(CLEAR_VARS)
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES :=testlib:/libs/testlib.jar
LOCAL_MODULE_TAGS := optional
include $(BUILD_MULTI_PREBUILT)
上面是合併的寫法,前面說過,這個jar很可能要被多個apk引用,為了管理方便,我們可以分開來寫:
Apk的Android.mk
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_STATIC_JAVA_LIBRARIES := testlib
# Only compile source java files in this apk.
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := SayhelloFromJar
LOCAL_PRIVILEGED_MODULE := true
#LOCAL_SDK_VERSION := current
include $(BUILD_PACKAGE)
那麼,jar的mk檔案怎麼寫呢?其實就是挪到了兩個不同的make檔案中了而已。
include $(CLEAR_VARS)
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES :=testlib:/libs/testlib.jar
LOCAL_MODULE_TAGS := optional
#實際上就是一個copy的過程,把jar包放到了out/...obj/下
include $(BUILD_MULTI_PREBUILT)
錯誤提示:
錯誤1:
如果有引用第三方jar包但是沒有進行以上配置,則會出現類似如下錯誤:
make: *** No rule to make target 'out/target/common/obj/JAVA_LIBRARIES/BaiduLBS_Android_intermediates/javalib.jar', needed by 'out/target/common/obj/APPS/Voice_intermediates/classes-full-debug.jar'. Stop.
make: *** Waiting for unfinished jobs....
錯誤2:
還有一點需要注意,:=和+=是不一樣的:=是賦值操作,也就是如果你要引用多個jar包,就不能都用:=了,有兩種方式:
第一種就是不同的jar的定義用 \ 隔開,比如這樣:
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := android-support-v4:/libs/android-support-v4.jar \
BaiduLBS_Android:/libs/BaiduLBS_Android.jar \
bugly_crash_release:/libs/bugly_crash_release.jar
或者這樣寫:
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := android-support-v4:/libs/android-support-v4.jar
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES += BaiduLBS_Android:/libs/BaiduLBS_Android.jar
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES += bugly_crash_release:/libs/bugly_crash_release.jar
雖然是很細微的區別,但是很容易在這裡踩坑,切記切記;
新增自己的原始碼包依賴
不知道怎麼形容,所以就暫時稱之為原始碼包依賴把,所謂原始碼包依賴,就是還沒有打包成jar包的java原始碼,也就是原始碼,Android系統遠嗎中是沒有jar包的,都是使用原始碼的方式,這充分體現了Android系統開源的本質。這個原始碼包就是可以用來編譯成jar檔案的原始碼資料夾。我們需要把這個原始碼資料夾在系統原始碼中通過編寫make檔案的方式編譯成所需要的jar,從而被apk使用。
假如我們要使用的原始碼的模組名定義為testlib,那麼Android.mk檔案可以這樣寫:
jar的Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
#jar標識,被其他模組引用的時候就是直接飲用這個標識
LOCAL_MODULE := testlib
#可以寫可以不寫,這裡寫8就是說這個程式碼班一起的版本號為8
LOCAL_SDK_VERSION := 8
#原始碼所在目錄,這裡表示包含了所有當前目錄下所有的java檔案
LOCAL_SRC_FILES := $(call all-java-files-under, src/java)
#把這個jar包編譯成java靜態程式碼庫的方式
include $(BUILD_STATIC_JAVA_LIBRARY)
含有jni原生代碼(不包含So檔案)
對於含有jni的專案但是不含有so檔案或者.a或者其他目標檔案,則需要修改上述新建的Android.mk,如下(也可以參照原始碼樹中的SimpleJNI工程,位於development/samples/SimpleJNI):
TOP_LOCAL_PATH:= $(call my-dir)
# 定義LOCAL_PATH,這兩句的意思相當於LOCAL_PATH:= $(call my-dir)
LOCAL_PATH:= $(TOP_LOCAL_PATH)
#清理快取變數
include $(CLEAR_VARS)
#表示目標模式,sample表示是示例程式碼,我們根據實際需要修改
LOCAL_MODULE_TAGS := samples
#編譯java檔案
LOCAL_SRC_FILES := $(call all-subdir-java-files)
#模組名稱
LOCAL_PACKAGE_NAME := SimpleJNI
#編譯成的so檔名稱,so檔案會安裝到輸出目錄的system/lib下
LOCAL_JNI_SHARED_LIBRARIES := libmynative
#去掉程式碼混淆,預設是混淆的
LOCAL_PROGUARD_ENABLED := disabled
#使用當前sdk版本編譯
LOCAL_SDK_VERSION := current
#編譯目標apk
include $(BUILD_PACKAGE)
# ============================================================
# 如果該目錄下還有其他的mk檔案也進行編譯,因為這個專案裡面jni相關的檔案放在了jni目錄下編譯
# 推薦大家熟練以後也這麼做,看起來好看很多,當然你也可以把mk檔案合併為一個。
include $(call all-makefiles-under,$(LOCAL_PATH))
jni目錄下的Android.mk如下(假設jni目錄下有inc和src目錄,inc下):
LOCAL_PATH := $(call my-dir)
include (CLEAR_VARS)
# 目標模式,可以不寫
LOCAL_MODULE_TAGS := samples
# 編譯成的莫表模組名稱,引用的時候就是引用這個
LOCAL_MODULE := libmynative
# 原檔案所在路徑,這裡只有一個c檔案
LOCAL_SRC_FILES := src/mynative.c
# jni裡面連結到的共享庫,沒有可以不寫
LOCAL_SHARED_LIBRARIES := libutils liblog
# 引用的靜態哭,沒有可以不寫或者像下面這樣
LOCAL_STATIC_LIBRARIES :=
# jni標頭檔案通過這種方式包含進來
LOCAL_C_INCLUDES += $(JNI_H_INCLUDE)
# 同上
LOCAL_C_INCLUDES += $(LOCAL_PATH)/inc
# 編譯共享庫,生成的so檔案唄安裝到輸出目錄的system/lib下
include $(BUILD_SHARED_LIBRARY)
注意:所有jni相關的程式碼產生的目標檔案和的使用和jar包不一樣,因為它是在system目錄下,因此如果不把so檔案刷到裝置,裝置是沒有這個本地庫的。如果沒有則需要PRODUCT_PACKAGES新增
含有so檔案不含C檔案
這種情況在引用第三方sdk的時候經常用到,比如百度地圖高德地圖,比如騰訊bugly,為了敘述簡單,這裡只提新增so檔案,後面會有一個整合的例子,大家明白就好。這裡我就把自己的專案中使用到的程式碼貼出來了。
這個裡面涉及到三處需要注意,一個是目標apk的Android.mk檔案的修改,這裡是引用so檔案的主體工程,引用的方式則是通過指定LOCAL_SHARED_LIBRARIES,第二處是so檔案對應的Android.mk檔案的編寫,第三處是PRODUCT_PACKAGES新增so的標識名。
(1) 在主apk工程的mk檔案裡面加上 LOCAL_SHARED_LIBRARIES,多個庫引用之間空格隔開。假設我們引用的so庫檔案有三個,分別為libBaiduMapBase libBaiduMapSDK libBaiduLocation,位置則是在AppPath/jni/下。
LOCAL_SHARED_LIBRARIES := libBaiduMapBase libBaiduMapSDK libBaiduLocation
如果你的百度地圖的庫放在你的工程目錄下,需要你編譯製定的mk檔案則可以在主apk工程的mk檔案後面加上:
#假設so對應的mk檔案在jni目錄下
include $(LOCAL_PATH)/jni/Android.mk
#或者
include $(call all-makefiles-under,$(LOCAL_PATH))
#其中,這個呼叫當前目錄所有的make檔案則會自動搜尋不需要指定目錄
(2) So檔案對應的Android.mk檔案
LOCAL_PATH := $(call my-dir)
##################### add libBaiduMapSDK_map_v4_5_0 library #######################
include $(CLEAR_VARS)
LOCAL_MODULE:=libBaiduMapSDK_map_v4_5_0
LOCAL_SRC_FILES:=libBaiduMapSDK_map_v4_5_0.so
LOCAL_MODULE_TAGS:=optional
LOCAL_MODULE_CLASS:=SHARED_LIBRARIES
LOCAL_MODULE_SUFFIX:=.so
include $(BUILD_PREBUILT)
##################### add liblocSDK7a library #######################
include $(CLEAR_VARS)
LOCAL_MODULE:=liblocSDK7a
LOCAL_SRC_FILES:=liblocSDK7a.so
LOCAL_MODULE_TAGS:=optional
LOCAL_MODULE_CLASS:=SHARED_LIBRARIES
LOCAL_MODULE_SUFFIX:=.so
include $(BUILD_PREBUILT)
##################### libBaiduMapSDK_base_v4_5_0 #######################
include $(CLEAR_VARS)
LOCAL_MODULE:=libBaiduMapSDK_base_v4_5_0
LOCAL_SRC_FILES:=libBaiduMapSDK_base_v4_5_0.so
LOCAL_MODULE_TAGS:=optional
LOCAL_MODULE_CLASS:=SHARED_LIBRARIES
LOCAL_MODULE_SUFFIX:=.so
include $(BUILD_PREBUILT)
注意,不建議你修改LOCAL_MODULE:=libBaiduMapSDK_base_v4_5_0
因為生成so檔案的時候就是按照這個來生成的,可能java呼叫jni的時候需要對應上so檔名,這個我沒驗證,但是為了保險起見建議不要修改,不過也說不定,因為這個LOCAL_MODULE只是用來在build系統中標識的,改了也應該沒關係,有興趣的朋友可以試試。
網上還有一種寫法,這裡也寫出來供大家參考,實現的效果是一樣的。
##################### add third part library #######################
#include $(CLEAR_VARS)
#LOCAL_MODULE := libBaiduMapSDK_base_v4_5_0
#LOCAL_SRC_FILES := libBaiduMapSDK_base_v4_5_0.so
#include $(PREBUILT_SHARED_LIBRARY)
既有so又有第三方jar
這種場景也很常見,很多廠商的sdk不僅帶有jar,jar裡面還有原生代碼的引用,有些是為了加密防止核心程式碼洩露,有些使用到了自定義的底層庫,不管怎麼樣,我們來搞一搞;
因為前面有介紹單獨關於so檔案的移植和第三方jar的移植,這個例子只是對這兩個做一個合併,其實合併起來也是非常簡單,在哪個裡面引用到了就在哪個裡面新增引用的程式碼,可能有些朋友被前面的東西搞暈了,因此我還是寫出來,幫助大家梳理一下;
這是一個既有so引用又有jar引用的apk的Android.mk,以百度地圖為例
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_STATIC_JAVA_LIBRARIES := libbaidumapapi
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_PACKAGE_NAME := MyMaps
include $(BUILD_PACKAGE)
###########有人說可以這樣寫,我沒試過,有興趣可以試試#############
#include $(CLEAR_VARS)
#LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES :=libbaidumapapi:libs/baidumapapi.jar
#LOCAL_PREBUILT_LIBS :=libBMapApiEngine_v1_3_1:libs/armeabi/libBMapApiEngine_v1_3_1.so
#LOCAL_MODULE_TAGS := optional
#include $(BUILD_MULTI_PREBUILT)
###########################################################
###########我自己是這麼寫的###################################
#新增so檔案依賴(So的寫法參照《含有so檔案不含C檔案》)
LOCAL_SHARED_LIBRARIES := libBMapApiEngine_v1_3_1
include $(CLEAR_VARS)
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES :=libbaidumapapi:libs:/libs/baidumapapi.jar
LOCAL_MODULE_TAGS := optional
include $(BUILD_MULTI_PREBUILT)
######## Use the following include to make our testapk.
include $(callall-makefiles-under,$(LOCAL_PATH))
含有jni檔案
如果公司在做一套軟體硬體結合的產品,通常會遇到需要在應用層打包So庫的情況,當然,即使應用層不需要關心怎麼打包so庫,懂這個方法總比不懂強;畢竟現在安卓工程師越來越不好找工作,懂點高階的知識是和新手拉開差距的必要手段;
以一個HelloJni的工程為例,程式碼的實現請移步Idea開發Jni程式(HelloJni)這裡直說Android.mk怎麼實現,以及在原始碼樹打包我們需要的so檔案並作為apk工程的一部分;
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello-jni
LOCAL_SRC_FILES := hello-jni.c
include $(BUILD_SHARED_LIBRARY)
在應用中使用這個so庫則只要使用:
static {
System.loadLibrary("hello-jni");
}
注意:
hello-jni和mk中的LOCAL_MODULE 一一對應,寫錯了就找不到的;
如果你寫的是cpp而不是c記得在Android.mk中新增這行,否則會報錯誤fatal error: iostream: No such file or directory
:
LOCAL_CPP_EXTENSION := .cxx .cpp .cc
其實HelloJni在安卓原始碼樹的development/ndk/samples/hello-jni/下也有提供示例程式碼,在示例程式碼中我們發現不只有Android.mk,同級目錄下還有一個Application.mk,這個檔案是可選的用來詳細描述你的專案,儘管你開始的話不一定需要它,但是它允許你使用更多的CPU或者覆蓋編譯器/連結器的標記;
當然,如果你只是針對某一個產品編譯的話,不需要關心這個;
含有So檔案且含有jni檔案
這裡我就不寫了,因為我自己沒有這樣實現,但是我相信這個思路是正確的。Android在make的時候,根據mk檔案來編譯的,mk中可以制定編譯成的共享庫或者java庫或者apk或者其他的,但是不管怎麼樣,沒有mk生成的目標檔案都是有指定module名稱的,要引用這個module或者庫,只需要指定引用的名稱和方式即可,所以,建議給具體的庫單獨寫一個mk檔案以方便其他應用使用。
注意和總結
前面提醒過的一天我這裡在強調一下,因為很多朋友看文章的時候不仔細,常常漏掉在core.mk中配置apk或者so庫,從而導致刷如手機後沒有這個應用或者應用開啟後崩潰說沒有滿足的庫或者本地方法。
因此不僅僅是編寫專案的mk檔案就行了,一定要在/build/target/product/core.mk下的PRODUCT_PACKAGES新增自己的模組,或者在devices下新增(表示只在編譯指定target的時候生效,我這裡是6735的程式碼,因此目錄是device/lentek/lentk6735_35gc_l1/device.mk)
一定不要忘記把PRODUCT_PACKAGES加進去。
注意2
如果只是在原始碼上區域性編譯,且只需要使用編譯成功的apk(而不是把整個系統重新刷寫),則需要注意的是,apk中有使用so檔案,則需要把這個so檔案推送到手機的對應目錄中:
#進入到生成so檔案的目錄(所有生成的so都在這裡,而不是在apk中,並安裝到system.img)
cd <source_root>\out\target\product\<productname>\system\lib
#如果手機沒有root
adb root
#不執行這一步則push會被拒絕
adb remount
#把檔案推送到裝置
adb push xxx.so /system/lib/xxx.so
#可能需要重啟,非系統應用則不需要
adb reboot
純手打,摸索出來的成果,請尊重勞動成果,註明出處。