深刻Android系統(一)Build系統

深刻Android系統這本書是以Android5.0爲基礎講解,但本人使用的是Android9.0的源碼,因此和原書內容會有些出入。html

對於Android的構建系統,在Android7.0以後Google就已經使用Soong構建系統,旨在取代 Make。它利用 Kati GNU Make 克隆工具和 Ninja 構建系統組件來加速 Android 的構建。這裏是官方構建傳送門java

讀書是一個引導學習的過程,讀此書的目的是全面瞭解Android系統,當有一個全面瞭解後再來看新版特性吧。5.0 確實老了點。哈哈哈(PS:公司的項目都是9.0的,兩個版本對比學習吧)linux

Android的Build系統是基於GNU MakeShell構建的一套編譯環境。因爲Android是一個龐大的系統,包含了不少模塊,爲了管理整套源碼的編譯,Android專門開發了本身的Build系統android

Android的Build系統能夠作:c++

  • 系統二進制文件或apk應用程序的編譯、連接、打包工做
  • 生成目標文件系統的鏡像以及各類配置文件
  • 維護模塊間的依賴關係
  • 確保某個模塊的修改能引發依賴文件的從新編譯

從大的方面講,Android的Build系統能夠分紅三部分:算法

  • AndroidBuild系統的框架和核心,位於build/core目錄下
  • 各個產品的配置文件,位於device目錄下
  • 各個模塊的配置文件:Android.mk,位於各個模塊的源文件目錄下

Android Build系統 核心

編譯源碼時經常使用的三條指令是:shell

source build/envsetup.sh
lunch eva_userdebug
make
複製代碼

咱們就順着這三條指令,一步步分析整個編譯過程。編程

編譯環境的創建

envsetup.sh文件的做用

執行Android系統的編譯,必須先運行envsetup.sh腳本,這個腳本會創建Android的編譯環境。咱們看下envsetup.sh的部分代碼:api

function add_lunch_combo() # 忽略實現
function print_lunch_menu() # 忽略實現
function lunch() # 忽略實現
function m() # 忽略實現
function findmakefile() # 忽略實現
function mm() # 忽略實現
function mmm() # 忽略實現
function mma() # 忽略實現
function mmma() # 忽略實現
function croot() # 忽略實現
function cproj() # 忽略實現
複製代碼

咱們看到envsetup.sh文中定義了不少shell命令,上面列出的是一部分經常使用指令的定義,在執行完envsetup.sh腳本後,這些指令就能夠調用了。數組

envsetup.sh文件中會執行的命令是:

# add the default one here
add_lunch_combo aosp_arm-eng
add_lunch_combo aosp_arm64-eng
add_lunch_combo aosp_mips-eng
add_lunch_combo aosp_mips64-eng
add_lunch_combo aosp_x86-eng
add_lunch_combo aosp_x86_64-eng

# Execute the contents of any vendorsetup.sh files we can find.
for f in `test -d device && find -L device -maxdepth 4 -name 'vendorsetup.sh' 2> /dev/null | sort` \
         `test -d vendor && find -L vendor -maxdepth 4 -name 'vendorsetup.sh' 2> /dev/null | sort` \
         `test -d product && find -L product -maxdepth 4 -name 'vendorsetup.sh' 2> /dev/null | sort`
do
    echo "including $f"
    . $f
done
unset f
複製代碼

這部分代碼中:

  • add_lunch_combo增長了6條編譯選項
  • for循環查找deviceproductvendor目錄下的vendorsetup.sh文件,並執行它們

以公司的業務爲例,在vendor/formovie/goblin/vendorsetup.sh中的內容以下:

add_lunch_combo goblin-eng
add_lunch_combo goblin-user
add_lunch_combo goblin-userdebug
add_lunch_combo goblin_fact-userdebug
複製代碼

這樣,envsetup.sh作了三件事情:

  • 建立shell命令
  • 查找第三方定義的vendorsetup.sh
  • 執行add_lunch_combo

那麼add_lunch_combo是在幹啥呢?
咱們看下實現:

unset LUNCH_MENU_CHOICES
function add_lunch_combo()
{
    local new_combo=$1
    local c
    # for循環查找數組中是否已存在新元素
    for c in ${LUNCH_MENU_CHOICES[@]} ; do
        if [ "$new_combo" = "$c" ] ; then
            return
        fi
    done
    # 將新元素加入 LUNCH_MENU_CHOICES 中
    LUNCH_MENU_CHOICES=(${LUNCH_MENU_CHOICES[@]} $new_combo)
}
複製代碼

add_lunch_combo的功能是將調用該命令時傳入的參數存放到一個全局變量數組LUNCH_MENU_CHOICES中。代碼中有註釋哈。==shell 的語法規則看着很隨意啊,沒有俺們Java嚴禁呢==

lunch指令打印的就是這個數組的內容

既然envsetup.sh定義了那麼編譯指令,咱們看下都是幹啥用的吧:

命令 說明
lunch 指定當前編譯的產品
tapas 設置Build環境變量
croot 快速切換到源碼的根目錄
m 編譯整個源碼,無需切換到根目錄
mm 編譯當前目錄下的全部模塊,可是不編譯它們的依賴
mma 編譯當前目錄下的全部模塊,同時編譯它們的依賴
mmm 編譯指定目錄下的全部模塊,可是不編譯它們的依賴
mmma 編譯指定目錄下的全部模塊,同時編譯它們的依賴
cgrep 對系統全部的c/c++文件執行grep命令
ggrep 對系統全部本地的Gradle文件執行grep命令
jgrep 對系統全部的Java文件執行grep命令
resgrep 對系統中全部res目錄下的xml文件執行grep命令
sgrep 對系統中的全部源文件執行grep命令
godir 根據godir後的參數文件名在整個源碼目錄中查找,而後切換到該目錄

lunch命令的功能

咱們看下lunch的源碼(加註釋的那種):

