前言
如果要對一個 APP 進行更新,你會怎么做呢,或許你的宿主 App 有若干功能,每一個功能都是一個插件,每次在功能更新的時候,直接更新插件就好,但是這種方式也有局限,它不能進行宿主程序更新,對于非常大的 apk ,增量更新還是有必要的,可以節省流量。增量更新的難點在于不同市場,不同版本都需要生成對應的差分包。差分包是將手機上已安裝 apk 與服務器端最新 apk 進行二進制對比得到的,用戶更新程序時,只需要下載差分包,并在本地使用差分包與已安裝 apk,合成新版 apk。假設現在已經有三個版本version1.apk, version2.apk ,version3.apk ,最新版本是 version4.apk,那么需要在服務器端與最新版本version4.apk 相比較生成 version1.patch, version2.patch , version3.patch 三個差分包。假設客戶端現在是 version3.apk 這個版本,那么就需要下載 version3.patch 差分包,下載之后在客戶端完成合并操作并且提示用戶進行安裝。那么我們現在至少有兩個問題要解決,如何生成差分包?如何對差分包進行合并?
差量更新算法的核心思想是,盡可能多的利用old文件中已有的內容,盡可能少的加入新的內容來構建new文件。通常的做法是對old文件和new文件做子字符串匹配或使用hash技術,提取公共部分,將new文件中剩余的部分打包成patch包 apk文件的差分、合成,可以通過開源的二進制比較工具 bsdiff( https://github.com/mendsley/bsdiff) 來實現,又因為bsdiff依賴bzip2,所以我們還需要用到 bzip2,在bsdiff中bsdiff.c 用于生成差分包,bspatch.c 用于合成差分包。傳送門( http://www.daemonology.net/bsdiff/ )
一、在服務端生成差分包
1、下載bsdiff文件
如圖,把里面所有的C/C 文件和.h文件全部添加到Visual Studio中(bspatch.cpp文件可以不添加,這個是用于客戶端合并的,bsdiff.cpp才是文件差分)。
圖1-bsdiff文件內容
2、新建web工程,定義jni函數。
web項目結構.png
jni函數是用于調用bsdiff.cpp中的main函數的,bsdiff.cpp文件main函數是實現生成差分包的入口,通過分析代碼可以知道,main函數中需要傳遞四個參數,第一個參數沒用到,可以隨便指定,第二個參數是舊版本apk的路徑,第三個參數是新版本apk的路徑,第四個參數是生成差分包所在的路徑。所以jin函數定義成這樣:
/* * Class: com_wangjing_patch_BsDiff * Method: bsdiff * Signature: (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V */ JNIEXPORT void JNICALL Java_com_wangjing_patch_BsDiff_bsdiff (JNIEnv *, jclass, jstring, jstring, jstring);
3、在bsdiff.cpp實現bsdiff函數。
將main函數修改成bsdiffmain,在bsdiff函數中調用。
JNIEXPORT void JNICALL Java_com_wangjing_patch_BsDiff_bsdiff(JNIEnv * env, jclass cla, jstring old_apk_path_str, jstring new_apk_path_str, jstring diff_packet_file_path_str){ char * old_apk_path = (char*)env-gt;GetStringUTFChars(old_apk_path_str,NULL); char * new_apk_path = (char*)env-gt;GetStringUTFChars(new_apk_path_str, NULL); char * diff_packet_file_path = (char*)env-gt;GetStringUTFChars(diff_packet_file_path_str, NULL); char *argv[4]; argv[0] = quot;bisdiffquot;; argv[1] = old_apk_path; argv[2] = new_apk_path; argv[3] = diff_packet_file_path; bsdiffmain(4,argv); env-gt;ReleaseStringUTFChars(old_apk_path_str,old_apk_path); env-gt;ReleaseStringUTFChars(new_apk_path_str, new_apk_path); env-gt;ReleaseStringUTFChars(diff_packet_file_path_str, diff_packet_file_path); }
4、編譯成dll動態庫
這個過程遇到很多錯誤,比如這個開源庫用到了一些不安全的函數,在文件中添加#define _CRT_SECURE_NO_WARNINGS。還有指針未初始化的等等,編譯成功后,我們將生成的dll文件放到服務器目錄下。
5、加載動態庫
public class BiDiffTest { public static void main(String[] args) { BsDiff.bsdiff(Constant.old_apk_path, Constant.new_apk_path, Constant.diff_file_path); } }
public class BsDiff { /** * * @param oldApkPath * @param newApkPath * @param pacthPacketPath */ public static native void bsdiff(String oldApkPath,String newApkPath,String pacthPacketPath); static { System.loadLibrary(quot;PatchDiffquot;); } }
二、在客戶端合并差分包
1、項目配置
1.1 新建一個android項目,最好將androidStudio升級到2.2,這樣寫C代碼時候更加方便。然后將下載的bizip2文件復制到cpp目錄下以及將bsdiff中bspatch.cpp復制到cpp目錄下。
android項目新建.png
1.2. 配置gralde
gradle配置.png
1.3配置CMakeLists.txt
將項目根目錄的CMakeLists.txt移動到cpp目錄下,編輯打開添加下面兩行代碼
set(bzip2_src_DIR ${CMAKE_SOURCE_DIR}) add_subdirectory(${bzip2_src_DIR}/bzip2)
在bizp2文件中新建一個CMakeLists.txt文件,內容是:PROJECT(bzip2)
2、 定義jni函數
調用 bspatch.cpp 中的 main 函數,這一步與上面的調用 bsdiff.cpp 步驟類似。
public class BsPatch { public native static void patch(String oldfile, String newfile, String patchfile); static{ System.loadLibrary(quot;bspatchquot;); } }
3、在bspatch.cpp中實現jni函數
首先將bspatch.cpp中的main函數重命名成bspatch_main,在下面調用
JNIEXPORT void JNICALL Java_apk_wangjing_com_apkpatch_utils_BsPatch_patch (JNIEnv *env, jclass jcls, jstring oldfile_jstr, jstring newfile_jstr, jstring patchfile_jstr){ int argc = 4; char* oldfile = (char*)(*env)-gt;GetStringUTFChars(env,oldfile_jstr, NULL); char* newfile = (char*)(*env)-gt;GetStringUTFChars(env,newfile_jstr, NULL); char* patchfile = (char*)(*env)-gt;GetStringUTFChars(env,patchfile_jstr, NULL); //參數(第一個參數無效) char *argv[4]; argv[0] = quot;bspatchquot;; argv[1] = oldfile; argv[2] = newfile; argv[3] = patchfile; __android_log_print(ANDROID_LOG_DEBUG,quot;bspatchquot;,quot;合并開始quot;); bspatch_main(argc,argv); (*env)-gt;ReleaseStringUTFChars(env,oldfile_jstr, oldfile); (*env)-gt;ReleaseStringUTFChars(env,newfile_jstr, newfile); (*env)-gt;ReleaseStringUTFChars(env,patchfile_jstr, patchfile); }
4、下載/合并差分包,并且提示用戶安裝
public class DownLoaderTask extends AsyncTasklt;Void, Void, Booleangt; { @Override protected Boolean doInBackground(Void... params) { try { //1、下載服務器中的差分包 File patchFile = DownloadUtils.download(Constants.URL_PATCH_DOWNLOAD); if (patchFile != null) { //2、獲取本地(data/dada/app目錄)apk文件 String oldFilePath = ApkUtils.getSourceApkPath(MainActivity.this, getPackageName()); String newFilePath = Constants.NEW_APK_PATH; //3、將本地apk文件與差分包進行合并 BsPatch.patch(oldFilePath, newFilePath, patchFile.getAbsolutePath()); } return true; } catch (Exception e) { return false; } } @Override protected void onPostExecute(Boolean aBoolean) { super.onPostExecute(aBoolean); if(aBoolean){ Toast.makeText(MainActivity.this, quot;開始更新quot;, Toast.LENGTH_SHORT).show(); ApkUtils.installApk(MainActivity.this, Constants.NEW_APK_PATH); }else { Toast.makeText(MainActivity.this, quot;更新失敗quot;, Toast.LENGTH_SHORT).show(); } } }
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mApkCodeTv = (TextView) findViewById(R.id.apk_code); ApkUtils.Apk apkInfo = ApkUtils.getAPPVersionCodeFromAPP(this); mApkCodeTv.setText(apkInfo.toString()); new DownLoaderTask().execute(); }
到此 ,如果順利的話,apk就跟新成功了。最后附上一個開源項目,也是用bsdiff實現的增量更新---- Smart App Updates( https://github.com/cundong/SmartAppUpdates)
Tags: JNI C 安卓開發
文章來源:http://www.jianshu.com/p/2551a01916df