主頁 > 移動端開發 > Android OTA升級詳細流程分析(non-AB)

Android OTA升級詳細流程分析(non-AB)

2021-01-09 11:18:51 移動端開發

一、.ota_from_target_files.py分析

if __name__ == '__main__':
  try:
    # common.CloseInheritedPipes()是用于在macOS環境下關閉檔案描述符,
    # 通過platform.system() != "Darwin"判斷是否是MacOS
    common.CloseInheritedPipes()
    main(sys.argv[1:])
  except common.ExternalError:
    logger.exception("\n   ERROR:\n")
    sys.exit(1)
  finally:
    common.Cleanup()

common.CloseInheritedPipes()是用于在macOS環境下關閉檔案描述符,之后執行ota_from_target_files的主體部分代碼,最后common.Cleanup()清理垃圾檔案,

以下先說明common.CloseInheritedPipes()和common.Cleanup()的代碼,因為它們非常簡單,

def main(argv):

  # 此處代碼在common.ParseOptions傳入
  def option_handler(o, a):
    if o in ("-k", "--package_key"):
      OPTIONS.package_key = a
    # 制作差量包
    elif o in ("-i", "--incremental_from"):
      OPTIONS.incremental_source = a
    elif o == "--full_radio":
      OPTIONS.full_radio = True
    elif o == "--full_bootloader":
      OPTIONS.full_bootloader = True
    # 清除用戶資料
    elif o == "--wipe_user_data":
      OPTIONS.wipe_user_data = True
    elif o in ("-n", "--no_prereq"):
      OPTIONS.omit_prereq = True
    elif o == "--downgrade":
      OPTIONS.downgrade = True
      OPTIONS.wipe_user_data = True
    elif o == "--override_timestamp":
      OPTIONS.downgrade = True
    elif o in ("-o", "--oem_settings"):
      OPTIONS.oem_source = a.split(',')
    elif o == "--oem_no_mount":
      OPTIONS.oem_no_mount = True
    elif o in ("-e", "--extra_script"):
      OPTIONS.extra_script = a
    elif o in ("-t", "--worker_threads"):
      if a.isdigit():
        OPTIONS.worker_threads = int(a)
      else:
        raise ValueError("Cannot parse value %r for option %r - only "
                         "integers are allowed." % (a, o))
    elif o in ("-2", "--two_step"):
      OPTIONS.two_step = True
    elif o == "--include_secondary":
      OPTIONS.include_secondary = True
    elif o == "--no_signing":
      OPTIONS.no_signing = True
    elif o == "--verify":
      OPTIONS.verify = True
    elif o == "--block":
      OPTIONS.block_based = True
    elif o in ("-b", "--binary"):
      OPTIONS.updater_binary = a
    elif o == "--stash_threshold":
      try:
        OPTIONS.stash_threshold = float(a)
      except ValueError:
        raise ValueError("Cannot parse value %r for option %r - expecting "
                         "a float" % (a, o))
    elif o == "--log_diff":
      OPTIONS.log_diff = a
    elif o == "--payload_signer":
      OPTIONS.payload_signer = a
    elif o == "--payload_signer_args":
      OPTIONS.payload_signer_args = shlex.split(a)
    elif o == "--payload_signer_key_size":
      OPTIONS.payload_signer_key_size = a
    elif o == "--extracted_input_target_files":
      OPTIONS.extracted_input = a
    elif o == "--skip_postinstall":
      OPTIONS.skip_postinstall = True
    elif o == "--retrofit_dynamic_partitions":
      OPTIONS.retrofit_dynamic_partitions = True
    elif o == "--skip_compatibility_check":
      OPTIONS.skip_compatibility_check = True
    elif o == "--output_metadata_path":
      OPTIONS.output_metadata_path = a
    else:
      return False
    return True

  # 決議argv(傳入的)引數,并回傳所有不是標記的引數,__doc__指需要呼叫的模塊,extra_opts和extra_long_opts都是標記,由傳入者定義,它們將會交給option_handler進行處理,
  # 這一步完成之后,生成OTA腳本的所有配置應當都準備就緒了,存盤在common.OPTIONS中,當前腳本有一個指向common.OPTIONS的參考,名為common.OPTIONS
  args = common.ParseOptions(argv, __doc__,
                             extra_opts="b:k:i:d:ne:t:a:2o:",
                             extra_long_opts=[
.....

  # 如果之前處理后獲得的引數不是兩個,輸出本腳本使用說明并退出程式
  if len(args) != 2:
    common.Usage(__doc__)
    sys.exit(1)
  
  # 初始化日志模塊
  common.InitLogging()

分析如下:

主函式main是python的入口函式,我們從main函式開始看,大概看一下main函式(腳本最后)里的流程就能知道腳本的執行程序了,

① 在main函式的開頭,首先將用戶設定的option選項存入OPTIONS變數中,它是一個python中的類,緊接著判斷有沒有額外的腳本,如果有就讀入到OPTIONS變數中,
② 解壓縮輸入的zip包,即我們在上文生成的原始zip包,然后判斷是否用到device-specific extensions(設備擴展)如果用到,隨即讀入到OPTIONS變數中,
③ 判斷是否簽名,然后判斷是否有新內容的增量源,有的話就解壓該增量源包放入一個臨時變數中(source_zip),自此,所有的準備作業已完畢,隨即會呼叫該 腳本中最主要的函式WriteFullOTAPackage(input_zip,output_zip)
④ WriteFullOTAPackage函式的處理程序是先獲得腳本的生成器,默認格式是edify,然后獲得metadata元資料,此資料來至于Android的一些環境變數,然后獲得設備配置引數比如api函式的版本,然后判斷是否忽略時間戳,
⑤ WriteFullOTAPackage函式做完準備作業后就開始生成升級用的腳本檔案(updater-script)了,生成腳本檔案后將上一步獲得的metadata元資料寫入到輸出包out_zip,
⑥至此一個完整的update.zip升級包就生成了,生成位置在:out/target/product/tcc8800/trinket.zip,將升級包拷貝到SD卡中就可以用來升級了,
四、 Android OTA增量包update.zip的生成

1.2代碼中的方法分析
顯示進度條 (寫入update_script) script.ShowProgress(增加百分比,持續時間)edify_generator.py

顯示文字之類的 script.Print -> ui_print

顯示進度條 script.ShowProgress -> show_progress

解壓(寫入)到指定區域 script.WriteRawImage -> package_extract_file

卸載所有磁區 script.UnmountAll -> unmount

將另一個腳本的內容附加到當前腳本 script.AppendScript

二、update.zip升級包的制作


2.1 update.zip包的目錄結構

|----boot.img
|----system/
|----recovery/
`|----recovery-from-boot.p
`|----etc/
`|----install-recovery.sh
|---META-INF/
`|CERT.RSA
`|CERT.SF
`|MANIFEST.MF
`|----com/
`|----google/
`|----android/
`|----update-binary
`|----updater-script
`|----android/
`|----metadata
2.2 update.zip包目錄結構詳解

以上是我們用命令make otapackage 制作的update.zip包的標準目錄結構,
2.2.1、boot.img是更新boot磁區所需要的檔案,這個boot.img主要包括kernel+ramdisk,

2.2.2、system/目錄的內容在升級后會放在系統的system磁區,主要用來更新系統的一些應用或則應用會用到的一些庫等等,可以將Android原始碼編譯out/target/product/trinket/system/中的所有檔案拷貝到這個目錄來代替,

2.2.3、recovery/目錄中的recovery-from-boot.p是boot.img和recovery.img的補丁(patch),主要用來更新recovery磁區,其中etc/目錄下的install-recovery.sh是更新腳本,
2.2.4、update-binary是一個二進制檔案,相當于一個腳本解釋器,能夠識別updater-script中描述的操作,該檔案在Android原始碼編譯后out/target/product/trinket/system bin/updater生成,可將updater重命名為update-binary得到,
該檔案在具體的更新包中的名字由原始碼中bootable/recovery/install.c中的宏ASSUMED_UPDATE_BINARY_NAME的值而定,
2.2.5、updater-script:此檔案是一個腳本檔案,具體描述了更新程序,我們可以根據具體情況撰寫該腳本來適應我們的具體需求,該檔案的命名由原始碼中bootable/recovery/updater/updater.c檔案中的宏SCRIPT_NAME的值而定,
2.2.6、 metadata檔案是描述設備資訊及環境變數的元資料,主要包括一些編譯選項,簽名公鑰,時間戳以及設備型號等,
2.2.7、我們還可以在包中添加userdata目錄,來更新系統中的用戶資料部分,這部分內容在更新后會存放在系統的/data目錄下,

2.2.8、update.zip包的簽名:update.zip更新包在制作完成后需要對其簽名,否則在升級時會出現認證失敗的錯誤提示,而且簽名要使用和目標板一致的加密公鑰,加密公鑰及加密需要的三個檔案在Android原始碼編譯后生成的具體路徑為:

out/host/linux-x86/framework/signapk.jar

build/target/product/security/releasekey.x509.pem

build/target/product/security/releasekey.pk8 ,

我們用命令make otapackage制作生成的update.zip包是已簽過名的,如果自己做update.zip包時必須手動對其簽名,

具體的加密方法:$ java -Xmx2048m -Djava.library.path="out/host/linux-x86/lib64" -jar out/host/linux-x86/framework/signapk.jar –w build/target/product/security/releasekey.x509.pem build/target/product/security/releasekey.pk8 update.zip update_signed.zip
以上命令在update.zip包所在的路徑下執行,其中signapk.jar releasekey.x509.pem以及releasekey.pk8檔案的參考使用絕對路徑,update.zip 是我們已經打好的包,update_signed.zip包是命令執行完生成的已經簽過名的包,
2.2.9、MANIFEST.MF:這個manifest檔案定義了與包的組成結構相關的資料,類似Android應用的mainfest.xml檔案,
2.2.10、CERT.RSA:與簽名檔案相關聯的簽名程式塊檔案,它存盤了用于簽名JAR檔案的公共簽名,
2.2.11、CERT.SF:這是JAR檔案的簽名檔案,其中前綴CERT代表簽名者,
另外,在具體升級時,對update.zip包檢查時大致會分三步:①檢驗SF檔案與RSA檔案是否匹配,②檢驗MANIFEST.MF與簽名檔案中的digest是否一致,③檢驗包中的檔案與MANIFEST中所描述的是否一致
2.3 Android升級包的生成程序分析

在原始碼根目錄下執行make otapackage命令生成update.zip包主要分為兩步,第一步是根據Makefile執行編譯生成一個update原包(zip格式),第二步是運行一個python腳本,并以上一步準備的zip包作為輸入,最終生成我們需要的升級包,下面進一步分析這兩個程序,

第一步:編譯Makefile,對應的Makefile檔案所在位置:build/core/Makefile,開始會生成一個zip包,這個包最后會用來制作OTA package 或者filesystem image,先將這部分的對應的Makefile貼出來如下:

# -----------------------------------------------------------------  
# A zip of the directories that map to the target filesystem.  
# This zip can be used to create an OTA package or filesystem image  
# as a post-build step.  
#  
name := $(TARGET_PRODUCT)  
ifeq ($(TARGET_BUILD_TYPE),debug)  
  name := $(name)_debug  
endif  
name := $(name)-target_files-$(FILE_NAME_TAG)  
  
intermediates := $(call intermediates-dir-for,PACKAGING,target_files)  
BUILT_TARGET_FILES_PACKAGE := $(intermediates)/$(name).zip  
$(BUILT_TARGET_FILES_PACKAGE): intermediates := $(intermediates)  
$(BUILT_TARGET_FILES_PACKAGE): \  
        zip_root := $(intermediates)/$(name)  
  
# $(1): Directory to copy  
# $(2): Location to copy it to  
# The "ls -A" is to prevent "acp s/* d" from failing if s is empty.  
define package_files-copy-root  
  if [ -d "$(strip $(1))" -a "$$(ls -A $(1))" ]; then \  
    mkdir -p $(2) && \  
    $(ACP) -rd $(strip $(1))/* $(2); \  
  fi  
endef  
  
built_ota_tools := \  
    $(call intermediates-dir-for,EXECUTABLES,applypatch)/applypatch \  
    $(call intermediates-dir-for,EXECUTABLES,applypatch_static)/applypatch_static \  
    $(call intermediates-dir-for,EXECUTABLES,check_prereq)/check_prereq \  
    $(call intermediates-dir-for,EXECUTABLES,updater)/updater  
$(BUILT_TARGET_FILES_PACKAGE): PRIVATE_OTA_TOOLS := $(built_ota_tools)  
  
$(BUILT_TARGET_FILES_PACKAGE): PRIVATE_RECOVERY_API_VERSION := $(RECOVERY_API_VERSION)  
  
ifeq ($(TARGET_RELEASETOOLS_EXTENSIONS),)  
# default to common dir for device vendor  
$(BUILT_TARGET_FILES_PACKAGE): tool_extensions := $(TARGET_DEVICE_DIR)/../common  
else  
$(BUILT_TARGET_FILES_PACKAGE): tool_extensions := $(TARGET_RELEASETOOLS_EXTENSIONS)  
endif  
  
# Depending on the various images guarantees that the underlying  
# directories are up-to-date.  
$(BUILT_TARGET_FILES_PACKAGE): \  
        $(INSTALLED_BOOTIMAGE_TARGET) \  
        $(INSTALLED_RADIOIMAGE_TARGET) \  
        $(INSTALLED_RECOVERYIMAGE_TARGET) \  
        $(INSTALLED_SYSTEMIMAGE) \  
        $(INSTALLED_USERDATAIMAGE_TARGET) \  
        $(INSTALLED_ANDROID_INFO_TXT_TARGET) \  
        $(built_ota_tools) \  
        $(APKCERTS_FILE) \  
        $(HOST_OUT_EXECUTABLES)/fs_config \  
        | $(ACP)  
    @echo "Package target files: $@"  
    $(hide) rm -rf $@ $(zip_root)  
    $(hide) mkdir -p $(dir $@) $(zip_root)  
    @# Components of the recovery image  
    $(hide) mkdir -p $(zip_root)/RECOVERY  
    $(hide) $(call package_files-copy-root, \  
        $(TARGET_RECOVERY_ROOT_OUT),$(zip_root)/RECOVERY/RAMDISK)  
ifdef INSTALLED_KERNEL_TARGET  
    $(hide) $(ACP) $(INSTALLED_KERNEL_TARGET) $(zip_root)/RECOVERY/kernel  
endif  
ifdef INSTALLED_2NDBOOTLOADER_TARGET  
    $(hide) $(ACP) \  
        $(INSTALLED_2NDBOOTLOADER_TARGET) $(zip_root)/RECOVERY/second  
endif  
ifdef BOARD_KERNEL_CMDLINE  
    $(hide) echo "$(BOARD_KERNEL_CMDLINE)" > $(zip_root)/RECOVERY/cmdline  
endif  
ifdef BOARD_KERNEL_BASE  
    $(hide) echo "$(BOARD_KERNEL_BASE)" > $(zip_root)/RECOVERY/base  
endif  
ifdef BOARD_KERNEL_PAGESIZE  
    $(hide) echo "$(BOARD_KERNEL_PAGESIZE)" > $(zip_root)/RECOVERY/pagesize  
endif  
    @# Components of the boot image  
    $(hide) mkdir -p $(zip_root)/BOOT  
    $(hide) $(call package_files-copy-root, \  
        $(TARGET_ROOT_OUT),$(zip_root)/BOOT/RAMDISK)  
ifdef INSTALLED_KERNEL_TARGET  
    $(hide) $(ACP) $(INSTALLED_KERNEL_TARGET) $(zip_root)/BOOT/kernel  
endif  
ifdef INSTALLED_2NDBOOTLOADER_TARGET  
    $(hide) $(ACP) \  
        $(INSTALLED_2NDBOOTLOADER_TARGET) $(zip_root)/BOOT/second  
endif  
ifdef BOARD_KERNEL_CMDLINE  
    $(hide) echo "$(BOARD_KERNEL_CMDLINE)" > $(zip_root)/BOOT/cmdline  
endif  
ifdef BOARD_KERNEL_BASE  
    $(hide) echo "$(BOARD_KERNEL_BASE)" > $(zip_root)/BOOT/base  
endif  
ifdef BOARD_KERNEL_PAGESIZE  
    $(hide) echo "$(BOARD_KERNEL_PAGESIZE)" > $(zip_root)/BOOT/pagesize  
endif  
    $(hide) $(foreach t,$(INSTALLED_RADIOIMAGE_TARGET),\  
                mkdir -p $(zip_root)/RADIO; \  
                $(ACP) $(t) $(zip_root)/RADIO/$(notdir $(t));)  
    @# Contents of the system image  
    $(hide) $(call package_files-copy-root, \  
        $(SYSTEMIMAGE_SOURCE_DIR),$(zip_root)/SYSTEM)  
    @# Contents of the data image  
    $(hide) $(call package_files-copy-root, \  
        $(TARGET_OUT_DATA),$(zip_root)/DATA)  
    @# Extra contents of the OTA package  
    $(hide) mkdir -p $(zip_root)/OTA/bin  
    $(hide) $(ACP) $(INSTALLED_ANDROID_INFO_TXT_TARGET) $(zip_root)/OTA/  
    $(hide) $(ACP) $(PRIVATE_OTA_TOOLS) $(zip_root)/OTA/bin/  
    @# Files that do not end up in any images, but are necessary to  
    @# build them.  
    $(hide) mkdir -p $(zip_root)/META  
    $(hide) $(ACP) $(APKCERTS_FILE) $(zip_root)/META/apkcerts.txt  
    $(hide) echo "$(PRODUCT_OTA_PUBLIC_KEYS)" > $(zip_root)/META/otakeys.txt  
    $(hide) echo "recovery_api_version=$(PRIVATE_RECOVERY_API_VERSION)" > $(zip_root)/META/misc_info.txt  
ifdef BOARD_FLASH_BLOCK_SIZE  
    $(hide) echo "blocksize=$(BOARD_FLASH_BLOCK_SIZE)" >> $(zip_root)/META/misc_info.txt  
endif  
ifdef BOARD_BOOTIMAGE_PARTITION_SIZE  
    $(hide) echo "boot_size=$(BOARD_BOOTIMAGE_PARTITION_SIZE)" >> $(zip_root)/META/misc_info.txt  
endif  
ifdef BOARD_RECOVERYIMAGE_PARTITION_SIZE  
    $(hide) echo "recovery_size=$(BOARD_RECOVERYIMAGE_PARTITION_SIZE)" >> $(zip_root)/META/misc_info.txt  
endif  
ifdef BOARD_SYSTEMIMAGE_PARTITION_SIZE  
    $(hide) echo "system_size=$(BOARD_SYSTEMIMAGE_PARTITION_SIZE)" >> $(zip_root)/META/misc_info.txt  
endif  
ifdef BOARD_USERDATAIMAGE_PARTITION_SIZE  
    $(hide) echo "userdata_size=$(BOARD_USERDATAIMAGE_PARTITION_SIZE)" >> $(zip_root)/META/misc_info.txt  
endif  
    $(hide) echo "tool_extensions=$(tool_extensions)" >> $(zip_root)/META/misc_info.txt  
ifdef mkyaffs2_extra_flags  
    $(hide) echo "mkyaffs2_extra_flags=$(mkyaffs2_extra_flags)" >> $(zip_root)/META/misc_info.txt  
endif  
    @# Zip everything up, preserving symlinks  
    $(hide) (cd $(zip_root) && zip -qry ../$(notdir $@) .)  
    @# Run fs_config on all the system files in the zip, and save the output  
    $(hide) zipinfo -1 $@ | awk -F/ 'BEGIN { OFS="/" } /^SYSTEM\// {$$1 = "system"; print}' | $(HOST_OUT_EXECUTABLES)/fs_config > $(zip_root)/META/filesystem_config.txt  
    $(hide) (cd $(zip_root) && zip -q ../$(notdir $@) META/filesystem_config.txt)  
  
  
target-files-package: $(BUILT_TARGET_FILES_PACKAGE)  
  
  
ifneq ($(TARGET_SIMULATOR),true)  
ifneq ($(TARGET_PRODUCT),sdk)  
ifneq ($(TARGET_DEVICE),generic)  
ifneq ($(TARGET_NO_KERNEL),true)  
ifneq ($(recovery_fstab),)  

第一步:創建一個root_zip根目錄,并依次在此目錄下創建所需要的如下其他目錄

①創建RECOVERY目錄,并填充該目錄的內容,包括kernel的鏡像和recovery根檔案系統的鏡像,此目錄最終用于生成recovery.img,

②創建并填充BOOT目錄,包含kernel和cmdline以及pagesize大小等,該目錄最終用來生成boot.img,
③向SYSTEM目錄填充system image,
④向DATA填充data image,
⑤用于生成OTA package包所需要的額外的內容,主要包括一些bin命令,
⑥創建META目錄并向該目錄下添加一些文本檔案,如apkcerts.txt(描述apk檔案用到的認證證書),misc_info.txt(描述Flash記憶體的塊大小以及boot、recovery、system、userdata等磁區的大小資訊),
⑦使用保留連接選項壓縮我們在上面獲得的root_zip目錄,
⑧使用fs_config(build/tools/fs_config)配置上面的zip包內所有的系統檔案(system/下各目錄、檔案)的權限屬主等資訊,fs_config包含了一個頭檔案#include“private/android_filesystem_config.h”,在這個頭檔案中以硬編碼的方式設定了system目錄下各檔案的權限、屬主,執行完配置后會將配置后的資訊以文本方式輸出 到META/filesystem_config.txt中,并再一次zip壓縮成我們最終需要的原始包,

第二步:上面的zip包只是一個編譯程序中生成的原始包,

這個原始zip包在實際的編譯程序中有兩個作用,一是用來生成OTA update升級包,二是用來生成系統鏡像,在編譯程序中若生成OTA update升級包時會呼叫一個名為ota_from_target_files的python腳本,位置在/build/tools/releasetools/ota_from_target_files,這個腳本的作用是以第一步生成的zip原始包作為輸入,最終生成可用的OTA升級zip包,

㈠ 首先看一下這個腳本開始部分的幫助檔案,

Usage: ota_from_target_files [flags] input_target_files output_ota_package
-b 過時的,
-k 簽名所使用的密鑰
-i 生成增量OTA包時使用此選項,后面我們會用到這個選項來生成OTA增量包,
-w 是否清除userdata磁區
-n 在升級時是否不檢查時間戳,預設要檢查,即預設情況下只能基于舊版本升級,
-e 是否有額外運行的腳本
-m 執行程序中生成腳本(updater-script)所需要的格式,目前有兩種即amend和edify,對應上兩種版本升級時會采用不同的解釋器,預設會同時生成兩種格式的腳 本,
-p 定義腳本用到的一些可執行檔案的路徑,
-s 定義額外運行腳本的路徑,
-x 定義額外運行的腳本可能用的鍵值對,
-v 執行程序中列印出執行的命令,
-h 命令幫助

三、Recovery服務的核心方法 install_package


和Recovery服務中的wipe_data、wipe_cache不同,install_package()是升級update.zip特有的一部分,也是最核心的部分,在這一步才真正開始對我們的update.zip包進行處理,下面就開始分析這一部分,

Android系統進行升級的時候,有兩種途徑:

一種是通過介面傳遞升級包路徑自動升級(Android系統SD卡升級),升級完之后系統自動重啟,

另一種是手動進入recovery模式下,選擇升級包進行升級,升級完成之后停留在recovery界面,需要手動選擇重啟,

前者多用于手機廠商的客戶端在線升級,后者多用于開發和測驗人員,但不管哪種,原理都是一樣的,都要在recovery模式下進行升級,

下面介紹的是升級包保存在cache目錄下,且升級包路徑保存在/cache/recovery/command中的方式(升級包的存放路徑,從BCB或者/cache/recovery/command里面決議得到的),

重啟進入升級主要流程:

  1. 系統重啟進入Recovery模式,讀取BCB的command,讀取到”boot-recovery”后,加載recovery.img,啟動recovery,
  2. 在install.cpp進行升級操作
  3. try_update_binary執行升級腳本
  4. 呼叫finish_recovery方法,清除BCB資訊,重啟

1、系統重啟進入Recovery模式

系統重啟時會判斷/cache/recovery目錄下是否有command檔案,如果存在就進入recovery模式,否則就正常啟動,

進入到Recovery模式下,將執行recovery.cpp的main函式,下面貼出關鍵代碼片段:

static Device::BuiltinAction prompt_and_wait(Device* device, int status) {
  ...
    ui->SetProgressType(RecoveryUI::EMPTY);

    size_t chosen_item = ui->ShowMenu(
...
    // handled in the switch statement below.
    Device::BuiltinAction chosen_action =
        (chosen_item == static_cast<size_t>(RecoveryUI::KeyError::TIMED_OUT))
            ? Device::REBOOT
            : device->InvokeMenuItem(chosen_item);

    switch (chosen_action) {
      case Device::NO_ACTION:
        break;
.....

      case Device::APPLY_ADB_SIDELOAD:
      case Device::APPLY_SDCARD:
      case Device::ENTER_RESCUE: {
        save_current_log = true;

        bool adb = true;
        Device::BuiltinAction reboot_action;
        if (chosen_action == Device::ENTER_RESCUE) {
          // Switch to graphics screen.
          ui->ShowText(false);
          status = ApplyFromAdb(device, true /* rescue_mode */, &reboot_action);
        } else if (chosen_action == Device::APPLY_ADB_SIDELOAD) {
          status = ApplyFromAdb(device, false /* rescue_mode */, &reboot_action);
        } else {
		  adb = false;
		  int required_battery_level;
		  if(is_battery_ok(&required_battery_level)){
			  status = ApplyFromSdcard(device, ui);
....
...
}

  

對上述函式的流程分析
1.bootable/recovery/recovery.cpp ShowMenu顯示recovery各種選單顯示
InvokeMenuItem 表示選中的具體哪個item
2.bootable/recovery/recoveryui/device.cpp

 Device::BuiltinAction Device::InvokeMenuItem(size_t menu_position) {
      return g_menu_actions[menu_position].second;
    }
    static std::vector<std::pair<std::string, Device::BuiltinAction>> g_menu_actions{
      { "Reboot system now", Device::REBOOT },
      { "Reboot to bootloader", Device::REBOOT_BOOTLOADER },
      { "Enter fastboot", Device::ENTER_FASTBOOT },
      { "Apply update from ADB", Device::APPLY_ADB_SIDELOAD },
      { "Apply update from SD card", Device::APPLY_SDCARD },
      { "Wipe data/factory reset", Device::WIPE_DATA },
      { "Wipe cache partition", Device::WIPE_CACHE },
      { "Mount /system", Device::MOUNT_SYSTEM },
      { "View recovery logs", Device::VIEW_RECOVERY_LOGS },
      { "Run graphics test", Device::RUN_GRAPHICS_TEST },
      { "Run locale test", Device::RUN_LOCALE_TEST },
      { "Enter rescue", Device::ENTER_RESCUE },
      { "Power off", Device::SHUTDOWN },
    };


我們一般選擇從sdcard選擇升級,故選擇Apply update from SD card 對應的device type是APPLY_SDCARD
走到ApplyFromSdcard方法,其中ApplyFromSdcard是在bootable/recovery/install/fuse_sdcard_install.cpp中定義的 代碼如下:
int ApplyFromSdcard(Device* device, RecoveryUI* ui) {
......... result = install_package(FUSE_SIDELOAD_HOST_PATHNAME, false, false, 0 /*retry_count*/, ui);
break;
}
其中最為關鍵的方法就是install_package(bootable/recovery/install/install.cpp) 最終執行的是
really_install_package

 static int really_install_package(const std::string& path, bool* wipe_cache, bool ...

  if (needs_mount) {
    if (path[0] == '@') {
      ensure_path_mounted(path.substr(1));
    } else {
      ensure_path_mounted(path);
    }
  }

  // Verify package.驗證簽名
  if (!verify_package(package.get(), ui)) {
    ...
  }

  // Try to open the package.打開升級包 
  ZipArchiveHandle zip = package->GetZipArchiveHandle();
    ....

  // Additionally verify the compatibility of the package if it's a fresh install.
  if (retry_count == 0 && !verify_package_compatibility(zip)) {
   .....
. // 執行升級腳本檔案,開始升級
  int result =
      try_update_binary(path, zip, wipe_cache, log_buffer, retry_count, max_temperature, .
}

try_update_binary執行升級腳本

// If the package contains an update binary, extract it and run it.
static int try_update_binary(const std::string& package, ZipArchiveHandle zip, bool* wipe_cache,
                             std::vector<std::string>* log_buffer, int retry_count,
                             int* max_temperature, RecoveryUI* ui) {
  std::map<std::string, std::string> metadata;
  if (!ReadMetadataFromPackage(zip, &metadata)) {
    LOG(ERROR) << "Failed to parse metadata in the zip file";
    return INSTALL_CORRUPT;
  }

  bool is_ab = android::base::GetBoolProperty("ro.build.ab_update", false);
  // Verifies against the metadata in the package first.
  if (int check_status = is_ab ? CheckPackageMetadata(metadata, OtaType::AB) : 0;
      check_status != 0) {
    log_buffer->push_back(android::base::StringPrintf("error: %d", kUpdateBinaryCommandFailure));
    return check_status;
  }
.......

  std::vector<std::string> args;
  if (int update_status =
          is_ab ? SetUpAbUpdateCommands(package, zip, pipe_write.get(), &args)
                : SetUpNonAbUpdateCommands(package, zip, retry_count, pipe_write.get(), &args);
      
......

  return INSTALL_SUCCESS;
}


通過獲取設備ro.build.ab_update資訊獲得 是否為AB動態磁區 如果是AB磁區升級(update_engine)進入CheckPackageMetadata 判斷當前版本資訊(版本號 系列號 fingerprint等)與目標版本資訊是否匹配 否則fail
最終AB磁區呼叫SetUpAbUpdateCommands 非AB磁區升級呼叫SetUpNonAbUpdateCommands
其中SetUpAbUpdateCommands 其實就是呼叫update_engine完成升級
*cmd = {
"/system/bin/update_engine_sideload",
"--payload=file://" + package,
android::base::StringPrintf("--offset=%ld", payload_offset),
"--headers=" + std::string(payload_properties.begin(), payload_properties.end()),
android::base::StringPrintf("--status_fd=%d", status_fd),
};

OTA升級成功,清空misc磁區(BCB置零),并將保存到記憶體系統的升級日志/tmp/recovery.log保存到/cache/recovery/last_log,重啟設備進入Main System,升級完成,


四 recovery升級程序中log除錯方法

有客戶反饋不知道如何除錯recovery,在這里介紹下recovery的除錯方法,

1. 如何在recovery模式使用adb

在recovery模式下,init程式加載的rc檔案是bootable/recovery/etc/init.rc,

service adbd /sbin/adbd recovery
    disabled

# Always start adbd on userdebug and eng builds
on property:ro.debuggable=1
    write /sys/class/android_usb/android0/enable 1
    start adbd

這里可以看到如果是在userdebug或者eng編譯模式,adbd服務是啟動的,執行adb shell時提示不存在system/bin/sh,因為這個時候recovery/root/system為空,并沒有sh程式可執行,

雖然adb shell不能執行,但adb的其他很多命令都是能夠使用的,

adb devices

adb push/pull

2. 如何增加log

在recovery的代碼中能看到有兩種方式添加的列印資訊:printf和UI->Print,

printf輸出到stdout好理解,UI->Print呼叫screen_ui的print函式,將資訊顯示在螢屏上,

void ScreenRecoveryUI::Print(const char *fmt, ...)
{
    char buf[256];
    va_list ap;
    va_start(ap, fmt);
    vsnprintf(buf, 256, fmt, ap);
    va_end(ap);
 
    fputs(buf, stdout);

除了顯示在螢屏上,也將資訊輸出到了stdout標準輸出,

3. 標準輸出資訊在哪

int
main(int argc, char **argv) {
    
    // If these fail, there's not really anywhere to complain...
    freopen(TEMPORARY_LOG_FILE, "a", stdout); setbuf(stdout, NULL);
    freopen(TEMPORARY_LOG_FILE, "a", stderr); setbuf(stderr, NULL);

recovery.cpp的main函式中,最開始就將stdout的輸出資訊保存在了/tmp/recovery.log檔案中,也就是說我們需要查看的log資訊都在這里,

再看看recovery結束時做了什么

static void
finish_recovery(const char *send_intent) {
    // Copy logs to cache so the system can find out what happened.
    copy_log_file(TEMPORARY_LOG_FILE, LOG_FILE, true);
    copy_log_file(TEMPORARY_LOG_FILE, LAST_LOG_FILE, false);
    copy_log_file(TEMPORARY_INSTALL_FILE, LAST_INSTALL_FILE, false);
    chmod(LOG_FILE, 0600);
    chown(LOG_FILE, 1000, 1000);   // system user
    chmod(LAST_LOG_FILE, 0640);
    chmod(LAST_INSTALL_FILE, 0644);

將"/tmp/recovery.log"拷貝到了"/cache/recovery/last_log",

4. 如何查看log

1)如果能使用adb,在recovery模式下就能使用adb pull出log資訊

adb pull /tmp/recovery.log .\

2)即使adb服務不能使用,前一次recovery的log也會保存到cache/recovery目錄下,在reboot正常進入系統后pull 出來查看,

adb pull /cache/recovery/last_log .\

cache/recovery/last_install保存的是最后一次更新的OTA包,

eng 版本 如何在recovery mode下抓取LOG

[SOLUTION]

1、在recovery mode下,升級動作之后 adb pull /tmp/recovery.log

如果是KK之前版本:

2、在nomal mode下 adb pull /cache/recovery/last_log

如果是KK版本:

2、在nomal mode下 adb pull /cache/recovery/last_log_r

此兩種方法均可

如果是user版本:

In recovery mode

目前沒有辦法在user版本也看到recovery.log,目前的辦法是

直接用eng版本的recovery.img替換user版本的recovery.img,然后抓取log

Reboot to normal mode

user版本也會產生/cache/recovery/last_log,但是可能會不能用adb pull出來!目前的辦法是做完recoveryrebootnormal mode后,重新燒boot.img,用eng版本的boot.img替換user 版本的boot.img,然后將log pull出來!

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/246578.html

標籤:其他

上一篇:安卓基礎學習 Day02 |常用布局-線性布局

下一篇:Android7.0修改時間服務器

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more