Android OTA升级包制作脚本详解(三,打包)

这是在ota_from_target_files中mian函数中打包的主要流程语句:

#抽象一个新的临时文件
  temp_zip_file = tempfile.NamedTemporaryFile()
  #创建一个zip包用来进行打包
  output_zip = zipfile.ZipFile(temp_zip_file, "w",
                               compression=zipfile.ZIP_DEFLATED)
  #判断是差分还是整包
  if OPTIONS.incremental_source is None:
    WriteFullOTAPackage(input_zip, output_zip)
    if OPTIONS.package_key is None:
	  #判断是否制定了签名的key,如果没有则使用默认的key进行签名,关于平台的签名问题,相关博文已经设计,请同学们自行查阅
      OPTIONS.package_key = OPTIONS.info_dict.get(
          "default_system_dev_certificate",
          "build/target/product/security/testkey")
  else:
    #这里是制作差分包所涉及的一系列操作,暂时略过。
    print "unzipping source target-files..."
    OPTIONS.source_tmp, source_zip = common.UnzipTemp(OPTIONS.incremental_source)
    OPTIONS.target_info_dict = OPTIONS.info_dict
    OPTIONS.source_info_dict = common.LoadInfoDict(source_zip)
    if OPTIONS.package_key is None:
      OPTIONS.package_key = OPTIONS.source_info_dict.get(
          "default_system_dev_certificate",
          "build/target/product/security/testkey")
    if OPTIONS.verbose:
      print "--- source info ---"
      common.DumpInfoDict(OPTIONS.source_info_dict)
    WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)

  output_zip.close()
第二步:制作整包

下面是制作整包主函数WriteFullOTAPackage中的部分代码。

#函数的处理过程是先获得脚本的生成器。默认格式是edify。然后获得metadata元数据,此数据来至于Android的一些环境变量。然后获得设备配置参数比如api函数的版本。然后判断是否忽略时间戳。
def WriteFullOTAPackage(input_zip, output_zip):
  # TODO: how to determine this?  We don't know what version it will
  # be installed on top of.  For now, we expect the API just won't
  # change very often.
  #这里引入了一个新的模块edify_generator,并且抽象一个脚本生成器,用来生成edify脚本。这里的脚脚本指的就是updater-script,它安装脚本,它是一个文本文件。
  #edify有两个主要的文件。这些文件可以在.zip文件内的META-INF/com/google/android文件夹中找到。①update-binary -- 当用户选择刷入.zip(通常是在恢复模式中)时所执行的二进制解释器。②updater-script -- 安装脚本,它是一个文本文件。
  #那么edify是什么呢?edify是用于从.zip文件中安装CyanogenMod和其它软件的简单脚本语言。edify脚本不一定是用于更新固件。它可以用来替换/添加/删除特定的文件,甚至格式分区。通常情况下,edify脚本运行于用户在恢复模式中选择“刷写zip”时。
  script = edify_generator.EdifyGenerator(3, OPTIONS.info_dict)
  #创建一个元数据字典用来封装更行包的相关的系统属性,如ro.build.fingerprint(系统指纹)等。
  metadata = {"post-build": GetBuildProp("ro.build.fingerprint",
                                         OPTIONS.info_dict),
              "pre-device": GetBuildProp("ro.product.device",#(采用的设备)
                                         OPTIONS.info_dict),
              "post-timestamp": GetBuildProp("ro.build.date.utc",#(系统编译的时间(数字版),没必要修改)
                                             OPTIONS.info_dict),
              }
  #获得一些环境变量,封装在DEviceSpecificParams类当中,这是一个封装了设备特定属性的类;下面每个设备参数之前都有提到过,这里不再赘述。
  device_specific = common.DeviceSpecificParams(
      input_zip=input_zip,
      input_version=OPTIONS.info_dict["recovery_api_version"],
      output_zip=output_zip,
      script=script,
      input_tmp=OPTIONS.input_tmp,
      metadata=metadata,
      info_dict=OPTIONS.info_dict)
  #下面这段代码我们可以理解为不允许降级,也就是说在脚本中的这段Assert语句,使得update zip包只能用于升级旧版本。
  if not OPTIONS.omit_prereq:
    ts = GetBuildProp("ro.build.date.utc", OPTIONS.info_dict)#得到系统编译世界时间
    ts_text = GetBuildProp("ro.build.date", OPTIONS.info_dict)#得到编译日期
    script.AssertOlderBuild(ts, ts_text)
  #下面的Assert语句,表示update zip包只能用于同一设备,即目标设备的 ro.product.device 必须跟update.zip中的相同。
  AppendAssertions(script, OPTIONS.info_dict)
  #回调函数,用于调用设备相关代码。开始升级时调用
  device_specific.FullOTA_Assertions()

  def FullOTA_Assertions(self):
    """Called after emitting the block of assertions at the top of a
    full OTA package.  Implementations can add whatever additional
    assertions they like."""
    return self._DoCall("FullOTA_Assertions")

  def _DoCall(self, function_name, *args, **kwargs):
    """Call the named function in the device-specific module, passing
    the given args and kwargs.  The first argument to the call will be
    the DeviceSpecific object itself.  If there is no module, or the
    module does not define the function, return the value of the
    'default' kwarg (which itself defaults to None)."""
    if self.module is None or not hasattr(self.module, function_name):
      return kwargs.get("default", None)
    return getattr(self.module, function_name)(*((self,) + args), **kwargs)

上面只是很简单的调用,不过也简单的说一下,这里self.module的值为None,在此之前我们并未没有载入“device_specific”模块。在build/tools/release tools/目录下面也没有找到相关模块文件(注! 也可参考在制作整包时所打印的log如“unable to load device-specific module; assuming none”)

