android升级–升级包的制作

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

5、完整包详解

一、目录结构

|----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

二、目录结构详解

1boot.img是更新boot分区所需要的文件,这个boot.img主要包括kernel+ramdisk

2logo.img是更新logo分区所需要的文件,它主要用于决定升级时显示什么图标。

3bootloader.img是更新bootloader分区所需要的文件,它主要用于启动内核,为系统准备合适的软硬件环境。

4system/目录的内容在升级后会放到系统的system分区。主要用来更新系统的一些应用和应用所需的一些库等等。可以将Android源码编译

aml8726mx.0521/out/target/product/g18ref/system/中的所有文件拷贝到这个目录来代替。

5recovery/目录中的recovery-from-boot.pboot.imgrecovery.img的补丁(patch),主要用来更新recovery分区,其中etc/目录下的install-recovery.sh是其更新脚本。

6update-binary是一个二进制文件,相当于一个脚本解释器,能够识别updater-script中描述的操作。该文件在Android源码编译后aml8726mx.0521/out/target/product/g18ref/system/bin/updater生成,可将updater重命名为update-binary得到。该文件在更新包中的名字由源码中bootable/recovery/install.cppASSUMED_UPDATE_BINARY_NAME的值而定。

7updater-script:此文件是一个脚本文件,具体描述了更新过程。我们可以根据具体情况编写该脚本来适应我们的具体需求。该文件的命名由源码中bootable/recovery/updater/install.c文件中的宏SCRIPT_NAME的值而定。

8 metadata文件是描述设备信息及环境变量的元数据。主要包括一些编译选项,签名公钥,时间戳以及设备型号等。

9我们还可以在包中添加userdata目录,来更新系统中的用户数据部分。这部分内容在更新后会存放在系统的/data目录下。

10update.zip包的签名:update.zip更新包在制作完成后需要对其签名,否则在升级时会出现认证失败的错误提示。

而且签名要使用和目标板一致的加密公钥。加密公钥及加密需要的三个文件在Android源码编译后生成的具体路径为:

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

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

               build/target/product/security/testkey.pk8 

              我们用命令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文件。
12CERT.RSA:与签名文件相关联的签名程序块文件,它存储了用于签名JAR文件的公共签名。
13CERT.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) == 010


class 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



原文:http://www.xuebuyuan.com/1154055.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值