一、NDK:增量更新

增量更新在Android开发中是一种很常见的技术。

增量更新的原理

增量更新的原理非常简单,就是将本地apk与服务器端最新版本比对,并得到差异包。用户更新App时只需要下载差异包即可。
关键有两点:

  1. 根据新旧版本的APK生成差异包
  2. 使用差异包和旧版的APK合并成新版APK

生成差异包使用的是一个C++的开源库:bsdiff
百度第一项就是

bsdiff

由于我们是在windows环境下开发,所以下载windows的版本

点击红色方框处
下载源码
解压后

实验

直接使用不确定因素太多,所以先实验后再使用,这里先使用Eclipse和Visual Studio来实验bsdiff是否可以完成拆分和合并的工作,并熟悉流程。

准备工作

生成新旧两个版本的APK。
旧版的APK需要完成差异包和旧版APK合并的工作,所以需要用到NDK开发,这一块放在后面详细介绍。
这里使用最普通的APK,先介绍如何生成差异包。

生成差异包

  1. 新建Eclipse工程 BsDiffTest
    该工程用来调用C/C++的代码。核心代码是C/C++。此处主要是Jni的调用。

  2. 新建Visual Studio的C++工程 BsDiffTest。
    该工程是用来生成dll动态库(windows环境)或者so动态库(linux环境)。

  3. 复制bsdiff的源码到VS工程的工程目录下。并删除bspatch.cpp
    该文件是用于合并的,在生成差异包的时候使用的是bsdiff.cpp


    bspatch.cpp
  4. 复制 jni.h和jni_md.h到VS工程的工程目录下。

  5. Eclipse工程中新建类

     package com.relengxing;
     public class BsDiff {
     static{
       System.loadLibrary("BsDiffTest");
     }
     /**
      * 调用本地方法生成差异包
      * @param oldfile
      * @param newfile
      * @param patchfile
      */
     public native static void diff(String oldfile,String newfile,String patchfile);
     }
    

在main方法中调用

    package com.relengxing;

    public class BsDiffTest {

    public static final String OLD_APK_PATH = "D:/apk/app_old.apk";

    public static final String NEW_APK_PATH = "D:/apk/app_new.apk";

    public static final String PATCH_PATH = "D:/apk/apk.patch";

    public static void main(String[] args) {
    
        BsDiff.diff(OLD_APK_PATH, NEW_APK_PATH, PATCH_PATH);
    
        }
    }
  1. 使用javah生成BsDiff类的头文件。如果此处不会请参考Jni系列,里面有详细说明。
    把生成的头文件复制到VS的工程目录
    修改#include<jni.h>为#include“jni.h”
    把头文件中定义的方法赋值到bsdiff.cpp的最后并实现。

     JNIEXPORT void JNICALL Java_com_relengxing_BsDiff_diff
     (JNIEnv *env, jclass jcls, jstring oldfile_jstr, jstring newfile_jstr, jstring patchfile_jstr) {
         int argc = 4;
         char *argv[4];
    
         char *oldfile = (char *)env->GetStringUTFChars(oldfile_jstr, NULL);
         char *newfile = (char *)env->GetStringUTFChars(newfile_jstr, NULL);
         char *patchfile = (char *)env->GetStringUTFChars(patchfile_jstr, NULL);
    
         argv[0] = "bsdiff";
         argv[1] = oldfile;
         argv[2] = newfile;
         argv[3] = patchfile;
    
         bsdiff_main(argc, argv);
    
         env->ReleaseStringUTFChars(oldfile_jstr, oldfile);
         env->ReleaseStringUTFChars(newfile_jstr, newfile);
         env->ReleaseStringUTFChars(patchfile_jstr, patchfile);
     }
    
  2. 修改bsdiff.cpp中的main方法为bsdiff_main
    在Java_com_relengxing_BsDiff_diff中调用bsdiff_main()方法,
    该方法需要传递两个参数

     int bsdiff_main(int argc,char *argv[])
    

int argc,char *argv[];
在源码中可以找到以下代码,意思就是argc必须等于4,否则就报错

    if(argc!=4) errx(1,"usage: %s oldfile newfile patchfile\n",argv[0]);在源码中依次可以找到如下注释

