編輯:關於Android編程
Android增量更新技術在很多公司都在使用,網上也有一些相關的文章,但大家可能未必完全理解實現的方式,本篇博客,我將一步步的帶大家實現增量更新。
當我們開發完一個項目之後,上線維護 , 增加新功能 , 添加第三方庫 , APK大小從4 - 5M , 增長到10+M , 用戶由原來的幾十秒下載 , 到現在幾分鐘以上的下載 , 網絡情況不好的時候 , 或許就是十分鐘不等。每次全量下載 , 無論從體驗還是流量上 , 都是不友好的 , 所有增量更新還是有必要的 (小公司好像沒幾個用 , 一般大公司在用,如QQ空間)。

說白了,增量更新就是:用戶手機上安裝著某個應用,下載了增量包,手機上的apk和增量包合並形成新的包,然後再次安裝(注意這個過程是要重新安裝的,當然部分應用市場有root權限你可能感知不到)。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPjxzdHJvbmc+yrXP1su8wrejujwvc3Ryb25nPsq508O/qtS0z+7Ev2JzZGlmZiC9+NDQzsS8/sTayN2xyL3Po6yyosfSyrnTw8HLYnppcDK9+NDQzsS8/tG5y/Ugo6wgy/nT0LXDs/a1xLLut9aw/L/JxNyxyMDtwtvWtdKq0KEgo6wgvfjSu7K9v8nS1Lz1ydnTw7unwffBvyCho9T2wb+4/NDCIKOsIL3Pzqq52Lz8tcSyv7fWvs3Kx8n6s8my7rfWsPwgo6wgvavQwr7JQVBLvfjQ0LHIvc8go6wgyfqzydK7uPbQwrXEzsS8/iChozwvcD4NCjxoMSBpZD0="需要使用的資源">需要使用的資源
進行增加更新主要是通過開源項目bsdiff項目來進行實現,還需要一些輔助的工具,列表如下:
bsdiff — bsdiff (win環境) 生成差分包及合並差分包庫 , 源碼內已包含bzip2
項目地址:
https://github.com/hymanAndroid/tools/tree/master/bsdiff-4.3
bzip2 — bzip2 bsdiff 依賴
服務器 — Tomcat 7.0 (模擬網絡環境)放置差分包 , 供APP下載
開發工具 — Eclipse NDK開發 , 目前建議使用Eclipse開發
開發工具 — VS 因為服務器搭建在windows平台 , 所以使用VS開發JNI
下載完bsdiff之後 , 我們看到如下目錄:

看這麼多文件 , 還有一些亂七八糟的不知道什麼的文件 , 那麼我們只關注 , 我們想要的文件 , 將C/C++源文件以及.h頭文件,找出來 ,放到一個干淨的文件夾中 。
1.使用visual studio去新建一個項目

2.將bsdiff中的.c和.cpp文件放到VS項目源文件中,把.h文件放到VS項目頭文件中

然後嘗試進行編譯,發現報錯,用到了過時的函數

錯誤內容是:使用了非安全的函數 , 解決方法是在文件中聲明#define _CRT_SECURE_NO_WARNINGS即可 。如果是多個文件都需要進行聲明,顯然一條條的聲明太麻煩了,所以可以在屬性中聲明,所有的文件都適用。

修改完畢,再次嘗試編譯,還是報錯,內容如下:

原因是在VS中通不過安全語法檢查 , 在VS中進行如下設置,將SDL檢查由“是”改為“否”:

還需要在文件中添加#define _CRT_NONSTDC_NO_DEPRECATE預處理指令。

然後我們需要生成.dll動態庫,需要我們在配置中,將“配置類型”改為“動態庫(.dll)”

編譯生成.dll動態庫後 , 賦值到服務器項目的目錄下 , 或是Java項目也可以 。
編譯生成.dll動態庫時,還可能出現一個坑,那就是:變量未初始化
u_char *old = nullptr,*_new = nullptr;
off_t oldsize,newsize;
off_t *I,*V = nullptr;
可能會是這幾個 , 將其賦值為nullptr就可以了。 如果還有其他錯誤 , 可執行分析 , google , 也歡迎評論留言 , 多多交流 。
3.了解bsdiff的用法 , 在main函數中尋找思路
int 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;
if(argc!=4) errx(1,"usage: %s oldfile newfile patchfile\n",argv[0]);
在bsdiff.cpp文件找到帶參數的main函數 , 並且有一個關於用法的線索 , 那就是:
if(argc!=4) errx(1,"usage: %s oldfile newfile patchfile\n",argv[0]);
我們可以根據這句話來推測 , 需要四個參數 , 並且三個參數必須傳入的文件路徑 , 剩下的一個參數不會有影響,直接傳入任何值都可以。
4.創建JNI方法 , 修改main函數
既然知道了需要傳入的參數 , 那麼就可以創建一個Java工程 , 編寫JNI方法了。
public class BsDiff {
/**
* 生成差分包
*
* @param oldfile 老版本文件路徑
* @param newfile 新版本文件路徑
* @param patchfile 生成差分包文件路徑
*/
public native static void diff(String oldfile, String newfile, String patchfile);
static{
System.loadLibrary("bsdiff");
}
}
接下來使用javah命令,生成頭文件 , 將.h頭文件拷貝到我們 VS的工程中,同時com_dispatch_bsdiff_BsDiff.h頭文件還需要依賴jni.h和jni_md.h兩個文件

com_dispatch_bsdiff_BsDiff.h中引入jni.h

