blockimgdiff中方法分析01

关于blockimgdiff文件,本篇文档将详细分析初步生成transfer对象和生成diff的字典,并对几个重要方法 进行解析.   

一、传入blockimgdiff的参数分析  

二、blockimgdiff中方法分析

 

一、传入blockimgdiff的参数分析

我们都知道生成差分包从ota_from_target_file.py 的WriteBlockIncrementalOTAPackage方法开始,有部分 流程我在之前的文档做包问题中提到过,有一部分我直接copy过来,核心代码部分

以上截图首先拿到system_src,system_tgt,通过getimage方法
  system_src = GetImage("system", OPTIONS.source_tmp)
  system_tgt = GetImage("system", OPTIONS.target_tmp)
而getimage的返回值为spare_img对象
return sparse_img.SparseImage(path, mappath, clobbered_blocks)

传入system_diff = common.BlockDifference("system", system_tgt, system_src,
                                       check_first_block,
                                       version=blockimgdiff_version,
                                       disable_imgdiff=disable_imgdiff)

common.blockdifference,跳转到common.py  class BlockDifference(object):中
主要做了两步
1.生成blockimgdiff对象,传入对应src,tgt参数,也就是之前获得的spare_img对象
b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
                                    version=self.version,
                                    disable_imgdiff=self.disable_imgdiff)
2.调用blockimgdiff的Compute方法
b.Compute(self.path)

以上就是调用到blockimgdiff的大致流程,接下来进入我们的主要分析部分

  二、blockimgdiff中方法分析

1.blockimgdiff init  其中有一些参数大概做下说明
  def __init__(self, tgt, src=None, threads=None, version=4,
               disable_imgdiff=False):
    #声明两个空的字典,后面会用到
    self.src_basenames = {}
    self.src_numpatterns = {}
    #声明了一个空的集合.重点关注,后面会使用到
    self.transfers = []
    # The range sets in each filemap should comprise a partition of
    # the care map.
    #src tgt 也就是传进来的sparse_img对象,那么这其中给到我们的有用的信息,caremap和filemap
    #0-32766 32768-32769 32865-32866 .......360925-364543 这些数字就是caremap,也就是
    system分区中文件占用的block块,为什么会有空的呢.因为sparse格式里面会存在头文件等
    print(src.care_map)
    #/system/bin/applypatch    打印出来可以看到file_map中的key值为system.map文件中的文件名
    print(src.file_map.keys())
    #<RangeSet("130771-130825")>  values为该文件所占的block区间
    print(src.file_map.values())
    self.AssertPartition(src.care_map, src.file_map.values())
    self.AssertPartition(tgt.care_map, tgt.file_map.values())
    
    这里简单说明下,本身python 中只有range列表,上面出现的rangeset是Android为了装载所占用block的
    区间导入的Rangelib例如上面的130771-130825

 

2.Compute(self, prefix): 接下来就是调用compute生成最终的差分包中transfer.list的形态

,本次我们先 介绍前三个方法     

#名称转换,将基础版本中的文件名转换为    

self.AbbreviateSourceNames()     

#生成Transer对象    

self.FindTransfers()      #