而在argv[]中第一个参数是无用的,随便填什么都可以,第二个参数是旧版Apk的地址,第三个参数是新版Apk的地址,第四个参数是生成的差分包的地址。

    /* Allocate oldsize+1 bytes instead of oldsize bytes to ensure
    that we never try to malloc(0) and get a NULL pointer */
    //org:
    //if(((fd=open(argv[1],O_RDONLY,0))<0) ||
    //  ((oldsize=lseek(fd,0,SEEK_END))==-1) ||
    //  ((old=malloc(oldsize+1))==NULL) ||
    //  (lseek(fd,0,SEEK_SET)!=0) ||
    //  (read(fd,old,oldsize)!=oldsize) ||
    //  (close(fd)==-1)) err(1,"%s",argv[1]);
    //new:
    //Read in chunks, don't rely on read always returns full data!
        
    /* Allocate newsize+1 bytes instead of newsize bytes to ensure
    that we never try to malloc(0) and get a NULL pointer */
    //org:
    //if(((fd=open(argv[2],O_RDONLY,0))<0) ||
    //  ((newsize=lseek(fd,0,SEEK_END))==-1) ||
    //  ((_new=malloc(newsize+1))==NULL) ||
    //  (lseek(fd,0,SEEK_SET)!=0) ||
    //  (read(fd,_new,newsize)!=newsize) ||
    //  (close(fd)==-1)) err(1,"%s",argv[2]);
    //new:
    //Read in chunks, don't rely on read always returns full data!

    /* Create the patch file */
    //org:
    //if ((pf = fopen(argv[3], "w")) == NULL)     
    //new:
    //if((fd=open(argv[3],O_CREAT|O_TRUNC|O_WRONLY|O_BINARY|O_NOINHERIT,0666))<0)
  1. 工程中有很多错误,我们依次来解决。
    根据自己的环境选择生成dll动态库
    编译工程


    错误1

    当遇到以上错误时,在解决方案的属性其他选项中添加
    -D _CRT_SECURE_NO_WARNINGS


    解决方案1

    然后再编译:
    错误2

    当遇到以上错误时,在解决方案的属性其他选项中添加
    -D _CRT_NONSTDC_NO_DEPRECATE
    解决方案2

    再编译:


    错误3

    遇到以上错误时把SDL检查选为否
    解决方案3

    再编译:成功生成dll动态库
    dll动态库
  2. 自行修改环境变量,在jni系列有详述。
    然后在eclipse中运行。运行需要一段时间,运行完成后在我们设置的文件夹下可以查找到一下文件图中apk.patch就是差异包


    差异包

合并差分包(windows端)

合并差分包是在手机端做的事,这里先在windows端实验一遍,合并和生成差分包有相似之处,这里就简单的写了。

  1. 同样新建Eclipse工程BsPatchTest。
  2. 新建VS工程BsPatchTest。
  3. 复制bsdiff的源码到VS工程的工程目录下。并删除bsdiff.cpp该文件是用于合并的,在生成差异包的时候使用的是bspatch.cpp
  4. 复制 jni.h和jni_md.h到VS工程的工程目录下。
  5. Eclipse工程中新建类
  6. 使用javah生成BsDiff类的头文件
  7. 修改bspatch.cpp中的main方法为bspatch_main
    在Java_com_relengxing_BsDiff_diff中调用bsdiff_main()方法,
  8. 工程中有很多错误,我们依次来解决。
  9. 自行修改环境变量,在jni系列有详述。

头文件的代码复制到bspatch.cpp中实现:

JNIEXPORT void JNICALL Java_com_relengxing_BsPatch_patch
(JNIEnv *env, jclass jcls, jstring oldfile_jstr, jstring newfile_jstr, jstring patchfile_jstr) {
    int argc = 4;
    char* oldfile = (char*)env->GetStringUTFChars(oldfile_jstr, NULL);
    char* newfile = (char*)env->GetStringUTFChars(newfile_jstr, NULL);
    char* patchfile = (char*)env->GetStringUTFChars(patchfile_jstr, NULL);

    //参数(第一个参数无效)
    char *argv[4];
    argv[0] = "bspatch";
    argv[1] = oldfile;
    argv[2] = newfile;
    argv[3] = patchfile;

    bspatch_main(argc, argv);

    env->ReleaseStringUTFChars(oldfile_jstr, oldfile);
    env->ReleaseStringUTFChars(newfile_jstr, newfile);
    env->ReleaseStringUTFChars(patchfile_jstr, patchfile);

}

把之前的文件名修改一下

运行前

运行eclipse工程

运行后

经过比较,生成的文件和之前的文件大小一样。
所以使用bsdiff来做增量更新是可以实现的。

实际使用

服务器端

服务器端有各种语言,使用bsdiff的话,核心代码还是C/C++,最后就是和java一样调用C/C++。由于我不懂服务器端的技术。。所以这里使用一个最偷懒的办法,手动生成差分包,然后把差分包放在tomcat里,这样就可以在局域网内访问了。

