Android的增量更新,差分更新--伺服器端&客戶端
阿新 • • 發佈:2018-12-23
前言
隨著應用越來越大,應用更新耗時間和流量的問題,就顯得格外突出.
目前原生app的更新分為兩種:重新下載原始檔,還有一種就是差分包更新,也叫增量更新.
在有些應用市場,例如google play,會對安裝包進行拆分和合並,來達到差分更新的目的.
首先解釋一下差分包:
差分包是apk新版本和舊版本之間的包,可以稱之為patch.
About Me
應用流程:
操作流程
- 確保客戶端是old_app
- 改變app大小生成新的new_app
- 執行伺服器生成patch程式
- 將patch包放在伺服器供客戶端下載
- 伺服器合併安裝
實現原理:
1.相應下載
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]);
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
7.將生成的apk.patch放到web伺服器上供客戶端下載.
這邊的伺服器上傳配置等,我還沒來得及整理,可百度...
android客戶端(類似於伺服器端)
客戶端要做的就是bspatch,整合old_app和patch生成new_app.
1.編寫native方法,生成標頭檔案(別忘了新增相應許可權).
2.新增本地支援
3.將bzip2原始碼,bspatch.c引入到專案的jni目錄,並且將android.mk中的bspatch.cpp改為bspatch.c
4.修改bspatch.c原始碼,並實現native方法.
//合併
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包放在伺服器供客戶端下載
- 伺服器合併安裝