function lunch()
{
    local answer
    # 判斷lunch指令後是否有跟隨參數
    if [ "$1" ] ; then
        answer=$1
    else
        # 無參數打印可用項目列表
        print_lunch_menu
        # 強行推薦一波
        echo -n "Which would you like? [aosp_arm-eng] "
        # 等待用戶輸入並賦值到 answer
        read answer
    fi

    local selection=
    
    # 若是answer 是空,給個默認值aosp_arm-eng
    if [ -z "$answer" ]
    then
        selection=aosp_arm-eng
    # 判斷是不是數字,做者這是把 grep 用到了極致了啊
    # grep -q 表示quite模式,有匹配當即返回 0
    # grep -e 後面跟隨一個匹配表達式,多個表達式須要多個-e
    elif (echo -n $answer | grep -q -e "^[0-9][0-9]*$")
    then
        # answer 在數組範圍中,就給selection賦值
        if [ $answer -le ${#LUNCH_MENU_CHOICES[@]} ]
        then
            selection=${LUNCH_MENU_CHOICES[$(($answer-1))]}
        fi
    else
        # 不是數字,不爲空,直接使用傳入的參數
        selection=$answer
    fi

    export TARGET_BUILD_APPS=

    local product variant_and_version variant version
    #從左向右剔除出現第一個`-`及其後面的字符,賦值給 product
    product=${selection%%-*} # Trim everything after first dash
    #從左向右剔除出現第一個`-`及其前面的字符,賦值給 variant_and_version
    variant_and_version=${selection#*-} # Trim everything up to first dash
    
    # 這裏的邏輯是要取出variant 的類型是eng、user、userdebug
    if [ "$variant_and_version" != "$selection" ]; then
        variant=${variant_and_version%%-*}
        # version解析,沒有解析到的話也不會報錯
        if [ "$variant" != "$variant_and_version" ]; then
            version=${variant_and_version#*-}
        fi
    fi

    # 若是 product 爲空的話,返回錯誤
    if [ -z "$product" ]
    then
        echo
        echo "Invalid lunch combo: $selection"
        return 1
    fi
    
    TARGET_PRODUCT=$product \
    TARGET_BUILD_VARIANT=$variant \
    TARGET_PLATFORM_VERSION=$version \
    # 設置build變量
    build_build_var_cache
    # 判斷 build_build_var_cache 的返回值,若是不爲0,退出
    if [ $? -ne 0 ]
    then
        return 1
    fi
    
    export TARGET_PRODUCT=$(get_build_var TARGET_PRODUCT)
    export TARGET_BUILD_VARIANT=$(get_build_var TARGET_BUILD_VARIANT)
    if [ -n "$version" ]; then
      # 字符串長度不爲0,設置 TARGET_PLATFORM_VERSION
      export TARGET_PLATFORM_VERSION=$(get_build_var TARGET_PLATFORM_VERSION)
    else
      # 字符串長度爲0,取消設置 TARGET_PLATFORM_VERSION
      unset TARGET_PLATFORM_VERSION
    fi
    export TARGET_BUILD_TYPE=release

    echo
    # 設置一些編譯時的環境,好比輸出路徑、輸出鏡像的序列號等
    set_stuff_for_environment
    #打印配置信息
    printconfig
    # 清除本地緩存
    destroy_build_var_cache
}
複製代碼

lunch 代碼主要做用是根據用戶輸入或者選擇的產品名來設置與具體產品相關的環境變量。

lunch中賦值的相關變量主要是(以goblin_fact-userdebug爲例):

  • TARGET_PRODUCT:對應goblin_fact
  • TARGET_BUILD_VARIANT:對應userdebug
  • TARGET_BUILD_TYPE:對應release

Build相關的環境變量

執行完lunch命令後,輸出以下:

...
此處顯示add_lunch_combo添加的編譯項目,並標有序列號
...
Which would you like? [aosp_arm-eng] 
複製代碼

等待確認使用哪一個項目進行編譯,咱們使用65號goblin_fact-userdebug項目,環境變量信息輸出以下:

Which would you like? [aosp_arm-eng]  65

============================================
PLATFORM_VERSION_CODENAME=REL
PLATFORM_VERSION=9
TARGET_PRODUCT=goblin_fact
TARGET_BUILD_VARIANT=userdebug
TARGET_BUILD_TYPE=release
TARGET_ARCH=arm
TARGET_ARCH_VARIANT=armv7-a-neon
TARGET_CPU_VARIANT=cortex-a9
HOST_ARCH=x86_64
HOST_OS=linux
HOST_OS_EXTRA=Linux-4.15.0-70-generic-x86_64-Ubuntu-16.04.6-LTS
BUILD_ID=PQ3B.190705.003
OUT_DIR=out
複製代碼

這些環境變量將影響編譯過程。咱們簡單看下變量表明的含義:

  • PLATFORM_VERSION_CODENAME:表示平臺版本的名稱,一般是AOSP(Android Open Source Project)
  • PLATFORM_VERSION:Android平臺的版本號
  • TARGET_PRODUCT:所編譯產品的名稱
  • TARGET_BUILD_VARIANT:編譯產品的類型,可能有enguseruserdebug
  • TARGET_BUILD_TYPE:表示編譯的類型,包括releasedebug。當選擇debug時會加入一些調試信息。
  • TARGET_ARCH:編譯目標的CPU架構
  • TARGET_ARCH_VARIANT:編譯目標的CPU架構版本
  • TARGET_CPU_VARIANT:編譯目標的CPU架構代號
  • HOST_ARCH:編譯平臺的架構
  • HOST_OS:編譯平臺的操做系統
  • HOST_OS_EXTRA:編譯平臺操做系統的額外信息
  • BUILD_IDBUILD_ID會出如今編譯的版本信息中,能夠利用這個環境變量來定義公司特有標識
  • OUT_DIR:指定編譯結果的輸出目錄

Build系統的層次關係

執行完lunch命令後,就可使用make命令來執行編譯Build

咱們先從編譯的角度描述下Build系統的功能:

  • 生成用於刷機的各類image文件。過程當中能夠進行:
    • 文件拷貝
    • 模塊編譯
    • 生成配置文件
  • 可以根據產品需求靈活配置編譯模塊
  • 可以提供單個模塊的編譯功能

知道了功能,那咱們再來看下Build系統是怎麼實現這些功能的。

執行make命令後的調用流程:

graph LR
build/envsetup.sh-make-->build/soong/soong_ui.bash
build/soong/soong_ui.bash-->build/soong/ui/build/kati.go
build/soong/ui/build/kati.go --> build/make/core/main.mk
複製代碼

這部分其實和書本上差異挺大的,9.0多了一些東西

  • 增長kati框架把Android.mk轉換成ninja file。
  • 增長soong框架把Android.bp轉換成ninja file。
  • 增長Android.bp是jason形式組織的文件,未來逐步取代Android.mk

咱們這裏明確一點就好,build/make/core/main.mk開始,經過include命令將其它的.mk文件包含進來,最終造成一個包括全部編譯腳本的集合。這個集合至關於一個巨大的Makefile文件

雖然Makefile看上去很龐大,其實主要由三種內容構成:

  • 變量定義
  • 函數定義
  • 目標依賴規則

Android的Makefile簡要體系以下:

image

針對上圖橘色標識的三個部分,Build系統會在這裏分別引入具體產品的配置文件AndroidProduct.mkBoardConfig.mk。以及各模塊的編譯文件Android.mk

build/make/core/combo目錄下的mk文件定義了GCC編譯器的版本和參數。
build/make/core/clang目錄下的mk文件定義了LLVM編譯器clang的路徑和參數。

Build系統主要.mk文件簡介:

文件名 說明
main.mk Android Build系統的主控文件。該文件的主要做用是include其餘mk文件,以及定義幾個重要的編譯目標,如droid、sdk、ndk等。同時檢查編譯工具的版本,如make、gcc、javac等。
config.mk Android Build系統的配置文件,主要定義了許多常量來負責不一樣類型模塊的編譯,定義編譯器參數並引入產品的BoardConfig.mk文件來配置產品參數,同時也定義了一些編譯工具的路徑,如aapt、mkbooting、javajar等。
envsetup.mk 包含進product_config.mk文件,並根據其內容設置編譯產品所須要的環境變量,如TARGET_PRODUCTTARGET_BUILD_VARIANTHOST_OS等。並檢查變量值的合法性,指定編譯結果的輸出路徑。
product_config.mk 包含進了系統中全部的AndroidProduct.mk文件,並根據當前產品的配置文件來設置產品編譯相關的變量
product.mk 定義product_config.mk文件中使用的各類函數
definitions.mk 定義了大量Build系統中使用的函數。
dexpreopt.mk 定義與dex優化有關的路徑和參數
Makefile 定義了系統最終完成編譯完成所須要的各類目標和規則

==書中5.0的編譯腳本和9.0比變化太大。。。。。不深究細節實現了,複雜的事情交給腳本吧,先簡單瞭解主要.mk文件的include關係==

Android的產品配置文件

產品配置文件的做用是按照Build系統的要求,將生成產品的各類image文件所須要的配置信息(如版本號、各類參數等)、資源(圖片、字體等)、二進制文件(apk、jar包、so庫等)組織起來,同時能夠靈活增長或刪除一些模塊。

一般產品配置文件放在device目錄下,而vendor目錄下放一些硬件的HAL庫和放第三方廠商的定製代碼。

以goblin爲例的配置文件

一般device目錄中分爲如下幾個子目錄:

  • common:用來存放各個產品通用的配置腳本、文件等。
  • sample:一個產品配置的例子。
  • generic:存放用於模擬器的產品,包括x8六、arm、mips架構
  • 廠商產品目錄(samsung、amlogic、formovie等)

針對goblin項目,配置文件集中在device/formovie/goblin路徑下,核心文件包括:vendorsetup.shAndroidProduct.mkBoardConfig.mk。咱們逐個看下。

vendorsetup.sh

前面提到過,vendorsetup.sh會在初始化編譯環境時被envsetup.sh文件包含進去。它主要的做用是調用add_lunch_combo來添加產品名稱。

以goblin爲例,它的vendorsetup.sh內容以下:

add_lunch_combo goblin-eng
add_lunch_combo goblin-user
add_lunch_combo goblin-userdebug
add_lunch_combo goblin_fact-userdebug
複製代碼

AndroidProduct.mk

AndroidProduct.mk會在Build系統的product_config.mk文件中被包含進去,這個文件最重要的做用是定義一個變量PRODUCT_MAKEFILES,這個變量定義的是本配置目錄中全部編譯的入口文件。

以goblin爲例,它的AndroidProduct.mk內容以下:

ifeq ($(TARGET_PRODUCT),goblin)
PRODUCT_MAKEFILES := $(LOCAL_DIR)/goblin.mk
else ifeq ($(TARGET_PRODUCT),goblin_fact)
PRODUCT_MAKEFILES := $(LOCAL_DIR)/goblin_fact.mk
複製代碼

根據lunch的不一樣產品執行不一樣的.mk文件。

BoardConfig.mk

BoardConfig.mk會被Build系統的envsetup.sh包含進去。這個文件主要定義了和設備硬件(包括CPU、WIFI、CPS等)相關的一些參數。咱們看下部分編譯變量的做用:

  • TARGET_CPU_ABI:表示CPU的編程接口。好比armeabi-v7a。==ABI是啥?下面詳解==
  • TARGET_CPU_ABI2:表示CPU的編程接口。
  • TARGET_CPU_SMP:表示CPU是否爲多核CPU。
  • TARGET_ARCH:定義CPU的架構。好比arm
  • TARGET_ARCH_VARIANT:定義CPU的架構版本。好比:armv7-a-neon
  • TARGET_CPU_VARIANT:定義CPU的代號。好比:contex-a9
  • TARGET_NO_BOOTLOADER:若是爲true,編譯出的image不包含bootloader。
  • BOARD_KERNEL_BASE:裝載kernel鏡像時的基地址。好比:0x0
  • BOARD_KERNEL_OFFSET:kernel鏡像的偏移地址。好比:0x1080000
  • BOARD_KERNEL_CMDLINE:裝載kernel時傳給kernel的命令行參數。好比:--cmdline "root=/dev/mmcblc0p20"
  • BOARD_MKBOOTIMG_ARGS:使用mkbooting工具生成boot.img時的參數。好比:--kernel_offset 0x1080000
  • BOARD_USES_ALSA_AUDIO:爲true時,表示主板聲音系統使用的是ALSA架構。
  • BOARD_HAVE_BLUETOOTH:爲true時,表示主板支持藍牙。
  • BOARD_WLAN_DEVICE:定義WiFi設備名稱。
  • TARGET_BOARD_PLATFORM:表示主板平臺的型號。好比tl1
  • TARGET_USERIMAGES_USE_EXT4:爲true時,表示目標文件系統採用EXT4格式。
  • TARGET_NO_RADIOIMAGE:爲true時,表示編譯的鏡像中沒有射頻部分。
  • WPA_SPPLICANT_VERSION:定義 WIFI WPA 的版本。
  • BOARD_WPA_SUPPLICANT_DRIVER:指定一種WPA_SUPPLICANT的驅動。
  • BOARD_HOSTAPD_DRIVER:指定WiFi熱點的驅動。
  • WIFI_DRIVER_FW_PATH_PARAM:指定WiFi驅動的參數路徑。
  • WIFI_DRIVER_FW_PATH_AP:定義WiFi熱點firmware文件的路徑。

以上是相對核心的變量,Build系統中的變量實在是太多了。。。。
咱們解釋下

ABI是啥?

大部分來自百度百科

首先,咱們看下API的解釋:應用程序接口(Application Programming Interface,API),又稱爲應用編程接口,就是軟件系統不一樣組成部分銜接的約定。因爲軟件的規模日益龐大,經常須要把複雜的系統劃分紅小的組成部分,編程接口的設計十分重要。程序設計的實踐中,編程接口的設計首先要使軟件系統的職責獲得合理劃分。良好的接口設計能夠下降系統各部分的相互依賴,提升組成單元的內聚性,下降組成單元間的耦合程度,從而提升系統的維護性和擴展性。

ABI是Application Binary Interface的縮寫,叫作應用程序二進制接口。不一樣於APIAPI定義了源代碼和庫之間的接口,所以一樣的代碼能夠在支持這個API的任何系統中編譯 ,然而ABI容許編譯好的目標代碼在使用兼容ABI的系統中無需改動就能運行。同時 ABI掩蓋了各類細節,例如:調用約定控制着函數的參數如何傳送以及如何接受返回值;系統調用的編碼和一個應用如何向操做系統進行系統調用;以及在一個完整的操做系統ABI中,對象文件的二進制格式、程序庫等等。

它描述了應用程序與OS之間的底層接口。ABI涉及了程序的各個方面,好比:目標文件格式、數據類型、數據對齊、函數調用約定以及函數如何傳遞參數、如何返回值、系統調用號、如何實現系統調用等。

一套完整的ABI(好比:Intel Binary Compatibility Standard (iBCS)),可讓程序在全部支持該ABI的系統上運行,而無需對程序進行修改。

另外幾個重要的.mk

device/formovie/goblin路徑下還有幾個和Build相關的.mk

goblin_fact.mk    goblin.mk    device.mk
複製代碼

goblin_fact.mkgoblin.mk就是咱們在AndroidProduct.mk中定義的兩個入口文件。而device.mk主要作下面幾個事情:

  • 將 kernel 的鏡像複製到目標系統裏
  • 將 Linux 系統的初始化文件和分區表等複製到目標系統裏
  • 定義系統支持的分辨率
  • 指定系統的overlay目錄
  • 添加一些模塊
  • 設置系統屬性值
  • include一些其餘配置文件

咱們看下部份內容:

PRODUCT_DIR := goblin
PRODUCT_NAME := goblin_fact #產品名稱
PRODUCT_DEVICE := goblin #設備名稱,很是關鍵
PRODUCT_BRAND := 產品品牌
PRODUCT_MODEL := 產品型號
PRODUCT_MANUFACTURER := 製造商

# 增長編譯模塊 Settings 和 Launcher
PRODUCT_PACKAGES += \
    Settings \
    Launcher

# 拷貝一個c++庫到 vendor/lib/ 下
PRODUCT_COPY_FILES += \
    device/formovie/goblin/files/libstdc++.so:vendor/lib/libstdc++.so

# include 一個 device.mk 腳本
$(call inherit-product, device/formovie/$(PRODUCT_DIR)/device.mk)

########################################################
# device.mk #
########################################################

# 指定系統的overlay目錄
DEVICE_PACKAGE_OVERLAYS := \
    device/formovie/$(PRODUCT_DIR)/overlay

# 設置系統屬性值
PRODUCT_PROPERTY_OVERRIDES += \
    persist.sys.usb.config=mtp

# 定義系統支持的分辨率
PRODUCT_AAPT_CONFIG := xlarge hdpi xhdpi tvdpi
PRODUCT_AAPT_PREF_CONFIG := hdpi
複製代碼

上面一些重要的編譯變量解釋以下:

  • PRODUCT_COPY_FILES:將編譯目錄下的一個文件複製到目標文件系統,若是文件夾不存在,會自動建立。
  • PRODUCT_PACKAGES:用來定義產品的模塊列表,全部在模塊列表中定義的模塊都會被編譯進去。
  • PRODUCT_AAPT_CONFIG:指定系統支持的屏幕密度類型(dip)。所謂支持,是指在編譯時,會將相應的資源文件添加到framework_res.apk文件中。
  • PRODUCT_AAPT_PREF_CONFIG:指定系統實際的屏幕密度類型。
  • DEVICE_PACKAGE_OVERLAYS:很重要的變量,指定了系統的overlay目錄。系統編譯時會使用overlay目錄下存放的資源文件替換系統或者末班原有的資源文件。這樣在不改動原生資源文件的狀況下,就能實現產品的個性化。並且overlay的目錄能夠有多個,它們會按照在變量中的前後順序來替換資源文件,利用這個特性能夠定義公共的overlay目錄,以及各個產品專屬的overlay目錄,最大限度的重用資源文件。
  • PRODUCT_PROPERTY_OVERRIDES:定義系統的屬性值。若是屬性以ro.開頭,那麼這個屬性是隻讀屬性。一旦設置,屬性將不能改變。若是屬性以persisit.開頭,當設置這個屬性時,它的值將寫入/data/property中。

編譯類型enguseruserdebug

eng類型

默認的編譯類型。執行make至關於執行make eng

編譯時會將下列模塊編譯進系統:

  • Android.mk中用LOCAL_MODULE_TAGS定義了標籤:engdebugshell_$(TARGET_SHELL)userdevelopment的模塊
  • 非APK模塊而且不帶任何標籤的模塊
  • 全部產品配置文件中指定的APK模塊

編譯的系統帶有下列屬性:

  • ro.secure=0
  • ro.debuggable=1
  • ro.kernel.android.checkjni=1

編譯的系統中默認狀況下adb是打開的

user類型

編譯時會將下面這些模塊編譯進系統:

  • Android.mk中用LOCAL_MODULE_TAGS定義了標籤:shell_$(TARGET_SHELL)user的模塊
  • 非APK模塊而且不帶任何標籤的模塊
  • 全部產品配置文件中指定的APK模塊,同時忽略其標籤屬性

編譯後的系統屬性:

  • ro.secure=1
  • ro.debuggable=0

編譯的系統缺省狀況下adb是不可用的,須要在設置中手動打開

userdebug類型

編譯時會將下列模塊編譯進系統:

  • Android.mk中用LOCAL_MODULE_TAGS定義了標籤:debugshell_$(TARGET_SHELL)user的模塊
  • 非APK模塊而且不帶任何標籤的模塊
  • 全部產品配置文件中指定的APK模塊,同時忽略其標籤屬性

編譯後的系統屬性:

  • ro.secure=1
  • ro.debuggable=1

編譯的系統缺省狀況下adb是不可用的,須要在設置中手動打開

產品的image文件

Android 編譯完成後會生成一個image文件,包括:

  • boot.img:包含經過 mkbootimg 組合在一塊兒的內核映像和 RAM 磁盤
  • system,img:主要包含 Android 框架
  • recovery.img:用於存儲在 OTA 過程當中啓動的恢復映像。
  • ==cache.img==:用於存儲臨時數據,若是設備使用 A/B 更新,則能夠不要此分區。
  • ==misc.img==:供恢復映像使用,存儲空間不能小於 4KB。
  • userdata.img:包含用戶安裝的應用和數據,包括自定義數據。
  • ==metadata.img==:若是設備被加密,則須要使用 metadata 分區,該分區的存儲空間不能小於 16MB。
  • ==vendor.img==:包含全部不可分發給 Android 開源項目 (AOSP) 的二進制文件。若是沒有專有信息,則能夠省略此分區。(應該是第三方廠商的相關數據)
  • ==radio.img==:包含無線裝置映像。只有包含無線裝置且在專用分區中包含無線裝置專用軟件的設備才須要此分區。
  • ==tos.img==:用於存儲 Trusty 操做系統的二進制映像文件,僅在設備包含 Trusty 時使用。

黃色標識是書中未說起的,簡單記錄下。

boot.img

boot.img是Android自定義的文件格式。該格式包括了一個2*1024的文件頭,頭文件後面使用gzip壓縮過的kernel映像,後面是一個ramdisk映像,最後是一個載入器程序(可選部分)。

boot.img結構以下:

- kernel.img
- ramdisk.img
  -/
    - init.rc
    - init
    - etc -> /system/etc
    - system/ (mount point)
    - vendor/ (mount point)
    - odm/ (mount point)
    ...
複製代碼

boot.imgpage爲單位存放數據,編譯時經過BOARD_KERNEL_PAGESIZE 指定。一般值爲2048。

ramdisk.img是一個小型文件系統,它包括了初始化Linux系統所須要的所有核心文件。

recovery.img

recovery.img至關於一個小型文本界面的Linux系統,它有本身的內核和文件系統。

recovery.img的做用是恢復或升級系統。所以,在sbin目錄下會有一個recovery程序;同時也包括adbd和系統配置文件init.rc。不過這幾個文件和boot.img中的不相同的。

system.img

system.img就是設備中system目錄的鏡像,裏面包含了Android系統的主要目錄和文件。目錄包括:

- system.img
  -/
    - app           #存放通常的apk文件
    - bin           #存放一些Linux的工具,大部分是toolbox的連接
    - etc           #存放系統的配置文件
    - fonts         #存放系統的字體文件
    - framework     #存放系統平臺的全部jar包和資源文件包
    - lib           #存放系統的共享庫
    - media         #存放系統的多媒體資源,主要是鈴聲。
    - priv-app      #存放系統核心的apk文件
    - tts           #存放系統的語音合成文件
    - usr           #存放各類鍵盤佈局、時間區域文件
    - vendor        #存放一些第三方廠商的配置文件(現有版本已經拎出來做爲一個單獨的分區了)
    - xbin          #存放系統管理工具,至關於標準Linux文件系統中的sbin
    - build.prop    #系統屬性的定義文件
複製代碼

userdata.img

userdata.img是設備中/data目錄的鏡像,初始時通常不包含任何文件。在Android系統初始化時會在/data目錄下建立一些子目錄和文件。

提高編譯速度

內存

儘量大的內存

SSD

SSD提高文件讀寫速度

CCCache

編譯優化,增長緩存。

export USE_CACHE = 1
export CCACHE_DIR=/<path_of_your_choice>/.ccache
prebuilts/misc/linux-x86/ccache/ccache -M 50G
複製代碼

須要注意的是CCCache並不能提升第一次編譯的速度。原理是將一些系統庫的編譯結果緩存起來,下次編譯時若是檢測到庫沒有變化,就直接使用緩存的文件(印象中坑比較多)。

編譯Android模擬器

操做以下:

. build/envsetup.sh
lunch sdk-eng
make
複製代碼

編譯完成後

emulator
複製代碼

對於x86的PC,啓動模擬器以前,能夠先執行位於SDK的extras/intel/Hardware_Accelerated_Execution_Manager下的intelhaxm來提高運行速度。

編譯Android的模塊

和Android相關的編譯變量到底有哪些,都是幹啥的,官方講解在build/make/core/build-system.html中。 迷茫的時候就看官方解釋吧。

Android 中的各類模塊,不管是apk應用、可執行程序仍是jar包,均可以經過Build系統編譯生成。在每一個模塊的源碼目錄下,都有一個Android.mk文件,裏面包含了模塊代碼的位置、模塊的名稱、須要連接的動態庫等一系列的定義。

以Settings爲例:

# 設置LOCAL_PATH爲當前目錄
LOCAL_PATH:= $(call my-dir)
# 清除"LOCAL_PATH"外,全部的"LOCAL_"變量
include $(CLEAR_VARS)
######上面這部分每一個module直接抄過來就好######

# 指定模塊的名稱
LOCAL_PACKAGE_NAME := Settings
# 爲true時,會使用sdk的hide的api來編譯
LOCAL_PRIVATE_PLATFORM_APIS := true
# 指定模塊簽名使用platform簽名
LOCAL_CERTIFICATE := platform
# 爲true時表示apk將安裝到priv-app下
LOCAL_PRIVILEGED_MODULE := true
# 定義模塊的標籤爲optional
LOCAL_MODULE_TAGS := optional
# 使用AAPT2優化版本
LOCAL_USE_AAPT2 := true

# 指定依賴的共享 Java 類庫
LOCAL_JAVA_LIBRARIES := \
    bouncycastle \
    telephony-common \
    ims-common

# 指定依賴的靜態 Java 類庫
LOCAL_STATIC_JAVA_LIBRARIES := \
    android-arch-lifecycle-runtime \
    android-arch-lifecycle-extensions \
    guava \
    jsr305 \
    settings-logtags \

# 聲明調用android的包
LOCAL_STATIC_ANDROID_LIBRARIES := \
    android-slices-builders \
    android-slices-core \
    android-slices-view \
    android-support-compat \
    android-support-v4 \
    android-support-v13 \
    android-support-v7-appcompat \
    android-support-v7-cardview \
    android-support-v7-preference \
    android-support-v7-recyclerview \
    android-support-v14-preference \

# 定義源文件列表
LOCAL_SRC_FILES := \
        $(call all-logtags-files-under, src)

# 指定混淆的標誌
LOCAL_PROGUARD_FLAG_FILES := proguard.flags
# 指定編譯模塊類型爲 APK
include $(BUILD_PACKAGE)
# 將源碼目錄下其他的Android.mk都包含進來
include $(call all-makefiles-under,$(LOCAL_PATH))
複製代碼

模塊編譯變量

Android.mk文件能編譯出不一樣的模塊,是經過include某個模塊編譯文件來實現的。好比編譯apk時的指令:

include $(BUILD_PACKAGE)
複製代碼

通過一系列的grep,發現這些模塊編譯變量都是在build/make/core/config.mk文件中定義的。咱們看下部分變量的聲明(不少都沒見過。。。用到的時候再查吧):

# ###############################################################
# Build system internal files
# ###############################################################

# 清除Local_變量
CLEAR_VARS:= $(BUILD_SYSTEM)/clear_vars.mk
# 產生編譯平臺使用的本地靜態庫
BUILD_HOST_STATIC_LIBRARY:= $(BUILD_SYSTEM)/host_static_library.mk
# 產生編譯平臺使用的本地共享庫
BUILD_HOST_SHARED_LIBRARY:= $(BUILD_SYSTEM)/host_shared_library.mk
# 產生目標平臺使用的本地靜態庫
BUILD_STATIC_LIBRARY:= $(BUILD_SYSTEM)/static_library.mk
BUILD_HEADER_LIBRARY:= $(BUILD_SYSTEM)/header_library.mk
BUILD_AUX_STATIC_LIBRARY:= $(BUILD_SYSTEM)/aux_static_library.mk
BUILD_AUX_EXECUTABLE:= $(BUILD_SYSTEM)/aux_executable.mk
# 產生編譯平臺使用的共享庫
BUILD_SHARED_LIBRARY:= $(BUILD_SYSTEM)/shared_library.mk
# 產生目標系統使用的Linux可執行程序
BUILD_EXECUTABLE:= $(BUILD_SYSTEM)/executable.mk
# 產生編譯平臺下可以使用的可執行程序
BUILD_HOST_EXECUTABLE:= $(BUILD_SYSTEM)/host_executable.mk
# 產生apk文件
BUILD_PACKAGE:= $(BUILD_SYSTEM)/package.mk
BUILD_PHONY_PACKAGE:= $(BUILD_SYSTEM)/phony_package.mk
BUILD_RRO_PACKAGE:= $(BUILD_SYSTEM)/build_rro_package.mk
BUILD_HOST_PREBUILT:= $(BUILD_SYSTEM)/host_prebuilt.mk
# 定義預編譯模塊目標,目的是將預編譯模塊引入系統
BUILD_PREBUILT:= $(BUILD_SYSTEM)/prebuilt.mk
# 定義多個預編譯模塊目標
BUILD_MULTI_PREBUILT:= $(BUILD_SYSTEM)/multi_prebuilt.mk
# 產生Java共享庫
BUILD_JAVA_LIBRARY:= $(BUILD_SYSTEM)/java_library.mk
# 產生Java靜態庫
BUILD_STATIC_JAVA_LIBRARY:= $(BUILD_SYSTEM)/static_java_library.mk
# 產生用於編譯平臺下的Java共享庫
BUILD_HOST_JAVA_LIBRARY:= $(BUILD_SYSTEM)/host_java_library.mk
BUILD_DROIDDOC:= $(BUILD_SYSTEM)/droiddoc.mk
BUILD_APIDIFF:= $(BUILD_SYSTEM)/apidiff.mk
# 用來將 LOCAL_COPY_HEADERS 變量定義的文件拷貝到 LOCAL_COPY_HEADERS_TO 定義的路徑中
BUILD_COPY_HEADERS := $(BUILD_SYSTEM)/copy_headers.mk
BUILD_NATIVE_TEST := $(BUILD_SYSTEM)/native_test.mk
BUILD_NATIVE_BENCHMARK := $(BUILD_SYSTEM)/native_benchmark.mk
BUILD_HOST_NATIVE_TEST := $(BUILD_SYSTEM)/host_native_test.mk
BUILD_FUZZ_TEST := $(BUILD_SYSTEM)/fuzz_test.mk
BUILD_HOST_FUZZ_TEST := $(BUILD_SYSTEM)/host_fuzz_test.mk
複製代碼

因此 include $(BUILD_PACKAGE)其實就是

include build/make/core/package.mk
複製代碼

Google爸爸很貼心哈。

感興趣的同窗能夠深刻研究下這些.mk文件喲。

經常使用模塊定義實例

編譯一個 apk 文件

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
# 指定依賴的共享Java庫
LOCAL_JAVA_LIBRARIES := 
# 指定依賴的靜態Java庫
LOCAL_STATIC_JAVA_LIBRARIES := 

# 指定模塊的標籤
LOCAL_MODULE_TAGS := optional/user/userdebug/eng
# 指定模塊的名稱
LOCAL_PACKAGE_NAME := apkName
# 指定模塊的簽名方式
LOCAL_CERTIFICATE := platform

# 不使用代碼混淆的工具進行代碼混淆
LOCAL_PROGUARD_ENABLED:= disabled
# 使用hide api編譯
LOCAL_PRIVATE_PLATFORM_APIS := true
# 安裝在priv-app目錄下
LOCAL_PRIVILEGED_MODULE := true

# 指定 AndroidManifest.xml 文件路徑
LOCAL_MANIFEST_FILE := app/src/main/AndroidManifest.xml
# 指定資源路徑
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/app/src/main/res
# 指定源碼路徑
LOCAL_SRC_FILES := \
        $(call all-java-files-under, src)

include $(BUILD_PACKAGE)
複製代碼

編譯一個Java共享庫

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

# 編譯模塊的標籤
LOCAL_MODULE_TAGS := optional/user/userdebug/eng
# 編譯模塊的名稱
LOCAL_MODULE := java_lib_name
# 源碼編譯路徑
LOCAL_SRC_FILES := $(call all-java-files-under,java)

include $(BUILD_JAVA_LIBRARY)
複製代碼

編譯一個Java靜態庫

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

# 編譯模塊的名稱
LOCAL_MODULE := java_static_lib_name
# 源碼編譯路徑
LOCAL_SRC_FILES := $(call all-java-files-under,java)

include $(BUILD_STATIC_JAVA_LIBRARY)
複製代碼

編譯一個Java資源包文件

資源部也是一個 apk 文件,可是沒有代碼。

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

# 使用 AAPT2 打包資源
LOCAL_USE_AAPT2 := true
# 值爲true的話,說明手動寫依賴的jar包了。不管是系統的仍是用戶本身的
LOCAL_NO_STANDARD_LIBRARIES := true
# 指定簽名類型
LOCAL_CERTIFICATE := platform
# 指定 AAPT 工具參數(如今使用AAPT2優化版本)
LOCAL_AAPT_FLAGS := -x
# 定義模塊名字
LOCAL_PACKAGE_NAME := java_res_name_lib
# 定義模塊標籤爲 user
LOCAL_MODULE_PATH := user
# 定義模塊編譯後的輸出路徑
LOCAL_MODULE_PATH := $(TARGET_OUT_JAVA_LIBRARIES)
# 值爲true時,其餘的apk能夠引用本模塊的資源
LOCAL_EXPORT_PACKAGE_RESOURCES := true
include $(BUILD_PACKAGE)
複製代碼

編譯一個可執行文件

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
# 定義源碼路徑
LOCAL_SRC_FIFLES := Executor.cpp
# 指定模塊須要連接的動態庫
LOCAL_SHARE_LIBRARIES := libutils libbinder
# 定義編譯標誌
ifeq ($(TARGET_OS),linux)
    LOCAL_CFLAGS += -DXP_UNIX
endif
# 指定模塊的名稱
LOCAL_MODULE := executor_name
include $(BUILD_EXECUTABLE)
複製代碼

編譯一個native共享庫

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE_TAGS := optional/user/userdebug/eng
LOCAL_MODULE := lib_native_share_name
# 指定須要編譯的源文件
LOCAL_SRC_FIFLES := Native.cpp
# 指定模塊須要連接的靜態庫
LOCAL_SHARE_LIBRARIES := \
    libutils \
    libbinder
# 指定模塊依賴的靜態庫
LOCAL_STATIC_LIBRARIES := lib_native_static_name
#指定頭文件查找路徑
LOCAL_C_INCLUDES += \
    $(JNI_H_INCLUDE) \
    $(LOCAL_PATH)/../include
# 定義編譯標誌
LOCAL_CFLAGS += -O
include $(BUILD_SHARED_LIBRARY)
複製代碼

編譯一個native的靜態庫

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE_TAGS := optional/user/userdebug/eng
LOCAL_MODULE := lib_native_static_name
# 指定模塊的源文件
LOCAL_SRC_FIFLES := \
    NativeStatic.cpp
# 指定頭文件查找路徑
LOCAL_C_INCLUDES:=  \
# 定義編譯標誌
LOCAL_CFLAGS :=-D_ANDROID_
include $(BUILD_STATIC_LIBRARY)
複製代碼

預編譯模塊的定義

在實際開發中,有不少apk、文件、jar包等都是預先編譯好的。編譯系統時須要將這些二進制文件複製到生成的image文件中。

經常使用的方法是經過PRODUCT_COPY_FILES變量將文件複製到生成的image文件中,像這樣:

PRODUCT_COPY_FILES += \
	$(LOCAL_PATH)/media/autovideo_265.mkv:/system/factory/autovideo_265.mkv 
複製代碼

可是對於apk、或jar包,須要使用系統的簽名才能正常運行;另一些動態庫文件多是源碼中某些模塊依賴的。這樣用上述的方式就行不通了。

Android 提供了預編譯模塊的方法來對策這個問題,大致流程以下:

  • 經過LOCAL_SRC_FILES指定二進制文件(apk或jar包)的路徑
  • 經過LOCAL_MODULE_CLASS指定編譯模塊的類型
  • 經過include $(BUILD_PREBUILT)執行預編譯

LOCAL_MODULE_CLASS 能夠指定哪些類型呢?

首先,Google爸爸說:

LOCAL_MODULE_CLASS
Which kind of module this is. This variable is used to construct other variable names used to locate the modules. See base_rules.mk and envsetup.mk.
複製代碼

那咱們就從這兩個文件看下

base_rules.mk:

EXECUTABLES
SHARED_LIBRARIES
STATIC_LIBRARIES
NATIVE_BENCHMARK
HEADER_LIBRARIES
NATIVE_TESTS
JAVA_LIBRARIES
APPS
複製代碼

不過envsetup.mk 並無明確的聲明。

以預編譯 apk 文件爲例

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE := Factory
LOCAL_SRC_FILES := release/factory_test-release.apk
LOCAL_PRIVILEGED_MODULE := true
LOCAL_MODULE_CLASS := APPS
LOCAL_CERTIFICATE := platform
LOCAL_MODULE_TAGS := optional

include $(BUILD_PREBUILT)
複製代碼

經常使用的LOCAL_變量

再次說明,變量含義這部分在官方build/make/core/build-system.html都有說明哈,那裏的更專業和詳細。

上面的模塊編譯中用到了那麼多LOCAL_變量,方便查找先把經常使用的幾個記錄一下:

變量名 說明
LOCAL_ASSET_FILES 編譯 APK 文件時用於指定資源列表,一般寫成LOCAL_ASSET_FILES += $(call find-subdir-assets)
LOCAL_CC 自定義C編譯器來替換默認的編譯器
LOCAL_CXX 自定義C++編譯器來替換默認的編譯器
LOCAL_CFLAGS 定義額外的C/C++編譯器參數
LOCAL_CPPFLAGS 定義額外的C++編譯器參數,不用在C編譯器中
LOCAL_CPP_EXTENSION 自定義C++源文件的後綴。例如:LOCAL_CPP_EXTENSION := .cc。一旦定義,模塊中的全部源文件都必須使用改後綴,目前不支持混合使用。
LOCAL_C_INCLUDES 指定頭文件的搜索路徑。
LOCAL_FORCE_STATIC_EXECUTABLE 若是編譯時須要連接的庫有共享和靜態兩種。此變量爲true時會優先連接靜態庫。一般這種狀況只會在編譯root/sbin目錄下的應用纔會用到,由於它們執行時間比較早,文件系統的其餘部分尚未加載
LOCAL_GENERATED_SOURCES Files that you add to LOCAL_GENERATED_SOURCES will be automatically generated and then linked in when your module is built.(書中寫的不時很理解,直接上原文)
LOCAL_MODULE_TAGS 定義模塊標籤,Build系統根據標籤來決定哪些模塊須要編譯。
LOCAL_REQUIRED_MODULES 指定依賴的模塊,一旦當前模塊被安裝,經過此變量指定的模塊也會被安裝
LOCAL_JAVACFLAGS 定義額外的javac編譯器的參數
LOCAL_JAVA_LIBRARIES 指定模塊依賴的Java共享庫
LOCAL_LDFLAGS 定義鏈接器ld的參數
LOCAL_LDLIBS 指定模塊鏈接時依賴的庫。若是庫文件不存在,並不會引起它們的編譯。(LOCAL_LDLIBS allows you to specify additional libraries that are not part of the build for your executable or library. Specify the libraries you want in -lxxx format; they're passed directly to the link line. However, keep in mind that there will be no dependency generated for these libraries. It's most useful in simulator builds where you want to use a library preinstalled on the host. The linker (ld) is a particularly fussy beast, so it's sometimes necessary to pass other flags here if you're doing something sneaky. Some examples:LOCAL_LDLIBS += -lcurses -lpthreadLOCAL_LDLIBS += -Wl,-z,origin
LOCAL_NO_MANIFEST 爲true時,表示軟件包中沒有AndroidManifest.xml。(資源包中經常使用)
LOCAL_PACKAGE_NAME 指定APP應用名稱
LOCAL_PATH 指定Android.mk文件所在目錄
LOCAL_POST_PROCESS_COMMAND 在編譯host相關模塊時,能夠用此變量定義一條命令在link完成後執行
LOCAL_PREBUILT_LIBS 指定預編譯C/C++動態和靜態庫列表,用於預編譯模塊
LOCAL_PREBUILT_JAVA_LIBRARIES 指定預編譯Java庫列表,用於預編譯模塊
LOCAL_SHARED_LIBRARIES 指定模塊依賴的C/C++共享庫列表
LOCAL_SRC_FILES 指定源文件列表
LOCAL_STATIC_LIBRARIES 指定模塊依賴的C/C++靜態庫列表
LOCAL_MODULE 除apk模塊使用LOCAL_PACKAGE_NAME指定模塊名外,其他的模塊都使用LOCAL_MODULE指定模塊名
LOCAL_MODULE_PATH 指定模塊在目標系統的安裝路徑。可執行文件和動態庫模塊通常會搭配LOCAL_UNSTRIPPED_PATH使用
LOCAL_UNSTRIPPED_PATH 指定模塊的unstrpped版本在out目錄下保存的路徑
LOCAL_ADDITIONAL_DEPENDENCIES 模塊須要依賴其餘任何實際未內置的模塊,則能夠將這些make目標添加到 LOCAL_ADDITIONAL_DEPENDENCIES。一般,這是針對其餘一些不會自動建立的依賴項的解決方法。
LOCAL_MODULE_CLASS 定義模塊的分類。根據分類,生成的模塊會被安裝到對應的目錄下。例如:APPS,安裝到/system/app;SHARED_LIBRARIES,安裝到/system/lib;EXECUTABLE,安裝到/system/bin下;ETC,安裝到/system/etc;可是若是同時使用LOCAL_MODULE_PATH定義了路徑,則安裝到該路徑。
LOCAL_MODULE_NAME 指定模塊名
LOCAL_MODULE_SUFFIX 指定當前模塊的後綴。一旦指定,系統在產生目標文件時,會以模塊名+後綴來建立
LOCAL_STRIP_MODULE 指定模塊是否須要strip,通常在編譯可執行文件和動態庫時才使用該變量

Android中的簽名

在Android系統中,全部安裝到系統中的apk都須要簽名,所謂簽名就是給應用附加一個數字證書。雖然數字證書有不少用途,可是在Android中,它惟一的做用就是代表製做者的身份。

Android應用簽名方法

建立用於簽名的證書文件

Android 使用Java工具keytool生成數字證書,命令以下:

keytool -genkey -v -keystore lee.keystore -alias lee.keystore -keyalg RSA -validity 30000
複製代碼

參數解釋:

  • -keystore lee.keystore 表示證書文件名
  • -alias lee.keystore 表示證書的別名
  • -keyalg RSA 表示採用的加密算法
  • -validity 30000 表示證書的有效期

執行命令後,終端提示須要設置輸入的信息以下:

  • 首先,須要設置一個keystore的密碼,並重復輸入確認。
  • 而後輸入各類我的信息,輸入yes確認信息。
  • 確認完我的信息後須要設置一個key的密碼,並重復輸入確認。

爲何有兩個密碼?

  • 第一個設置的密碼是存放數字證書的容器(keystore)的密碼。
  • 第二個設置的密碼是新建立的數字證書的密碼,在簽名時使用。

用指令查看下剛纔建立的證書容器:

keytool -list -keystore lee.keystore
複製代碼

此時,就須要輸入第一個設置的密碼了,輸入後打印以下:

lijie@leeMBP:~$ keytool -list -keystore lee.keystore
輸入密鑰庫口令:  
密鑰庫類型: jks
密鑰庫提供方: SUN

您的密鑰庫包含 1 個條目

lee.keystore, 2020-7-19, PrivateKeyEntry, 
證書指紋 (SHA1): BD:6A:85:18:22:34:91:25:F9:38:1F:CB:A2:BA:EC:91:78:3C:67:9F
複製代碼

應用簽名步驟

簽名命令

jarsigner -verbose -keystore lee.keystore -signedjar android-release-signed.apk android-release-unsigned.apk lee.keystore
複製代碼
  • -verbose 表示要打印簽名過程
  • -keystore 用來指定 keystore 文件
  • -signedjar 指定簽名後的apk名稱
  • android-release-unsigned.apk 待簽名文件
  • 最後lee.keystore是證書的別名

執行過程遇到了以下錯誤:

lijie@leeMBP:~/Desktop$ jarsigner -verbose -keystore ~/lee.keystore -signedjar Feng-Factory-signed.apk Feng-Factory.apk lee.keystore
輸入密鑰庫的密碼短語: 
輸入lee.keystore的密鑰口令: 
jarsigner: 沒法對 jar 進行簽名: java.util.zip.ZipException: invalid entry compressed size (expected 26171 but got 26705 bytes)
複製代碼

由於Feng-Factory.apk是被簽過名的,此時須要把META-INF文件夾刪掉(去掉舊的簽名文件),再次執行簽名,OK了。輸出以下:

....
>>> 簽名者
    X.509, CN=lee, OU=hualee, O=hualee.top, L=PK, ST=PK_HD, C=CN
    [可信證書]

jar 已簽名。
警告: 
簽名者證書爲自簽名證書
複製代碼

簽名完成後,咱們來查看下簽名信息:

jarsigner -verify -verbose -certs Feng-Factory-signed.apk 
複製代碼

會獲得以下返回:

....
- 由 "CN=lee, OU=hualee, O=hualee.top, L=PK, ST=PK_HD, C=CN" 簽名
    摘要算法: SHA-256
    簽名算法: SHA256withRSA, 2048 位密鑰

jar 已驗證。
複製代碼

通過簽名修改後,在應用發佈前還須要進行APK對齊,命令以下:

zipalign -f -v infile.apk outfile.apk
複製代碼

APK對齊第一次接觸。。。。。根據書中的描述,是爲了提高資源訪問效率。

對齊後的文件可使用以下指令檢查:

zipalign -c -v 4 test.apk 
複製代碼

Android 系統簽名

Android 系統簽名的方法和應用簽名不大同樣。在build/make/target/product/security目錄中存放着4組後綴名爲.x509.pem.pk8的文件,以下:

- security
    - Android.mk
    - README
    - media.pk8
    - media.x509.pem
    - platform.pk8
    - platform.x509.pem
    - shared.pk8
    - shared.x509.pem
    - testkey.pk8
    - testkey.x509.pem
    - verity.pk8
    - verity.x509.pem
    - verity_key
複製代碼
  • *.pk8文件是私鑰
  • *.x509.pem 文件是公鑰
  • testkey 用於普通 APK
  • platform 用於系統核心的 APK
  • shared 用於Launcher、contacts等重要 APK
  • media 用於系統的多媒體和下載類 APK

生成系統的簽名文件

須要使用源碼中的development/tools/make_key命令。

經過make_key生成系統簽名指令以下:

make_key testkey '/C=CN/ST=PK/L=OK_HD/O=hualee/OU=hualee.top/CN=lee/emailAddress=lee@mail.com'
複製代碼
  • 第一個參數是key的名字,包括mediaplatformsharedtestkey
  • 第二個參數是key的相關信息,C=國家、ST=省、L=地區、O=組織、OU=組織單元、CN=姓名

經過make_key生成的簽名文件,
若是全部產品都想使用同一種簽名,能夠直接用這些文件覆蓋掉build/make/target/product/security目錄下的文件。
若是不一樣產品還須要不一樣的簽名,能夠新建一個目錄,經過編譯變量PRODUCT_DEFAULT_DEV_CERTIFICATE來指定。例如:

PRODUCT_DEFAULT_DEV_CERTIFICATE := # your security files path
複製代碼

而在Build系統編譯時,簽名是自動完成的。Build中的簽名是經過signapk.jar完成的,指令以下:

java -jar signapk.jar platform.x509.pem platform.pk8 test.apk test_signed.apk
複製代碼

生成系統簽名的 keystore 文件

當咱們開發系統應用時,爲了方便調試,可能須要在AS中添加系統簽名文件。

下面記錄下如何生成系統簽名的keystore文件:

  • 取源碼目錄中build/make/target/product/securityplatform.pk8 platform.x509.pem放到一個目錄下

  • 生成shared.priv.pem,指令: openssl pkcs8 -in platform.pk8 -inform DER -outform PEM -out shared.priv.pem -nocrypt

  • 生成pkcs12,指令:openssl pkcs12 -export -in platform.x509.pem -inkey shared.priv.pem -out shared.pk12 -name androiddebugkey

    • 輸入密碼,android
    • 確認密碼,android
  • 生成debug.keystore,指令:keytool -importkeystore -deststorepass android -destkeypass android -destkeystore debug.keystore -srckeystore shared.pk12 -srcstoretype PKCS12 -srcstorepass android -alias androiddebugkey

結語

Android Build系統終於看到了一個大概,雖然Google早就已經使用Soong來進行系統構建了,可是Android的歷史遺留問題多滴很那。一步一步來吧,原理啥的應該都仍是共同的。

寫完本篇,瞭解了Android系統的龐大,之前看不懂的include倒也瞭然了一些,哈哈哈,加油!

奧奧,還有個事情,想知道源碼怎麼下載麼?在圖片下面喲:

image

不謝 ( ̄_, ̄ )

相關文章
相關標籤/搜索