编写的文章《浅析android应用增量升级》详细描述了增量更新的原理。简单来说,增量更新步骤如下:
- 准备新旧两个版本的apk(A,B);
- 对A,B进行差分比较,并生成差分包(diff(A,B) => patch),同时生成B的MD5;
- 新apk的合成:B = A + patch,新合成包的MD5和服务端更新下来的MD5进行比对,相同即可安装。
假设A 4m,B 5m,在服务端生成A —> B的差分包为 B-A = C1m(举个例子而已,可能实际变化的部分不止1M),客户端在更新的时候,将文件C下载,C与旧版A合成新的安装包D,校验B和D的MD5,若相同,则安装,否则更新失败。
demo运行过程中,所产生的文件:
在编码代码之前,需要做一些准备工作:
- 新旧apk(A,B)的准备。在demo中,提供old.apk、new.apk。
- 差分包生成和合并的jar包。javaxdelta.jar trove.jar。
1. 生成patch文件
主要用到的核心代码:
1. /**
2. * @param sourceFile 旧版本文件(.apk)
3. * @param targetFile 新版本文件(.apk)
4. * @param output 输出文件(.patch)
5. * */
6. throws IOException
1. /**
2. * 生成差分包:old_new.patch = diff(old.apk, old.apk)
3. *
4. * */
5. private void createPatch() {
6. try {
7. String sd = Environment.getExternalStorageDirectory().getPath();
8.
9. "/aDiff/old.apk";
10. "/aDiff/new.apk";
11. "/aDiff/old_new.patch";
12.
13. null;
14. null;
15. null;
16.
17. new File(oldFile);
18. new File(newFile);
19. new GDiffWriter(new DataOutputStream(
20. new BufferedOutputStream(new FileOutputStream(new File(
21. patchFile)))));
22.
23. if (sourceFile.length() > Integer.MAX_VALUE
24. || targetFile.length() > Integer.MAX_VALUE) {
25. System.err
26. "source or target is too large, max length is "
27. + Integer.MAX_VALUE);
28. "aborting..");
29.
30. }
31.
32. new Delta();
33. d.compute(sourceFile, targetFile, output);
34.
35. "生成完成!", Toast.LENGTH_LONG)
36. .show();
37. catch (Exception e) {
38. e.printStackTrace();
39. }
40. }
2、合成差分包
思路:将patch和旧版本文件合成,并比对MD5,若生成文件的MD5与服务器下发的MD5匹配,则提示合成成功,否则删除该文件。
核心代码:
1. /**
2. * 合成
3. * @param sourceFile 旧版本文件
4. * @param patchFile 更新包
5. * @param outputFile 新版本文件(生成)
6. *
7. * */
8. throws IOException
1. /**
2. * 合成差分包:new.apk = old.apk + old_new.patch
3. * */
4. private void mixPatch() {
5. try {
6.
7. String sd = Environment.getExternalStorageDirectory()
8. .getAbsolutePath();
9. "/aDiff/new.apk";
10. "/aDiff/old.apk";
11. "/aDiff/old_new.patch";
12.
13. "/aDiff/mix.apk";
14.
15. new File(serviceFile));
16.
17. DiffTool.mergeApk(source, patch, target, newMD5);
18.
19. "合成完成!", Toast.LENGTH_LONG)
20. .show();
21. catch (Exception e) {
22. e.printStackTrace();
23. }
24. }
25.
26. private static File mergeFile(final String source, final String patch,
27. throws Exception {
28. new GDiffPatcher();
29. new File(patch);
30. new File(target);
31. new File(source), deffFile, updatedFile);
32. return updatedFile;
33. }
34.
35. public static File mergeApk(final String source, final String patch,
36. final String target, String newApkMd5) throws Exception {
37. File updateFile = mergeFile(source, patch, target);
38. String ufpMd5 = getMD5(updateFile);
39. System.out
40. "服务端下发的md5:" + newApkMd5 + ",新合并后的apk MD5:" + ufpMd5);
41. if (ufpMd5 == null || !newApkMd5.equalsIgnoreCase(ufpMd5)) {
42. if (updateFile.exists()) {
43. updateFile.delete();
44. }
45. throw new Exception("MD5错误,不能成功合并!");
46. }
47.
48. return updateFile;
49. }
接下来就是最后一步,安装APK。
3、安装apk
1. /**
2. * 安装apk。 这边路径已经写死,实际应用中,apk路径需要当参数传入
3. * */
4. private void installAPK() {
5. new File(Environment.getExternalStorageDirectory()
6. "/aDiff/mix.apk");
7. if (!apkfile.exists()) {
8. return;
9. }
10. new Intent(Intent.ACTION_VIEW);
11. i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
12. "file://" + apkfile.toString()),
13. "application/vnd.android.package-archive");
14. this.startActivity(i);
15.
16. }
注释:
- 在该demo中,路径都是写死的,在实际应用中,应在上述三个关键方法中,设置路径参数。
- 差分包的生成、合成都没有写在异步方法里面。为了提高用户体验,应编写相应的异步方法,如run、async等。
- 差分包的生成写在了客户端,实际应该是放在服务端。如果服务端用java来写,那么很幸运,代码直接复制即可。下面我将提供服务端为.NET的差分包生成方式。其他语言没有做研究,额额。。
4、.NET服务端的.patch文件生成
同样是调用javaxdelta,trove,当然并不是直接调用jar包,首先要将这两个包编译成dll文件,供.NET调用。在这里感谢博客园的一位朋友,详细的讲解了IKVM的使用方法
通过IKVM这个工具,将上述两个jar包转成对应的dll文件:javaxdelta.dll、trove.dll,只有这两个包是不够的,还需要将IKVM.OpenJDK.Core.dll、IKVM.Runtime.dll、IKVM.Runtime.JNI.dll三个类库同时引入,才可以正常编译。
.NET服务端.patch生成代码如下:
1. /// <summary>
2. /// 生成差分包
3. /// </summary>
4. /// <param name="oldApkURL"></param>
5. /// <param name="newApkUrl"></param>
6. /// <param name="patchFileUrl"></param>
7. private bool CreateFile(string oldApkURL, string newApkUrl, string patchFileUrl)
8. {
9. try
10. {
11. null;
12. null;
13. null;
14.
15. new java.io.File(oldApkURL);
16. new java.io.File(newApkUrl);
17.
18. if (sourceFile.exists() && targetFile.exists())
19. {
20.
21. new com.nothome.delta.GDiffWriter(new java.io.DataOutputStream(
22. new java.io.BufferedOutputStream(new java.io.FileOutputStream(new java.io.File(
23. patchFileUrl)))));
24.
25. if (sourceFile.length() > int.MaxValue
26. int.MaxValue)
27. {
28. }
29.
30. new com.nothome.delta.Delta();
31. d.compute(sourceFile, targetFile, output);
32.
33. return true;
34. }
35. else
36. {
37.
38. this.ShowMessage("源文件不存在!");
39.
40. return false;
41. }
42. }
43. catch (Exception e)
44. {
45. return false;
46. }
47. }
通过上述操作,就可以实现增量更新。额额。。文件下载什么的好像还没有加撒~这部分的内容可以直接参考度娘的写法即可了~嘿嘿~