本文转自http://blog.csdn.net/u013705351/article/details/22722499
@hmg25编写的文章《浅析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文件
主要用到的核心代码:
- /**
- * @param sourceFile 旧版本文件(.apk)
- * @param targetFile 新版本文件(.apk)
- * @param output 输出文件(.patch)
- * */
- com.nothome.delta.Delta.compute(File sourceFile, File targetFile, DiffWriter output) throws IOException
- /**
- * 生成差分包:old_new.patch = diff(old.apk, old.apk)
- *
- * */
- private void createPatch() {
- try {
- String sd = Environment.getExternalStorageDirectory().getPath();
- String oldFile = sd + "/aDiff/old.apk";
- String newFile = sd + "/aDiff/new.apk";
- String patchFile = sd + "/aDiff/old_new.patch";
- DiffWriter output = null;
- File sourceFile = null;
- File targetFile = null;
- sourceFile = new File(oldFile);
- targetFile = new File(newFile);
- output = new GDiffWriter(new DataOutputStream(
- new BufferedOutputStream(new FileOutputStream(new File(
- patchFile)))));
- if (sourceFile.length() > Integer.MAX_VALUE
- || targetFile.length() > Integer.MAX_VALUE) {
- System.err
- .println("source or target is too large, max length is "
- + Integer.MAX_VALUE);
- System.err.println("aborting..");
- }
- Delta d = new Delta();
- d.compute(sourceFile, targetFile, output);
- Toast.makeText(getApplicationContext(), "生成完成!", Toast.LENGTH_LONG)
- .show();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
思路:将patch和旧版本文件合成,并比对MD5,若生成文件的MD5与服务器下发的MD5匹配,则提示合成成功,否则删除该文件。
核心代码:
- /**
- * 合成
- * @param sourceFile 旧版本文件
- * @param patchFile 更新包
- * @param outputFile 新版本文件(生成)
- *
- * */
- com.nothome.delta.GDiffPatcher.patch(File sourceFile, File patchFile, File outputFile) throws IOException
- /**
- * 合成差分包:new.apk = old.apk + old_new.patch
- * */
- private void mixPatch() {
- try {
- String sd = Environment.getExternalStorageDirectory()
- .getAbsolutePath();
- String serviceFile = sd + "/aDiff/new.apk";
- String source = sd + "/aDiff/old.apk";
- String patch = sd + "/aDiff/old_new.patch";
- String target = sd + "/aDiff/mix.apk";
- String newMD5 = DiffTool.getMD5(new File(serviceFile));
- DiffTool.mergeApk(source, patch, target, newMD5);
- Toast.makeText(getApplicationContext(), "合成完成!", Toast.LENGTH_LONG)
- .show();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- private static File mergeFile(final String source, final String patch,
- String target) throws Exception {
- GDiffPatcher patcher = new GDiffPatcher();
- File deffFile = new File(patch);
- File updatedFile = new File(target);
- patcher.patch(new File(source), deffFile, updatedFile);
- return updatedFile;
- }
- public static File mergeApk(final String source, final String patch,
- final String target, String newApkMd5) throws Exception {
- File updateFile = mergeFile(source, patch, target);
- String ufpMd5 = getMD5(updateFile);
- System.out
- .println("服务端下发的md5:" + newApkMd5 + ",新合并后的apk MD5:" + ufpMd5);
- if (ufpMd5 == null || !newApkMd5.equalsIgnoreCase(ufpMd5)) {
- if (updateFile.exists()) {
- updateFile.delete();
- }
- throw new Exception("MD5错误,不能成功合并!");
- }
- return updateFile;
- }
接下来就是最后一步,安装APK。
3、安装apk
- /**
- * 安装apk。 这边路径已经写死,实际应用中,apk路径需要当参数传入
- * */
- private void installAPK() {
- File apkfile = new File(Environment.getExternalStorageDirectory()
- .getAbsolutePath() + "/aDiff/mix.apk");
- if (!apkfile.exists()) {
- return;
- }
- Intent i = new Intent(Intent.ACTION_VIEW);
- i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- i.setDataAndType(Uri.parse("file://" + apkfile.toString()),
- "application/vnd.android.package-archive");
- MainActivity.this.startActivity(i);
- }
注释:
- 在该demo中,路径都是写死的,在实际应用中,应在上述三个关键方法中,设置路径参数。
- 差分包的生成、合成都没有写在异步方法里面。为了提高用户体验,应编写相应的异步方法,如run、async等。
- 差分包的生成写在了客户端,实际应该是放在服务端。如果服务端用java来写,那么很幸运,代码直接复制即可。下面我将提供服务端为.NET的差分包生成方式。其他语言没有做研究,额额。。
4、.NET服务端的.patch文件生成
同样是调用javaxdelta,trove,当然并不是直接调用jar包,首先要将这两个包编译成dll文件,供.NET调用。在这里感谢博客园的一位朋友,详细的讲解了IKVM的使用方法:@xiaotie的博文将java库转换为.net库 在这就不详细介绍了。在转换过程中,若遇到问题,可以给我留言,嘿嘿~~
.NET服务端.patch生成代码如下:
- /// <summary>
- /// 生成差分包
- /// </summary>
- /// <param name="oldApkURL"></param>
- /// <param name="newApkUrl"></param>
- /// <param name="patchFileUrl"></param>
- private bool CreateFile(string oldApkURL, string newApkUrl, string patchFileUrl)
- {
- try
- {
- com.nothome.delta.DiffWriter output = null;
- java.io.File sourceFile = null;
- java.io.File targetFile = null;
- sourceFile = new java.io.File(oldApkURL);
- targetFile = new java.io.File(newApkUrl);
- if (sourceFile.exists() && targetFile.exists())
- {
- output = new com.nothome.delta.GDiffWriter(new java.io.DataOutputStream(
- new java.io.BufferedOutputStream(new java.io.FileOutputStream(new java.io.File(
- patchFileUrl)))));
- if (sourceFile.length() > int.MaxValue
- || targetFile.length() > int.MaxValue)
- {
- }
- com.nothome.delta.Delta d = new com.nothome.delta.Delta();
- d.compute(sourceFile, targetFile, output);
- return true;
- }
- else
- {
- this.ShowMessage("源文件不存在!");
- return false;
- }
- }
- catch (Exception e)
- {
- return false;
- }
- }
源码下载:
Android增量更新