【原】Android热更新开源项目Tinker源码解析系列之二:资源文件热更新,androidtinker
上一篇文章介绍了Dex文件的热更新流程,本文将会分析Tinker中对资源文件的热更新流程。
同Dex,资源文件的热更新同样包括三个部分:资源补丁生成,资源补丁合成及资源补丁加载。
本系列将从以下三个方面对Tinker进行源码解析:
转载请标明本文来源:http://www.cnblogs.com/yyangblog/p/6252490.html
更多内容欢迎star作者的github:https://github.com/LaurenceYang/article
如果发现本文有什么问题和任何建议,也随时欢迎交流~
一、资源补丁生成
ResDiffDecoder.patch(File oldFile, File newFile)主要负责资源文件补丁的生成。
如果是新增的资源,直接将资源文件拷贝到目标目录。
如果是修改的资源文件则使用dealWithModeFile函数处理。
1 // 如果是新增的资源,直接将资源文件拷贝到目标目录.
2 if (oldFile == null || !oldFile.exists()) {
3 if (Utils.checkFileInPattern(config.mResIgnoreChangePattern, name)) {
4 Logger.e("found add resource: " + name + " ,but it match ignore change pattern, just ignore!");
5 return false;
6 }
7 FileOperation.copyFileUsingStream(newFile, outputFile);
8 addedSet.add(name);
9 writeResLog(newFile, oldFile, TypedValue.ADD);
10 return true;
11 }
12 ...
13 // 新旧资源文件的md5一样,表示没有修改.
14 if (oldMd5 != null && oldMd5.equals(newMd5)) {
15 return false;
16 }
17 ...
18 // 修改的资源文件使用dealWithModeFile函数处理.
19 dealWithModeFile(name, newMd5, oldFile, newFile, outputFile);
dealWithModeFile会对文件大小进行判断,如果大于设定值(默认100Kb),采用bsdiff算法对新旧文件比较生成补丁包,从而降低补丁包的大小。
如果小于设定值,则直接将该文件加入修改列表,并直接将该文件拷贝到目标目录。
1 if (checkLargeModFile(newFile)) { //大文件采用bsdiff算法
2 if (!outputFile.getParentFile().exists()) {
3 outputFile.getParentFile().mkdirs();
4 }
5 BSDiff.bsdiff(oldFile, newFile, outputFile);
6 //treat it as normal modify
7 // 对生成的diff文件大小和newFile进行比较,只有在达到我们的压缩效果后才使用diff文件
8 if (Utils.checkBsDiffFileSize(outputFile, newFile)) {
9 LargeModeInfo largeModeInfo = new LargeModeInfo();
10 largeModeInfo.path = newFile;
11 largeModeInfo.crc = FileOperation.getFileCrc32(newFile);
12 largeModeInfo.md5 = newMd5;
13 largeModifiedSet.add(name);
14 largeModifiedMap.put(name, largeModeInfo);
15 writeResLog(newFile, oldFile, TypedValue.LARGE_MOD);
16 return true;
17 }
18 }
19 modifiedSet.add(name); // 加入修改列表
20 FileOperation.copyFileUsingStream(newFile, outputFile);
21 writeResLog(newFile, oldFile, TypedValue.MOD);
22 return false;
BsDiff属于二进制比较,其具体实现大家可以自行百度。
ResDiffDecoder.onAllPatchesEnd()中会加入一个测试用的资源文件,放在assets目录下,用于在加载补丁时判断其是否加在成功。
这一步同时会向res_meta.txt文件中写入资源更改的信息。
1 //加入一个测试用的资源文件
2 addAssetsFileForTestResource();
3 ...
4 //first, write resource meta first
5 //use resources.arsc's base crc to identify base.apk
6 String arscBaseCrc = FileOperation.getZipEntryCrc(config.mOldApkFile, TypedValue.RES_ARSC);
7 String arscMd5 = FileOperation.getZipEntryMd5(extractToZip, TypedValue.RES_ARSC);
8 if (arscBaseCrc == null || arscMd5 == null) {
9 throw new TinkerPatchException("can't find resources.arsc's base crc or md5");
10 }
11
12 String resourceMeta = Utils.getResourceMeta(arscBaseCrc, arscMd5);
13 writeMetaFile(resourceMeta);
14
15 //pattern
16 String patternMeta = TypedValue.PATTERN_TITLE;
17 HashSet<String> patterns = new HashSet<>(config.mResRawPattern);
18 //we will process them separate
19 patterns.remove(TypedValue.RES_MANIFEST);
20
21 writeMetaFile(patternMeta + patterns.size());
22 //write pattern
23 for (String item : patterns) {
24 writeMetaFile(item);
25 }
26 //write meta file, write large modify first
27 writeMetaFile(largeModifiedSet, TypedValue.LARGE_MOD);
28 writeMetaFile(modifiedSet, TypedValue.MOD);
29 writeMetaFile(addedSet, TypedValue.ADD);
30 writeMetaFile(deletedSet, TypedValue.DEL);
最后的res_meta.txt文件的格式范例如下:
resources_out.zip,4019114434,6148149bd5ed4e0c2f5357c6e2c577d6
pattern:4
resources.arsc
r/*
res/*
assets/*
modify:1
r/g/ag.xml
add:1
assets/only_use_to_test_tinker_resource.txt
到此,资源文件的补丁打包流程结束。
二、补丁下发成功后资源补丁的合成
ResDiffPatchInternal.tryRecoverResourceFiles会调用extractResourceDiffInternals进行补丁的合成。
合成过程比较简单,没有使用bsdiff生成的文件直接写入到resources.apk文件;
使用bsdiff生成的文件则采用bspatch算法合成资源文件,然后将合成文件写入resouces.apk文件。
最后,生成的resouces.apk文件会存放到/data/data/${package_name}/tinker/res对应的目录下。
1 / 首先读取res_meta.txt的数据
2 ShareResPatchInfo.parseAllResPatchInfo(meta, resPatchInfo);
3<