android增量更新--服務器端&客戶端
前言
隨著應用越來越大,應用更新耗時間和流量的問題,就顯得格外突出.
目前原生app的更新分為兩種:重新下載源文件,還有一種就是差分包更新,也叫增量更新.
在有些應用市場,例如google play,會對安裝包進行拆分和合并,來達到差分更新的目的.
首先解釋一下差分包:
差分包是apk新版本和舊版本之間的包,可以稱之為patch.
應用流程:
流程圖
操作流程
- 確保客戶端是old_app
- 改變app大小生成新的new_app
- 執行服務器生成patch程序
- 將patch包放在服務器供客戶端下載
- 客戶端合并安裝
實現原理:
1.相應下載
自己的github項目(包括服務器端,android端,C++端),閱讀此文之前,最好下載完畢研究一下 https://github.com/ccj659/incremental-update-master
原理是采用的是bsdiff,而它是一個優秀的開源C庫,大家可以去看下
linux 的相關diff/patch下載 http://www.daemonology.net/bsdiff/
windows 上的bsdiff http://sites.inka.de/tesla/others.html#bsdiff
相關依賴bzip文檔及下載 http://www.bzip.org/downloads.html
2.原理分析
Binary diff是依賴bzip壓縮庫的開源庫,其實是一種文件比較的一種算法實現,是一個二進制比較工具.
這里有兩個文件:老版本的app:old_app.apk 新版本的app:new_app.apk.
首先是Binarys diff:
1.首先將老文件old_app轉為二進制文件.
2.在新文件new_app中找到和老文件相同的二進制數據.
3.在新文件生成的二進制數據中,分離new_app中老文件數據和新的二進制數據patch.
4.將patch數據打上新數據的標簽,重新打包生成apk.patch.
然后是Binarys patch:
1.通過bzip壓縮算法,將old_app和patch重新打包.
關于bzip,
實現過程
windows服務器端
1.分析bsdiff.cpp源碼,找到main入口
/閱讀源碼得知,此處第一個參數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>
2.新建javaWeb項目,并生成需要的頭文件.
生成的操作步驟請看我的
這里寫圖片描述
3.根據下載的bsdiff4.3-win32-src代碼,生成dll動態庫,用于得到差分包
在visual studio下 新建C++項目,并導入bsdiff源碼(c,cpp,h)
這里寫圖片描述
要注意的是,編譯過程并不是一帆風順的,這里需要做什么修正.
用了不安全的函數->在首處添加 #define _CRT_SECURE_NO_WARNINGS
用了過時的函數->添加 #define _CRT_NONSTDC_NO_DEPRECATE
如果還報錯,可以選擇關閉SDL檢查
這里寫圖片描述
4.修改bsdiff.cpp源文件編寫JNI函數供Java層調用(注意統一編碼)
1.在此文件中,引入頭文件 #include"app_update_service_AppBsDiff.h". 并實現其中的方法(在文件末尾實現). 2.將main函數作為jni調用的函數.即將main函數改名為bsdiff_main,然后由jni調用./* * 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); }5.編譯,生成解決方案,生成 E:\WorkSpace\VSWork\app_bsdiff\x64\Debug\app_bsdiff.dll文件
如何生成,請參照下面的教程6.將dll.放入web工程的根目錄.將應用生成的兩個新舊apk放到指定目錄,運行即可c生成差分包apk.patch
詳情參照我的代碼 -- 增量更新github
7.將生成的apk.patch放到web服務器上供客戶端下載.
這邊的服務器上傳配置等,我還沒來得及整理,可百度...
這里寫圖片描述
android客戶端(類似于服務器端)
客戶端要做的就是bspatch,整合old_app和patch生成new_app.代碼參考- github的android應用項目app_update-
1.編寫native方法,生成頭文件(別忘了添加相應權限).
這里寫圖片描述
2.添加本地支持
3.將bzip2源碼,bspatch.c引入到項目的jni目錄,并且將android.mk中的bspatch.cpp改為bspatch.c
這里寫圖片描述
4.修改bspatch.c源碼,并實現native方法.
詳情請參考代碼- github的android應用項目app_update-
//合并 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); }5.編寫更新下載方法
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); } } } }操作流程
- 確保客戶端是old_app
-
改變app大小生成新的new_app
-
執行服務器生成patch程序
-
將patch包放在服務器供客戶端下載
-
服務器合并安裝
來自:http://www.jianshu.com/p/d2c55a443fe4