android增量更新--服務(wù)器端&客戶端


前言

隨著應(yīng)用越來越大,應(yīng)用更新耗時間和流量的問題,就顯得格外突出.

目前原生app的更新分為兩種:重新下載源文件,還有一種就是差分包更新,也叫增量更新.

在有些應(yīng)用市場,例如google play,會對安裝包進(jìn)行拆分和合并,來達(dá)到差分更新的目的.

首先解釋一下差分包:
差分包是apk新版本和舊版本之間的包,可以稱之為patch.

應(yīng)用流程:

流程圖

操作流程

  • 確保客戶端是old_app
  • 改變app大小生成新的new_app
  • 執(zhí)行服務(wù)器生成patch程序
  • 將patch包放在服務(wù)器供客戶端下載
  • 客戶端合并安裝

實現(xiàn)原理:

1.相應(yīng)下載

自己的github項目(包括服務(wù)器端,android端,C++端),閱讀此文之前,最好下載完畢研究一下https://github.com/ccj659/incremental-update-master

原理是采用的是bsdiff,而它是一個優(yōu)秀的開源C庫,大家可以去看下

linux 的相關(guān)diff/patch下載 http://www.daemonology.net/bsdiff/

windows 上的bsdiff http://sites.inka.de/tesla/others.html#bsdiff

相關(guān)依賴bzip文檔及下載http://www.bzip.org/downloads.html

2.原理分析

Binary diff是依賴bzip壓縮庫的開源庫,其實是一種文件比較的一種算法實現(xiàn),是一個二進(jìn)制比較工具.
這里有兩個文件:老版本的app:old_app.apk 新版本的app:new_app.apk.
首先是Binarys diff:

1.首先將老文件old_app轉(zhuǎn)為二進(jìn)制文件.

2.在新文件new_app中找到和老文件相同的二進(jìn)制數(shù)據(jù).

3.在新文件生成的二進(jìn)制數(shù)據(jù)中,分離new_app中老文件數(shù)據(jù)和新的二進(jìn)制數(shù)據(jù)patch.

4.將patch數(shù)據(jù)打上新數(shù)據(jù)的標(biāo)簽,重新打包生成apk.patch.

然后是Binarys patch:
1.通過bzip壓縮算法,將old_app和patch重新打包.
關(guān)于bzip,

實現(xiàn)過程

windows服務(wù)器端

1.分析bsdiff.cpp源碼,找到main入口


    /*閱讀源碼得知,此處第一個參數(shù)argc必須是4,argv是一個字符串指針數(shù)組*/
    /***如下,此處需要四個參數(shù) 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;
    /**********************如下,此處需要四個參數(shù) 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項目,并生成需要的頭文件.

生成的操作步驟請看我的 

JNI開發(fā)極簡教程

這里寫圖片描述

3.根據(jù)下載的bsdiff4.3-win32-src代碼,生成dll動態(tài)庫,用于得到差分包

在visual studio下 新建C++項目,并導(dǎo)入bsdiff源碼(c,cpp,h)
這里寫圖片描述

要注意的是,編譯過程并不是一帆風(fēng)順的,這里需要做什么修正.

用了不安全的函數(shù)->在首處添加 #define _CRT_SECURE_NO_WARNINGS

用了過時的函數(shù)->添加 #define _CRT_NONSTDC_NO_DEPRECATE

如果還報錯,可以選擇關(guān)閉SDL檢查


這里寫圖片描述

4.修改bsdiff.cpp源文件編寫JNI函數(shù)供Java層調(diào)用(注意統(tǒng)一編碼)

1.在此文件中,引入頭文件 #include"app_update_service_AppBsDiff.h". 并實現(xiàn)其中的方法(在文件末尾實現(xiàn)).
2.將main函數(shù)作為jni調(diào)用的函數(shù).即將main函數(shù)改名為bsdiff_main,然后由jni調(diào)用.

/*
* 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);

    //參數(shù)(第一個參數(shù)無效)
    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文件

如何生成,請參照下面的教程

JNI開發(fā)極簡教程

6.將dll.放入web工程的根目錄.將應(yīng)用生成的兩個新舊apk放到指定目錄,運行即可c生成差分包apk.patch

詳情參照我的代碼 --增量更新github

7.將生成的apk.patch放到web服務(wù)器上供客戶端下載.

這邊的服務(wù)器上傳配置等,我還沒來得及整理,可百度...
這里寫圖片描述

android客戶端(類似于服務(wù)器端)

客戶端要做的就是bspatch,整合old_app和patch生成new_app.

代碼參考-github的android應(yīng)用項目app_update-

1.編寫native方法,生成頭文件(別忘了添加相應(yīng)權(quán)限).

這里寫圖片描述

2.添加本地支持

博文請參考 eclipse搭建NDK開發(fā)環(huán)境

3.將bzip2源碼,bspatch.c引入到項目的jni目錄,并且將android.mk中的bspatch.cpp改為bspatch.c

這里寫圖片描述

4.修改bspatch.c源碼,并實現(xiàn)native方法.

詳情請參考代碼-github的android應(yīng)用項目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);

    //參數(shù)(第一個參數(shù)無效)
    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.編寫更新下載方法

詳情請參考代碼-github的android應(yīng)用項目app_update-


    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);
                
                //獲取當(dāng)前應(yīng)用的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, "您正在進(jìn)行更新", Toast.LENGTH_SHORT).show();
                ApkUtils.installApk(MainActivity.this, Constants.NEW_APK_PATH);
            }
        }
        
    }
    }


操作流程

  • 確保客戶端是old_app
  • 改變app大小生成新的new_app
  • 執(zhí)行服務(wù)器生成patch程序
  • 將patch包放在服務(wù)器供客戶端下載
  • 服務(wù)器合并安裝


About Me

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容