Find the ordering dependencies among transfers (this is O(n^2)     # in the number of transfers).    

#生成gose_before,gose_after两个字典,相当于存放着基础版本文件和升级版本的差异和差异block的大小     self.GenerateDigraph()     

第一个方法只是做了简单的替换,我们将着重分析后两个方法,基本上每个方法将生成对应的数据为下一步的     计算做准备

3.self.AbbreviateSourceNames()
    for k in self.src.file_map.keys():
      #self.src.file_map.keys() 我们之前说明了self.src.file_map.keys这个方法获得的就是system.map
中的文件名,当然也包含文件路径,os.path.basename方法的作用就是抹去文件路径,只保留文件名称
例如:k=/system/bin/applypatch,那么b=applypatch
      b = os.path.basename(k)
      #print("AbbreviateSourceNames" + "++++b" + b)
      #以下代码的含义是python字典的写法,表示的是字典中key为b,values为k
      self.src_basenames[b] = k
      #sub为替换字符串的方法 [0-9]为正则表达式,表示如果b中有0-9的数字,会执行替换为#,重新赋值为b	
      b = re.sub("[0-9]+", "#", b)
      #print("AbbreviateSourceNames" + "----b" + b)
      #装载到src_numpatterns,key为b,values为k
      self.src_numpatterns[b] = k

4.def FindTransfers(self):方法分析

  print("Finding transfers...")

    empty = RangeSet()
    for tgt_fn, tgt_ranges in self.tgt.file_map.items():
      
      if tgt_fn == "__ZERO":
        print("tgt_fn == __ZERO"+ "++++tgt_fn=" + tgt_fn)
        # the special "__ZERO" domain is all the blocks not contained
        # in any file and that are filled with zeros.  We have a
        # special transfer style for zero blocks.
        src_ranges = self.src.file_map.get("__ZERO", empty)
        AddTransfer(tgt_fn, "__ZERO", tgt_ranges, src_ranges,
                    "zero", self.transfers)
        continue

      elif tgt_fn == "__COPY":
        print("tgt_fn == __COPY" + "++++tgt_fn=" + tgt_fn)
        # "__COPY" domain includes all the blocks not contained in any
        # file and that need to be copied unconditionally to the target.
        AddTransfer(tgt_fn, None, tgt_ranges, empty, "new", self.transfers)
        continue

      elif tgt_fn in self.src.file_map:
        print("tgt_fn in self.src.file_map" + "++++tgt_fn=" + tgt_fn)
        # Look for an exact pathname match in the source.
        AddTransfer(tgt_fn, tgt_fn, tgt_ranges, self.src.file_map[tgt_fn],
                    "diff", self.transfers, True)
        continue

      b = os.path.basename(tgt_fn)
      if b in self.src_basenames:
        print("b in self.src_basenames" + "++++tgt_fn=" + tgt_fn + "self.transfers=" + self.transfers)
        # Look for an exact basename match in the source.
        src_fn = self.src_basenames[b]
        AddTransfer(tgt_fn, src_fn, tgt_ranges, self.src.file_map[src_fn],
                    "diff", self.transfers, True)
        continue

      b = re.sub("[0-9]+", "#", b)
      if b in self.src_numpatterns:
        print("b in self.src_numpatterns" + "++++tgt_fn=" + tgt_fn)
        # Look for a 'number pattern' match (a basename match after
        # all runs of digits are replaced by "#").  (This is useful
        # for .so files that contain version numbers in the filename
        # that get bumped.)
        src_fn = self.src_numpatterns[b]
        AddTransfer(tgt_fn, src_fn, tgt_ranges, self.src.file_map[src_fn],
                    "diff", self.transfers, True)
        continue

      AddTransfer(tgt_fn, None, tgt_ranges, empty, "new", self.transfers)
在做包的log中可以看到,执行的打印finding transfer..
声明一个空的列表
    empty = RangeSet()
开启for循环,
    for tgt_fn, tgt_ranges in self.tgt.file_map.items():
遍历tgt_fn和tgt_ranges,这里面有很多的判断条件,大部分都
进入了
elif tgt_fn in self.src.file_map:
AddTransfer(tgt_fn, tgt_fn, tgt_ranges, 
self.src.file_map[tgt_fn],"diff", self.transfers, True)
也就是当tgt文件名存在与src中时,这个时候会进入addTranster
方法,并将一些参数传入,"diff" 是指的存在差分,self.transfers=[]
如果前五个条件都走完了,如果有APK都不符合,那么就进入最后的
AddTransfer(tgt_fn, None, tgt_ranges, empty, "new", self.transfers)
传入的src_fn,src_ranges为空,也就是作为新增文件了

这里其他类似的方法我们先不做分析,先只分析主线diff流程
AddTransfer/AddSplitTransfers
这个方法作为一个过度,单独对odex化做了一些处理,进而将参数进一步传入了AddSplitTransfers
def AddTransfer(tgt_name, src_name, tgt_ranges, src_ranges, style, by_id,split=False):
......
  # Add the transfer(s).
  AddSplitTransfers(tgt_name, src_name, tgt_ranges, src_ranges, style, by_id)
这里面的参数style就是第一步传入的"diff" by_id=self.transfer=[],接下来我们分析下这个方法

 def AddSplitTransfers(tgt_name, src_name, tgt_ranges, src_ranges,
      #声明分割个数,首次为0
      pieces = 0
              #拿到cache的大小
      cache_size = common.OPTIONS.cache_size
      split_threshold = 0.125
      #定义单个分割大小,以cache的8分之1除以定义的blockize4096
      max_blocks_per_transfer = int(cache_size * split_threshold /
                                    self.tgt.blocksize)

      # Change nothing for small files.
      #针对较小的文件,如果tgt和src区间大小都不够单个分割大小,则不做处理,直接生成Transfer对象
      if (tgt_ranges.size() <= max_blocks_per_transfer and
          src_ranges.size() <= max_blocks_per_transfer):

        Transfer(tgt_name, src_name, tgt_ranges, src_ranges,
                 self.tgt.RangeSha1(tgt_ranges), self.src.RangeSha1(src_ranges),
                 style, by_id)
        return
#如果tgt和src的区间大于分割比例,将进入一个while循环,
      while (tgt_ranges.size() > max_blocks_per_transfer and src_ranges.size() > max_blocks_per_transfer):
        #这里的语法是字段拼接的意思,最终生成的split_name为tgt_name-pieces,例如第一次进while循环是/system/app/a.apk-0
        tgt_split_name = "%s-%d" % (tgt_name, pieces)
        src_split_name = "%s-%d" % (src_name, pieces)
        #取出区间中的分割所占的区间范围
        tgt_first = tgt_ranges.first(max_blocks_per_transfer)
        src_first = src_ranges.first(max_blocks_per_transfer)
        #传入生成Transfer对象
        Transfer(tgt_split_name, src_split_name, tgt_first, src_first,
                 self.tgt.RangeSha1(tgt_first), self.src.RangeSha1(src_first),
                 style, by_id)
        #去除tgt_ranges tgt_first,剩余的继续在while中分割
        tgt_ranges = tgt_ranges.subtract(tgt_first)
        src_ranges = src_ranges.subtract(src_first)
        #因为还在循环中,我们并不知道还会分割多少次,所以这里加1,之后生成的split_name就是/system/app/a.apk-1
        pieces += 1
      #这里代码的意思是,while循环走完后,剩余的部分需要处理,例如/system/app/a.apk-1
      if tgt_ranges.size() or src_ranges.size():
        # Must be both non-empty.
        assert tgt_ranges.size() and src_ranges.size()
        tgt_split_name = "%s-%d" % (tgt_name, pieces)
        src_split_name = "%s-%d" % (src_name, pieces)
        Transfer(tgt_split_name, src_split_name, tgt_ranges, src_ranges,
                 self.tgt.RangeSha1(tgt_ranges), self.src.RangeSha1(src_ranges),
                 style, by_id)
生成Transfer对象
走完FindTransfer后进入class Transfer(object):的inti程序
  def __init__(self, tgt_name, src_name, tgt_ranges, src_ranges, tgt_sha1,
               src_sha1, style, by_id):
    #将我们传入的tgt_name,src_name,tgt_ranges,src_ranges,tgt_sha1,src_sha1
    self.tgt_name = tgt_name
    self.src_name = src_name
    self.tgt_ranges = tgt_ranges
    self.src_ranges = src_ranges
    self.tgt_sha1 = tgt_sha1
    self.src_sha1 = src_sha1
    self.style = style
    self.intact = (getattr(tgt_ranges, "monotonic", False) and
                   getattr(src_ranges, "monotonic", False))
    #print(getattr(tgt_ranges, "monotonic", False))
    # We use OrderedDict rather than dict so that the output is repeatable;
    # otherwise it would depend on the hash values of the Transfer objects.
    #声明两个字典,装载这两本字典将是下个方法做的事情
    self.goes_before = OrderedDict()
    self.goes_after = OrderedDict()
    self.stash_before = []
    self.use_stash = []
    #这里的by_id其实就是之前的self.transfer,这里记录的id其实就是当前self.Transfer的长度
    self.id = len(by_id)
    #把当前的Transfer对象放入self.transfer列表中,其中他的位置其实就是他的id号,这个参数将用到下个方法中,用于取出diff
    by_id.append(self)
5.GenerateDigraph(self):方法分析 这个方法中最主要的是包含了两个for循环,以下的流程中将引入一些例子
print("Generating digraph...")
   #声明一个空的列表
   source_ranges = []
    #遍历self.transfer集合,取出所有的Transfer对象,b=0: <130771-130825 diff to 32629-32683>  如果直接打印Transer对象,会出现这样的字符串
    因为在初始化Transer时添加了一个string方法,按这种格式进行输出
    for b in self.transfers:
      #遍历Transfer对象的src_range区间,这里拿到的s,e是区间的开头和结尾
      for s, e in b.src_ranges:
        #如果e大于source_range列表的长度,
        if e > len(source_ranges):
          #那么我们会将该长度追加到source_range,所以循环走完后,其实这个列表的长度就是e的长度,也就是system.map中最大的block值
          source_ranges.extend([None] * (e-len(source_ranges)))
        #遍历range(s,e),注意,这里已经变成了普通的range列表,这时候会取出列表中所有的元素range(130771,130825)
        for i in range(s, e):
          #如果source_ranges i位置上是空的
          if source_ranges[i] is None:
            #那么我们在i位置上放入一本字典,OrderedDict.fromkeys([b])是有序字典的写法,会生成一本字典key为含有b的列表,values为None
            #那么这个时候source_ranges 130771-130824位置上放入的就是b这个Transfer对象
            source_ranges[i] = OrderedDict.fromkeys([b])
          else:
            source_ranges[i][b] = None
其实这里第一步就是为了生成source_ranges这个列表,根据我们上面的例子,可以发现,列表的长度就是system.map最大的block值,而
每个位置上放入的其实是一本字典,而这个Transfer对象中,src_range的区间大小,就是该对象在source_ranges列表中的个数,注意,以上循环中使用的
是src_range.这样我们就按基本版本的block值,完完全全按顺序放在了列表中,
下面分析第二个for循环,取出小于基础版本最大区间目标版本中的Transfer对象
#遍历self.Transfer,取出Transer对象    
for a in self.transfers:
      #声明一个新的有序字典,intersections 
      intersections = OrderedDict()
      #遍历tgt_ranges,注意使用的是tgt_ranges,上个循环使用的是src_ranges
      for s, e in a.tgt_ranges:
        #用tgt_ranges的区间生成一个新的range列表
        for i in range(s, e):
          #如果i的值大于我们之前的source_ranges,将中断整个循环,为什么会这样呢,因为如果i>source_range,那说明操过了src_range的最大值.                    
          #也就不再处理了
          if i >= len(source_ranges): break
          # Add all the Transfers in source_ranges[i] to the (ordered) set.
          #如果source_ranger[i]不为空,也就是说该位置上有Transfer对象,
          if source_ranges[i] is not None:
            #这里要注意下,首先遍历的对象是source_ranges[i],也就是对应i位置上的元素,一个字典,我们遍历字典,其实j是取出的Transfer对象
            for j in source_ranges[i]:
              #这是字典的赋值方法,该字典key为j ,values为None,有序字典是有去重功能的,所以如果遍历ranges(s,e)存在相同的元素,这里只会出现一个
              #如果是不同的,都会放进去
              intersections[j] = None
最终生成gose_before,goes_after两个字典
    #注意这个for循环是是在for a in self.transfers:中的,并且与for s, e in a.tgt_ranges:同一个缩进的,
    #遍历intersections这个字典,实际打印发现有intersections=()的情况,那就说明tgt_range完全超出了src_range_max
    print("intersections================")
    print(intersections)
    for b in intersections:
        #如果a与b相同,那么中断当前的循环,继续等待下一个b的进入,进行对比
        if a is b: continue
        # If the blocks written by A are read by B, then B needs to go before A.
        #如果a这个Transer对象与b不同,那么取出a对象的tgt_ranges和b对象的src_ranges的intersect,也就是交集
        i = a.tgt_ranges.intersect(b.src_ranges)
        if i:
          if b.src_name == "__ZERO":
            # the cost of removing source blocks for the __ZERO domain
            # is (nearly) zero.
            size = 0
          else:
            #取出交集的长度size,也就是基础版本和升级版本相交的block
            size = i.size()
          #装载b的gose_before对象,内容为{a,size}
          b.goes_before[a] = size
          #装载a的goes_after对象,内容为{a,size}
          a.goes_after[b] = size
          这里好像看起来很乱,字面意思仿佛就是是谁杀了我,而我又杀了谁,下面我们以一个简单的例子进行分析
关于生成gose_before,gose_after
a.Tansfer
src_name = a.apk
tgt_name = a.apk
src_range = 0-1000
tgt_range = 500-2000

b.Transfer
src_name = b.apk
tgt_name = b.apk
src_range = 1001-2000
tgt_range = 2001-3000

i = a.tgt_ranges.intersect(b.src_ranges)
这时候用a的tgt_ranges 与 b.src_ranges 取交集,很明显可以取到500-2000
那么b的gose_before中存储了与其src_range有交集的a的tgt_range
b.goes_before[a] = size
那么a的goes_after中存储了与其tgt_range有交集的b的src_range
a.goes_after[b] = size
那么可能也会有c和d,
b的goes_after中存储了与其tgt_range有交集的c的src_range
b.gose_after[c] = size
a的gose_before中存储了与其src_range有交集的d的tgt_range
a.goes_before[d] = size
这样我们相当于会取出与当前APK基础版本的交集和升级版本的交集区间diff,以用于下一步的操作,这一部分的代码参数很多,我也是打印了很多的log
才慢慢看出了生成文件的作用.下一步将去操作这两个字典中存储的diff.

 

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值