Android Recovery OTA升級(一)—— make otapackage


文件夾


概述

make otapackage是Android Build系統支持的命令。用來生成Recovery系統能夠進行升級的zip包。

因此。想要了解Android的OTA升級機制。我們首先須要學習make otapackage命令的運行過程。

為了防止泄密,下面源代碼內容都是基於Android4.4.2_r1分支進行分析。


make otapackage

make otapackage是一個.PHONY偽目標。make系統中,偽目標並非一個文件。僅僅是一個標簽,因為偽目標不是文件,所以make無法生成它的依賴關系和決定它是否要運行。

我們僅僅能通過顯示的指明這個“目標”才干讓其生效。

須要注意的是,偽目標的取名不能和文件名稱重名。不然就失去偽目標的意義了。


了解了.PHONY之后。我們來看一下make otapackage的make源代碼:

.PHONY: otapackage
otapackage: $(INTERNAL_OTA_PACKAGE_TARGET)

通過make代碼。我們看到otapackage這個偽目標是依賴於$(INTERNAL_OTA_PACKAGE_TARGET)的,接下來,我會分析一下依賴文件的生成源代碼:

INTERNAL_OTA_PACKAGE_TARGET := $(PRODUCT_OUT)/$(name).zip
$(INTERNAL_OTA_PACKAGE_TARGET): KEY_CERT_PAIR := $(DEFAULT_KEY_CERT_PAIR)
$(INTERNAL_OTA_PACKAGE_TARGET): $(BUILT_TARGET_FILES_PACKAGE) $(DISTTOOLS)
    @echo "Package OTA: $@"
    $(hide) ./build/tools/releasetools/ota_from_target_files -v \
    -p $(HOST_OUT) \
    -k $(KEY_CERT_PAIR) \
    $(BUILT_TARGET_FILES_PACKAGE) $@

能夠看到,$(INTERNAL_OTA_PACKAGE_TARGET)依賴於$(KEY_CERT_PAIR),$(HOST_OUT),$(BUILT_TARGET_FILES_PACKAGE)這三個文件和$(DISTTOOLS)所代表的jar包。
而這些所依賴的文件,最后都會作為參數傳遞給ota_from_target_files這個python腳本。這個python腳本會進一步生成終於的recovery升級包。

演示樣例參數例如以下:

['-v', '-p', 'out/host/linux-x86', '-k', 'build/target/product/security/testkey', 'out/target/product/xxx/obj/PACKAGING/target_files_intermediates/product-target_files-wangzhengyi.zip', 'out/target/product/xxx/product_20150724.1246-ota.zip']

從參數中能夠看出,out/target/product/xxx/product_20150724.1246-ota.zip是終於的recovery升級包,而out/target/product/xxx/obj/PACKAGING/target_files_intermediates/product-target_files-wangzhengyi.zip是中間的暫時zip包。它事實上就是$(BUILT_TARGET_FILES_PACKAGE),接下來我們看一下$(BUILT_TARGET_FILES_PACKAGE)的生成代碼。


BUILT_TARGET_FILES_PACKAGE

BUILT_TARGET_FILES_PACKAGE的make源代碼例如以下:

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
endif

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,sqlite3)/sqlite3 \
    $(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)
$(BUILT_TARGET_FILES_PACKAGE): PRIVATE_RECOVERY_FSTAB_VERSION := $(RECOVERY_FSTAB_VERSION)

# 開始構建中間zip包
$(BUILT_TARGET_FILES_PACKAGE): \
        $(INSTALLED_BOOTIMAGE_TARGET) \
        $(INSTALLED_RADIOIMAGE_TARGET) \
        $(INSTALLED_RECOVERYIMAGE_TARGET) \
        $(INSTALLED_SYSTEMIMAGE) \
        $(INSTALLED_USERDATAIMAGE_TARGET) \
        $(INSTALLED_CACHEIMAGE_TARGET) \
        $(INSTALLED_VENDORIMAGE_TARGET) \
        $(INSTALLED_ANDROID_INFO_TXT_TARGET) \
        $(SELINUX_FC) \
        $(built_ota_tools) \
        $(APKCERTS_FILE) \
        $(HOST_OUT_EXECUTABLES)/fs_config \
        | $(ACP)
    @echo "Package target files: $@"
    # 刪除之前的zip文件
    $(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) $(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_PAGESIZE
    $(hide) echo "$(BOARD_KERNEL_PAGESIZE)" > $(zip_root)/RECOVERY/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)