客户端

Android客户端需要实现几个功能

  • 下载差分包
  • 合并差分包
  • 安装合并后的APK

这里我是使用Android Studio 2.2 Bate2开发的

新建工程

如图勾选此处


支持C++
开发界面

关于CMakeLists.txt这个文件我也不是很懂,反正前面是#的就是注释掉的。

这一行和版本号有关系

cmake_minimum_required(VERSION 3.4.1)

这一段大概意思就是创建并命名一个库,设置它是STATIC或者SHARED,并提供了其源代码的相对路径,你可以定义多个库,CMake会为你构建,Gradle自动打包共享库给你的apk。
这里可以把下面这段的native-lib和src/main/cpp/native-lib.cpp改成自己的

    add_library( # Sets the name of the library.
         native-lib

         # Sets the library as a shared library.
         SHARED

         # Provides a relative path to your source file(s).
         # Associated headers in the same location as their source
         # file are automatically included.
         src/main/cpp/native-lib.cpp )

后面还有两段这里先不研究了,以后如果有研究再写下来。

代码搬运工

Android是Linux的。
所以bsdiff要下载linux版本的

bsdiff
解压后

这里只有两个文件是有用的,就是两个点C文件。
bsdiff用来生成差分包,bspatch用来合并差分包。
但是只有这个.c文件是不够的,这个项目还依赖bzip2,所以再去把bzip2下载下来,这里把bspatch.c复制到工程的Cpp目录下。删除之前的native-lib.cpp。
把下载下来的bzip2源码解压出来,把所有的.c和.h文件复制粘贴到cpp目录下新建一个bzip2文件夹中。
修改CMakeLists.txt中下面两段,同步一下。


Cmakelists.txt
Cmakelists.txt

修改要加载的动态库


加载动态库

在bspatch.c中添加如下文件,至于为什么是这些文件,听说是多编译几次就知道了。

#include "bzip2/bzlib.c"
#include "bzip2/crctable.c"
#include "bzip2/compress.c"
#include "bzip2/decompress.c"
#include "bzip2/randtable.c"
#include "bzip2/blocksort.c"
#include "bzip2/huffman.c"

动态库源码的搬运工作到这里就结束了,剩下的就得自己写代码了。

写调用jni的类

新建一个类BsPatch

BsPatch

patch的方法名是红色的,是因为定义了该方法,但是在本地的方法中却找不到,这时按Alt+Enter快捷键

按Alt+Enter快捷键
jni\bspatch.c

这里重新生成了一个jni的文件夹,在jni的文件夹下又生成了一个bspatch.c的文件。
最上面那行提示代码的意思是这个文件没有在这个工程中,加我们添加到工程里面去......不理他,只复制我们要的东西。把这个里面的东西复制到cpp\bapatch.c的最后。和之前一样修改main为bspatch_main,在本地方法中调用

调用

至于怎么下载,怎么安装这块就不详细说了,具体的到时候看Git的代码好了。核心的代码就是合并这块,合并就是调用刚刚写的这个函数,然后把三个地址传递进来。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,706评论 4 366
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,002评论 1 301
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,462评论 0 250
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,375评论 0 216
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,763评论 3 294
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,849评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,033评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,768评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,490评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,734评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,204评论 1 264
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,566评论 3 260
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,227评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,137评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,934评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,926评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,774评论 2 274

推荐阅读更多精彩内容

  • 最近就是在练习ndk开发,刚好遇到android增量更新的话题,主要是工具的运用,略带使用第三方so库的流程~~~...
    红黑军团号阅读 529评论 1 3
  • 上一节我们学习了NDK来处理文件的拆分和合并操作,那时候我们纯手工来敲C语言的代码,今天我们来用C语言代码搞搞ND...
    Lypop阅读 292评论 0 0
  • 版权声明:本文为LooperJing原创文章,转载请注明出处! 前言 如果要对一个 APP 进行更新,你会怎么做呢...
    LooperJing阅读 3,405评论 0 28
  • 注:首发地址 0. 前言 如果只学理论,不做实践,不踩踩坑,一般很难发现真正实践项目中的问题的,也比较难以加深对技...
    cfanr阅读 9,379评论 4 51
  • 一 1912年8月3日,《华盛顿邮报》有这样的一篇报道:中国北京城内一片男子的哭声,很多男子反复摸着自己的辫子舍不...
    鹰鹰儿阅读 1,742评论 7 30