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命令行文件打包基本流程,其实本人理解的也不是很透彻,也参考了一些网上的资料,如果有疑问,欢迎指正,讨论。

时间: 2024-10-25 18:39:14

Android OTA升级包制作脚本详解(三,打包)的相关文章

Android OTA升级包制作脚本详解(二,解压缩)

第一步:解压缩(ota_from_target_files) print "unzipping target target-files..." OPTIONS.input_tmp, input_zip = common.UnzipTemp(args[0]) 上面的代码是开始进行解压缩的入口 def UnzipTemp(filename, pattern=None): """Unzip the given archive into a temporary d

Android OTA升级包制作脚本详解(四,生成升级脚本updater-script)

updater-script简介: updater-script是我们升级时所具体使用到的脚本文件,它主要用以控制升级流程的主要逻辑.具体位置位于更新包中/META-INFO/com/google/android/目录下,在我们制作升级包的时候产生. updater-script生成: 那么升级脚本updater-script是如何产生的呢,我们来看ota_from_target_file中的一条语句,这个之前有过介绍. #在/build/tools/releasetools/目录下的模块edi

Android OTA升级包制作脚本详解(五,升级脚本updater-script的执行<1>)

写在前面: 首先当我们执行升级脚本updater-script的时候,就表示我们已经进入了升级安装状态.那么在我们就从实际的安装作为入口开始分析.也就是说我们从install.cpp中的install_package函数开始一步步来分析. 这里主要分析与脚本相关的部分,其他的请参考这位朋友的博文http://blog.chinaunix.net/uid-22028566-id-3533856.html,我也很受启发.这里也借用一张图来帮助流程上的分析. 下面是调用的流程:install_pack

Android OTA升级包制作脚本详解(一,参数解析)

写在前面: "build/tools/releasetools/ota_from_target_files  -u lk.bin  -n target.zip update.zip"这是制作整包的命令,很显然这里支持lk升级.本系列博文主要对该命令的执行流程及原理进行一个系统的分析,涉及到/build/tools/releasetools/目录下多个模块如ota_from_target_files.common等.由于本人对python了解粗浅,文中所涉及到的python语法大都做了注

android OTA升级包制作

0.签名 java -Xmx2048m -jar out/host/linux-x86/framework/signapk.jar -w build/target/product/security/testkey.x509.pem build/target/product/security/testkey.pk8 这个key是debug模式下自动生成的key 你也可以通过openssl创建自己私有的key OTA打包过程中会自动使用生成的key进行签名,OTA升级包里签名的位置如下 │ [OTA

android OTA升级包制作【转】

本文转载自:http://www.thinksaas.cn/topics/0/445/445670.html 0.签名 java -Xmx2048m -jar out/host/linux-x86/framework/signapk.jar -w build/target/product/security/testkey.x509.pem build/target/product/security/testkey.pk8 这个key是debug模式下自动生成的key 你也可以通过openssl创

Android高效率编码-第三方SDK详解系列(三)——JPush推送牵扯出来的江湖恩怨,XMPP实现推送,自定义客户端推送

Android高效率编码-第三方SDK详解系列(三)--JPush推送牵扯出来的江湖恩怨,XMPP实现推送,自定义客户端推送 很久没有更新第三方SDK这个系列了,所以更新一下这几天工作中使用到的推送,写这个系列真的很要命,你要去把他们的API文档大致的翻阅一遍,而且各种功能都实现一遍,解决各种bug各种坑,不得不说,极光推送真坑,大家使用还是要慎重,我们看一下极光推送的官网 https://www.jpush.cn/common/ 推送比较使用,很多软件有需要,所以在这个点拿出来多讲讲,我们本节

Android基础入门教程——8.3.6 Paint API之—— Xfermode与PorterDuff详解(三)

Android基础入门教程--8.3.6 Paint API之-- Xfermode与PorterDuff详解(三) 标签(空格分隔): Android基础入门教程 本节引言: 上一节,我们学习了Xfermode中的三儿子:PorterDuffXfermode构造方法中的为一个参数: PorterDuff.Mode,我们在观看了16种图片混排模式后,又自己写代码来验证了一下文档中 18种不同的混排模式,18种是新增了ADD和OVERLAY两种模式!当然,仅仅验证知道是不够的, 本节我们来写个例子

Android触摸屏事件派发机制详解与源码分析三(Activity篇)

PS一句:最终还是选择CSDN来整理发表这几年的知识点,该文章平行迁移到CSDN.因为CSDN也支持MarkDown语法了,牛逼啊! [工匠若水 http://blog.csdn.net/yanbober] 该篇承接上一篇<Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)>,阅读本篇之前建议先阅读. 1 背景 还记得前面两篇从Android的基础最小元素控件(View)到ViewGroup控件的触摸屏事件分发机制分析吗?你可能看完会有疑惑,View的事件是ViewGro