96SEO 2026-02-19 09:37 38
。

文档主要包括两部分#xff0c;第一部分为OTA升级包的制作过程分析#xff0c;…1、概述
前段时间学习了AndroidRecovery模式及OTA升级过程为加深理解和防止以后遗忘所以写这篇文档进行一个总结和梳理以便日后查阅回顾。
文档主要包括两部分第一部分为OTA升级包的制作过程分析第二部分为Recovery模式下OTA升级包安装过程的分析其中包括Recovery模式分析及服务流程。
《Android系统Recovery工作原理之使用update.zip升级过程分析》
所谓OTAOver-***-AirTechnology是指手机终端通过无线网络下载远程服务器上的升级包对系统或应用进行升级的技术。
有关网络部分不做过多讨论本文重点放在系统升级这一概念上。
以PC机进行类比假设计算机操作系统装在C盘当加电启动时引导程序会将C盘的系统程序装入内存并运行而系统升级或重装系统则是将C盘中原来的系统文件部分或全部重写。
对于手机及其上的Android系统而言同样如此需要一个存储系统文件的“硬盘”。
是某款手机的存储设备结构图其存储区红色框图部分分为四部分SRAM、Nand
Flash中存储着全部系统数据通过专门的烧写工具将编译后的映象文件download到Nand
Flash中工具由IC厂商提供包括boot.img、system.img、recovery.img等因此Nand
Flash即是上文所说的手机上的“硬盘”。
图1最右部分图中绿色框图部分是Nand
Flash存储区更详细的划分我们将主要关注system分区蓝色框图因为OTA升级主要是对这部分系统数据的重写当然boot分区也可升级。
除此之外蓝黑色区域标示的misc分区也应值得注意它在OTA升级中发挥着重要的作用。
OK一言以蔽之所谓OTA就是将升级包zip压缩包写入到系统存储区因此我们需要考虑两个问题1、升级包是如何生成的2、升级包是如何写入到system分区的
1、boot.img是更新boot分区所需要的文件。
这个boot.img主要包括kernelramdisk包括应用会用到的一些库等等。
可以将Android源码编译out/target/product/
3、recovery/下的install-recovery.sh是执行更新的脚本。
4、update-binary是一个二进制文件相当于一个脚本解释器能够识别updater-script中描述的操作。
它是Android源码编译后生成的out/target/product/xxxx/system
bin/updater文件可将updater重命名为update-binary得到。
该文件在具体的更新包中的名字由源码中bootable/recovery/install.c中的宏ASSUMED_UPDATE_BINARY_NAME的值而定。
5、updater-script此文件是一个脚本文件具体描述了更新过程。
我们可以根据具体情况编写该脚本来适应我们的具体需求。
该文件的命名由源码中bootable/recovery/updater/updater.c文件中的宏SCRIPT_NAME的值而定。
metadata文件是描述设备信息及环境变量的元数据。
主要包括一些编译选项签名公钥时间戳以及设备型号等。
8、update.zip包的签名update.zip更新包在制作完成后需要对其签名否则在升级时会出现认证失败的错误提示。
而且签名要使用和目标板一致的加密公钥。
加密公钥及加密需要的三个文件在Android源码编译后生成的具体路径为
out/host/linux-x86/framework/signapk.jar
build/target/product/security/testkey.x509.pem
build/target/product/security/testkey.pk8。
otapackage制作生成的update.zip包是已签过名的如果自己做update.zip包时必须手动对其签名。
具体的加密方法
out/host/linux/framework/signapk.jar
build/target/product/security/testkey.x509.pem
build/target/product/security/testkey.pk8
以上命令在update.zip包所在的路径下执行其中signapk.jar
testkey.x509.pem以及testkey.pk8文件的引用使用绝对路径。
update.zip
是我们已经打好的包update_signed.zip包是命令执行完生成的已经签过名的包。
9、MANIFEST.MF这个manifest文件定义了与包的组成结构相关的数据。
类似Android应用的mainfest.xml文件。
10、CERT.RSA与签名文件相关联的签名程序块文件它存储了用于签名JAR文件的公共签名。
11、CERT.SF这是JAR文件的签名文件其中前缀CERT代表签名者。
另外在具体升级时对update.zip包检查时大致会分三步①检验SF文件与RSA文件是否匹配。
②检验MANIFEST.MF与签名文件中的digest是否一致。
③检验包中的文件与MANIFEST中所描述的是否一致。
12、在做的MTK平台的项目如8670、9976需要增加与项目强相关的适配文件scatter.txt、SEC_VER.txt、type.txtscatter.txt分散加载文件将可执行映像文件分散加载到不同的内存段文件内容指定不同内存段的起始地址。
type.txt是build升级包过程生成的里面的值1代表FullOTA0代表DiffOTAandroid的上层的update流程中会check这个值。
scatter.txt也是build升级包过程生成的里面的内容来自于/mediatek/misc/ota_scatter.txt。
具体code可见脚本ota_from_target_files。
而mediatek/misc/ota_scatter.txt是在build
ota时会产生。
该文件主要用于在升级的时候check升级前后parition
SEC_VER.TXT是在编译时从alps\mediatek\custom\$PROJECT\security\recovery下copy过来的用于在打开SUPPORT_SBOOT_UPDATE之后会使用具体可以参考[FAQ05739]
new时会执行ptgen执行ptgen会依赖OTA_SCATTER_FILE见mediatek/build/libs/pregen.mk218然后再build/tools/makeMtk.mk中的142以及
升级包有整包与差分包之分。
顾名思义所谓整包即包含整个system分区中的数据文件而差分包则仅包含两个版本之间改动的部分。
利用整包升级好比对电脑进行重作系统格式化系统分区并将新系统数据写入分区而利用差分包升级不会格式化system分区只是对其中部分存储段的内容进行重写。
除升级包之外制作过程中还会涉及到另一种zip包代码中称之为8675-cota-target_files-xxx.zip我称之为差分资源包。
首先阐述下整包的制作过程。
otapackage命令即可完成整包的制作而此命令可分为两个阶段进行。
首先执行./build/core/Makefile中的代码
#-----------------------------------------------------------------
($(TARGET_BUILD_TYPE),debug)name
$(name)-ota-$(FILE_NAME_TAG)INTERNAL_OTA_PACKAGE_TARGET:
$(INTERNAL_OTA_PACKAGE_TARGET):
$(INTERNAL_OTA_PACKAGE_TARGET):$(BUILT_TARGET_FILES_PACKAGE)
./build/tools/releasetools/ota_from_target_files-v
#-----------------------------------------------------------------
如上代码是Makefile文件中目标otapackage的执行代码
otapackage命令会执行Makefile(./build/core/Makefile)中otapackage的目标代码(如代码1所示)。
由代码可知otapackage目标的执行只依赖于$(INTERNAL_OTA_PACKAGE_TARGET)而不存在任何规则(根据Makefile语法规则必须以TAB键开始而目标otapackage的定义之后却是变量name的声明因此不存在规则)因此只需要关注目标$(INTERNAL_OTA_PACKAGE_TARGET)的生成。
显然此目标的生成依赖于目标文件$(BUILT_TARGET_FILES_PACKAGE)和$(OTATOOLS)且其执行的命令为./build/tools/releasetools/ota_from_target_files。
也就是说make
otapackage所完成的功能全是通过这两个目标文件和执行的命令完成的我们将分别对这三个关键点进行分析。
$(HOST_OUT_EXECUTABLES)/minigzip
$(HOST_OUT_EXECUTABLES)/mkbootfs
$(HOST_OUT_EXECUTABLES)/mkbootimg
$(HOST_OUT_EXECUTABLES)/fs_config
$(HOST_OUT_EXECUTABLES)/mkyaffs2image
$(HOST_OUT_EXECUTABLES)/zipalign
$(HOST_OUT_EXECUTABLES)/imgdiff
$(HOST_OUT_JAVA_LIBRARIES)/dumpkey.jar
$(HOST_OUT_JAVA_LIBRARIES)/signapk.jar
$(HOST_OUT_EXECUTABLES)/mkuserimg.sh
$(HOST_OUT_EXECUTABLES)/genext2fs
$(HOST_OUT_EXECUTABLES)/tune2fs
$(HOST_OUT_EXECUTABLES)/make_ext4fs
可以看出变量OTATOOLS为系统中一系列文件的集合。
那么这些文件又有什么用处呢
事实上这些文件用于压缩(minigzip用于gzip文件make_ext4fs将文件转换为ext4类型mkyaffs2image用于yaffs文件系统......)、解压缩、差分(bsdiffimgdiff)、签名(singapk.jar)等功能结合代码段1可得到如下结论目标$(INTERNAL_OTA_PACKAGE_TARGET)的执行依赖于这一系列系统工具仅此而已。
也就是说目标文件$(OTATOOLS)仅仅指定了命令执行所需要的工具并未执行任何操作。
注变量$(HOST_OUT_EXECUTABLES)指代的是out/host/linux-x86/bin这意味着我们可以从此目录下找到上述工具并为我们所用。
目标OTATOOLS指明了执行makeotapackage命令所需要的系统工具而目标$(BUILT_TARGE_FILES_PACKAGE)的生成则完成了两件事重新打包system.img文件和生成差分资源包。
$(BUILT_TARGE_FILES_PACKAGE)的编译规则如下所示
-----------------------------------------------------------------
$(name)-target_files-$(FILE_NAME_TAG)
intermediates-dir-for,PACKAGING,target_files)
14.$(BUILT_TARGET_FILES_PACKAGE):
15.$(BUILT_TARGET_FILES_PACKAGE):
intermediates-dir-for,EXECUTABLES,applypatch)/applypatch
intermediates-dir-for,EXECUTABLES,applypatch_static)/applypatch_static
intermediates-dir-for,EXECUTABLES,check_prereq)/check_prereq
intermediates-dir-for,EXECUTABLES,sqlite3)/sqlite3
intermediates-dir-for,EXECUTABLES,updater)/updater
34.$(BUILT_TARGET_FILES_PACKAGE):
36.$(BUILT_TARGET_FILES_PACKAGE):
($(TARGET_RELEASETOOLS_EXTENSIONS),)
40.$(BUILT_TARGET_FILES_PACKAGE):
42.$(BUILT_TARGET_FILES_PACKAGE):
$(TARGET_RELEASETOOLS_EXTENSIONS)
($(TARGET_USERIMAGES_USE_EXT4),true)
49.$(BUILT_TARGET_FILES_PACKAGE):
52.$(BUILT_TARGET_FILES_PACKAGE):
$(INSTALLED_RECOVERYIMAGE_TARGET)
$(INSTALLED_FACTORYIMAGE_TARGET)
$(INSTALLED_USERDATAIMAGE_TARGET)
$(INSTALLED_ANDROID_INFO_TXT_TARGET)
$(HOST_OUT_EXECUTABLES)/fs_config
$(TARGET_RECOVERY_ROOT_OUT),$(zip_root)/RECOVERY/RAMDISK)
$(INSTALLED_2NDBOOTLOADER_TARGET)
$(TARGET_FACTORY_ROOT_OUT),$(zip_root)/FACTORY/RAMDISK)
$(INSTALLED_2NDBOOTLOADER_TARGET)
$(TARGET_ROOT_OUT),$(zip_root)/BOOT/RAMDISK)
$(INSTALLED_2NDBOOTLOADER_TARGET)
$(PRODUCT_OUT)/uboot_eyang77_ics2.bin
t,$(INSTALLED_RADIOIMAGE_TARGET),\
$(SYSTEMIMAGE_SOURCE_DIR),$(zip_root)/SYSTEM)
$(TARGET_OUT_DATA),$(zip_root)/DATA)
$(INSTALLED_ANDROID_INFO_TXT_TARGET)
mediatek/custom/$(MTK_PROJECT)/security/recovery/SEC_VER.txt
mediatek/custom/$(MTK_PROJECT)/security/recovery/SEC_VER.txt
recovery_api_version$(PRIVATE_RECOVERY_API_VERSION)
blocksize$(BOARD_FLASH_BLOCK_SIZE)
boot_size$(BOARD_BOOTIMAGE_PARTITION_SIZE)
BOARD_RECOVERYIMAGE_PARTITION_SIZE
recovery_size$(BOARD_RECOVERYIMAGE_PARTITION_SIZE)
BOARD_SYSTEMIMAGE_PARTITION_SIZE
system_size$(BOARD_SYSTEMIMAGE_PARTITION_SIZE)
BOARD_SECROIMAGE_PARTITION_SIZE
secro_size$(BOARD_SECROIMAGE_PARTITION_SIZE)
BOARD_CACHEIMAGE_PARTITION_SIZE
cache_size$(BOARD_CACHEIMAGE_PARTITION_SIZE)
BOARD_USERDATAIMAGE_PARTITION_SIZE
userdata_size$(BOARD_USERDATAIMAGE_PARTITION_SIZE)
tool_extensions$(tool_extensions)
mkyaffs2_extra_flags$(mkyaffs2_extra_flags)
INTERNAL_USERIMAGES_SPARSE_EXT_FLAG
extfs_sparse_flag$(INTERNAL_USERIMAGES_SPARSE_EXT_FLAG)
default_system_dev_certificate$(DEFAULT_KEY_CERT_PAIR)
extra_recovery_keys$(PRODUCT_EXTRA_RECOVERY_KEYS)
$(HOST_OUT_EXECUTABLES)/fs_config
$(zip_root)/META/filesystem_config.txt
$(HOST_OUT_EXECUTABLES)/fs_config
$(zip_root)/META/boot_filesystem_config.txt
$(HOST_OUT_EXECUTABLES)/fs_config
$(zip_root)/META/recovery_filesystem_config.txt
system.img文件的重新打包是通过$(BUILT_TARGE_FILES_PACKAGE)的依赖条件$(INSTALLED_SYSTEMIMAGE)目标文件的编译来完成的而$(BUILT_TARGE_FILES_PACKAGE)所有的执行命令(代码第66行至最后)都只为完成一件事生成差分资源包所对应的目录并将其打包为ZIP包。
具体的操作包括
创建$(zip_root)目录(代码第66~68行)$(zip_root)即out/target/product/product-name/obj/PACKAGING/target_files_from_intermedias/product-name-target_files-version-name
创建/$(zip_root)/RECOVERY目录并将COPY相关文件(代码69~86)
创建/$(zip_root)/FACTORY目录并将COPY相关文件(代码87~106)
创建/$(zip_root)/BOOT目录并将COPY相关文件(代码107~131)
将$(zip_root)目录压缩为资源差分包(代码192~198)等。
经过目标文件$(BUILT_TARGE_FILES_PACKAGE)的执行后system.img已经被重新打包且差分资源包也已经生成剩下的工作就是将差分资源包即target-files
zipfile下文将统一使用“差分资源包”这一概念传递给ota_
总之前述代码的作用就在于将系统资源包括system、recovery、boot等结构
其中OTA下存在着一个至关重要的文件OTA/bin/updater后文会有详述。
生成的差分资源包被传递给./build/tools/releasetools/
ota_from_target_files执行第二阶段的操作制作升级包。
图3./build/tools/releasetools目录下的文件
图3是./build/tools/releasetools目录下所包含的文件这组文件是Google提供的用来制作升级包的代码工具核心文件为ota_from_target_files和img_from_target_files。
其中前者用来制作recovery模式下的升级包后者则用来制作fastboot下的升级包fastboot貌似是一种更底层的刷机操作不太懂。
其他文件则是为此二者提供服务的比如common.py中包含有制作升级包所需操作的代码,各种工具类,参数处理/META文件处理/image生成/signcertification/patch
操作等等edify_generator.py则用于生成recovery模式下升级的脚本文件升级包.zip/META-INF/com/google/android/updater-script。
文件ota_from_target_files是本文所关注的重点其中定义了两个主要的方法WriteFullOTAPackage和WriteIncrementalOTAPackage。
前者用于生成整包后者用来生成差分包。
接着上文当Makefile调用ota_from_target_files并将差分资源包传递进来时会执行WriteFullOTAPackage。
此方法完成的工作包括1将system目录boot.img等文件添加到整包中2生成升级包中的脚本文件升级包.zip/META-INF/com/google/android/updater-script3将上文提到的可执行文件OTA/bin/updater添加到升级包中META-INF/com/google/android/updater-binary。
摘取部分代码片段如下
script.FormatPartition(/system)
script.UnpackPackageDir(recovery,
script.UnpackPackageDir(system,
script.RetouchBinaries(retouch_files)
script.UndoRetouchBinaries(retouch_files)
其中的script为edify_generator对象其FormatPartition、UnpackPackageDir等方法分别是向脚本文件update-script中写入格式化分区、解压包等指令
META-INF/com/google/android/updater-script,
input_zip.read(OTA/bin/updater)
META-INF/com/google/android/update-binary,
WriteFullOTAPackage执行的最后会调用此方法。
将资源差分包中OTA/bin/updater文件copy到升级包中META-INF/com/google/android/update-binary。
此文件是OTA升级的关键其将在recovery模式下被执行用来将代码段2中生成的指令转换为相应的函数去执行从而完成对系统数据的重写。
生成差分包调用的是文件./build/tools/releasetools/ota_from_target_files中的WriteIncrementalOTA方法调用时需要将两个版本的差分资源包作为参数传进来形如./build/tools/releasetools/ota_from_target_files–n
其中参数n表示忽略时间戳i表示生成增量包即差分包ota_v1.zip与ota_v2.zip分别代表前后两个版本的差分资源包而update.zip则表示最终生成的差分包。
WriteIncrementalOTA函数会计算输入的两个差分资源包中版本的差异并将其写入到差分包中同时将updater及生成脚本文件udpate-script添加到升级包中。
制作完升级包后之后便是将其写入到相应存储区中这部分工作是在recovery模式下完成的。
在这先简单描述一下这个过程。
recovery模式下通过创建一个新的进程读取并执行脚本文件META-INF/com/google/android/updater-script。
见如下代码
分析代码之前首先介绍linux中函数fork与execv的用法。
void)用于创建新的进程fork调用的一个奇妙之处就是它仅仅被调用一次却能够返回两次它可能有三种不同的返回值
在fork函数执行完毕后如果创建新进程成功则出现两个进程一个是子进程一个是父进程。
在子进程中fork函数返回0在父进程中fork返回新创建子进程的进程ID。
我们可以通过fork返回的值来判断当前进程是子进程还是父进程。
execv会停止执行当前的进程并且以progname应用进程替换被停止执行的进程进程ID没有改变。
progname:被执行的应用程序。
argv:
注意这个数组的第一个参数应该是应用程序名字本身并且最后一个参数应该为NULL不参将多个参数合并为一个参数放入数组。
代码4见于bootable/recovery/install.c的try_update_binary函数中是OTA升级的核心代码之一。
通过对fork及execv函数的介绍可知代码4创建了一个新的进程并在新进程中运行升级包中的META-INF/com/google/android/updater-binary文件参数binary已在此前赋值此文件将按照META-INF/com/google/android/updater-script中的指令将升级包里的数据写入到存储区中。
OK我们来看下META-INF/com/google/android/updater-binary文件的来历。
在源代码的./bootable/recovery/updater目录下存在着如下几个文件
通过查看Android.mk代码可知文件install.c、updater.c将会被编译为可执行文件updater存放到目录out/target/product/product-name/obj/EXECUTABLES/updater_intermediates/中而在生成差分资源包target-fileszipfile时会将此文件添加到压缩包中。
intermediates-dir-for,EXECUTABLES,applypatch)/applypatch
intermediates-dir-for,EXECUTABLES,applypatch_static)/applypatch_static
intermediates-dir-for,EXECUTABLES,check_prereq)/check_prereq
intermediates-dir-for,EXECUTABLES,sqlite3)/sqlite3
intermediates-dir-for,EXECUTABLES,updater)/updater
$(INSTALLED_ANDROID_INFO_TXT_TARGET)
如代码段5Makefile中定义了执行OTA所需要的一组工具built_ota_tools其中便包括由图4中文件编译而成的文件updater而在生成差分资源包时会将这组工具拷贝到差分资源包的OTA/bin目录中见代码段6在生成升级包时无论是执行WriteFullOTAPackage还是WriteIncrementalOTAPackage最后都会调用edify_generator的AddToZip方法将updater添加到升级包中更名为META-INF/com/google/android/update-binary最终在recovery模式下被执行这便是其来龙去脉。
而关于updater的执行也大致的描述下吧。
由前文可知updater主要由bootable/recovery/updater目录下的install.c和updater.c编译而成主函数位于updater.c。
其中在install.c中定义了读写系统存储区的操作函数这才是重写系统数据的真正代码并将这些函数与updater-script中的指令映射起来。
而在updater.c会首先装载install.c定义的函数之后便解析升级脚本updater-script执行其对应的操作命令。
与此同时执行updater的进程还会与父进程通信通知父进程进行UI的相关操作代码见bootable/recovery/install.c中的try_update_binary函数。
工具箱系统进入到这种模式下时可以通过按键选择相应的操作菜单从而实现相应的功能比如
卡进行OTA系统升级及固件firmware升级等。
我们公司的手机一般进入
模式的方法是电源键加音量上键。
后面会详细分析系统进入Recovery模式的过程以及在Recovery模式下进行OTA升级的过程。
设置模块中进行恢复出厂设置OTA升级patch升级及firmware升级等操作系统一共做了两件事
重启系统后必须进入Recovery模式接下来分析进入Recovery模式的流程。
bootloader为后面内核的加载作好准备。
在一些系统启动必要的初始完成之后系统会通过检测三个条件来判断要进入何种工作模式流程如图。
这一部分代码的源文件在\bootable\bootloader\lk\app\aboot\aboot.c
从源代码中可以看出开机时按住HOME键或音量上键不同手机对此会有修改会进入Recovery模式按着返回键或音量下键会进入fastboot模式。
如果没有组合键代码中成为magic
key按下则会检测SMEM中reboot_mode变量的值代码如下
reboot_mode可取的值为RECOVERY_MODE和FASTBOOT_MODE的宏定义分别为
reboot_mode的取值由check_reboot_mode()函数返回接下来我们看该函数在\bootable\bootloader\lk\target\msm8660_surf\init.c中的定义
可以看到该函数先读取地址restart_reason_addr处的值最后返回該值restart_reason_addr地址定义为0x2A05F65C从此处读完值后会向改地址写入0x00即将其内容擦除这样做是为了防止下次进入时又进入Recovery模式。
如果restart_reason_addr处没有值被读到则会继续读取MISC分区的BCB段进行判断调用的函数为recovery_init()。
这里解释一下BCBBootloaderControl
update-hboot更新后bootloader会将执行结果写入到这个字段。
该文件存储的是一个字符串必须以“recovery\n”开头否则这个字段的所有内容域会被忽略。
“recovery\n”之后的部分是/cache/recovery/command文件支持的命令。
可以将其理解为
BCB,然后读取/cache/recovery/command然后将二者重新写回
解释完BCB字段的内容我们再回过头来调用recovery_init()的代码如下
该函数的定义在bootloader\lk\app\aboot\recovery.c中。
recovery_init()函数会先通过get_recovery_message(msg)函数把BCB段的内容读取到recovery_message结构体中再读取其command字段如果字段是boot-recovery则进入recovery模式如果是update-radio则进入固件升级流程。
get_recovery_message(msg)函数的代码如下。
系统判断进入哪种工作模式的流程如下图所示。
如果以上条件皆不满足则进入正常启动序列系统会加载
4、初始化程序init和初始化配置文件init.rc都不一样。
这就是系统没有进入图形界面而进入了类似文本界面并可以通过简单的组合键进行恢复的原因。
与正常启动系统类似也是启动内核然后启动文件系统。
在进入文件系统后会执行/initinit
/init.rc。
这个配置文件位于bootable/recovery/etc/init.rc。
查看这个文件我们可以看到它做的事情很简单
的工作需要整个软件平台的配合从通信架构上来看主要有以下三个部分
Android的正常工作模式。
更新时在这种模式中我们的上层操作就是使用
boot.img相同包含了标准的内核和根文件系统。
进入该模式后主要是运行
这三个实体之间的通信是必不可少的他们相互之间有如下两个通信接口一个是通过
分区中的三个文件command、log、intent另一个是前面提到的MISC分区的BCB段。
本文主要关心OTA升级的流程所以下面的内容主要解释从上层应用点击进行OTA升级到重启进入Recovery模式进行升级包安装的过程。
我们只看从MainSystem如何进入Recovery模式其他的通信暂不讨论。
先从Main
System使用update.zip包进行升级时系统会重启并进入Recovery模式。
在系统重启之前我们可以看到Main
System一定会向BCB中的command域写入boot-recovery(粉红色线)用来告知Bootloader重启后进入recovery模式。
这一步是必须的。
至于Main
System是否向recovery域写入值我们在源码中不能肯定这一点。
即便如此重启进入Recovery模式后Bootloader会从/cache/recovery/command中读取值并放入到BCB的recovery域。
而MainSystem在重启之前肯定会向/cache/recovery/command中写入Recovery将要进行的操作命令。
假设我们进入系统更新应用后已下载完OTA包到SD卡会弹出一个对话框提示已有update.zip包是否现在更新我们从这个地方跟踪。
这个对话框的源码是SystemUpdateInstallDialog.java。
在mNowButton按钮的监听事件里会调用mService.rebootAndUpdatenew
File(mFile)。
这个mService就是SystemUpdateService的实例。
这个类所在的源码文件是SystemUpdateService.java。
这个函数的参数是一个文件。
它肯定就是我们的update.zip包了。
我们可以证实一下这个猜想。
②mFile的值在SystemUpdateInstallDialog.java中的ServiceConnection中我们可以看到这个mFile的值有两个来源。
mFile的一个来源是这个是否立即更新提示框接受的上一个Activity以“file”标记传来的值。
这个Activity就是SystemUpdate.java。
它是一个PreferenceActivity类型的。
在其onPreferenceChange函数中定义了向下一个Activity传送的值这个值是根据我们不同的选择而定的。
如果我们在之前选择了从SD卡安装则这个传下去的“file”值为“/sdcard/update.zip”。
如果选择了从NAND安装则对应的值为“/nand/update.zip”。
另个一来源是从mService.getInstallFile()获得。
我们进一步跟踪就可发现上面这个函数获得的值就是“/cache”mUpdateFileURL.getFile();这就是OTA在线下载后对应的文件路径。
不论参数mFile的来源如何我们可以发现在mNowButton按钮的监听事件里是将整个文件也就是我们的update.zip包作为参数往rebootAndUpdate中传递的。
③rebootAndUpdate:在这个函数中MainSystem做了重启前的准备。
继续跟踪下去会发现在SystemUpdateService.java中的rebootAndUpdate函数中新建了一个线程在这个线程中最后调用的就是RecoverySystem.installPackage(mContext,mFile)我们的update.zip包也被传递进来了。
④RecoverySystem类RecoverySystem类的源码所在文件路径为
*****/frameworks/base/core/java/android/os/RecoverySystem.java。
我们关心的是installPackage(Contextcontext,FilepackageFile)函数。
这个函数首先根据我们传过来的包文件获取这个包文件的绝对路径filename。
然后将其拼成arg“--update_package”filename。
它最终会被写入到BCB中。
这个就是重启进入Recovery模式后Recovery服务要进行的操作。
它被传递到函数bootCommand(context,arg)。
⑤bootCommand()在这个函数中才是MainSystem在重启前真正做的准备。
主要做了以下事情首先创建/cache/recovery/下的command和log可能不存在文件在sqlite数据库中的备份。
然后将上面④步中的arg命令写入到/cache/recovery/command文件中。
下一步就是真正重启了。
接下来看一下在重启函数reboot中所做的事情。
⑥pm.reboot()重启之前先获得了PowerManager电源管理并进一步获得其系统服务。
然后调用了pm.reboot(“recovery”)函数。
该函数最后找到是E:\MTK6592(Original)\alps\system\core\libcutils\android_reboot.c中的reboot函数。
这个函数实际上是一个系统调用即__reboot(LINUX_REBOOT_MAGIC1,LINUX_REBOOT_MAGIC2,LINUX_REBOOT_CMD_RESTART2,
arg);从这个函数我们可以看出前两个参数就代表了我们的组合键LINUX_REBOOT_CMD_RESTART2就是我们传过来的“recovery”。
再进一步跟踪就到了汇编代码了我们无法直接查看它的具体实现细节。
但可以肯定的是这个函数只将“recovery”参数传递过去了之后将“boot-recovery”写入到了MISC分区的BCB数据块的command域中。
这样在重启之后Bootloader才知道要进入Recovery模式。
在这里我们无法肯定MainSystem在重启之前对BCB的recovery域是否进行了操作。
其实在重启前是否更新BCB的recovery域是不重要的因为进入Recovery服务后Recovery会自动去/cache/recovery/command中读取要进行的操作然后写入到BCB的recovery域中。
至此MainSystem就开始重启并进入Recovery模式。
在这之前Main
System做的最实质的就是两件事一是将“boot-recovery”写入BCB的command域二是将--update_package/cache/update.zip”或则“--update_package/sdcard/update.zip”写入/cache/recovery/command文件中。
下面的部分就开始重启并进入Recovery服务了。
这个过程我们在上文对照第一个图已经讲过了。
从Bootloader开始如果没有组合键按下就从MISC分区读取BCB块的command域在主系统时已经将“boot-recovery”写入。
然后就以Recovery模式开始启动。
与正常启动不同的是Recovery模式下加载的镜像是recovery.img。
这个镜像同boot.img类似也包含了标准的内核和根文件系统。
其后就与正常的启动系统类似也是启动内核然后启动文件系统。
在进入文件系统后会执行/initinit的配置文件就是/init.rc。
这个配置文件来自bootable/recovery/etc/init.rc。
查看这个文件我们可以看到它做的事情很简单
等等。
这里最重要的就是当然就recovery服务了。
在Recovery服务中将要完成我们的升级工作。
从/bootable/recovery/recovery.c的代码注释中我们可以看到Recovery的服务内容主要有三类
SYSTEMENABLE/DISABLE使能/关闭加密文件系统。
具体的每一类服务的大概工作流程注释中都有下文中会详细说下OTA
INSTALL的工作流程。
这三类服务的大概的流程都是通用的只是不同操作体现与不同的操作细节。
下面我们看Recovery服务的通用流程。
INSTALL的流程为例具体分析相关函数的调用过程如下图所示。
我们顺着流程图分析从recovery.c的main函数开始
ui_init()Recovery服务使用了一个基于framebuffer的简单uiminiui系统。
这个函数对其进行了简单的初始化。
在Recovery服务的过程中主要用于显示一个背景图片正在安装或安装失败和一个进度条用于显示进度。
另外还启动了两个线程一个用于处理进度条的显示progress_thread另一个用于响应用户的按键input_thread。
get_arg()这个函数主要做了上图中get_arg()往右往下直到parsearg/v的工作。
我们对照着流程一个一个看。
①get_bootloader_message()主要工作是根据分区的文件格式类型mtd或emmc从MISC分区中读取BCB数据块到一个临时的变量中。
②然后开始判断Recovery服务是否有带命令行的参数/sbin/recovery根据现有的逻辑是没有的若没有就从BCB中读取recovery域。
如果读取失败则从/cache/recovery/command中读取然后。
这样这个BCB的临时变量中的recovery域就被更新了。
在将这个BCB的临时变量写回真实的BCB之前又更新的这个BCB临时变量的command域为“boot-recovery”。
这样做的目的是如果在升级失败比如升级还未结束就断电了时系统在重启之后还会进入Recovery模式直到升级完成。
③在这个BCB临时变量的各个域都更新完成后使用set_bootloader_message()写回到真正的BCB块中。
parserargc/argv解析我们获得参数。
注册所解析的命令register_update_command在下面的操作中会根据这一步解析的值进行一步步的判断然后进行相应的操作。
if(update_package)判断update_package是否有值若有就表示需要升级更新包此时就会调用install_package()即图中红色的第二个阶段。
在这一步中将要完成安装实际的升级包。
这是最为复杂也是升级update.zip包最为核心的部分。
我们在下一节详细分析这一过程。
为从宏观上理解Recovery服务的框架我们将这一步先略过假设已经安装完成了。
我们接着往下走看安装完成后Recovery怎样一步步结束服务并重启到新的主系统的。
if(wipe_data/wipe_cache)这一步判断实际是两步在源码中是先判断是否擦除data分区用户数据部分的然后再判断是否擦除cache分区。
值得注意的是在擦除data分区的时候必须连带擦除cache分区。
在只擦除cache分区的情形下可以不擦除data分区。
maybe_install_firmware_update()如果升级包中包含/radio/hbootfirmware的更新则会调用这个函数。
查看源码发现在注释中OTA
INSTALL有这一个流程。
但是main函数中并没有显示调用这个函数。
目前尚未发现到底是在什么地方处理。
但是其流程还是向上面的图示一样。
即①
先向BCB中写入“boot-recovery”和“—wipe_cache”之后将cache分区格式化然后将firmwareimage
写入原始的cache分区中。
②将命令“update-radio/hboot”和“—wipe_cache”写入BCB中然后开始重新安装firmware并刷新firmware。
③之后又会进入图示中的末尾即finish_recovery()。
prompt_and_wait()这个函数是在一个判断中被调用的。
其意义是如果安装失败update.zip包错误或验证签名失败则等待用户的输入处理如通过组合键reboot等。
finish_recovery()这是Recovery关闭并进入MainSystem的必经之路。
其大体流程如下
将intent字符串的内容作为参数传进finish_recovery中。
如果有intent需要告知Main
System则将其写入/cache/recovery/intent中。
这个intent的作用尚不知有何用。
将内存文件系统中的Recovery服务的日志/tmp/recovery.log拷贝到cache/cache/recovery/log分区中以便告知重启后的Main
擦除MISC分区中的BCB数据块的内容以便系统重启后不在进入Recovery模式而是进入更新后的主系统。
删除/cache/recovery/command文件。
这一步也是很重要的因为重启后Bootloader会自动检索这个文件如果未删除的话又会进入Recovery模式。
原理在上面已经讲的很清楚了。
reboot()这是一个系统调用。
在这一步Recovery完成其服务重启并进入Main
System。
这次重启和在主系统中重启进入Recovery模式调用的函数是一样的但是其方向是不一样的。
所以参数也就不一样。
查看源码发现其重启模式是RB_AUTOBOOT。
这是一个系统的宏。
至此我们对Recovery服务的整个流程框架已有了大概的认识。
下面就是升级update.zip包时特有的也是Recovery服务中关于安装升级包最核心的第二个阶段。
即我们图例中的红色2的那个分支。
安装升级包所调用的函数为install_package()源码位于/bootable/recovery/install.cpp。
该函数调用really_install_package(path,wipe_cache)该函数的流程为
①ensure_path_mount()先判断所传的update.zip包路径所在的分区是否已经挂载。
如果没有则先挂载。
②load_keys()加载公钥源文件路径位于/res/keys。
这个文件在Recovery镜像的根文件系统中。
③verify_file()对升级包update.zip包进行签名验证。
④mzOpenZipArchive()打开升级包并将相关的信息拷贝到一个临时的ZipArchinve变量中。
这一步并未对我们的update.zip包解压。
⑤try_update_binary()在这个函数中才是对我们的update.zip升级的地方。
这个函数一开始先根据我们上一步获得的zip包信息以及升级包的绝对路径将update_binary文件拷贝到内存文件系统的/tmp/update_binary中。
以便后面使用。
⑦fork()创建子进程。
其中的子进程主要负责执行binaryexecv(binary,args)即执行我们的安装命令脚本父进程负责接受子进程发送的命令去更新ui显示显示当前的进度。
子父进程间通信依靠管道。
⑧其中在创建子进程后父进程有两个作用。
一是通过管道接受子进程发送的命令来更新UI显示。
二是等待子进程退出并返回INSTALLSUCCESS。
其中子进程在解析执行安装脚本的同时所发送的命令有以下几种
firmware”hboot”|”radio”filename升级firmware时使用在API
execv(binary,args)的作用就是调用META-INF/com/google/android路径下的可执行文件update-binary这个程序的实质就是去解析update.zip包中的updater-script脚本中的命令并执行。
由此Recovery服务就进入了实际安装update.zip包的过程。
上述的子进程所执行的程序binary实际上就是update.zip包中的update-binary。
实际上Recovery服务在做这一部分工作的时候是先将包中update-binary拷贝到内存文件系统中的/tmp/update_binary然后再执行的。
升级包中update-binary的在升级包制作那一小节中已有说明。
通过install.c源码来分析下update-binary程序的执行过程
②获取管道并打开在执行此程序的过程中向该管道写入命令用于通知其父进程根据命令去更新UI显示。
③读取updater-script脚本从update.zip包中将updater-script脚本读到一块动态内存中供后面执行。
functions注册脚本中的语句处理函数即识别脚本中命令的函数。
主要有以下几类
RegisterBuiltins()注册程序中控制流程的语句如ifelse、assert、abort、stdout等。
RegisterInstallFunctions()实际安装过程中安装所需的功能函数比如mount、format、set_progress、set_perm等等。
RegisterDeviceExtensions()与设备相关的额外添加項在源码中并没有任何实现。
⑤Parse***script调用yy*库函数解析脚本并将解析后的内容存放到一个Expr类型的python类中。
主要函数是yy_scan_string()和yyparse()。
⑥执行脚本核心函数是Evaluate它会调用其他的callback函数而这些callback函数又会去调用Evaluate去解析不同的脚本片段从而实现一个简单的脚本解释器。
⑦错误信息提示最后就是根据Evaluate执行后的返回值给出一些打印信息。
这一执行过程非常简单最主要的函数就是Evaluate。
它负责最终执行解析的脚本命令。
而安装过程中的命令就是updater-script。
src-dir表示原文件夹dst-dir表示目的文件夹[times**p]表示时间戳
用将src-dir文件夹中的内容复制到dst-dir文件夹中。
dst-dir文件夹中的原始内容
将会保存不变除非src-dir文件夹中有相同的内容这样dst-dir中的内容将被覆盖
SYSTEM:app/Calculator.apk删除手机systen文件夹中app中的Calculator.apk文件
file-or-dirN]表示要删除的文件或文件夹可以使多个中间用空格隔开
DATA:dalvik-cache删除/data/dalvik-cache文件夹下的所有内容
PACKAGE:install_busybox.sh执行升级包中的install_busybox.sh脚本
uid表示用户名称gid表示用户组名称mode表示权限模式path[...
作用设置单个文件或目录的所有者和权限像linux中的chmod、chown或chgrp命令一样只是集中在了一个命令当中
SYSTEM:etc/init.goldfish.sh设置手机system中的etc/init.goldfish.sh的用户为root用户组为shell所有者以及所属用户组成员可以进行读取和执行操作其他用户无操作权限
表示用户gid表示用户组dir-mode表示文件夹的权限file-mode表示文件的权限path
SYSTEM:app设置手机system/app文件夹及其中文件的用户为root用户组为rootapp文件夹权限为所有者可以进行读、写、执行操作其他用户可以进行读取和执行操作其中的文件的权限为所有者可以进行读写操作其他用户可以进行读取操作
作用为下面进行的程序操作显示进度条进度条会根据duration进行前进当操作时间是确定的时候会更快
link-target表示链接到的目标link-path表示快捷方式的路径
用相当于linux中的ln命令将link-target在link-path处创建一个软链
接link-target的格式应为绝对路径或许相对路径也可以link-path为“根目录路径”的形式
SYSTEM:xbin/su在手机中system中的xbin中建立一个/system/bin/su的快捷方式
作用此命令用来判断表达式boolexpr的正确与否当表达式错误时程序终止执行※此作用有待验证
11.package_extract_file/dir语法package_extract_file(file/dir,file/dir)
package_extract_file(system/bin/modelid_cfg.sh,/tmp/modelid_cfg.sh);
举例write_radio_imagePACKAGE:radio.img
作用将系统bootloader镜像写入手机src-image表示镜像位置此命令在直到在所有的程序安装结束之后才会起作用
举例write_hboot_imagePACKAGE:hboot.img
14.write_raw_image语法write_raw_imagesrc-image
参数详解:srcfile-------------------字符串要打补丁的源文件(要读入的文件)
Tgtfile-------------------字符串补丁文件要写入的目标文件
tgtsha1-----------------字符串写入补丁文件的目标文件的sha1哈希值
sha1_x------------------字符串要写入目标文件的补丁数据的sha1哈希值patch1_x----------------字符串实际上应用到目标文件的补丁
参数详解:file----------------------字符串要检查的文件
sha1_x------------------要检查的哈希值
检查文件是否已经被打补丁或者能不能被打补丁。
需要检查“applypatch_check
参数详解:bytes-------------------检查的字节的数字
filename----------------字符串要读取内容的文件名
参数详解:data------要计算sha1哈希值的文件的内容-必须是只读文件格式
sha1_hexN------文件数据要匹配的特定的十六进制sha1_hex哈希值字符串
如果只指定data参数这个函数返回data参数的十六进制sha1_hex哈希值字符串。
其他参数用来确认你检查的文件是不是列表中的哈希值的一个它返回匹配的哈希值或者在没有匹配任何哈希值时返回空。
META-INF/com/google/android/updater-script
recovery/etc/install-recovery.sh
另一个很重要的文件是/etc/recovery.fstab内容由EMMC分区方案确定。
--------------------------------------------
otgpackage编译脚本会根据这个文件填充updater-script后面可以看到。
这个文件存在于recovery分区中进入recovery模式后可以访问到它。
进入recovery模式的方式多种多样但每种方式都需要bootloader的配合。
进入recovery模式后会对升级包进行验证过程不表失败退出。
进入recovery流程后主要关心updater-script的工作。
首先是updater-script代码中可以很容易分析出他的工作流程如下
//挂载system分区。
这里有/dev/block/mmcblk0p5和/system的对应关系来源于前文提到的recovery.fstab。
package_extract_dir(recovery,/system);//将zip包中的recovery将来升级recovery分区时使用install-recovery.shrecovery-from-boot.p
package_extract_dir(system,/system);
symlink(toolbox,/system/bin/cat,
retouch_binaries(/system/lib/libbluedroid.so,.....);
package_extract_file(boot.img,/dev/block/mmcblk0p1);
//将boot.img解压到相应block设备完成boot分区的升级。
boot分区包含了kernel
---------------------------------------------
system分区和boot升级完成接下来重启进入正常系统。
正常启动的系统init.rc中定义了一个用于烧写recovery分区的服务也就是执行install-recovery.sh每次启动都要执行一次。
/system/etc/install-recovery.sh
install-recovery.sh是recovery模式中updater-script解压出来的内容如下
-------/system/etc/install-recovery.sh
EMMC:/dev/block/mmcblk0p2:4642816:c125924fef5a1351c9041ac9e1d6fd1f9738ff77;***n
applypatchEMMC:/dev/block/mmcblk0p1:3870720:aee24fadd281e9e2bd4883ee9962a86fc345dcabEMMC:/dev/block/mmcblk0p2
c125924fef5a1351c9041ac9e1d6fd1f9738ff77
4642816aee24fadd281e9e2bd4883ee9962a86fc345dcab:/system/recovery-from-boot.p
-------------------------------------------
otapackage命令时编译脚本(ota_from_target_files)比较boot.img和recovery.img得出patch文件recovery-from-boot.p。
recovery-from-boot.p也是在recovery模式中updater-script解压到systemboot分区(kernel
以上是标准的Android升级流程我们自己添加的分区可以参考以上几种方式实现。
自定义的分区采用何种升级方式需要细细考量关系到升级包的内容结构和签名过程。
-生成target包主要是由build/core/Makfile中定义的目标规则生成是生成ota升级包的中间状态包
主要是先生成target包然后把target包转换为ota包把target包转换为ota包主要实现是在build/tools/releasetools/ota_from_target_files完成。
./build/tools/releasetools/ota_from_target_files
build/target/product/security/testkey
out/host/linux-x86/framework/signapk.jar
build/target/product/security/testkey.x509.pem
build/target/product/security/testkey.pk8
./build/tools/releasetools/ota_from_target_files
后生成的升级包ota.zip后还需对其进行加密签名操作具体流程解压ota.zip-
本文档参考了CSDN上和参考文献中关于Recovery模式及OTA升级的博客和文档按照自己的理解思路重新梳理可能会有很多理解的偏差欢迎大家批评指正。
基本的思路就是从OTA包的制作到下载后点击升级如何进入Recovery模式以及在Recovery模式下是怎样实现OTA包的安装升级的。
升级测试时下载成功了升级包在点击立即更新后手机一直处于提示“正在更新中”没能重启进行升级。
应用不具备系统权限。
导致其无法在目录/cache/recovery
问题分析通过分析日志升级失败系在对系统文件进行校验时无法通过校验。
跟踪编译流程发现生成的版本文件和用于生成
升级包的目录文件不一致。
根本原因是在生成版本文件后的编译目标文件的过程中许多模块重新进行了编译。
从而导致版本文件和目标文件中存在有着差异的文件。
从而导致升级因校验失败而无法正常升级。
解决方案针对这种情况在编译完目标文件后重新打包生成版本文件就可以解决两者不一致的问题。
原生签名。
集成流和发布流版本使用公司签名故差分包也必须使用公司签名。
sd卡时有时会出现导入失败原因是从命令提示符中看到已经导入成功实际上差分包的部分数据还在缓存中没有完全导入
SD卡的数据不完整而校验失败解决方法将升级包(update.zip包)导入
校验失败。
验证方法将差分包解压此时会提示解压失败正常的差分包应该是能正常解压的。
问题分析由于升级过程中需要校验ro.product.device若新版本中修改了该属性值则使用前向版本升级时由于
解决方案将assert(getprop(ro.product.device)的脚本语句屏蔽。
file_getprop(/system/build.prop,
问题分析由于差分包是基于前后两个版本进行差分后升级若使用的源版本不对应便会导致差分包不匹配而升级失败。
在开发板上可使用getprop
进入系统设置查看手机版本是否与差分包的ro.build.fingerprint
权限对手机中的文件进行了修改而升级中刚好会升级这些文件便会出现升级被改动文件失败的情况。
目录所有文件和用于制作差分包的源版本包中的文件进行比对找出该文件为何被修改的原因。
如果是版本集成问题需要重新编译版本。
脚本中的以下语句assert(apply_patch_space(number))通过计算
分区容量number则是原因版本中某个被修改的文件很大该大文件一般是版本中的
由于版本中若修改了内核的起始地址将会导致制作出来的差分包在校验内核时
解决方案对于第一种情况则对内核不能使用差分的形式而要使用整体的形式进行升级即将对内核的
文件系统package_extract_file(boot.img,/dev/block/mmcblk0p8)或
文件系统assert(package_extract_file(boot.img,/tmp/boot.img),write_raw_image(/tmp/boot.img,boot),delete(/tmp/boot.img));
问题分析由于差分包升级过程中是需要校验的恢复到一半的时候断电会导致差分包与源文件对不上号而导致升级失败。
解决方案升级中提示用户不能拔电池或者使用整包升级而不是差分升级包
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback