android增量更新--服务器端&客户端

feixue_ 9年前
   <h2><strong>前言</strong></h2>    <p>随着应用越来越大,应用更新耗时间和流量的问题,就显得格外突出.</p>    <p>目前原生app的更新分为两种:重新下载源文件,还有一种就是差分包更新,也叫增量更新.</p>    <p>在有些应用市场,例如google play,会对安装包进行拆分和合并,来达到差分更新的目的.</p>    <p>首先解释一下差分包:</p>    <p>差分包是apk新版本和旧版本之间的包,可以称之为patch.</p>    <h2><strong>应用流程:</strong></h2>    <p><img src="https://simg.open-open.com/show/e5d56f7674333eb3dd3919ce901932ac.png"></p>    <p style="text-align: center;">流程图</p>    <h2><strong>操作流程</strong></h2>    <ul>     <li>确保客户端是old_app</li>     <li>改变app大小生成新的new_app</li>     <li>执行服务器生成patch程序</li>     <li>将patch包放在服务器供客户端下载</li>     <li>客户端合并安装</li>    </ul>    <h2><strong>实现原理:</strong></h2>    <h3><strong>1.相应下载</strong></h3>    <p>自己的github项目(包括服务器端,android端,C++端),阅读此文之前,最好下载完毕研究一下 <a href="/misc/goto?guid=4959715448978435605" rel="nofollow,noindex">https://github.com/ccj659/incremental-update-master</a></p>    <p><a href="/misc/goto?guid=4959629540213754940" rel="nofollow,noindex">原理是采用的是bsdiff,而它是一个优秀的开源C库,大家可以去看下</a></p>    <p>linux 的相关diff/patch下载 <a href="/misc/goto?guid=4959657728383194216" rel="nofollow,noindex">http://www.daemonology.net/bsdiff/</a></p>    <p>windows 上的bsdiff <a href="/misc/goto?guid=4959715449116649433" rel="nofollow,noindex">http://sites.inka.de/tesla/others.html#bsdiff</a></p>    <p>相关依赖bzip文档及下载 <a href="/misc/goto?guid=4959657728476775491" rel="nofollow,noindex">http://www.bzip.org/downloads.html</a></p>    <h3><strong>2.原理分析</strong></h3>    <p>Binary diff是依赖bzip压缩库的开源库,其实是一种文件比较的一种算法实现,是一个二进制比较工具.</p>    <p>这里有两个文件:老版本的app:old_app.apk 新版本的app:new_app.apk.</p>    <p>首先是Binarys diff:</p>    <p>1.首先将老文件old_app转为二进制文件.</p>    <p>2.在新文件new_app中找到和老文件相同的二进制数据.</p>    <p>3.在新文件生成的二进制数据中,分离new_app中老文件数据和新的二进制数据patch.</p>    <p>4.将patch数据打上新数据的标签,重新打包生成apk.patch.</p>    <p>然后是Binarys patch:</p>    <p>1.通过bzip压缩算法,将old_app和patch重新打包.</p>    <p>关于bzip,</p>    <h2><strong>实现过程</strong></h2>    <h3><strong>windows服务器端</strong></h3>    <p><strong>1.分析bsdiff.cpp源码,找到main入口</strong></p>    <pre>  /*阅读源码得知,此处第一个参数argc必须是4,argv是一个字符串指针数组*/      /***如下,此处需要四个参数 1.随便的值,2.ldfile     3.newfile 4.patchfile***************************/      int bsdiff_main(int argc,char *argv[])      {      int fd;      u_char *old,*_new;      off_t oldsize,newsize;      off_t *I,*V;      off_t scan,pos,len;      off_t lastscan,lastpos,lastoffset;      off_t oldscore,scsc;      off_t s,Sf,lenf,Sb,lenb;      off_t overlap,Ss,lens;      off_t i;      off_t dblen,eblen;      u_char *db,*eb;      u_char buf[8];      u_char header[32];      FILE * pf;      BZFILE * pfbz2;      int bz2err;      /**********************如下,此处需要四个参数 1.随便的值,2.ldfile 3.newfile 4.patchfile***************************/      if(argc!=4) errx(1,"usage: %s oldfile newfile patchfile\n",argv[0]);        /* 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!      if(((fd=open(argv[1],O_RDONLY|O_BINARY|O_NOINHERIT,0))<0) ||          ((oldsize=lseek(fd,0,SEEK_END))==-1) ||          ((old=(u_char*)malloc(oldsize+1))==NULL) ||          (lseek(fd,0,SEEK_SET)!=0))                  err(1,"%s",argv[1]);</pre>    <p><strong>2.新建javaWeb项目,并生成需要的头文件.</strong></p>    <pre>  生成的操作步骤请看我的</pre>    <p><a href="/misc/goto?guid=4959715449255785879" rel="nofollow,noindex">JNI开发极简教程</a></p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/fec0b65c301f6420203a96189fe85ccf.png"></p>    <p style="text-align: center;">这里写图片描述</p>    <p><strong>3.根据下载的bsdiff4.3-win32-src代码,生成dll动态库,用于得到差分包</strong></p>    <pre>  在visual studio下 新建C++项目,并导入bsdiff源码(c,cpp,h)</pre>    <p><img src="https://simg.open-open.com/show/2ca1480ed607bc29394ba034ec588581.png"></p>    <p style="text-align: center;">这里写图片描述</p>    <p>要注意的是,编译过程并不是一帆风顺的,这里需要做什么修正.</p>    <p>用了不安全的函数->在首处添加 #define _CRT_SECURE_NO_WARNINGS</p>    <p>用了过时的函数->添加 #define _CRT_NONSTDC_NO_DEPRECATE</p>    <p>如果还报错,可以选择关闭SDL检查</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/f1fe6c3925f482a061f455d23cb1614c.png"></p>    <p style="text-align: center;">这里写图片描述</p>    <p><strong>4.修改bsdiff.cpp源文件编写JNI函数供Java层调用(注意统一编码)</strong></p>    <pre>  1.在此文件中,引入头文件 #include"app_update_service_AppBsDiff.h". 并实现其中的方法(在文件末尾实现).  2.将main函数作为jni调用的函数.即将main函数改名为bsdiff_main,然后由jni调用.</pre>    <pre>  /*  * Class:     app_update_service_AppBsDiff  * Method:    diff  * Signature: (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V  */    JNIEXPORT void JNICALL Java_app_update_service_AppBsDiff_diff  (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] = "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);  }</pre>    <p><strong>5.编译,生成解决方案,生成 E:\WorkSpace\VSWork\app_bsdiff\x64\Debug\app_bsdiff.dll文件</strong></p>    <pre>  如何生成,请参照下面的教程</pre>    <p><a href="/misc/goto?guid=4959715449255785879" rel="nofollow,noindex">JNI开发极简教程</a></p>    <p><strong>6.将dll.放入web工程的根目录.将应用生成的两个新旧apk放到指定目录,运行即可c生成差分包apk.patch</strong></p>    <p>详情参照我的代码 -- <a href="/misc/goto?guid=4959715448978435605" rel="nofollow,noindex">增量更新github</a></p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/b215dab8f7cc83c49915091c3832fd5e.png"></p>    <p><strong>7.将生成的apk.patch放到web服务器上供客户端下载.</strong></p>    <pre>  这边的服务器上传配置等,我还没来得及整理,可百度...</pre>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/3455b158dea4500305a9e99a603c5e91.png"></p>    <p style="text-align: center;">这里写图片描述</p>    <h3><strong>android客户端(类似于服务器端)</strong></h3>    <pre>  客户端要做的就是bspatch,整合old_app和patch生成new_app.</pre>    <p>代码参考- <a href="/misc/goto?guid=4959715448978435605" rel="nofollow,noindex">github的android应用项目app_update-</a></p>    <p><strong>1.编写native方法,生成头文件(别忘了添加相应权限).</strong></p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/6b9f68326ae6117e10ccff685d1fa6f8.png"></p>    <p style="text-align: center;">这里写图片描述</p>    <p><strong>2.添加本地支持</strong></p>    <p><strong>3.将bzip2源码,bspatch.c引入到项目的jni目录,并且将android.mk中的bspatch.cpp改为bspatch.c</strong></p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/59b698d7dbfedba6c4ce23727f3d8091.png"></p>    <p style="text-align: center;">这里写图片描述</p>    <p><strong>4.修改bspatch.c源码,并实现native方法.</strong></p>    <p>详情请参考代码- <a href="/misc/goto?guid=4959715448978435605" rel="nofollow,noindex">github的android应用项目app_update-</a></p>    <pre>  //合并  JNIEXPORT void JNICALL Java_com_example_app_1update_utils_BsPatch_patch    (JNIEnv *env, jclass jcls, jstring oldfile_jstr, jstring newfile_jstr, jstring patchfile_jstr){      int argc = 4;      char* oldfile = (char*)(*env)->GetStringUTFChars(env,oldfile_jstr, NULL);      char* newfile = (char*)(*env)->GetStringUTFChars(env,newfile_jstr, NULL);      char* patchfile = (char*)(*env)->GetStringUTFChars(env,patchfile_jstr, NULL);        //参数(第一个参数无效)      char *argv[4];      argv[0] = "bspatch";      argv[1] = oldfile;      argv[2] = newfile;      argv[3] = patchfile;        bspatch_main(argc,argv);        (*env)->ReleaseStringUTFChars(env,oldfile_jstr, oldfile);      (*env)->ReleaseStringUTFChars(env,newfile_jstr, newfile);      (*env)->ReleaseStringUTFChars(env,patchfile_jstr, patchfile);    }</pre>    <p><strong>5.编写更新下载方法</strong></p>    <p> </p>    <pre>  public class MainActivity extends Activity {        @Override      protected void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          setContentView(R.layout.activity_main);          findViewById(R.id.btn_update).setOnClickListener(new OnClickListener() {                @Override              public void onClick(View v) {                  // TODO Auto-generated method stub                  ApkUpdateTask apkUpdateTask=new ApkUpdateTask();                  apkUpdateTask.execute();              }          });      }        class ApkUpdateTask extends AsyncTask<Void, Void, Boolean>{            @Override          protected Boolean doInBackground(Void... params) {              try {                  //1.下载差分包                  Log.d("ccj", "开始下载");                  File patchFile = DownloadUtils.download(Constants.URL_PATCH_DOWNLOAD);                    //获取当前应用的apk文件/data/app/app                  String oldfile = ApkUtils.getSourceApkPath(MainActivity.this, getPackageName());                  //2.合并得到最新版本的APK文件                  String newfile = Constants.NEW_APK_PATH;                  String patchfile = patchFile.getAbsolutePath();                  BsPatch.patch(oldfile, newfile, patchfile);                    Log.i("ccj", "oldfile:"+oldfile);                  Log.i("ccj", "newfile:"+newfile);                  Log.i("ccj", "patch:"+patchfile);              } catch (Exception e) {                  e.printStackTrace();                  return false;              }                return true;          }            @Override          protected void onPostExecute(Boolean result) {              super.onPostExecute(result);              Log.d("ccj", "下载完成");              //3.安装              if(result){                  Toast.makeText(MainActivity.this, "您正在进行更新", Toast.LENGTH_SHORT).show();                  ApkUtils.installApk(MainActivity.this, Constants.NEW_APK_PATH);              }          }        }      }</pre>    <h2><strong>操作流程</strong></h2>    <ul>     <li>确保客户端是old_app</li>     <li> <p>改变app大小生成新的new_app</p> </li>     <li> <p>执行服务器生成patch程序</p> </li>     <li> <p>将patch包放在服务器供客户端下载</p> </li>     <li> <p>服务器合并安装</p> </li>    </ul>    <p> </p>    <p> </p>    <p>来自:http://www.jianshu.com/p/d2c55a443fe4</p>    <p> </p>