dotamax手机dota2客户端卡数据显示我和队友a共同比赛50场,而队友a则显示我和他打了100场,这是

安装后,您可能在通知栏看到不明来源的广告信息,影响您的使用体验。
Max+ 历史版本
版本:3.7.7
版本:3.7.6
版本:3.7.4
版本:3.7.1
版本:3.7.0
Max+ 使用技巧
Max+ 应用内最新内容
清枫(北京)科技有限公司
26 万人安装
12 万人安装
697 万人安装
11 万人安装
16 万人安装
61 万人安装
48 万人安装
8.7 万人安装
22 万人安装
156 万人安装
5.8 亿人安装
6.1 亿人安装
2.5 亿人安装
5.9 亿人安装
9316 万人安装
4.3 亿人安装
5.4 亿人安装
8711 万人安装
2.3 亿人安装
2.3 亿人安装
我们的产品
豌豆实验室
帮助与支持
24小时客服电话
除非特别注明,应用程序由第三方市场提供,豌豆荚仅根据你的指令提供搜索结果链接,与出处无关,详情请参阅
扫一扫安装豌豆荚安卓客户端> 雨焰的博客详情
& & & &OTA 升级是 Android 系统提供的标准软件升级方式。 它功能强大,提供了完全升级、增量升级模式,可以通过 SD 卡升级,也可以通过网络升级。
& & & &这里,我们先研究最简单的情况,通过 SD 卡进行完全升级。
& & & &如何执行升级就不多说了,网上有很多资料。(比如,介绍HTC手机如何升级)。我们感兴趣的是它是如何实现的,作为开发者,如何修改它以符合我们的定制化需求。
& & & &首先,我们研究一下 ota 升级包的编译过程。
Quick start
& & & &首先编译出android, 然后执行:
make otapackage
& & 即可获得:out/target/product/{product_name}/ {product_name}-ota-eng.{uid}.zip
& & 将该文件改名为update.zip放到T卡根目录, 即可开始recovery模式下的 OTA 升级。
编译过程研究
主要分两步,第一步, 会准备一个包,其中包含升级需要的内容(原材料),比如,system 目录。
第二步,运行python 脚本
./build/tools/releasetools/ota_from_target_files,以步骤一准备的ZIP包作为输入,最终生成需要的升级包。
编译脚本如下:
(From: build/core/Makefile)
1073 # Depending on the various images guarantees that the underlying &
1074 # directories are up-to-date. &
1075 $(BUILT_TARGET_FILES_PACKAGE): / &
1076 & & & & & & & & $(INSTALLED_BOOTIMAGE_TARGET) / &
1077 & & & & & & & & $(INSTALLED_RADIOIMAGE_TARGET) / &
1078 & & & & & & & & $(INSTALLED_RECOVERYIMAGE_TARGET) / &
1079 & & & & & & & & $(INSTALLED_FACTORYIMAGE_TARGET) / &
1080 & & & & & & & & $(INSTALLED_SYSTEMIMAGE) / &
1081 & & & & & & & & $(INSTALLED_USERDATAIMAGE_TARGET) / &
1082 & & & & & & & & $(INSTALLED_SECROIMAGE_TARGET) / &
1083 & & & & & & & & $(INSTALLED_ANDROID_INFO_TXT_TARGET) / &
1084 & & & & & & & & $(built_ota_tools) / &
1085 & & & & & & & & $(APKCERTS_FILE) / &
1086 & & & & & & & & $(HOST_OUT_EXECUTABLES)/fs_config / &
1087 & & & & & & & & | $(ACP) &
1088 & & & &
"Package target files: $@" &
1089 & & & & $(hide) rm -rf $@ $(zip_root) &
1090 & & & & $(hide) mkdir -p $(dir $@) $(zip_root) &
1091 & & & & @# Components of the recovery image &
1092 & & & & $(hide) mkdir -p $(zip_root)/RECOVERY &
1093 & & & & $(hide) $(call package_files-copy-root, / &
1094 & & & & & & & & $(TARGET_RECOVERY_ROOT_OUT),$(zip_root)/RECOVERY/RAMDISK) &
1095 ifdef INSTALLED_KERNEL_TARGET &
1096 & & & & $(hide) $(ACP) $(INSTALLED_KERNEL_TARGET) $(zip_root)/RECOVERY/kernel &
1097 & & & & $(hide) $(ACP) $(recovery_ramdisk) $(zip_root)/RECOVERY/ramdisk &
1098 endif &
1099 ifdef INSTALLED_2NDBOOTLOADER_TARGET &
1100 & & & & $(hide) $(ACP) / &
1101 & & & & & & & & $(INSTALLED_2NDBOOTLOADER_TARGET) $(zip_root)/RECOVERY/second &
1102 endif &
1103 ifdef BOARD_KERNEL_CMDLINE &
1104 & & & & $(hide) echo "$(BOARD_KERNEL_CMDLINE)" & $(zip_root)/RECOVERY/cmdline &
1105 endif &
1106 ifdef BOARD_KERNEL_BASE &
1107 & & & & $(hide) echo "$(BOARD_KERNEL_BASE)" & $(zip_root)/RECOVERY/base &
1108 endif &
1109 & & & & @# Components of the factory image &
1110 & & & & $(hide) mkdir -p $(zip_root)/FACTORY &
1111 & & & & $(hide) $(call package_files-copy-root, / &
1112 & & & & & & & & $(TARGET_FACTORY_ROOT_OUT),$(zip_root)/FACTORY/RAMDISK) &
1113 ifdef INSTALLED_KERNEL_TARGET &
1114 & & & & $(hide) $(ACP) $(INSTALLED_KERNEL_TARGET) $(zip_root)/FACTORY/kernel &
1115 endif &
1116 ifdef INSTALLED_2NDBOOTLOADER_TARGET &
1117 & & & & $(hide) $(ACP) / &
1118 & & & & & & & & $(INSTALLED_2NDBOOTLOADER_TARGET) $(zip_root)/FACTORY/second &
1119 endif &
1120 ifdef BOARD_KERNEL_CMDLINE &
1121 & & & & $(hide) echo "$(BOARD_KERNEL_CMDLINE)" & $(zip_root)/FACTORY/cmdline &
1122 endif &
1123 ifdef BOARD_KERNEL_BASE &
1124 & & & & $(hide) echo "$(BOARD_KERNEL_BASE)" & $(zip_root)/FACTORY/base &
1125 endif &
1126 & & & & @# Components of the boot image &
1127 & & & & $(hide) mkdir -p $(zip_root)/BOOT &
1128 & & & & $(hide) $(call package_files-copy-root, / &
1129 & & & & & & & & $(TARGET_ROOT_OUT),$(zip_root)/BOOT/RAMDISK) &
1130 ifdef INSTALLED_KERNEL_TARGET &
1131 & & & & $(hide) $(ACP) $(INSTALLED_KERNEL_TARGET) $(zip_root)/BOOT/kernel &
1132 & & & & $(hide) $(ACP) $(INSTALLED_RAMDISK_TARGET) $(zip_root)/BOOT/ramdisk &
1133 endif &
1134 ifdef INSTALLED_2NDBOOTLOADER_TARGET &
1135 & & & & $(hide) $(ACP) / &
1136 & & & & & & & & $(INSTALLED_2NDBOOTLOADER_TARGET) $(zip_root)/BOOT/second &
1137 endif &
1138 ifdef BOARD_KERNEL_CMDLINE &
1139 & & & & $(hide) echo "$(BOARD_KERNEL_CMDLINE)" & $(zip_root)/BOOT/cmdline &
1140 endif &
1141 ifdef BOARD_KERNEL_BASE &
1142 & & & & $(hide) echo "$(BOARD_KERNEL_BASE)" & $(zip_root)/BOOT/base &
1143 endif &
1144 & & & & $(hide) $(foreach t,$(INSTALLED_RADIOIMAGE_TARGET),/ &
1145 & & & & & & & & & & mkdir -p $(zip_root)/RADIO; / &
1146 & & & & & & & & & & $(ACP) $(t) $(zip_root)/RADIO/$(notdir $(t));) &
1147 & & & & @# Contents of the system image &
1148 & & & & $(hide) $(call package_files-copy-root, / &
1149 & & & & & & & & $(SYSTEMIMAGE_SOURCE_DIR),$(zip_root)/SYSTEM) &
1150 & & & & @# Contents of the data image &
1151 & & & & $(hide) $(call package_files-copy-root, / &
1152 & & & & & & & & $(TARGET_OUT_DATA),$(zip_root)/DATA) &
1153 & & & & @# Extra contents of the OTA package &
1154 & & & & $(hide) mkdir -p $(zip_root)/OTA/bin &
1155 & & & & $(hide) $(ACP) $(INSTALLED_ANDROID_INFO_TXT_TARGET) $(zip_root)/OTA/ &
1156 & & & & $(hide) $(ACP) $(PRIVATE_OTA_TOOLS) $(zip_root)/OTA/bin/ &
1157 & & & & @# Files that do not end up in any images, but are necessary to &
1158 & & & & @# build them. &
1159 & & & & $(hide) mkdir -p $(zip_root)/META &
1160 & & & & $(hide) $(ACP) $(APKCERTS_FILE) $(zip_root)/META/apkcerts.txt &
1161 & & & & $(hide) echo "$(PRODUCT_OTA_PUBLIC_KEYS)" & $(zip_root)/META/otakeys.txt &
1162 & & & & $(hide) echo "$(PRIVATE_RECOVERY_API_VERSION)" & $(zip_root)/META/recovery-api-version.txt &
1163 & & & & $(hide) echo "blocksize $(BOARD_FLASH_BLOCK_SIZE)" & $(zip_root)/META/imagesizes.txt &
1164 & & & & $(hide) echo "boot $(call image-size-from-data-size,$(BOARD_BOOTIMAGE_PARTITION_SIZE))" && $(zip_root)/META/imagesizes.txt &
1165 & & & & $(hide) echo "recovery $(call image-size-from-data-size,$(BOARD_RECOVERYIMAGE_PARTITION_SIZE))" && $(zip_root)/META/imagesizes.txt &
1166 & & & & $(hide) echo "system $(call image-size-from-data-size,$(BOARD_SYSTEMIMAGE_PARTITION_SIZE))" && $(zip_root)/META/imagesizes.txt &
1167 & & & & $(hide) echo "secro $(call image-size-from-data-size,$(BOARD_SECROIMAGE_PARTITION_SIZE))" && $(zip_root)/META/imagesizes.txt &
1168 & & & & $(hide) echo "userdata $(call image-size-from-data-size,$(BOARD_USERDATAIMAGE_PARTITION_SIZE))" && $(zip_root)/META/imagesizes.txt &
1169 & & & & $(hide) echo "$(tool_extensions)" & $(zip_root)/META/tool-extensions.txt &
1170 & & & & @# Zip everything up, preserving symlinks &
1171 & & & & $(hide) (cd $(zip_root) && zip -qry ../$(notdir $@) .) &
1172 & & & & @# Run fs_config on all the system files in the zip, and save the output &
1173 & & & & $(hide) zipinfo -1 $@ | awk -F/ 'BEGIN { OFS="/" } /^SYSTEM/// {$$1 = "system"; print}' | $(HOST_OUT_EXECUTABLES)/fs_config & $(zip_root)/META/filesystem_config.txt &
1174 & & & & $(hide) (cd $(zip_root) && zip -q ../$(notdir $@) META/filesystem_config.txt) &
可见往里面添加了很多内容。
L , 造一个目录。
L,填充 RECOVERY 子目录的内容。用于生成recovery.img。包括:kernel 的image, recovery 根文件系统的 image, recovery 根文件系统的内容:RECOVERY$ tree -L 2├── kernel├── ramdisk└── RAMDISK & &├── advanced_meta_init.rc & &├── data & &├── default.prop & &├── dev & &├── etc & &├── init & &├── init.factory.rc & &├── init.goldfish.rc & &├── init.mt6516.rc & &├── init.rc & &├── meta_init.rc & &├── proc & &├── res & &├── sbin & &├── sys & &├── system & &└── tmpL, 填充 FACTORY 子目录的内容, 没有用到,包括:kernel 的imageL, 填充 BOOT子目录的内容,用于生成boot.img。和 RECOVERY目录类似,包括:kernel 的image,根文件系统的 image,根文件系统的内容:BOOT$ tree -L 2.├── kernel├── ramdisk└── RAMDISK & &├── advanced_meta_init.rc & &├── data & &├── default.prop & &├── dev & &├── init & &├── init.factory.rc & &├── init.goldfish.rc & &├── init.mt6516.rc & &├── init.rc & &├── meta_init.rc & &├── proc & &├── res -& /system/res & &├── sbin & &├── sys & &└── system L, 填充 RADIO子目录的内容, 没有用到。L, 填充 SYSTEM子目录的内容。 这是升级的主要内容。L, 填充 DATA子目录的内容。缺省没有用到。L, 填充 OTA/bin子目录的内容,这是OTA升级自己使用的程序。后面会遇到。OTA/bin$ tree.├── applypatch├── applypatch_static├── check_prereq└── updaterL, 填充 META子目录的内容,这里包含了OTA脚本需要的一些附加信息。L,将所有内容打包。供下一阶段使用。L,生成 META/filesystem_config.txt 并将其加入到 zip 包中。该文件保存了 system 目录下各目录、文件的权限及 owner.$ head META/filesystem_config.txtsystem 0 0 755system/usr 0 0 755system/usr/srec 0 0 755system/usr/srec/config 0 0 755system/usr/srec/config/en.us 0 0 755system/usr/srec/config/en.us/grammars 0 0 755system/usr/srec/config/en.us/grammars/phone_type_choice.g2g 0 0 644system/usr/srec/config/en.us/grammars/VoiceDialer.g2g 0 0 644system/usr/srec/config/en.us/grammars/boolean.g2g 0 0 644system/usr/srec/config/en.us/g2p 0 0 755 这里,目录由 zipinfo –l 提供, 而权限则由 fs_config 设定。此程序的源码位于:build/tools/fs_config, 其中fs_config 包含了一个头文件:54 #include "private/android_filesystem_config.h"这个文件(system/core/include/private/android_filesystem_config.h)以hardcoding 的方式设定了 system 下各目录、文件的权限、属主。比如:152 & & { 00440, AID_ROOT, & & &AID_SHELL, & & "system/etc/init.goldfish.rc" },153 & & { 00550, AID_ROOT, & & &AID_SHELL, & & "system/etc/init.goldfish.sh" },154 & & { 00440, AID_ROOT, & & &AID_SHELL, & & "system/etc/init.trout.rc" },155 & & { 00550, AID_ROOT, & & &AID_SHELL, & & "system/etc/init.ril" }, 如果需要升级其它内容,比如 bootloader, 则可以在这里加入。 &步骤二
编译脚本如下:
(From: build/core/Makefile)
1186 name := $(TARGET_PRODUCT) &
1187 ifeq ($(TARGET_BUILD_TYPE),debug) &
1188 & name := $(name)_debug &
1189 endif &
1190 name := $(name)-ota-$(FILE_NAME_TAG) &
1192 INTERNAL_OTA_PACKAGE_TARGET := $(PRODUCT_OUT)/$(name).zip &
1194 $(INTERNAL_OTA_PACKAGE_TARGET): KEY_CERT_PAIR := $(DEFAULT_KEY_CERT_PAIR) &
1196 ifeq ($(TARGET_OTA_SCRIPT_MODE),) &
1197 # default to "auto" &
1198 $(INTERNAL_OTA_PACKAGE_TARGET): scriptmode := auto &
1199 else &
1200 $(INTERNAL_OTA_PACKAGE_TARGET): scriptmode := $(TARGET_OTA_SCRIPT_MODE) &
1201 endif &
1203 $(INTERNAL_OTA_PACKAGE_TARGET): $(BUILT_TARGET_FILES_PACKAGE) $(OTATOOLS) &
1204 & & & &
"Package OTA: $@" &
1205 & & & & $(hide) ./build/tools/releasetools/ota_from_target_files / &
1206 & & & & & &-m $(scriptmode) / &
1207 & & & & & &-p $(HOST_OUT) / &
1208 & & & & & &-k $(KEY_CERT_PAIR) / &
1209 & & & & & &$(BUILT_TARGET_FILES_PACKAGE) $@ &
核心是一个python脚本: ota_from_target_files, 它以前一步骤生成的ZIP包作为输入,生成可用于OTA升级的zip包。 具体内容我们后文继续分析。
Android OTA 升级之二:脚本 ota_from_target_files
作者: 宋立新
& & & &前面介绍了ota package 的编译过程,其中最核心的部分就是一个 python 脚本:ota_from_target_files. 现在我们分析这个脚本。
先看一下帮助
不带任何参数,先看一下它的帮助:
$ ./ota_from_target_files &&
Given a target-files zipfile, produces an OTA package that installs &
that build. &An incremental OTA is produced if -i is given, otherwise &
a full OTA is produced. &
Usage: &ota_from_target_files [flags] input_target_files output_ota_package &
& -b &(--board_config) &&file& &
& & & Deprecated. &
& -k &(--package_key) &&key& &
& & & Key to use to sign the package (default is &
& & & "build/target/product/security/testkey"). &
& -i &(--incremental_from) &&file& &
& & & Generate an incremental OTA using the given target-files zip as &
& & & the starting build. &
& -w &(--wipe_user_data) &
& & & Generate an OTA package that will wipe the user data partition &
& & & when installed. &
& -n &(--no_prereq) &
& & & Omit the timestamp prereq check normally included at the top of &
& & & the build scripts (used for developer OTA packages which &
& & & legitimately need to go back and forth). &
& -e &(--extra_script) &&file& &
& & & Insert the contents of file at the end of the update script. &
& -m &(--script_mode) &&mode& &
& & & Specify 'amend' or 'edify' scripts, or 'auto' to pick &
& & & automatically (this is the default). &
& -p &(--path) &&dir& &
& & & Prepend &dir&/bin to the list of places to search for binaries &
& & & run by this script, and expect to find jars in &dir&/framework. &
& -s &(--device_specific) &file& &
& & & Path to the python module containing device-specific &
& & & releasetools code. &
& -x &(--extra) &&key=value& &
& & & Add a key/value pair to the 'extras' dict, which device-specific &
& & & extension code may look at. &
& -v &(--verbose) &
& & & Show command lines being executed. &
& -h &(--help) &
& & & Display this usage message and exit. &
简单翻译一下:
-b 过时,不再使用。
-k 签名用的密钥
-i 生成增量OTA包时用于定义对比包
-w 是否清除 userdata 分区
-n 是否在升级时不检查时间戳,缺省情况下只能基于老的版本升级。
-e 定义额外运行的脚本
-m 定义采用的脚本格式,目前有两种,amend & edify, 其中amend为较老的格式。对应的,升级时会采用不同的解释器。缺省情况下,ota_from_target_files 会同时生成两个脚本。这提供了最大灵活性。
-p 定义脚本用到的一些可执行文件的路径
-s 定义额外运行的脚本的路径
-x 定义额外运行的脚本可能用到的键/值对
-v 老朋友,冗余模式,让脚本打印出执行的命令
-h 老朋友,这个就不用说了吧。
我们调用如下命令生成我们的升级包:
./build/tools/releasetools/ota_from_target_files /
& -m auto /
& -p out/host/linux-x86 /
& -k build/target/product/security/testkey -n /
out/target/product/{product-name}/obj/PACKAGING/target_files_intermediates/{product-name}-target_files-eng.{uid}.zip {output_zip}
ota_from_target_files为python 脚本,所以如果懂 python, 会更顺利一点。
文件有1000行。分析过程中,我们只是贴代码片段。 完整文件见:
build/tools/releasetools/ota_from_target_files (from Android 2.2)
入口:main
按照python惯例,单独执行的代码执行从__main__开始:
944 if __name__ == '__main__':945 & try:946 & & main(sys.argv[1:])947 & except common.ExternalError, e:948 & & print949 & & print " & ERROR: %s" % (e,)950 & & print951 & & sys.exit(1)
&它调用 main 函数:
844 def main(argv): &
846 & def option_handler(o, a): &
847 & & if o in ("-b", "--board_config"): &
848 & & & pass & # deprecated &
849 & & elif o in ("-k", "--package_key"): &
850 & & & OPTIONS.package_key = a &
851 & & elif o in ("-i", "--incremental_from"): &
852 & & & OPTIONS.incremental_source = a &
853 & & elif o in ("-w", "--wipe_user_data"): &
854 & & & OPTIONS.wipe_user_data = True &
855 & & elif o in ("-n", "--no_prereq"): &
856 & & & OPTIONS.omit_prereq = True &
857 & & elif o in ("-e", "--extra_script"): &
858 & & & OPTIONS.extra_script = a &
859 & & elif o in ("-m", "--script_mode"): &
860 & & & OPTIONS.script_mode = a &
861 & & elif o in ("--worker_threads"): &
862 & & & OPTIONS.worker_threads = int(a) &
863 & & else: &
864 & & & return False &
865 & & return True &
867 & args = common.ParseOptions(argv, __doc__, &
868 & & & & & & & & & & & & & & &extra_opts="b:k:i:d:wne:m:", &
869 & & & & & & & & & & & & & & &extra_long_opts=["board_config=", &
870 & & & & & & & & & & & & & & & & & & & & & & & "package_key=", &
871 & & & & & & & & & & & & & & & & & & & & & & & "incremental_from=", &
872 & & & & & & & & & & & & & & & & & & & & & & & "wipe_user_data", &
873 & & & & & & & & & & & & & & & & & & & & & & & "no_prereq", &
874 & & & & & & & & & & & & & & & & & & & & & & & "extra_script=", &
875 & & & & & & & & & & & & & & & & & & & & & & & "script_mode=", &
876 & & & & & & & & & & & & & & & & & & & & & & & "worker_threads="], &
877 & & & & & & & & & & & & & & &extra_option_handler=option_handler) &
879 & if len(args) != 2: &
880 & & common.Usage(__doc__) &
881 & & sys.exit(1) &
将用户设定的 Option 存入 OPTIONS 变量中。它是一个Python Class, 我们将其理解为一个C Struct 即可。 883 & if OPTIONS.script_mode not in ("amend", "edify", "auto"):884 & & raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,)) Script_mode 只能是amend/edify/auto之一, auto 目前是选择两者都支持。可以理解是为了向前兼容,(早期 Android 使用 amend) 886 & if OPTIONS.extra_script is not None:887 & & OPTIONS.extra_script = open(OPTIONS.extra_script).read() 读入 额外脚本的内容。(如果有) 889 & print "unzipping target target-files..."890 & OPTIONS.input_tmp = common.UnzipTemp(args[0]) 解开输入包。
892 & if OPTIONS.device_specific is None: &
893 & & # look for the device-specific tools extension location in the input &
894 & & try: &
895 & & & f = open(os.path.join(OPTIONS.input_tmp, "META", "tool-extensions.txt")) &
896 & & & ds = f.read().strip() &
897 & & & f.close() &
898 & & & if ds: &
899 & & & & ds = os.path.normpath(ds) &
900 & & & & print "using device-specific extensions in", ds &
901 & & & & OPTIONS.device_specific = ds &
902 & & except IOError, e: &
903 & & & if e.errno == errno.ENOENT: &
904 & & & & # nothing specified in the file &
905 & & & & pass &
906 & & & else: &
907 & & & & raise &
&处理 device-specific extensions, 没用到。 909 & common.LoadMaxSizes()910 & if not OPTIONS.max_image_size:911 & & print912 & & print " &WARNING: &Failed to
will not enforce"913 & & print " &image size limits."914 & & print 读入设定image大小的参数,没用到。 916 & OPTIONS.target_tmp = OPTIONS.input_tmp917 & input_zip = zipfile.ZipFile(args[0], "r")918 & if OPTIONS.package_key:919 & & temp_zip_file = tempfile.NamedTemporaryFile()920 & & output_zip = zipfile.ZipFile(temp_zip_file, "w",921 & & & & & & & & & & & & & & & & &compression=zipfile.ZIP_DEFLATED)922 & else:923 & & output_zip = zipfile.ZipFile(args[1], "w",924 & & & & & & & & &compression=zipfile.ZIP_DEFLATED) 设定输出文件,如果要签名(our case),则还需要一个临时输出文件。 926 & if OPTIONS.incremental_source is None:927 & & WriteFullOTAPackage(input_zip, output_zip)928 & else:929 & & print "unzipping source target-files..."930 & & OPTIONS.source_tmp = common.UnzipTemp(OPTIONS.incremental_source)931 & & source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r")932 & & WriteIncrementalOTAPackage(input_zip, source_zip, output_zip) 根据参数,调用增量和非增量创建 ZIP 创建函数,我们采用非增量模式。 934 & output_zip.close()935 & if OPTIONS.package_key:936 & & SignOutput(temp_zip_file.name, args[1])937 & & temp_zip_file.close()939 & common.Cleanup()941 & print "done."
&签名(如果需要的话),处理完毕。
下面我们看主要功能函数:WriteFullOTAPackage。
二.脚本ota_from_target_files(第二部分) &
14:56:25| &分类: Android OTA升级 | &标签: |字号大中小 订阅
主功能:WriteFullOTAPackage
&345 def WriteFullOTAPackage(input_zip, output_zip):
346 & if OPTIONS.script_mode == "auto":347 & & script = both_generator.BothGenerator(2)348 & elif OPTIONS.script_mode == "amend":349 & & script = amend_generator.AmendGenerator()350 & else:351 & & # TODO: how to determine this? &We don't know what version it will352 & & # be installed on top of. &For now, we expect the API just won't353 & & # change very often.354 & & script = edify_generator.EdifyGenerator(2) 首先,我们获得脚本生成器,他们的实现见脚本:edify_generator.py 等。 356 & metadata = {"post-build": GetBuildProp("ro.build.fingerprint", input_zip),357 & & & & & & & "pre-device": GetBuildProp("ro.product.device", input_zip),358 & & & & & & & "post-timestamp": GetBuildProp("ro.build.date.utc", input_zip),359 & & & & & & & } 获得一些环境变量,来自android 环境变量。 Google 一下即知其义。 361 & device_specific = common.DeviceSpecificParams(362 & & & input_zip=input_zip,363 & & & input_version=GetRecoveryAPIVersion(input_zip),364 & & & output_zip=output_zip,365 & & & script=script,366 & & & input_tmp=OPTIONS.input_tmp,367 & & & metadata=metadata) 设备相关参数,不深究。 369 & if not OPTIONS.omit_prereq:370 & & ts = GetBuildProp("ro.build.date.utc", input_zip)371 & & script.AssertOlderBuild(ts) 如果需要,在脚本中增加一个Assert语句,要求update zip包只能用于升级老的系统。 373 & AppendAssertions(script, input_zip) 如果需要,在脚本中增加一个Assert语句,要求update zip包只能用于同一设备,即目标设备的 ro.product.device必须跟update.zip中的相同。
374 & device_specific.FullOTA_Assertions() Callback, 用于调用设备相关代码。调用时机为即将开始升级。类似还有:FullOTA_InstallEnd IncrementalOTA_Assertions IncrementalOTA_VerifyEnd。 不深究。 376 & script.ShowProgress(0.5, 0) 在升级脚本中加入显示进度的语句, 参数一表示底下的操作(到下一条同类语句或者到末尾)将暂用的时间在总体时间的比例。参数二用于控制显示的速度。比如,50 则表示底下的操作估计50秒内完成,要求进度条显示线程用50秒显示这一部分的进度。0 表示不自动更新,手动控制(使用SetProgress) 378 & if OPTIONS.wipe_user_data:379 & & script.FormatPartition("userdata") 如果需要,在脚本中增加语句,擦除 userdata 分区。 381 & script.FormatPartition("system") 在脚本中增加语句,擦除 system分区。 382 & script.Mount("MTD", "system", "/system") 在脚本中增加语句,安装 system分区到 /system 目录。383 & script.UnpackPackageDir("recovery", "/system")384 & script.UnpackPackageDir("system", "/system")在脚本中增加语句,将recovery以及system中的内容拷贝到 /system目录。其中recovery 目录包含一个patch 以及应用该patch 的脚本。 386 & symlinks = CopySystemFiles(input_zip, output_zip)387 & script.MakeSymlinks(symlinks) 386 行从输入 ZIP 包 /system 拷贝文件到输出 ZIP 包 /system。由于这个过程不支持链接文件,所以它将这些文件返回。 于 387 行做继续处理。该行建立这些link 文件。所有的link文件都指向 toolbox 389 & boot_img = File("boot.img", common.BuildBootableImage(390 & & & os.path.join(OPTIONS.input_tmp, "BOOT")))391 & recovery_img = File("recovery.img", common.BuildBootableImage(392 & & & os.path.join(OPTIONS.input_tmp, "RECOVERY")))393 & MakeRecoveryPatch(output_zip, recovery_img, boot_img) 这个复杂,MakeRecoveryPatch 做了两件事:1.在输出 ZIP包中生成一个patch: recovery/recovery-from-boot.p(boot.img和 recovery.img的patch), 它最后会位于:system/recovery-from-boot.p2.在输出 ZIP包中生成一个脚本:recovery/etc/install-recovery.sh , 它最后会位于system/etc/install-recovery.sh.该脚本的内容为:#!/system/bin/shif ! applypatch -c MTD:recovery:ffb86a4a16cb26a; then &log -t recovery "Installing new recovery image" &applypatch MTD:boot:a72a20a9c2f958ba586a840ed773cf8f5244183 MTD:recovery f6c2a70c5f2b02b6a49c9f5ce2d389 a72a20a9c2f958ba586a840ed773cf8f5244183:/system/recovery-from-boot.pelse &log -t recovery "Recovery image already installed"fi 395 & Item.GetMetadata(input_zip) 从 META/filesystem_config.txt 中获得 system 目录下的各文件权限信息。 396 & Item.Get("system").SetPermissions(script) 在脚本中增加语句,设置 system 目录下文件的权限及属主等。 398 & common.CheckSize(boot_img.data, "boot.img") 检查 boot.img 文件大小是否超标. 399 & common.ZipWriteStr(output_zip, "boot.img", boot_img.data) 将boot.img 放到输出 ZIP 包中。 400 & script.ShowProgress(0.2, 0)402 & script.ShowProgress(0.2, 10) 更行进度条。 403 & script.WriteRawImage("boot", "boot.img") 在脚本中增加语句,将 boot.img 写到 boot 分区。 405 & script.ShowProgress(0.1, 0) 更行进度条。 406 & device_specific.FullOTA_InstallEnd() Callback, 同前。 408 & if OPTIONS.extra_script is not None:409 & & script.AppendExtra(OPTIONS.extra_script) 如果有额外脚本,加入。 411 & script.UnmountAll() 在脚本中增加语句,umount 所有分区。 412 & script.AddToZip(input_zip, output_zip) 1)将前面生成的脚本输出到:META-INF/com/google/android/updater-script (对于edify)&
assert(getprop("ro.product.device") == "thedevicename" || &
& & & &getprop("ro.build.product") == "theproductname"); &
show_progress(0.); &
format("MTD", "system"); &
mount("MTD", "system", "/system"); &
package_extract_dir("recovery", "/system"); &
package_extract_dir("system", "/system"); &
symlink("dumpstate", "/system/bin/dumpcrash"); &
symlink("toolbox", "/system/bin/cat", "/system/bin/chmod", &
& & & & "/system/bin/chown", "/system/bin/cmp", "/system/bin/date", &
& & & & "/system/bin/dd", "/system/bin/df", "/system/bin/dmesg", &
& & & & "/system/bin/fb2bmp", "/system/bin/getevent", "/system/bin/getprop", &
& & & & "/system/bin/hd", "/system/bin/id", "/system/bin/ifconfig", &
& & & & "/system/bin/iftop", "/system/bin/insmod", "/system/bin/ioctl", &
& & & & "/system/bin/kill", "/system/bin/ln", "/system/bin/log", &
& & & & "/system/bin/ls", "/system/bin/lsmod", "/system/bin/mkdir", &
& & & & "/system/bin/mount", "/system/bin/mv", "/system/bin/netstat", &
& & & & "/system/bin/newfs_msdos", "/system/bin/notify", "/system/bin/printenv", &
& & & & "/system/bin/ps", "/system/bin/reboot", "/system/bin/renice", &
& & & & "/system/bin/rm", "/system/bin/rmdir", "/system/bin/rmmod", &
& & & & "/system/bin/route", "/system/bin/schedtop", "/system/bin/sendevent", &
& & & & "/system/bin/setconsole", "/system/bin/setprop", "/system/bin/sleep", &
& & & & "/system/bin/smd", "/system/bin/start", "/system/bin/stop", &
& & & & "/system/bin/sync", "/system/bin/top", "/system/bin/umount", &
& & & & "/system/bin/vmstat", "/system/bin/watchprops", &
& & & & "/system/bin/wipe"); &
set_perm_recursive(0, 0, , "/system"); &
set_perm_recursive(0, , 0755, "/system/bin"); &
set_perm(0, , "/system/bin/netcfg"); &
set_perm(0, , "/system/bin/ping"); &
set_perm_recursive(, , "/system/etc/bluez"); &
set_perm(0, 0, 0755, "/system/etc/bluez"); &
set_perm(, 0440, "/system/etc/dbus.conf"); &
set_perm(, 0550, "/system/etc/dhcpcd/dhcpcd-run-hooks"); &
set_perm(0, , "/system/etc/init.goldfish.sh"); &
set_perm(0, 0, 0544, "/system/etc/install-recovery.sh"); &
set_perm_recursive(0, 0, , "/system/etc/ppp"); &
set_perm_recursive(0, , 0755, "/system/xbin"); &
show_progress(0.); &
show_progress(0.); &
assert(package_extract_file("boot.img", "/tmp/boot.img"), &
& & & &write_raw_image("/tmp/boot.img", "boot"), &
& & & &delete("/tmp/boot.img")); &
show_progress(0.); &
unmount("/system"); &
&2)将升级程序:OTA/bin/updater 从输入ZIP包中拷贝到输出ZIP包中的:META-INF/com/google/android/update-binary 413 & WriteMetadata(metadata, output_zip)
将前面获取的metadata 写入输出包的文件中: META-INF/com/android/metadata
至此,我们就得到了一个update.zip包。可以开始升级了。
1) 虽然提供了更新recovery分区的机制,但是没有看到触发该更新的语句。所以,缺省的情况是不会更新recovery分区的。大概是为了安全的原因吧。 但是,有时确实需要更新recovery 分区(比如,设备的硬件配置、分区表等发生改变),这该如何操作呢?
Android OTA 升级之三:生成recovery.img
作者: 宋立新
& & & &得到了ota升级包后,我们就可以用它来升级系统了。Android 手机开机后,会先运行 bootloader。 Bootloader 会根据某些判定条件(比如按某个特殊键)决定是否进入 recovery 模式。Recovery 模式会装载 recovery 分区, 该分区包含recovery.img。recovery.img 包含了标准内核(和boot.img中的内核相同)以及recovery 根文件系统。下面我们看一下它是如何生成的。
recovery.img生成过程 L630-L637 依赖关系
(From: build/core/Makefile)
630 $(INSTALLED_RECOVERYIMAGE_TARGET): $(MKBOOTFS) $(MKBOOTIMG) $(MINIGZIP) /631 & & & & & & & & $(INSTALLED_RAMDISK_TARGET) /632 & & & & & & & & $(INSTALLED_BOOTIMAGE_TARGET) /633 & & & & & & & & $(recovery_binary) /634 & & & & & & & & $(recovery_initrc) $(recovery_kernel) /635 & & & & & & & & $(INSTALLED_2NDBOOTLOADER_TARGET) /636 & & & & & & & & $(recovery_build_prop) $(recovery_resource_deps) /637 & & & & & & & & $(RECOVERY_INSTALL_OTA_KEYS)
INSTALLED_RECOVERYIMAGE_TARGET 为我们的编译目标:
584 INSTALLED_RECOVERYIMAGE_TARGET := $(PRODUCT_OUT)/recovery.img
它依赖很多其它目标:
1.MKBOOTFS, MINIGZIP, MKBOOTIMG,PC端工具软件:(From build/core/config.mk)265 MKBOOTFS := $(HOST_OUT_EXECUTABLES)/mkbootfs$(HOST_EXECUTABLE_SUFFIX)266 MINIGZIP := $(HOST_OUT_EXECUTABLES)/minigzip$(HOST_EXECUTABLE_SUFFIX)267 MKBOOTIMG := $(HOST_OUT_EXECUTABLES)/mkbootimg$(HOST_EXECUTABLE_SUFFIX)
2.INSTALLED_RAMDISK_TARGET,标准根文件系统 ramdisk.img:
326 BUILT_RAMDISK_TARGET := $(PRODUCT_OUT)/ramdisk.img328 # We just build this directly to the install location.329 INSTALLED_RAMDISK_TARGET := $(BUILT_RAMDISK_TARGET) 3.INSTALLED_BOOTIMAGE_TARGET, 即boot.img,标准内核及标准根文件系统:362 INSTALLED_BOOTIMAGE_TARGET := $(PRODUCT_OUT)/boot.img
4. recovery_binary, Recovery可执行程序,源码位于:bootable/recovery
590 recovery_binary := $(call intermediates-dir-for,EXECUTABLES,recovery)/recovery
5. recovery_initrc,recovery模式的init.rc, 位于 bootable/recovery/etc/init.rc
586 recovery_initrc := $(call include-path-for, recovery)/etc/init.rc
6. recovery_kernel, recovery 模式的kernel, 同标准内核
587 recovery_kernel := $(INSTALLED_KERNEL_TARGET) # same as a non-recovery system
7.INSTALLED_2NDBOOTLOADER_TARGET,我们不用。
8. recovery_build_prop, recovery 模式的build.prop, 同标准模式。589 recovery_build_prop := $(INSTALLED_BUILD_PROP_TARGET)
9. recovery_resource_deps, recovery 模式使用的res, 位于:recovery/custom/{product_name}/res, 以及设备自定义部分(我们没用到)
591 recovery_resources_common := $(call include-path-for, recovery)/custom/$(TARGET_PRODUCT)/res592 recovery_resources_private := $(strip $(wildcard $(TARGET_DEVICE_DIR)/recovery/res))593 recovery_resource_deps := $(shell find $(recovery_resources_common)594 & $(recovery_resources_private) -type f)&
10. &RECOVERY_INSTALL_OTA_KEYS, ota 密钥:
618 # Generate a file containing the keys that will be read by the619 # recovery binary.620 RECOVERY_INSTALL_OTA_KEYS := /621 & & & & $(call intermediates-dir-for,PACKAGING,ota_keys)/keys L638-L655 准备内容638 & & & &
----- Making recovery image ------639 & & & & rm -rf $(TARGET_RECOVERY_OUT)640 & & & & mkdir -p $(TARGET_RECOVERY_OUT)641 & & & & mkdir -p $(TARGET_RECOVERY_ROOT_OUT)642 & & & & mkdir -p $(TARGET_RECOVERY_ROOT_OUT)/etc643 & & & & mkdir -p $(TARGET_RECOVERY_ROOT_OUT)/tmp
准备recovery目录:out/target/product/{product_name}/recovery 及其子目录:
./root/etc
./root/tmp
644 & & & & echo Copying baseline ramdisk...645 & & & & cp -R $(TARGET_ROOT_OUT) $(TARGET_RECOVERY_OUT)646 & & & & echo Modifying ramdisk contents...647 & & & & rm -rf $(TARGET_RECOVERY_ROOT_OUT)/res
从标准根文件系统拷贝所有文件, 删除其res 目录。
&648 & & & & cp -f $(recovery_initrc) $(TARGET_RECOVERY_ROOT_OUT)/649 & & & & cp -f $(recovery_binary) $(TARGET_RECOVERY_ROOT_OUT)/sbin/ 拷贝recovery 模式的核心文件 init.rc 及 recovery 650 & & & & cp -rf $(recovery_resources_common) $(TARGET_RECOVERY_ROOT_OUT)/651 & & & & $(foreach item,$(recovery_resources_private), /652 & & & & & cp -rf $(item) $(TARGET_RECOVERY_ROOT_OUT)/)653 & & & & cp $(RECOVERY_INSTALL_OTA_KEYS) $(TARGET_RECOVERY_ROOT_OUT)/res/keys 拷贝资源文件及密钥文件。 &654 & & & & cat $(INSTALLED_DEFAULT_PROP_TARGET) $(recovery_build_prop) /655 & & & & & & & & & $(TARGET_RECOVERY_ROOT_OUT)/default.prop 生成属性文件 default.prop, 它包含了标准根文件系统的default.prop(out/target/product/{product_name}/root/default.prop)以及system分区的build.prop (out/target/product/{product_name}/system/build.prop) &L656-L661 最终生成recovery.img656 & & & & $(MKBOOTFS) $(TARGET_RECOVERY_ROOT_OUT) | $(MINIGZIP) & $(recovery_ramdisk) 压缩recovery根文件系统 657 & & & & build/quacomm/mkimage $(PRODUCT_OUT)/ramdisk-recovery.img RECOVERY & $(PRODUCT_OUT)/ramdisk_recovery.img 加一个标识头(RECOVERY) 658 & & & & mv $(PRODUCT_OUT)/ramdisk_recovery.img $(PRODUCT_OUT)/ramdisk-recovery.img659 & & & & $(MKBOOTIMG) $(INTERNAL_RECOVERYIMAGE_ARGS) --output $@660 & & & &
----- Made recovery image -------- $@661 & & & & $(hide) $(call assert-max-image-size,$@,$(BOARD_RECOVERYIMAGE_PARTITION_SIZE),raw)
和内核一起,生成recovery.img
附:Recovery 根文件系统目录结构
├── advanced_meta_init.rc
├── data
├── default.prop
├── dev
├── etc
├── init
├── init.factory.rc
├── init.goldfish.rc
├── init.quacomm.rc
├── init.rc
├── meta_init.rc
├── proc
├── res
│ & ├── images
│ & │ & ├── icon_error.png
│ & │ & ├── icon_installing.png
│ & │ & ├── indeterminate1.png
│ & │ & ├── indeterminate2.png
│ & │ & ├── indeterminate3.png
│ & │ & ├── indeterminate4.png
│ & │ & ├── indeterminate5.png
│ & │ & ├── indeterminate6.png
│ & │ & ├── progress_empty.png
│ & │ & └── progress_fill.png
│ & └── keys
├── sbin
│ & ├── adbd
│ & ├── advanced_meta_init
│ & ├── meta_init
│ & ├── meta_tst
│ & └── recovery
├── sys
├── system
└── tmp
Android OTA 升级之四:进入根文件系统
作者: 宋立新
& & & &从bootloader 进入Recovery 模式后,首先也是运行Linux内核,该内核跟普通模式没有区别(减轻了BSP开发者的任务)。区别从执行文件系统开始。 Recovery 模式的细节就隐藏在其根文件系统中。
& & & &下面,我们就看看进入Recovery 根文件系统都干些啥。
& & & &和正常启动一样,内核进入文件系统会执行/init, init 的配置文件就是 /init.rc, 前面文章讲过,这个文件来自:bootable/recovery/etc/init.rc,下面,我们看看它的内容。
& 1 &2 on init &3 & & export PATH /sbin &4 & & export ANDROID_ROOT /system &5 & & export ANDROID_DATA /data &6 & & export EXTERNAL_STORAGE /sdcard &7 &8 & & symlink /system/etc /etc &9 10 & & mkdir /sdcard11 & & mkdir /system12 & & mkdir /data13 & & mkdir /cache14 & & mount /tmp /tmp tmpfs15 16 on boot17 18 & & ifup lo19 & & hostname localhost20 & & domainname localdomain21 22 & & class_start default23 24 25 service recovery /sbin/recovery26 27 service adbd /sbin/adbd recovery28 & & disabled29 30 on property:persist.service.adb.enable=131 & & start adbd32 33 on property:persist.service.adb.enable=034 & & stop adbd
可以看到,它很非常简单:
1) & 设置几个环境变量。备用。
2) & 建立 etc 链接。
3) & 造几个目录。备用。
4) & Mount /tmp 目录为内存文件系统 tmpfs,后面会用到。
5) & Trival 设置,不必关心。
6) & 启动 recovery主程序。
7) & 如果是eng模式(此时persist.service.adb.enable),启动adb
当然,init主程序还会装载属性配置文件 /default.prop, 它包含了很多系统属性设置,比如,ro.build.*, 等等。
很明显,这里最重要的就是recovery主程序,下面,我们分析它。
先看一段注释
Recovery.c 中,作者写了一段注释,对我们理解recovery的实现很有帮助,下面看一下:(我就不翻译了)
89 /*90 &* The recovery tool communicates with the main system through /cache files.91 &* & /cache/recovery/command - INPUT - command line for tool, one arg per line92 &* & /cache/recovery/log - OUTPUT - combined log file from recovery run(s)93 &* & /cache/recovery/intent - OUTPUT - intent that was passed in94 &*95 &* The arguments which may be supplied in mand file:96 &* & --send_intent=anystring - write the text out to recovery.intent97 &* & --update_package=root:path - verify install an OTA package file98 &* & --wipe_data - erase user data (and cache), then reboot99 &* & --wipe_cache - wipe cache (but not user data), then reboot100 &* & --set_encrypted_filesystem=on|off - enables / diasables encrypted fs101 &*102 &* After completing, we remove /cache/recovery/command and reboot.103 &* Arguments may also be supplied in the bootloader control block (BCB).104 &* These important scenarios must be safely restartable at any point:105 &*106 &* FACTORY RESET107 &* 1. user selects "factory reset"108 &* 2. main system writes "--wipe_data" to /cache/recovery/command109 &* 3. main system reboots into recovery110 &* 4. get_args() writes BCB with "boot-recovery" and "--wipe_data"111 &* & &-- after this, rebooting will restart the erase --112 &* 5. erase_root() reformats /data113 &* 6. erase_root() reformats /cache114 &* 7. finish_recovery() erases BCB115 &* & &-- after this, rebooting will restart the main system --116 &* 8. main() calls reboot() to boot main system117 &*118 &* OTA INSTALL119 &* 1. main system downloads OTA package to /cache/some-filename.zip120 &* 2. main system writes "--update_package=CACHE:some-filename.zip"121 &* 3. main system reboots into recovery122 &* 4. get_args() writes BCB with "boot-recovery" and "--update_package=..."123 &* & &-- after this, rebooting will attempt to reinstall the update --124 &* 5. install_package() attempts to install the update125 &* & &NOTE: the package install must itself be restartable from any point126 &* 6. finish_recovery() erases BCB127 &* & &-- after this, rebooting will (try to) restart the main system --128 &* 7. ** if install failed **129 &* & &7a. prompt_and_wait() shows an error icon and waits for the user130 &* & &7b; the user reboots (pulling the battery, etc) into the main system131 &* 8. main() calls maybe_install_firmware_update()132 &* & &** if the update contained radio/hboot firmware **:133 &* & &8a. m_i_f_u() writes BCB with "boot-recovery" and "--wipe_cache"134 &* & & & &-- after this, rebooting will reformat cache & restart main system --135 &* & &8b. m_i_f_u() writes firmware image into raw cache partition136 &* & &8c. m_i_f_u() writes BCB with "update-radio/hboot" and "--wipe_cache"137 &* & & & &-- after this, rebooting will attempt to reinstall firmware --138 &* & &8d. bootloader tries to flash firmware139 &* & &8e. bootloader writes BCB with "boot-recovery" (keeping "--wipe_cache")140 &* & & & &-- after this, rebooting will reformat cache & restart main system --141 &* & &8f. erase_root() reformats /cache142 &* & &8g. finish_recovery() erases BCB143 &* & & & &-- after this, rebooting will (try to) restart the main system --144 &* 9. main() calls reboot() to boot main system145 &*146 &* ENCRYPTED FILE SYSTEMS ENABLE/DISABLE147 &* 1. user selects "enable encrypted file systems"148 &* 2. main system writes "--set_encrypted_filesystem=on|off" to149 &* & &/cache/recovery/command150 &* 3. main system reboots into recovery151 &* 4. get_args() writes BCB with "boot-recovery" and152 &* & &"--set_encrypted_filesystems=on|off"153 &* & &-- after this, rebooting will restart the transition --154 &* 5. read_encrypted_fs_info() retrieves encrypted file systems settings from /data155 &* & &Settings include: property to specify the Encrypted FS istatus and156 &* & &FS encryption key if enabled (not yet implemented)157 &* 6. erase_root() reformats /data158 &* 7. erase_root() reformats /cache159 &* 8. restore_encrypted_fs_info() writes required encrypted file systems settings to /data160 &* & &Settings include: property to specify the Encrypted FS status and161 &* & &FS encryption key if enabled (not yet implemented)162 &* 9. finish_recovery() erases BCB163 &* & &-- after this, rebooting will restart the main system --164 &* 10. main() calls reboot() to boot main system165 &*/
recovery 主程序559 int560 main(int argc, char **argv)561 {562 & & time_t start = time(NULL);563564 & & // If these fail, there's not really anywhere to complain...565 & & freopen(TEMPORARY_LOG_FILE, "a", stdout); setbuf(stdout, NULL);566 & & freopen(TEMPORARY_LOG_FILE, "a", stderr); setbuf(stderr, NULL);567 & & fprintf(stderr, "Starting recovery on %s", ctime(&start));568&
将标准输出和标准错误输出重定位到"/tmp/recovery.log",如果是eng模式,就可以通过adb pull /tmp/recovery.log, 看到当前的log信息,这为我们提供了有效的调试手段。后面还会看到,recovery模式运行完毕后,会将其拷贝到cache分区,以便后续分析。
&569 & & ui_init(); Recovery 使用了一个简单的基于framebuffer的ui系统,叫miniui,这里,进行了简单的初始化(主要是图形部分以及事件部分),并启动了一个 event 线程用于响应用户按键。 570 & & get_args(&argc, &argv);
从misc 分区以及 CACHE:recovery/command 文件中读入参数,写入到argc, argv ,并且,如果有必要,回写入misc分区。这样,如果recovery没有操作成功(比如,升级还没有结束,就拔电池),系统会一直进入recovery模式。提醒用户继续升级,直到成功。
&572 & & int previous_runs = 0;573 & & const char *send_intent = NULL;574 & & const char *update_package = NULL;575 & & int wipe_data = 0, wipe_cache = 0;576577 & &578 & & while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) {579 & & & & switch (arg) {580 & & & & case 'p': previous_runs = atoi(optarg);581 & & & & case 's': send_intent =582 & & & & case 'u': update_package =583 & & & & case 'w': wipe_data = wipe_cache = 1;584 & & & & case 'c': wipe_cache = 1;585 & & & & case '?':586 & & & & & & LOGE("Invalid command argument/n");587 & & & & & &588 & & & & }589 & & }590解析参数,p: previous_runs没有用到,其它含义见前面注释。 591 & & device_recovery_start(); 这个函数没干什么。看名字,它給设备制造商提供了一个调用机会,可写入设备相关初始化代码。592593 & & fprintf(stderr, "Command:");594 & & for (arg = 0; arg & arg++) {595 & & & & fprintf(stderr, " /"%s/"", argv[arg]);596 & & }597 & & fprintf(stderr, "/n/n");598打印出命令,比如,正常启动进入recovery模式,会打印:Command: "/sbin/recovery"599 & & property_list(print_property, NULL);600 & & fprintf(stderr, "/n");601打印出所有的系统属性(from default.prop)到log文件。 602 & & int status = INSTALL_SUCCESS;603604 & & if (update_package != NULL) {605 & & & & status = install_package(update_package);606 & & & & if (status != INSTALL_SUCCESS) ui_print("Installation aborted./n");607 & & } else if (wipe_data) {608 & & & & if (device_wipe_data()) status = INSTALL_ERROR;609 & & & & if (erase_root("DATA:")) status = INSTALL_ERROR;610 & & & & if (wipe_cache && erase_root("CACHE:")) status = INSTALL_ERROR;611 & & & & if (status != INSTALL_SUCCESS) ui_print("Data wipe failed./n");612 & & } else if (wipe_cache) {613 & & & & if (wipe_cache && erase_root("CACHE:")) status = INSTALL_ERROR;614 & & & & if (status != INSTALL_SUCCESS) ui_print("Cache wipe failed./n");615 & & } else {616 & & & & status = INSTALL_ERROR; &// No command specified617 & & } 根据用户提供参数,调用各项功能,比如,安装一个升级包,擦除cache分区, 擦除user data分区,install_package比较复杂,后面专门分析,其它都很简单。忽略。 618619 & & if (status != INSTALL_SUCCESS) ui_set_background(BACKGROUND_ICON_ERROR);622 & & if (status != INSTALL_SUCCESS) prompt_and_wait(); 如果前面已经做了某项操作并且成功,则进入重启流程。否则,等待用户选择具体操作。而用户可选操作为: reboot, 安装update.zip,除cache分区, 擦除user data分区,如前所述,只有安装package 比较复杂,其它简单。 623624 & & // Otherwise, get ready to boot the main system...625 & & finish_recovery(send_intent); 它的功能如下:1)将前面定义的intent字符串写入(如果有的话):CACHE:recovery/command2)将 /tmp/recovery.log 复制到 "CACHE:recovery/log";3)清空 misc 分区,这样重启就不会进入recovery模式4)删除command 文件:CACHE:recovery/ 626 & & ui_print("Rebooting.../n");627 & & sync();628 & & reboot(RB_AUTOBOOT);629 & & return EXIT_SUCCESS;630 }&
下面我们分析核心函数 install_package
install_package289 int290 install_package(const char *root_path)291 {292 & & ui_set_background(BACKGROUND_ICON_INSTALLING);294 & & ui_print("Finding update package.../n");295 & & LOGI("Finding update package.../n");296 & & ui_show_indeterminate_progress();297 & & LOGI("Update location: %s/n", root_path);298更新 UI 显示299 & & if (ensure_root_path_mounted(root_path) != 0) {300 & & & & LOGE("Can't mount %s/n", root_path);301 & & & & reset_mark_block();302 & & & & return INSTALL_CORRUPT;303 & & }304 确保升级包所在分区已经mount,通常为 cache 分区或者 SD 分区 305 & & char path[PATH_MAX] = "";306 & & if (translate_root_path(root_path, path, sizeof(path)) == NULL) {307 & & & & LOGE("Bad path %s/n", root_path);308 & & & & reset_mark_block();309 & & & & return INSTALL_CORRUPT;310 & & } 将根分区转化为具体分区信息.这些信息来自:全局数组:g_roots 313 & & ui_print("Opening update package.../n");314 & & LOGI("Opening update package.../n");315 & & LOGI("Update file path: %s/n", path);316317 & & int numK318 & & RSAPublicKey* loadedKeys = load_keys(PUBLIC_KEYS_FILE, &numKeys);319 & & if (loadedKeys == NULL) {320 & & & & LOGE("Failed to load keys/n");321 & & & & reset_mark_block();322 & & & & return INSTALL_CORRUPT;323 & & }324 & & LOGI("%d key(s) loaded from %s/n", numKeys, PUBLIC_KEYS_FILE); 从/res/keys中装载公钥。 326 & & // Give verification half the progress bar...328 & & ui_print("Verifying update package.../n");329 & & LOGI("Verifying update package.../n");330 & & ui_show_progress(331 & & & & & & VERIFICATION_PROGRESS_FRACTION,332 & & & & & & VERIFICATION_PROGRESS_TIME);333334 & &335 & & err = verify_file(path, loadedKeys, numKeys);336 & & free(loadedKeys);337 & & LOGI("verify_file returned %d/n", err);338 & & if (err != VERIFY_SUCCESS) {339 & & & & LOGE("signature verification failed/n");340 & & & & reset_mark_block();341 & & & & return INSTALL_CORRUPT;342 & & } 根据公钥验证升级包verify_file的注释说的很明白: & & & // Look for an RSA signature embedded in the .ZIP file comment given & & & // the path to the zip. &Verify it matches one of the given public & & & // keys. 344 & & /* Try to open the package.345 & & &*/346 & & ZipA347 & & err = mzOpenZipArchive(path, &zip);348 & & if (err != 0) {349 & & & & LOGE("Can't open %s/n(%s)/n", path, err != -1 ? strerror(err) : "bad");350 & & & & reset_mark_block();351 & & & & return INSTALL_CORRUPT;352 & & } 打开升级包,将相关信息存到ZuoArchive数据机构中,便于后面处理。 354 & & /* Verify and install the contents of the package.355 & & &*/356 & & int status = handle_update_package(path, &zip); 处理函数,我们后面继续分析。 357 & & mzCloseZipArchive(&zip);358 & &359 } 关闭zip包,结束处理。 handle_update_package204 static int205 handle_update_package(const char *path, ZipArchive *zip)206 {207 & & // Update should take the rest of the progress bar.208 & & ui_print("Installing update.../n");209210 & & int result = try_update_binary(path, zip);211 & & register_package_root(NULL, NULL); &// Unregister package root212 & &213 }&
它主要调用函数try_update_binary完成功能。
try_update_binary
84 // If the package contains an update binary, extract it and run it.
&85 static int
&86 try_update_binary(const char *path, ZipArchive *zip) {
&87 & & const ZipEntry* binary_entry =
&88 & & & & & & mzFindZipEntry(zip, ASSUMED_UPDATE_BINARY_NAME);
&89 & & if (binary_entry == NULL) {
&90 & & & & return INSTALL_CORRUPT;
&93 & & char* binary = "/tmp/update_binary";
&94 & & unlink(binary);
&95 & & int fd = creat(binary, 0755);
&96 & & if (fd & 0) {
&97 & & & & LOGE("Can't make %s/n", binary);
&98 & & & & return 1;
100 & & bool ok = mzExtractZipEntryToFile(zip, binary_entry, fd);
101 & & close(fd);
103 & & if (!ok) {
104 & & & & LOGE("Can't copy %s/n", ASSUMED_UPDATE_BINARY_NAME);
105 & & & & return 1;
&将升级包内文件META-INF/com/google/android/update-binary 复制为/tmp/update_binary
108 & & int pipefd[2];
109 & & pipe(pipefd);
111 & & // When executing the update binary contained in the package, the
112 & & // arguments passed are:
113 & & //
114 & & // & - the version number for this interface
115 & & //
116 & & // & - an fd to which the program can write in order to update the
117 & & // & & progress bar. &The program can write single-line commands:
118 & & //
119 & & // & & & &progress &frac& &secs&
120 & & // & & & & & &fill up the next &frac& part of of the progress bar
121 & & // & & & & & &over &secs& seconds. &If &secs& is zero, use
122 & & // & & & & & &set_progress commands to manually control the
123 & & // & & & & & &progress of this segment of the bar
124 & & //
125 & & // & & & &set_progress &frac&
126 & & // & & & & & &&frac& should be between 0.0 and 1.0; sets the
127 & & // & & & & & &progress bar within the segment defined by the most
128 & & // & & & & & &recent progress command.
129 & & //
130 & & // & & & &firmware &"hboot"|"radio"& &filename&
131 & & // & & & & & &arrange to install the contents of &filename& in the
132 & & // & & & & & &given partition on reboot.
133 & & //
134 & & // & & & & & &(API v2: &filename& may start with "PACKAGE:" to
135 & & // & & & & & &indicate taking a file from the OTA package.)
136 & & //
137 & & // & & & & & &(API v3: this command no longer exists.)
138 & & //
139 & & // & & & &ui_print &string&
140 & & // & & & & & &display &string& on the screen.
141 & & //
142 & & // & - the name of the package zip file.
143 & & //
注意看这段注释,它解释了以下代码的行为。结合代码,可知:
1) &将会创建新的进程,执行:/tmp/update_binary
2) &同时,会给该进程传入一些参数,其中最重要的就是一个管道fd,供新进程与原进程通信。
3) &新进程诞生后,原进程就变成了一个服务进程,它提供若干UI更新服务:
a) & progress
b) & set_progress
c) & ui_print
这样,新进程就可以通过老进程的UI系统完成显示任务。而其他功能就靠它自己了。
145 & & char** args = malloc(sizeof(char*) * 5);
146 & & args[0] =
147 & & args[1] = EXPAND(RECOVERY_API_VERSION); & // defined in Android.mk
148 & & args[2] = malloc(10);
149 & & sprintf(args[2], "%d", pipefd[1]);
150 & & args[3] = (char*)
151 & & args[4] = NULL;
153 & & pid_t pid = fork();
154 & & if (pid == 0) {
155 & & & & close(pipefd[0]);
156 & & & & execv(binary, args);
157 & & & & fprintf(stderr, "E:Can't run %s (%s)/n", binary, strerror(errno));
158 & & & & _exit(-1);
160 & & close(pipefd[1]);
162 & & char buffer[1024];
163 & & FILE* from_child = fdopen(pipefd[0], "r");
164 & & while (fgets(buffer, sizeof(buffer), from_child) != NULL) {
165 & & & & char* command = strtok(buffer, " /n");
166 & & & & if (command == NULL) {
167 & & & & & &
168 & & & & } else if (strcmp(command, "progress") == 0) {
169 & & & & & & char* fraction_s = strtok(NULL, " /n");
170 & & & & & & char* seconds_s = strtok(NULL, " /n");
172 & & & & & & float fraction = strtof(fraction_s, NULL);
173 & & & & & & int seconds = strtol(seconds_s, NULL, 10);
175 & & & & & & ui_show_progress(fraction * (1-VERIFICATION_PROGRESS_FRACTION),
176 & & & & & & & & & & & & & & &seconds);
177 & & & & } else if (strcmp(command, "set_progress") == 0) {
178 & & & & & & char* fraction_s = strtok(NULL, " /n");
179 & & & & & & float fraction = strtof(fraction_s, NULL);
180 & & & & & & ui_set_progress(fraction);
181 & & & & } else if (strcmp(command, "ui_print") == 0) {
182 & & & & & & char* str = strtok(NULL, "/n");
183 & & & & & & if (str) {
184 & & & & & & & & ui_print(str);
185 & & & & & & } else {
186 & & & & & & & & ui_print("/n");
187 & & & & & & }
188 & & & & } else {
189 & & & & & & LOGE("unknown command [%s]/n", command);
190 & & & & }
192 & & fclose(from_child);
195 & & waitpid(pid, &status, 0);
196 & & if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
197 & & & & LOGE("Error in %s/n(Status %d)/n", path, WEXITSTATUS(status));
198 & & & & return INSTALL_ERROR;
201 & & return INSTALL_SUCCESS;
这样,我们又回到了升级包中的文件:META-INF/com/google/android/update-binary,下回继续研究。
Android OTA 升级之五:updater
作者: 宋立新
& & & &可以说,前面分析的OTA升级的各部分代码都是在搭一个舞台,而主角现在终于登场,它就是updater. Google的代码架构设计非常好,各部分尽量松耦合。前面介绍升级脚本时,可知有两种类型的脚本,amend & edify. 他们各自对应一个updater. 这里,我们主要关注新的edify的updater.
& & & &Updater可以作为学习解释器/编译器的同学一个很好的实例,但是我们只关心产品化相关的内容,所以并不去深究lex/yacc相关的东西。
入口函数 main
(from: bootable/recovery/updater/updater.c)
62 // Where in the package we expect to find the edify script to execute.
&63 // (Note it's "updateR-script", not the older "update-script".)
&64 #define SCRIPT_NAME "META-INF/com/google/android/updater-script"
这里定义脚本的位置,注释说明本updater支持edify格式的脚本。
&66 int main(int argc, char** argv) {
&67 & & // Various things log information to stdout or stderr more or less
&68 & & // at random. &The log file makes more sense if buffering is
&69 & & // turned off so things appear in the right order.
&70 & & setbuf(stdout, NULL);
&71 & & setbuf(stderr, NULL);
&73 & & if (argc != 4) {
&74 & & & & fprintf(stderr, "unexpected number of arguments (%d)/n", argc);
&75 & & & & return 1;
&78 & & char* version = argv[1];
&79 & & if ((version[0] != '1' && version[0] != '2' && version[0] != '3') ||
&80 & & & & version[1] != '/0') {
&81 & & & & // We support version 1, 2, or 3.
&82 & & & & fprintf(stderr, "wrong updater binary API; expected 1, 2, or 3; "
&83 & & & & & & & & & & & & "got %s/n",
&84 & & & & & & & & argv[1]);
&85 & & & & return 2;
获取 version 参数。
&88 & & // Set up the pipe for sending commands back to the parent process.
&90 & & int fd = atoi(argv[2]);
&91 & & FILE* cmd_pipe = fdopen(fd, "wb");
&92 & & setlinebuf(cmd_pipe);
获取命令管道(用于图形显示等,见前篇)
&94 & & // Extract the script from the package.
&96 & & char* package_data = argv[3];
&97 & & ZipA
&99 & & err = mzOpenZipArchive(package_data, &za);
100 & & if (err != 0) {
101 & & & & fprintf(stderr, "failed to open package %s: %s/n",
102 & & & & & & & & package_data, strerror(err));
103 & & & & return 3;
106 & & const ZipEntry* script_entry = mzFindZipEntry(&za, SCRIPT_NAME);
107 & & if (script_entry == NULL) {
108 & & & & fprintf(stderr, "failed to find %s in %s/n", SCRIPT_NAME, package_data);
109 & & & & return 4;
112 & & char* script = malloc(script_entry-&uncompLen+1);
113 & & if (!mzReadZipEntry(&za, script_entry, script, script_entry-&uncompLen)) {
114 & & & & fprintf(stderr, "failed to read script from package/n");
115 & & & & return 5;
117 & & script[script_entry-&uncompLen] = '/0';
读入脚本 META-INF/com/google/android/updater-script
119 & & // Configure edify's functions.
121 & & RegisterBuiltins();
122 & & RegisterInstallFunctions();
123 & & RegisterDeviceExtensions();
124 & & FinishRegistration();
注册语句处理函数
126 & & // Parse the script.
128 & & Expr*
129 & & int error_count = 0;
130 & & yy_scan_string(script);
131 & & int error = yyparse(&root, &error_count);
132 & & if (error != 0 || error_count & 0) {
133 & & & & fprintf(stderr, "%d parse errors/n", error_count);
134 & & & & return 6;
调用yy* 库函数解析脚本。
137 & & // Evaluate the parsed script.
139 & & UpdaterInfo updater_
140 & & updater_info.cmd_pipe = cmd_
141 & & updater_info.package_zip = &
142 & & updater_info.version = atoi(version);
145 & & state.cookie = &updater_
146 & & state.script =
147 & & state.errmsg = NULL;
149 & & char* result = Evaluate(&state, root);
150 & & if (result == NULL) {
151 & & & & if (state.errmsg == NULL) {
152 & & & & & & fprintf(stderr, "script aborted (no error message)/n");
153 & & & & & & fprintf(cmd_pipe, "ui_print script aborted (no error message)/n");
154 & & & & } else {
155 & & & & & & fprintf(stderr, "script aborted: %s/n", state.errmsg);
156 & & & & & & char* line = strtok(state.errmsg, "/n");
157 & & & & & & while (line) {
158 & & & & & & & & fprintf(cmd_pipe, "ui_print %s/n", line);
159 & & & & & & & & line = strtok(NULL, "/n");
160 & & & & & & }
161 & & & & & & fprintf(cmd_pipe, "ui_print/n");
162 & & & & }
163 & & & & free(state.errmsg);
164 & & & & return 7;
165 & & } else {
166 & & & & fprintf(stderr, "script result was [%s]/n", result);
167 & & & & free(result);
解释执行脚本。 核心函数是 Evaluate。它会调用其他callback函数,而这些callback函数又会调用Evaluate去解析不同的脚本片段。从而实现一个简单的解释器。
170 & & mzCloseZipArchive(&za);
171 & & free(script);
173 & & return 0;
还没开始,就结束了。代码非常简单,因为细节隐藏在那些callback函数里。我们看一下。
RegisterBuiltins415 void RegisterBuiltins() {416 & & RegisterFunction("ifelse", IfElseFn);417 & & RegisterFunction("abort", AbortFn);418 & & RegisterFunction("assert", AssertFn);419 & & RegisterFunction("concat", ConcatFn);420 & & RegisterFunction("is_substring", SubstringFn);421 & & RegisterFunction("stdout", StdoutFn);422 & & RegisterFunction("sleep", SleepFn);423424 & & RegisterFunction("less_than_int", LessThanIntFn);425 & & RegisterFunction("greater_than_int", GreaterThanIntFn);426 }
这些语句控制执行流程。
RegisterInstallFunctions void RegisterInstallFunctions() {1038 & & RegisterFunction("mount", MountFn);1039 & & RegisterFunction("is_mounted", IsMountedFn);1040 & & RegisterFunction("unmount", UnmountFn);1041 & & RegisterFunction("format", FormatFn);1042 & & RegisterFunction("show_progress", ShowProgressFn);1043 & & RegisterFunction("set_progress", SetProgressFn);1044 & & RegisterFunction("delete", DeleteFn);1045 & & RegisterFunction("delete_recursive", DeleteFn);1046 & & RegisterFunction("package_extract_dir", PackageExtractDirFn);1047 & & RegisterFunction("package_extract_file", PackageExtractFileFn);1048 & & RegisterFunction("symlink", SymlinkFn);1049 & & RegisterFunction("set_perm", SetPermFn);1050 & & RegisterFunction("set_perm_recursive", SetPermFn); & & RegisterFunction("getprop", GetPropFn);1053 & & RegisterFunction("file_getprop", FileGetPropFn);1054 & & RegisterFunction("write_raw_image", WriteRawImageFn); & & RegisterFunction("apply_patch", ApplyPatchFn);1057 & & RegisterFunction("apply_patch_check", ApplyPatchCheckFn);1058 & & RegisterFunction("apply_patch_space", ApplyPatchSpaceFn); & & RegisterFunction("read_file", ReadFileFn);1061 & & RegisterFunction("sha1_check", Sha1CheckFn); & & RegisterFunction("ui_print", UIPrintFn); & & RegisterFunction("run_program", RunProgramFn);1066 }
这些语句执行各种功能。基本上,我们只需要知道用法就可以了。值得注意的是,run_program原语允许我们去执行自定义程序,这应该足够满足我们的个性化需求了。
人打赏支持
码字总数 42641
支付宝支付
微信扫码支付
打赏金额: ¥
已支付成功
打赏金额: ¥
& 开源中国(OSChina.NET) |
开源中国社区(OSChina.net)是工信部
指定的官方社区

我要回帖

更多关于 dota2客户端下载 的文章

 

随机推荐