將頭文件com_dispatch_bsdiff_BsDiff.h引入到bsdiff.cpp文件中

5.編寫調用的C函數 , 並修改main函數名稱,main函數作為入口函數 , 在JNI中就適用了 , 所有將main函數名稱改一下 , 在JNI的C函數中調用即可 。

編寫bsdiff.cpp的native函數
//JNI 調用
JNIEXPORT void JNICALL Java_com_dispatch_bsdiff_BsDiff_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);
}
一切修改完畢 ,重新編譯生成.dll動態庫 。
差分包工具類:
public class BsdiffUtils {
/**
* 生成差分包
* @param oldFilePath 老版本文件路徑
* @param newFilePath 新版本文件路徑
* @param patchFilePath 生成差分包文件路徑
*/
public static native void diff(String oldFilePath , String newFilePath , String patchFilePath) ;
}
使用差分包工具類:
public class BsdiffApk {
public static void main(String args[]) {
/**
* 文件差分
*/
BsdiffUtils.diff(Constract.OLD_FILE_PATH, Constract.NEW_FILE_PATH, Constract.PATCH_FILE_PATH);
}
}
常量類,主要用於存儲差分包路徑:
public class Constract {
public static final String OLD_FILE_PATH = "E:/javaee_workspace/AppUpdateServer/apk/app-old.apk" ;
public static final String NEW_FILE_PATH = "E:/javaee_workspace/AppUpdateServer/apk/app-new.apk" ;
public static final String PATCH_FILE_PATH = "E:/javaee_workspace/AppUpdateServer/apk/App_patch.patch" ;
}
生成差分包:

1.提取bzip2中的源文件

2.將bzip2加入到Android Studio項目中
首先將工程切換到Project模式 , 將bzip2文件夾復制到cpp目錄下 。因為最新的Android Studio采用的是CMake構建工具 , 所有需要在bzip2目錄下,創建一個CMakeLists.txt文件:

3.將bspatch.c復制到cpp目錄下 , 並將自動生成的CMakeList.txt文件拖拽到cpp目錄下 , 並添加子目錄參與編譯 。

修改了CMakeLists.txt文件的路徑之後 , 需要在build.gradle中修改一下配置了:

並且配置一下build環境

4.然後就是在java代碼中編寫合並差分文件的JNI方法
public class BspatchJNI {
/**
* 合並增量文件
* @param oldFilePath 當前APK路徑
* @param newFilePath 合成後的新的APK路徑
* @param patchFilePath 增量文件路徑
*/
public static native void bspatchJNI(String oldFilePath,String newFilePath,String patchFilePath) ;
static {
System.loadLibrary("bspatch");
}
}
5.編寫C函數 , 怎樣找執行函數 , 和前面拆分文件的套路是一樣的
/*合並APK*/
JNIEXPORT void JNICALL
Java_com_zeno_incrementupdate_ndk_BspatchJNI_bspatchJNI(JNIEnv *env, jclass type,
jstring oldFilePath_, jstring newFilePath_,
jstring patchFilePath_) {
const char *oldFilePath = (*env)->GetStringUTFChars(env, oldFilePath_, 0);
const char *newFilePath = (*env)->GetStringUTFChars(env, newFilePath_, 0);
const char *patchFilePath = (*env)->GetStringUTFChars(env, patchFilePath_, 0);
// if(argc!=4) errx(1,"usage: %s oldfile newfile patchfile\n",argv[0]);
int argc = 4 ;
char* argv[4] ;
argv[0] = "bspatch";
argv[1] = oldFilePath;
argv[2] = newFilePath;
argv[3] = patchFilePath;
bspatch_main(argc,argv);
LOGE("MainActivity","%s","合並APK完成");
(*env)->ReleaseStringUTFChars(env, oldFilePath_, oldFilePath);
(*env)->ReleaseStringUTFChars(env, newFilePath_, newFilePath);
(*env)->ReleaseStringUTFChars(env, patchFilePath_, patchFilePath);
}
需要注意的時 , 在bspatch.c中是需要引入bzip2的 , 所有需要在文件頭部, 引入bzip2 :
/ bzip2 #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" #define LOGE(TAG,FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,TAG,FORMAT,__VA_ARGS__)
拆分和合並的代碼就已經寫完了,其余下載的java代碼,大家可以根據自己項目的情況去進行編寫,這裡就不再貼出
6.打包
因為Android Studio使用了instant run技術 , 所以使用Android Studio生成APK最好是打正式包 , 並且包中內容要有差異性 , 然後再生成差分包 , 直接放置在WEB項目的WebContent根目錄下即可 。
Android中MVP的初步認識與簡單用法
概述認識MVP模式MVP 模式實際上指的是 Model-View-Presenter 主要的目的是為了劃分各個模塊的負責區域,分工明確,使代碼清晰了很多。也是為了減少 A
Android代碼混淆ProGuard工作原理簡介
ProGuard能夠對Java類中的代碼進行壓縮(Shrink),優化(Optimize),混淆(Obfuscate),預檢(Preveirfy)。 1. 壓縮(Sh
美團Android DEX自動拆包及動態加載簡介
概述作為一個android開發者,在開發應用時,隨著業務規模發展到一定程度,不斷地加入新功能、添加新的類庫,代碼在急劇的膨脹,相應的apk包的大小也急劇增加,
Android開發實例之登錄界面的實現
本文要演示的Android開發實例是如何完成一個Android中的miniTwitter登錄界面,下面將分步驟講解怎樣實現圖中的界面效果,讓大家都能輕松的做出美觀的登錄界