Android实践:你还没使用增量更新

来源:互联网 发布:儿童画画板软件 编辑:程序博客网 时间:2024/05/19 13:17
一、增量更新原理
从Android4.1开始,Google Play引入了Smart app update方式更新App,即增量更新。它的原理如下:
  1.服务端:生成最新版本apk和手机上已安装版本的apk二进制对比(bsddiff二进制比较工具)的差分包;
  2.手机端:发现需要更新时下载差分包,并使用差分包与已安装版本的apk合并成最新版的apk。使用散列算法(MD5或SHA1)校验合成的apk是否完成,如果完成即重新安装;
二、增量更新业务
1.生成差分包
因为当前使用用户可能分布在已发布的不同版本,故需要在服务端生成最新版本和已发布所有版本的差分包,如:已发布版本有1.0、2.0和3.0,当前发布版本4.0。则需要生成以下查分包:
  1.0->4.0差分包;
  2.0->4.0差分包;
  3.0->4.0差分包;
注意:对于版本跨度非常大的用户,未了避免生成大量的差分包,可以对这部分用户进行整包升级;
三、增量更新展示
1.下载bsdiff工具
下载地址:http://download.pokorra.de/coding/bsdiff_win_exe.zip;

注意:
采用gcc编译源码方式,下载源码时注意区分windows版本(bsdiff_win_src.zip)和bsd版本(bsdiff-4.1.tar.gz);
bsdiff依赖于bzip2代码,下载相关源码并修改源代码中的include;

提示1:如果是使用gcc源码在编译源码过程中,如果报错如下:
bspatch.c:39:6: error: conflicting types for 'errx'
 void errx(char a, char* format)
      ^
bspatch.c:34:6: note: previous definition of 'errx' was here
 void errx(char a, char* format, char* param)
处理1:方法的命名冲突,将两个函数的名称修改为不同名字,修改相关方法调用处即可;

2.使用bsdiff工具生成差分patch包(这里我们直接下载bsdiff_win_exe.zip)
D:\bsdiff>bsdiff.exe app-old.apk app-new.apk patch.patch

3.使用bspatch工具和差分patch包合并新版本app-patch.apk
D:\bsdiff>bspatch.exe app-old.apk app-patch.apk patch.patch

4.校验编译打包和合并生成的新版本apk的md5值,md5完全一致,实验成功!
D:\bsdiff>certutil -hashfile app-new.apk MD5MD5 哈希(文件 app-new.apk):02 75 d0 8e ff ca b9 4b c9 70 84 4e 86 b3 6b 5cCertUtil: -hashfile 命令成功完成。D:\bsdiff>certutil -hashfile app-patch.apk MD5MD5 哈希(文件 app-patch.apk):02 75 d0 8e ff ca b9 4b c9 70 84 4e 86 b3 6b 5cCertUtil: -hashfile 命令成功完成。
四、增量更新实践
1.NDK环境搭建
该实践环境为:Android Studio2.2.2,Gradle2.2.2;
SDK Tools:安装SDK Tools LLDB、CMake和NDK;

2.下载bsdiff和bzip2源码
bsdiff地址:http://ftp.bestcom.ru/FreeBSD/ports/distfiles/bsdiff-4.1.tar.gz
bzip2地址:http://www.bzip.org/1.0.6/bzip2-1.0.6.tar.gz
注意:Android系统上下载BSD Protection License版本bsdiff-4.1.tar.zz;
3.添加bsdiff和bzip2源码
在项目创建src/main/cpp目录,并将bsdiff和bzip2源码添加如下,删除bzip2中非.c后缀的文件删除:

4.修改bsdiff源码
修改bsdiff.c,bspatch.c源码,将原来的main方法暴露给java层调用,引入bzip2源码。如下:
bsdiff.c
#include <fcntl.h>... ...//引入bzip2源码#include "bzip2/bzlib.h"#define MIN(x,y) (((x)<(y)) ? (x) : (y))... ...//修改main方法为genpatchint genpatch(int argc,char *argv[]){    int fd;    u_char *old,*new;    ... ...    return 0;}//暴露方法给java层调用JNIEXPORT jint JNICALL Java_com_qunar_home_utils_DiffUtils_diff(JNIEnv *env,jclass cls, jstring old, jstring new, jstring patch){    int argc = 4;    char * argv[argc];    argv[0] = "bsdiff";    argv[1] = (char*) ((*env)->GetStringUTFChars(env, old, 0));    argv[2] = (char*) ((*env)->GetStringUTFChars(env, new, 0));    argv[3] = (char*) ((*env)->GetStringUTFChars(env, patch, 0));    //调用原来的main方法生成差分包    int ret = genpatch(argc, argv);    (*env)->ReleaseStringUTFChars(env, old, argv[1]);    (*env)->ReleaseStringUTFChars(env, new, argv[2]);    (*env)->ReleaseStringUTFChars(env, patch, argv[3]);    return ret;}
bspatch.c
//引入bzip2源码#include "bzip2/bzlib.h".. ...//修改原来main方法名为applypatchint applypatch(int argc,char * argv[]){    FILE * f, * cpf, * dpf, * epf;    ... ...    return 0;}//将方法暴露java层调用JNIEXPORT jint JNICALL Java_com_qunar_home_utils_PatchUtils_patch(JNIEnv *env,jobject obj, jstring old, jstring new, jstring patch){    char * ch[4];    ch[0] = "bspatch";    ch[1] = (char*) ((*env)->GetStringUTFChars(env, old, 0));    ch[2] = (char*) ((*env)->GetStringUTFChars(env, new, 0));    ch[3] = (char*) ((*env)->GetStringUTFChars(env, patch, 0));    //调用原来的main方法    int ret = applypatch(4, ch);    (*env)->ReleaseStringUTFChars(env, old, ch[1]);    (*env)->ReleaseStringUTFChars(env, new, ch[2]);    (*env)->ReleaseStringUTFChars(env, patch, ch[3]);    return ret;}
5.增加java层c代码调用方法声明;
创建DiffUtils.java,PatchUtils.java类,创建c代码的java声明。如下:
DiffUtils.java
/** * 二进制对比工具类,提供二进制对比生成查分包方法 */public class DiffUtils {    /**     * native方法声明,将旧文件和新文件进行二进制对比,生成查分文件。这里我们用来生成新就版本apk的查分包     *     * @param oldFilePatch 对比旧版本文件路径,这里为旧版本apk     * @param newFilePath  对比新版本文件路径,这里为新版本的apk     * @param patchPath    对比生成差分文件路径,这里为新旧版本apk的二进制差分文件     * @return 操作返回码,0-代表成功     */    public static native int diff(String oldFilePatch, String newFilePath, String patchPath);}
PatchUtils.java
/** * 二进制合并工具类,提供二进制合并方法 */public class PatchUtils {    /**     * native方法声明,将旧文件和差分文件进行合并,生成新文件。这里我们用来生成合并生成新版本的apk     *     * @param oldFilePatch 合并旧版本文件路径,这里为旧版本apk     * @param newFilePath  合并生成的新版本文件路径,这里为合成生成的新版本的apk     * @param patchPath    合并差分文件路径,这里为新老版本apk的二进制差分文件     * @return 操作返回码,0-代表成功     */    public static native int patch(String oldFilePatch, String newFilePath, String patchPath);}
5.添加CMakeLists.txt,修改build.gradle构建配置:
cpp/CMakeLists.txt
#CMake最低版本号要求cmake_minimum_required(VERSION 3.4.1)#项目信息project(smartupdate)#添加bzip2子目录add_subdirectory(bzip2)#查找当前目录下的所有源文件,并将名称保存到DIR_SRCS变量中aux_source_directory(. DIR_SRCS)add_library(smartupdate SHARED ${DIR_SRCS})#添加链接库log,bzip2find_library(log-lib log)target_link_libraries(smartupdate ${log-lib})target_link_libraries(smartupdate bzip2)
cpp/bzip2/CMakeLists.txt
# 查找当前目录下的所有源文件,并将名称保存到 DIR_LIB_SRCS 变量aux_source_directory(. DIR_LIB_SRCS)# 生成链接库add_library (bzip2 ${DIR_LIB_SRCS})
build.gradle
apply plugin: 'com.android.application'... ...android {    ... ...    externalNativeBuild {        cmake {            path "src/main/cpp/CMakeLists.txt"        }    }}... ...
6.Java代码调用c代码进行patch包的生成,apk包合并,合成apk的安装
SmartUpdateActivity.java
public class SmartUpdateActivity extends AppCompatActivity {    String externalStoragePath = Environment.getExternalStorageDirectory().getPath() + "/qproject/";    String newPath = externalStoragePath + "app-new.apk";    String oldPath = externalStoragePath + "app-old.apk";    String patchPath = externalStoragePath + "patch.patch";    String mergePath = externalStoragePath + "merge.apk";    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_smartupdate);    }    public void generateDiffPatch(View view) {        //产生差异patch包        int resultCode = DiffUtils.diff(oldPath, newPath, patchPath);        if (resultCode == 0) {            Toast.makeText(this, "generateDiffPatch success!", Toast.LENGTH_SHORT).show();        }    }    public void mergePatchApk(View view) {        //合并差异patch包,生成新apk        int resultCode = PatchUtils.patch(oldPath, mergePath, patchPath);        if (resultCode == 0) {            Toast.makeText(this, "mergePatchApk success!", Toast.LENGTH_SHORT).show();        }    }    public void updatePatchApk(View view) {        File mergeFile = new File(mergePath);        if (mergeFile.exists()) {            Intent intent = new Intent(Intent.ACTION_VIEW);            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);            intent.setDataAndType(Uri.fromFile(mergeFile), "application/vnd.android.package-archive");            startActivity(intent);        }    }    //加载smartupdate库    static {        System.loadLibrary("smartupdate");    }}
7.这里我们仅仅是为了演示diff和patch的过程,并没有考虑异步处理,网络包下载,md5校验等等具体业务逻辑,故预先在storage/sdcard0/qproject/目录下,放下新老apk文件;

8.运行测试
   

四、代码库

QProject:https://github.com/Pengchengxiang/QProject 分支:feature/smartupdate

提示2:
java.lang.NoSuchFieldError: com.qproject.smartupdate.R$id.sample_text
     at com.qproject.smartupdate.SmartUpdateActivity.onCreate(SmartUpdateActivity.java:15)
     at android.app.Activity.performCreate(Activity.java:5193)
     at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1090)
     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2189)
     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2286)
     at android.app.ActivityThread.access$600(ActivityThread.java:144)
     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1259)
     at android.os.Handler.dispatchMessage(Handler.java:99)
     at android.os.Looper.loop(Looper.java:137)
     at android.app.ActivityThread.main(ActivityThread.java:5166)
     at java.lang.reflect.Method.invokeNative(Native Method)
     at java.lang.reflect.Method.invoke(Method.java:525)
     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:768)
     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:584)
     at de.robv.android.xposed.XposedBridge.main(XposedBridge.java:132)
     at dalvik.system.NativeStart.main(Native Method)
处理2:引入的模块与原来的模块,是不是存在R.id的命令冲突;

提示3:
CMake Error at CMakeLists.txt:7 (add_subdirectory):
  The source directory
    C:/Users/chengxiang.peng.QUNARSERVERS/GitHubSources/QProject/smartupdate/src/main/cpp/bzip2
  does not contain a CMakeLists.txt file.
处理3:在bzip2目录下创建CMakeList.txt文件,具体代码如上;

新技术,新未来!欢迎大家关注“1024工场”微信服务号,时刻关注我们的最新的技术讯息!(甭客气!尽情的扫描或者长按!)

2 0
原创粉丝点击