编写的文章《浅析android应用增量升级》详细描述了增量更新的原理。简单来说,增量更新步骤如下:

  1. 准备新旧两个版本的apk(A,B);
  2. 对A,B进行差分比较,并生成差分包(diff(A,B)  => patch),同时生成B的MD5;
  3. 新apk的合成:B = A + patch,新合成包的MD5和服务端更新下来的MD5进行比对,相同即可安装。

假设A 4m,B 5m,在服务端生成A —> B的差分包为 B-A = C1m(举个例子而已,可能实际变化的部分不止1M),客户端在更新的时候,将文件C下载,C与旧版A合成新的安装包D,校验B和D的MD5,若相同,则安装,否则更新失败。




demo运行过程中,所产生的文件:




Android 差分包升级不成功 android差量更新_java




在编码代码之前,需要做一些准备工作:


  1. 新旧apk(A,B)的准备。在demo中,提供old.apk、new.apk。
  2. 差分包生成和合并的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.     }

    注释:


    1. 在该demo中,路径都是写死的,在实际应用中,应在上述三个关键方法中,设置路径参数。
    2. 差分包的生成、合成都没有写在异步方法里面。为了提高用户体验,应编写相应的异步方法,如run、async等。
    3. 差分包的生成写在了客户端,实际应该是放在服务端。如果服务端用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.         }



    通过上述操作,就可以实现增量更新。额额。。文件下载什么的好像还没有加撒~这部分的内容可以直接参考度娘的写法即可了~嘿嘿~