接着,我们得到一个recovery_image的文件对象。具体实现的代码如下:

  recovery_img = common.GetBootableImage("recovery.img", "recovery.img",
                                         OPTIONS.input_tmp, "RECOVERY")

def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
                     info_dict=None):
  """Return a File object (with name 'name') with the desired bootable
  image.  Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
  'prebuilt_name', otherwise construct it from the source files in
  'unpack_dir'/'tree_subdir'."""
  #连接两个文件名地址,例如os.path.join("D:\","join.txt")结果是D:\join.txt,如果我们有写的动作,就会生成相应目录下相应文件,否则不会有这个文件存在。
  prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
  if os.path.exists(prebuilt_path):
    print "using prebuilt %s..." % (prebuilt_name,)
    return File.FromLocalFile(name, prebuilt_path)
  else:
    print "building image from target_files %s..." % (tree_subdir,)
    fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
	#将创建的文件对象封装在File类中返回,这里我们参考File源码,就可以发现,File类只是对文件的一个抽象,具体还封装了写和读的操作
    return File(name, BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
                                         os.path.join(unpack_dir, fs_config),
                                         info_dict))

下面贴出File中具体函数,首先我们看到类的入口函数中有一个参数data,那么上面返回的File对象中的第二个参数即为data,可以理解为输入流:

class File(object):
  def __init__(self, name, data):
    self.name = name
    self.data = data
    self.size = len(data)
    self.sha1 = sha1(data).hexdigest()

  @classmethod
  def FromLocalFile(cls, name, diskname):
    f = open(diskname, "rb")
    data = f.read()
    f.close()
    return File(name, data)

  def WriteToTemp(self):
    t = tempfile.NamedTemporaryFile()
    t.write(self.data)
    t.flush()
    return t

  def AddToZip(self, z):
    ZipWriteStr(z, self.name, self.data)

DIFF_PROGRAM_BY_EXT = {
    ".gz" : "imgdiff",
    ".zip" : ["imgdiff", "-z"],
    ".jar" : ["imgdiff", "-z"],
    ".apk" : ["imgdiff", "-z"],
    ".img" : "imgdiff",
    }

def BuildBootableImage(sourcedir, fs_config_file, info_dict=None):
  """Take a kernel, cmdline, and ramdisk directory from the input (in
  'sourcedir'), and turn them into a boot image.  Return the image
  data, or None if sourcedir does not appear to contains files for
  building the requested image."""
  #作为access()的mode参数,测试path是否存在. 
  if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
      not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
    return None

  if info_dict is None:
    info_dict = OPTIONS.info_dict

  #创建临时文件对象
  ramdisk_img = tempfile.NamedTemporaryFile()
  img = tempfile.NamedTemporaryFile()

  if os.access(fs_config_file, os.F_OK):
    #使用mkbootfs工具(mkbootfs工具是编译完毕Android源代码以后,在源码目录下/out/host/linux-x86/bin自动生成的)创建ramdisk
    cmd = ["mkbootfs", "-f", fs_config_file, os.path.join(sourcedir, "RAMDISK")]
  else:
    cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
  p1 = Run(cmd, stdout=subprocess.PIPE)
  #fileno()用来取得参数stream指定的文件流所使用的文件描述词,而mkbootfs和minigzip是通过MKBOOTFS和MINIGZIP这两个变量描述的mkbootfs和minigzip工具来生成一个格式为cpio的ramdisk.img了。mkbootfs和minigzip这两个工具对应的源码分别位于system/core/cpio和external/zlib目录中。
  p2 = Run(["minigzip"],
           stdin=p1.stdout, stdout=ramdisk_img.file.fileno())

  p2.wait()
  p1.wait()
  assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (targetname,)
  assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (targetname,)
  #调用Android给的命令行文件mkbootimg(out/host/linux-x86/bin/)来打包
  cmd = ["mkbootimg", "--kernel", os.path.join(sourcedir, "kernel")]

  fn = os.path.join(sourcedir, "cmdline")
  if os.access(fn, os.F_OK):
    cmd.append("--cmdline")
    cmd.append(open(fn).read().rstrip("\n"))

  fn = os.path.join(sourcedir, "base")
  if os.access(fn, os.F_OK):
    cmd.append("--base")
    cmd.append(open(fn).read().rstrip("\n"))

  fn = os.path.join(sourcedir, "pagesize")
  if os.access(fn, os.F_OK):
    cmd.append("--pagesize")
    cmd.append(open(fn).read().rstrip("\n"))

  args = info_dict.get("mkbootimg_args", None)
  if args and args.strip():
    cmd.extend(args.split())

  #wschen 2013-06-06 for firmware version in bootimage header and limit max length to 15 bytes
  fn = os.path.join(sourcedir, "board")
  if os.access(fn, os.F_OK):
    cmd.append("--board")
    cmd.append(open(fn).read().rstrip("\n")[:15])

#  cmd.extend(["--ramdisk", ramdisk_img.name,
#              "--output", img.name])

  cmd.extend(["--ramdisk", os.path.join(sourcedir, "ramdisk"),
              "--output", img.name])

  p = Run(cmd, stdout=subprocess.PIPE)
  p.communicate()
  assert p.returncode == 0, "mkbootimg of %s image failed" % (
      os.path.basename(sourcedir),)

  img.seek(os.SEEK_SET, 0)
  data = img.read()

  ramdisk_img.close()
  img.close()

  return data
上面是使用Android命令行文件打包基本流程,其实本人理解的也不是很透彻,也参考了一些网上的资料,如果有疑问,欢迎指正,讨论。












郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。