ifdef BOARD_VENDORIMAGE_FILE_SYSTEM_TYPE
    @# Contents of the vendor image
    $(hide) $(call package_files-copy-root, \
            $(TARGET_OUT_VENDOR), $(zip_root)/VENDOR)
endif
   @# 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
   # 向otakeys.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
   $(hide) echo "fstab_version=$(PRIVATE_RECOVERY_FSTAB_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
    $(hide) echo "tool_extensions=$(tool_extensions)" >> $(zip_root)/META/misc_info.txt
    $(hide) echo "default_system_dev_certificate=$(DEFAULT_SYSTEM_DEV_CERTIFICATE)" >> $(zip_root)/META/misc_info.txt
ifdef PRODUCT_EXTRA_RECOVERY_KEYS
    $(hide) echo "extra_recovery_keys=$(PRODUCT_EXTRA_RECOVERY_KEYS)" >> $(zip_root)/META/misc_info.txt
endif
    $(hide) echo "mkbootimg_args=$(BOARD_MKBOOTIMG_ARGS)" >> $(zip_root)/META/misc_info.txt
    $(hide) echo "use_set_metadata=1" >> $(zip_root)/META/misc_info.txt
    $(hide) echo "update_rename_support=1" >> $(zip_root)/META/misc_info.txt
    $(call generate-userimage-prop-dictionary, $(zip_root)/META/misc_info.txt)

    # 打包zip包
    $(hide) (cd $(zip_root) && zip -qry ../$(notdir $@) .)
    $(hide) zipinfo -1 $@ | awk 'BEGIN { FS="SYSTEM/" } /^SYSTEM\// {print "system/" $$2}' | $(HOST_OUT_EXECUTABLES)/fs_config -C -S $(SELINUX_FC) > $(zip_root)/META/filesystem_config.txt
    $(hide) zipinfo -1 $@ | awk 'BEGIN { FS="BOOT/RAMDISK/" } /^BOOT\/RAMDISK\// {print $$2}' | $(HOST_OUT_EXECUTABLES)/fs_config -C -S $(SELINUX_FC) > $(zip_root)/META/boot_filesystem_config.txt
    $(hide) zipinfo -1 $@ | awk 'BEGIN { FS="RECOVERY/RAMDISK/" } /^RECOVERY\/RAMDISK\// {print $$2}' | $(HOST_OUT_EXECUTABLES)/fs_config -C -S $(SELINUX_FC) > $(zip_root)/META/recovery_filesystem_config.txt
    $(hide) (cd $(zip_root) && zip -q ../$(notdir $@) META/*filesystem_config.txt)

.PHONY: target-files-package
target-files-package: $(BUILT_TARGET_FILES_PACKAGE)

不知道大家第一次看到這段代碼,第一感覺怎樣。會不會認為非常難理解?
反正,我學習Android Build系統的makefile源代碼時候,都感覺非常暈、非常繞、非常難入手。畢竟我之前是寫解釋型語言的。這里給大家介紹兩個經驗:

  • 堅持的去看,堅持總是沒錯的。
  • 用warning和error去打log。去跟蹤。

接下來,我進一步解釋一下上面這塊目標(target-files-package)主要實現了哪些功能:

  1. 創建$(zip_root)根文件夾,接下來都是基於zip_root文件夾進行其他文件夾的創建。
  2. 創建並填充RECOVERY文件夾。包含:kernel鏡像文件、RAMDISK文件夾。此文件夾終於用來生成recovery.img。

  3. 創建並填充BOOT文件夾,包含:kernel鏡像文件、RAMDISK文件夾、ramdisk鏡像。此文件夾終於用來生成boot.img。
  4. 填充SYSTEM文件夾。
  5. 創建並填充OTA/bin文件夾。
  6. 創建META文件夾並向該文件夾下加入一些文本文件。
  7. 最后將文件夾打包成zip包。

ota_from_target_files

上面的make系統僅僅是生成了target-files-package這個暫時目標文件。而終於Recovery系統能使用的zip包還須要經過ota_from_target_files腳本處理。
ota_from_target_files腳本還是非常強大的。能夠生成:

  1. OTA全量包。
  2. OTA增量包。

這里我們僅僅介紹OTA全量包的生成過程。

在最初的otapackage的make源代碼中,我們已經看到。終於是會調用ota_from_target_files腳本的。而且還有必要的參數傳遞,這里給出演示樣例的參數例如以下:

['-v', '-p', 'out/host/linux-x86', '-k', 'build/target/product/security/testkey', 'out/target/product/xxx/obj/PACKAGING/target_files_intermediates/product-target_files-wangzhengyi.zip', 'out/target/product/xxx/product_20150724.1246-ota.zip']

參數作用:

  • -v : verbose標識,有這個標識在ota生成過程中會打印出很多其他的運行信息。

  • -p : 定義腳本中用到的可運行文件的路徑。
  • -k : 簽名時所用的密鑰。防止ota升級包的內容被篡改。

跟OTA增量升級相關的源代碼例如以下(為了理清邏輯關系。我的代碼順序和聲明順序正好是反過來的):

if __name__ == '__main__':
    try:
        main(sys.argv[1:])
    except common.ExternalError, e:
        print
        print " ERROR: %s" % (e,)
        print
        sys.exit(1)

def main(argv):
    # 調用getprop.getprop處理我們傳入的參數,這里省略處理參數代碼
    # 處理參數主要是為了給OPTIONS對象賦值
    # 以上面的傳入參數為例,處理之后,OPTIONS對象的成員屬性賦值例如以下:
    # OPTIONS.package_key = build/target/product/security/testkey
    # OPTIONS.verbose = True
    # OPTIONS.search_path = out/host/linux-x86

    if len(args) != 2:
        # args是getprop.getprop無法處理的參數
        # 這里的args是['target-files-package', 'final ota zip name']組成的list
        sys.exit(1)

    print "unzipping target target-files..."
    # OPTIONS.input_tmp保存解壓文件夾名稱
    # input_zip為依據target-files-package生成的zipfile對象
    OPTIONS.input_tmp, input_zip = common.UnzipTemp(args[0])
    OPTIONS.target_tmp = OPTIONS.input_tmp

    # 主要是解析例如以下三個文件,並將文件內容以(k,v)鍵值對形式保存到OPTIONS.info_dict中
    # 三個文件各自是:
    # 1. META/misc_info.txt
    # 2. SYSTEM/build.prop
    # 3. RECOVERY/RAMDISK/etc/recovery.fstab
    OPTIONS.info_dict = common.LoadInfoDict(input_zip)

    if "selinux_fc" in OPTIONS.info_dict:
        OPTIONS.info_dict["selinux_fc"] = os.path.join(OPTIONS.input_tmp, "BOOT", "RAMDISK", "file_contexts")
    if OPTIONS.device_specific is None:
        OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions", None)
    if OPTIONS.device_specific is not None:
        OPTIONS.device_specific = os.path.normpath(OPTIONS.device_specific)
        print "using device-specific extensions in", OPTIONS.device_specific

    temp_zip_file = tempfile.NamedTemporaryFile()
    output_zip = zipfile.ZipFile(temp_zip_file, "w", compression=zipfile.ZIP_DEFLATED)

    # 構造全量包時OPTIONS.incremental_source的值為None
    if OPTIONS.incremental_source is None:
        # 構造全量包的關鍵函數,略微重點分析
        WriteFullOTAPackage(input_zip, output_zip)
        if OPTIONS.package_key is None:
            OPTIONS.package_key = OPTIONS.info_dict.get(
                "default_system_dev_certificate",
                "build/target/product/security/testkey")

    output_zip.close()
    # 對全量包進行防篡改簽名,並進行重命名。略微重點分析
    SignOutput(temp_zip_file.name, args[1])
    temp_zip_file.close()

    print "done."


# common類的UnzipTemp函數源代碼
def UnzipTemp(filename, pattern=None):
    # 創建/tmp文件夾下暫時文件,用於存儲target-files-package解壓的內容
    tmp = tempfile.mkdtemp(prefix="targetfiles-")
    OPTIONS.tempfiles.append(tmp)

    def unzip_to_dir(filename, dirname):
        cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
        p = subprocess.Popen(cmd, stdout = subprocess.PIPE)
        p.communicate()
        if p.returncode != 0:
            raise ExternalError("failed to unzip input target-files")

    unzip_to_dir(filename, tmp)
    return tmp, zipfile.ZipFile(filename, "r")


# common類的LoadInfoDict函數
def LoadInfoDict(zip):
    d = {}
    try:
        for line in zip.read("META/misc_info.txt").split("\n"):
            line = line.strip()
            if not line or line.startswith("#"): continue
            k, v = line.split("=", 1)
            d[k] = v
    except KeyError:
        pass

    # 解析掛載信息,不上源代碼了
    d["fstab"] = LoadRecoveryFSTab(zip, d["fstab_version"])
    d["build.prop"] = LoadBuildProp(zip)
    return d


def LoadBuildProp(zip):
    try:
        data = zip.read("SYSTEM/build.prop")
    except KeyError:
        print "Warning: could not find SYSTEM/build.prop in %s" % zip
        data = ""

    d = {}
    for line in data.split("\n"):
        line = line.strip()
        if not line or line.startswith("#"): continue
        name, value = line.split("=", 1)
        d[name] = value
    return d

ota_from_target_files生成全量包的代碼如上所看到的。一些不必要看的代碼我已經省略了。可是還有兩處生成全量包的關鍵函數我這邊須要重點分析。


WriteFullOTAPackage

這是依據target-files-package生成全量包的關鍵函數,我們來看一下它的詳細實現。有一點須要解釋一下,依據我們的演示樣例參數,input_zip和output_zip各自是:

  • input_zip:out/target/product/xxx/obj/PACKAGING/target_files_intermediates/product-target_files-wangzhengyi.zip的zipfile對象
  • output_zip:/tmp文件夾下暫時文件的zipfile對象
def WriteFullOTAPackage(input_zip, output_zip):
    script = edify_generator.EdifyGenerator(3, OPTIONS.info_dict)

    # 從build.prop文件內容中獲取屬性構建metadata字典
    metadata = {"post-build": GetBuildProp("ro.build.fingerprint", 
                                            OPTIONS.info_dict),
                "pre-device": GetBuildProp("ro.product.device",
                                            OPTIONS.info_dict),
                "post-timestamp": GetBuildProp("ro.build.data.utc",
                                            OPTIONS.info_dict),
                }

    device_specific = common.DeviceSpecificParams(
        input_zip=input_zip,
        input_version=OPTIONS.info_dict["recovery_api_version"],
        output_zip=output_zip,
        script=script,
        input_tmp=OPTIONS.input_tmp,
        metadata=metadata,
        info_dict=OPTIONS.info_dict)

    # 在updater-script腳本添加時間推斷,假設須要升級的版本號晚於當前系統的時間,
    # 則不進行更新
    if not OPTIONS.omit_prereq:
        ts = GetBuildProp("ro.build.date.utc", OPTIONS.info_dict)
        ts_text = GetBuildProp("ro.build.date", OPTIONS.info_dict)
        script.AssertOlderBuild(ts, ts_text)

    # 在updater-script腳本中添加產品類型推斷
    AppendAssertions(script, OPTIONS.info_dict)

    # 在updater-script添加進度顯示
    script.ShowProgress(0.5, 0)

    # 安全相關。將BOOT/RAMDISK/file_contexts寫入到output_zip中 
    if "selinux_fc" in OPTIONS.info_dict:
        WritePolicyConfig(OPTIONS.info_dict["selinux_fc"], output_zip)

    # 在updater-script腳本中:
    # 1. 添加system分區格式化代碼
    # 2. 添加system分區掛載代碼
    # 3. 將recovery文件夾解壓到system分區
    # 4. 將system文件夾解壓到system分區
    script.FormatPartition("/system")
    script.Mount("/system")
    script.UnpackPackageDir("recovery", "/system")
    script.UnpackPackageDir("system", "/system")

    # 將input_zip的system文件夾文件復制到output_zip中,並返回鏈接文件名稱稱
    symlinks = CopySystemFiles(input_zip, output_zip)
    script.MakeSymlinks(symlinks)

    # 調用mkbootfs、minigzip、mkbootimg構造boot.img和recovery.img
    boot_img = common.GetBootableImage("boot.img", "boot.img", OPTIONS.input_tmp, "BOOT")
    recovery_img = common.GetBootableImage("recovery.img", "recovery.img", OPTIONS.input_tmp, "RECOVERY")
    MakeRecoveryPath(OPTIONS.input_tmp, output_zip, recovery_img, boot_img)

    # 在updater-script中設置system文件權限
    Item.Get("system").SetPermissions(script)

    # 將boot.img放入output_zip中。並在updater-script中添加寫入信息
    common.CheckSize(boot_img.data, "boot.img", OPTIONS.info_dict)
    common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
    script.ShowProgress(0.2, 0)

    script.ShowProgress(0.2, 10)
    script.WriteRawImage("/boot", "boot.img")
    # updater-script中添加分區卸載信息
    script.UmountAll()
    # 將updater-script和update-binary寫入到output_zip
    script.AddToZip(input_zip, output_zip)
    #將metadata信息寫入到output_zip的META-INF/com/android/metadata文件里
    WriteMetadata(metadata, output_zip)


def CopySystemFiles(input_zip, output_zip=None, substitute=None):
    symlinks = []

    for info in input_zip.infolist():
        if info.filename.startswith("SYSTEM/"):
            basefilename = info.filename[7:]
            if IsSymlink(info):
                #鏈接文件
                symlinks.append((input_zip.read(info.filename), "/system/" + basefilename))
            else:
                info2 = copy.copy(info)
                fn = info2.filename = "system/" + basefilename
                if output_zip is not None:
                    data = input_zip.read(info.filename)
                output_zip.writestr(info2, data)
                if fn.endswith("/"):
                    Item.Get(fn[:-1], dir=True)
                else:
                    Item.Get(fn, dir=False)i

    symlinks.sort()
    return symlinks


def AddToZip(self, input_zip, output_zip, input_path=None):
    common.ZipWriteStr(output_zip, "META-INF/com/google/android/updater-script",
                        "\n".join(self.script) + "\n")
    if input_path is None:
        data = input_zip.read("OTA/bin/updater")
    else:
        data = open(os.path.join(input_path, "updater")).read()
    common.ZipWriteStr(output_zip, "META-INF/com/google/android/update-binary",
                        data, perms=0755)

代碼里凝視寫的非常清楚了,大家有一點python基礎,應該非常好理解。
須要注意的是:這里的output_zip是tmp文件夾下的暫時zip文件,那么真正的OTA包是在哪里生成的呢?
這就須要去看一下SignOutput的源代碼了。


SignOutput

首先,我們先看一下main函數中對SignOutput的調用:

SignOutput(temp_zip_file.name, args[1])

當中:

  • temp_zip_file.name : 就是output_zip對象相應的文件名稱稱
  • args[1] : 就是終於的OTA zip包的名稱

所以,我們非常easy想到通過簽名之后才形成的正式OTA zip包。
源代碼例如以下:

def SignOutput(temp_zip_name, output_zip_name):
    key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
    pw = key_passwords[OPTIONS.package_key]

    common.SignFile(temp_zip_file, output_zip_name, OPTIONS.package_key, pw, whole_file=True)


def SignFile(input_name, output_name, key, password, align=None, whole_file=False):
    sign_name = output_name

    cmd = [OPTIONS.java_path, "-Xmx2048m", "-jar",
            os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
    cmd.extend(OPTIONS.extra_signapk_args)
    if whole_file:
        cmd.append("-w")
    cmd.extend([key + OPTIONS.public_key_suffix,
                key + OPTIONS.private_key_suffix,
                input_name, sign_name])
    p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
    if password is not None:
        password += "\n"
    p.communicate(password)

真正的簽名運行命令例如以下:

java -Xmx2048m -jar out/host/linux-x86/framework/signapk.jar -w build/target/product/security/testkey.xxx.pem build/target/product/security/testkey.xxx /tmp/tmpXZiMiN out/target/product/xxx/product_20150724.1246-ota.zip

總結

這篇博客主要是總結了運行make otapackage所須要了解的依賴關系和python生成OTA包的腳本原理。


接下來。我還會總結一篇文章。是從Recovery源代碼角度出發,看Recovery是怎樣解析OTA zip包。運行升級流程。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM