关于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.