1、前言
最近刚好在做android升级这一块,这是我在工作中总结和参考网上资料整理出来的文档,接触这一块时间也不会很久,纯属是自己的一些体会,如有错误,望大家指点!我工作板子型号是g18ref,采用的是晶晨半导体芯片aml8726mx,源码版本是aml8726mx.0521。当然说到升级,当然就要有升级包,升级包又分为完整包和增量包,所以我们得先从制作升级包说起。然后是Android系统的启动模式分析,Recovery工作原理,在上层选择更新包后是如何进入到Recovery服务的,以及在Recovery服务中具体是怎样处理update.zip升级包的,还有我们的安装脚本updater-script怎样被解析并执行的等一系列问题。下面就这些问题做一些大体的介绍!
2、完整包的制作
(1)cd ~/aml8726mx.0521(源码根目录)
(2). build/envsetup.sh
(3)lunch 10 (选择g18版本)
(4)cd common
(5)make mrproper (清除.o文件及破坏依赖关系,编译时才不会出错,否则有时会出错)
(6)cd ../
(7)make otapackage -j
(8)编译成功的话会在/out/target/product/g18ref/目录下看到g02ref-ota-[date].zip升级包、recovery.img内核文件u-boot.bin文件等,如果有修改了一些文件,然后编译又出错的时候,就删除以下目录内容aml8726mx.0521/out/target/product/g18ref/obj/KERNEL_OBJ/* 这样完整包就制作好了,在升级的时候选中它就可以升级了!
3、增量包的制作
(1)先在源码根目录创建一个目录
cd ~/aml8726mx.0521
mkdir ota
(2)先按正常编译方法编译出我们所需要的.zip包
cd ~/aml8726mx.0521
. build/envsetup.sh
lunch 10
make otapackage -j
通过以上步骤编译后,在以下目录找到我们所需的源码包g18ref-target_files-[date].zip
aml8726mx.0521/out/target/product/g18ref/obj/PACKAGING/target_files_intermediates
将以上编译产生的源码包g18ref-target_files-[date].zip拷贝到之前创建好的ota目录下
并将其改名为g18ref-[date]-old.zip,即用命令
mv g18ref-target_files-[date].zip g18ref-[date]-old.zip
(3)假设apk有更新,这时要编译成增量包,比如说:要加入优酷视频VST_1.0.7_7po.apk
apk或者自动装载apk都可以(见装载apk应用程序)
装载后再编译成.zip包,即:
cd ~/aml8726mx.0521
. build/envsetup.sh
lunch 10
make otapackage -j
通过以上步骤编译后,在以下目录找到我们所需的源码包g18ref-target_files-[date].zip
aml8726mx.0521/out/target/product/g18ref/obj/PACKAGING/target_files_intermediates
将以上编译产生的源码包g18ref-target_files-[date].zip拷贝到之前创建好的ota目录下
并将其改名为g18ref-[date]-new.zip
mv g18ref-target_files-[date].zip g18ref-[date]-new.zip
(4)然后在源码根目录下使用以下命令:
./build/tools/releasetools/ota_from_target_files -i ota/g18ref-[date]-old.zip ota/g18ref-[date]-new.zip ota/g18ref-[date]-update.zip
编译成功后会在ota目录下产生g18ref-[date]-update.zip,这个就是我们所需要的增量包
(5)进入到升级页面下,选择增量包g18ref-[date]-update.zip即可升级
4、装载apk应用程序
apk的装载,所以这里大体介绍一下。
.apk应用程序(在第一次编译时可采用,若已经有编译过了,还是建议用手动放入)
(1)修改文件:
aml8726mx.0521/device/amlogic/g18ref/g18ref.mk
在以上文件中的
PRODUCT_PACKAGES += \
...
加入VST_1.0.7_7po \
(2)并修改以下文件:
aml8726mx.0521/vendor/amloigc/prebuild/Android.mk
在以上文件中的
prebuilt_apps := \
...
加入VST_1.0.7_7po \
(3)再将所要加入的.apk应用程序放入以下目录中
aml8726mx.0521/vendor/amloigc/prebuild
apk应用程序(至少要编译过一次,才有这个目录)
(1)将所要加入的apk应用程序放入以下目录中即可
aml8726mx.0521/out/target/product/g18ref/system/app
、完整包详解
一、目录结构
|----boot.img
|----logo.img
|----bootloader.img
|----system/
|----recovery/
|----recovery-from-boot.p
|----etc/
|----install-recovery.sh|---META-INF/
|----com/
|----google/
|----android/
|----update-binary
|----updater-script
|----android/
|----metadata |----otacert
|CERT.RSA
|CERT.SF
|MANIFEST.MF
二、目录结构详解
1、boot.img是更新boot分区所需要的文件,这个boot.img主要包括kernel+ramdisk。
2、logo.img是更新logo分区所需要的文件,它主要用于决定升级时显示什么图标。
3、bootloader.img是更新bootloader分区所需要的文件,它主要用于启动内核,为系统准备合适的软硬件环境。
4、system/目录的内容在升级后会放到系统的system分区。主要用来更新系统的一些应用和应用所需的一些库等等。可以将Android源码编译
aml8726mx.0521/out/target/product/g18ref/system/中的所有文件拷贝到这个目录来代替。
5、recovery/目录中的recovery-from-boot.p是boot.img和recovery.img的补丁(patch),主要用来更新recovery分区,其中etc/目录下的install-recovery.sh是其更新脚本。
6、update-binary是一个二进制文件,相当于一个脚本解释器,能够识别updater-script中描述的操作。该文件在Android源码编译后aml8726mx.0521/out/target/product/g18ref/system/bin/updater生成,可将updater重命名为update-binary得到。该文件在更新包中的名字由源码中bootable/recovery/install.cpp宏ASSUMED_UPDATE_BINARY_NAME的值而定。
7、updater-script:此文件是一个脚本文件,具体描述了更新过程。我们可以根据具体情况编写该脚本来适应我们的具体需求。该文件的命名由源码中bootable/recovery/updater/install.c文件中的宏SCRIPT_NAME的值而定。
8、文件是描述设备信息及环境变量的元数据。主要包括一些编译选项,签名公钥,时间戳以及设备型号等。
9、我们还可以在包中添加userdata目录,来更新系统中的用户数据部分。这部分内容在更新后会存放在系统的/data目录下。
10、update.zip包的签名:update.zip更新包在制作完成后需要对其签名,否则在升级时会出现认证失败的错误提示。
而且签名要使用和目标板一致的加密公钥。加密公钥及加密需要的三个文件在Android源码编译后生成的具体路径为:
out/host/linux-x86/framework/signapk.jar
build/target/product/security/testkey.x509.pem
。
make otapackage制作生成的update.zip包是已签过名的,如果自己做update.zip包时必须手动对其签名。
具体的加密方法:
$ java –jar gingerbread/out/host/linux/framework/signapk.jar –w gingerbread/build/target/product/security/testkey.x509.pem gingerbread/build/target/product/security/testkey.pk8 update.zip update_signed.zip
以上命令在update.zip包所在的路径下执行,其中signapk.jar testkey.x509.pem以及testkey.pk8文件的引用使用绝对路径。update.zip 是我们已经打好的包,update_signed.zip包是命令执行完生成的已经签过名的包。
11、MANIFEST.MF:这个manifest文件定义了与包的组成结构相关的数据。类似Android应用的mainfest.xml文件。
12、CERT.RSA:与签名文件相关联的签名程序块文件,它存储了用于签名JAR文件的公共签名。
13、CERT.SF:这是JAR文件的签名文件,其中前缀CERT代表签名者。另外,在具体升级时,对update.zip包检查时大致会分三步:
①检验SF文件与RSA文件是否匹配。
②检验MANIFEST.MF与签名文件中的digest是否一致。
③检验包中的文件与MANIFEST中所描述的是否一致。
ota_from_target_files脚本说明
1、脚本源码(目录:/build/tools/releasetools/ota_from_target_files)
#!/usr/bin/env python
#
# Copyright (C) 2008 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
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
the value of default_system_dev_certificate from the input
target-files's META/misc_info.txt, or
"build/target/product/security/testkey" if that value is not
specified).
For incremental OTAs, the default value is based on the source
target-file, not the target build.
-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.
-a (--aslr_mode) <on|off>
Specify whether to turn on ASLR for the package (on by default).
"""
import sys
if sys.hexversion < 0x02040000:
print >> sys.stderr, "Python 2.4 or newer is required."
sys.exit(1)
import copy
import errno
import os
import re
import subprocess
import tempfile
import time
import zipfile
try:
from hashlib import sha1 as sha1
except ImportError:
from sha import sha as sha1
import common
import edify_generator
OPTIONS = common.OPTIONS
OPTIONS.package_key = None
OPTIONS.incremental_source = None
OPTIONS.require_verbatim = set()
OPTIONS.prohibit_verbatim = set(("system/build.prop",))
OPTIONS.patch_threshold = 0.95
OPTIONS.wipe_user_data = False
OPTIONS.no_wipe_system = False
OPTIONS.omit_prereq = False
OPTIONS.extra_script = None
OPTIONS.aslr_mode = True
OPTIONS.worker_threads = 3
boot_img_exists = 0
recovery_img_exists = 0
def MostPopularKey(d, default):
"""Given a dict, return the key corresponding to the largest
value. Returns 'default' if the dict is empty."""
x = [(v, k) for (k, v) in d.iteritems()]
if not x: return default
x.sort()
return x[-1][1]
def IsSymlink(info):
"""Return true if the zipfile.ZipInfo object passed in represents a
symlink."""
return (info.external_attr >> 16) == 0120777
def IsRegular(info):
"""Return true if the zipfile.ZipInfo object passed in represents a
symlink."""
return (info.external_attr >> 28) == 010class Item:
"""Items represent the metadata (user, group, mode) of files and
directories in the system image."""
ITEMS = {}
def __init__(self, name, dir=False):
self.name = name
self.uid = None
self.gid = None
self.mode = None
self.dir = dir
if name:
self.parent = Item.Get(os.path.dirname(name), dir=True)
self.parent.children.append(self)
else:
self.parent = None
if dir:
self.children = []
def Dump(self, indent=0):
if self.uid is not None:
print "%s%s %d %d %o" % (" "*indent, self.name, self.uid, self.gid, self.mode)
else:
print "%s%s %s %s %s" % (" "*indent, self.name, self.uid, self.gid, self.mode)
if self.dir:
print "%s%s" % (" "*indent, self.descendants)
print "%s%s" % (" "*indent, self.best_subtree)
for i in self.children:
i.Dump(indent=indent+1)
@classmethod
def Get(cls, name, dir=False):
if name not in cls.ITEMS:
cls.ITEMS[name] = Item(name, dir=dir)
return cls.ITEMS[name]
@classmethod
def GetMetadata(cls, input_zip):
try:
# See if the target_files contains a record of what the uid,
# gid, and mode is supposed to be.
output = input_zip.read("META/filesystem_config.txt")
except KeyError:
# Run the external 'fs_config' program to determine the desired
# uid, gid, and mode for every Item object. Note this uses the
# one in the client now, which might not be the same as the one
# used when this target_files was built.
p = common.Run(["fs_config"], stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
suffix = { False: "", True: "/" }
input = "".join(["%s%s\n" % (i.name, suffix[i.dir])
for i in cls.ITEMS.itervalues() if i.name])
output, error = p.communicate(input)
assert not error
for line in output.split("\n"):
if not line: continue
name, uid, gid, mode = line.split()
i = cls.ITEMS.get(name, None)
if i is not None:
i.uid = int(uid)
i.gid = int(gid)
i.mode = int(mode, 8)
if i.dir:
i.children.sort(key=lambda i: i.name)
# set metadata for the files generated by this script.
i = cls.ITEMS.get("system/recovery-from-boot.p", None)
if i: i.uid, i.gid, i.mode = 0, 0, 0644
i = cls.ITEMS.get("system/etc/install-recovery.sh", None)
if i: i.uid, i.gid, i.mode = 0, 0, 0544
def CountChildMetadata(self):
"""Count up the (uid, gid, mode) tuples for all children and
determine the best strategy for using set_perm_recursive and
set_perm to correctly chown/chmod all the files to their desired
values. Recursively calls itself for all descendants.
Returns a dict of {(uid, gid, dmode, fmode): count} counting up
all descendants of this node. (dmode or fmode may be None.) Also
sets the best_subtree of each directory Item to the (uid, gid,
dmode, fmode) tuple that will match the most descendants of that
Item.
"""
assert self.dir
d = self.descendants = {(self.uid, self.gid, self.mode, None): 1}
for i in self.children:
if i.dir:
for k, v in i.CountChildMetadata().iteritems():
d[k] = d.get(k, 0) + v
else:
k = (i.uid, i.gid, None, i.mode)
d[k] = d.get(k, 0) + 1
# Find the (uid, gid, dmode, fmode) tuple that matches the most
# descendants.
# First, find the (uid, gid) pair that matches the most
# descendants.
ug = {}
for (uid, gid, _, _), count in d.iteritems():
ug[(uid, gid)] = ug.get((uid, gid), 0) + count
ug = MostPopularKey(ug, (0, 0))
# Now find the dmode and fmode that match the most descendants
# with that (uid, gid), and choose those.
best_dmode = (0, 0755)
best_fmode = (0, 0644)
for k, count in d.iteritems():
if k[:2] != ug: continue
if k[2] is not None and count >= best_dmode[0]: best_dmode = (count, k[2])
if k[3] is not None and count >= best_fmode[0]: best_fmode = (count, k[3])
self.best_subtree = ug + (best_dmode[1], best_fmode[1])
return d
def SetPermissions(self, script):
"""Append set_perm/set_perm_recursive commands to 'script' to
set all permissions, users, and groups for the tree of files
rooted at 'self'."""
self.CountChildMetadata()
def recurse(item, current):
# current is the (uid, gid, dmode, fmode) tuple that the current
# item (and all its children) have already been set to. We only
# need to issue set_perm/set_perm_recursive commands if we're
# supposed to be something different.
if item.dir:
if current != item.best_subtree:
script.SetPermissionsRecursive("/"+item.name, *item.best_subtree)
current = item.best_subtree
if item.uid != current[0] or item.gid != current[1] or \
item.mode != current[2]:
script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
for i in item.children:
recurse(i, current)
else:
if item.uid != current[0] or item.gid != current[1] or \
item.mode != current[3]:
script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
recurse(self, (-1, -1, -1, -1))
def CopySystemFiles(input_zip, output_zip=None,
substitute=None):
"""Copies files underneath system/ in the input zip to the output
zip. Populates the Item class with their metadata, and returns a
list of symlinks. output_zip may be None, in which case the copy is
skipped (but the other side effects still happen). substitute is an
optional dict of {output filename: contents} to be output instead of
certain input files.
"""
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 substitute and fn in substitute and substitute[fn] is None:
continue
if output_zip is not None:
if substitute and fn in substitute:
data = substitute[fn]
else:
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)
symlinks.sort()
return symlinks
def CopyFirmwareFiles(input_zip, output_zip, script):
aml_logo_img_exists = 0
try:
aml_logo_img_info = input_zip.getinfo("LOGO/aml_logo")
aml_logo_img_exists = 1
aml_logo_img = common.File("aml_logo.img", input_zip.read("LOGO/aml_logo"))
except KeyError:
print 'WARNING: Did not find aml_logo'
if aml_logo_img_exists:
common.CheckSize(aml_logo_img.data, "aml_logo.img", OPTIONS.info_dict)
common.ZipWriteStr(output_zip, "aml_logo.img", aml_logo_img.data)
script.WriteRawImage("/aml_logo", "aml_logo.img")
logo_img_exists = 0
try:
logo_img_info = input_zip.getinfo("LOGO/logo")
logo_img_exists = 1
logo_img = common.File("logo.img", input_zip.read("LOGO/logo"))
except KeyError:
print 'WARNING: Did not find logo'
if logo_img_exists:
common.CheckSize(logo_img.data, "logo.img", OPTIONS.info_dict)
common.ZipWriteStr(output_zip, "logo.img", logo_img.data)
script.WriteRawImage("/logo", "logo.img")
param_exist = 0
for info in input_zip.infolist():
if info.filename.startswith("PARAM/"):
param_exist = 1
info2 = copy.copy(info)
info2.filename = info.filename[6:]
data = input_zip.read(info.filename)
output_zip.writestr(info2, data)
if param_exist:
try:
script.FormatPartition("/param")
script.Mount("/param")
script.UnpackPackageDir("param", "/param")
except KeyError:
print "WARNING: can not find param partition"
else:
print "WARNING: Did not find param"
def SignOutput(temp_zip_name, output_zip_name):
key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
pw = key_passwo