增量升級(省流量更新)的Android客戶端實現
By sgwhp (http://blog.csdn.net/sgwhp)轉載請註明出處
用過類似360手機助手應該對省流量更新都很熟悉了。詳細資料可以參考以下兩個部落格:
本文在以上兩篇部落格的基礎上再增加了異常處理,並將生成的so庫和Native程式碼一起打包。
1、準備工具
(1)bspatch原始碼(點選下載)、某個應用的兩個不同版本。或者直接下載上面提到的第一個博主提供的工具和素材。點選開啟連結 這裡麵包括了我們需要用到的bsdiff原始碼和apk等。
(2)除此之外,還需要下載bzip2。點選開啟連結
2、編譯環境
Linux、Windows均可,但必須有NDK,Windows下的NDK版本需在R7以上,否則還需要安裝cygwin。Windows下搭建NDK環境請參考:
3、編碼實現
(1)建立Native方法類先看程式碼:
重點在程式碼的第三行,其他幾個方法在後面介紹。編譯之後,會在專案的bin/classes下生成.class檔案,開啟cmd,切到該目錄,輸入public class PatchClient{ //定義Native方法 static private native int applyPatchToOldApk(String oldapk_filepath, String newapk_savepath , String patchpath); public static void loadLib(){ System.loadLibrary("PatchDroid"); } /** * * @param oldApkPath 舊版apk檔案的路徑 * @param newApkPath 新版apk檔案的路徑 * @param patchPath 增量包的路徑 * @throws IOException */ public static void applyPatch(String oldApkPath, String newApkPath, String patchPath) throws IOException{ applyPatchToOldApk(oldApkPath, newApkPath, patchPath); } /** * 根據context獲取本app的舊版apk檔案,與增量包合併生成新版apk * @param context * @param newApkPath 新版apk檔案路徑 * @param patchPath 增量包路徑 * @throws IOException */ public static void applyPatchToOwn(Context context, String newApkPath, String patchPath) throws IOException{ String old = context.getApplicationInfo().sourceDir; applyPatchToOldApk(old, newApkPath, patchPath); } }
javah cn.sgwhp.PatchDroid(包名.類名)
此時會生成標頭檔案cn_sgwhp_patchdroid_PatchClient.h,在專案的根目錄下建立jni資料夾,並把剛才生成的.h檔案剪下過去。
(2)實現Native方法
解壓下載的bzip2包以及bspatch原始碼到jni目錄下,將bspatch.c命名為cn_sgwhp_patchdroid_PatchClient.c,實現其中的Java_cn_sgwhp_patchdroid_PatchClient_applyPatchToOldApk方法。這裡只簡單地呼叫原始碼的main方法來實現:
#include "cn_sgwhp_patchdroid_PatchClient.h"
#include "bzlib_private.h"
JNIEXPORT jint JNICALL Java_cn_sgwhp_patchdroid_PatchClient_applyPatchToOldApk(JNIEnv *env,
jobject obj, jstring old, jstring new , jstring patch){
int argc=4;
char * argv[argc];
argv[0]="bspatch";
argv[1]=(char*)((*env)->GetStringUTFChars(env,old, 0));
argv[2]=(char*)((*env)->GetStringUTFChars(env,new, 0));
argv[3]=(char*)((*env)->GetStringUTFChars(env,patch, 0));
int ret=applypatch(argc, argv, env); //將原始碼中的main方法改成applypatch即可
(*env)->ReleaseStringUTFChars(env,old,argv[1]);
(*env)->ReleaseStringUTFChars(env,new,argv[2]);
(*env)->ReleaseStringUTFChars(env,patch,argv[3]);
return ret;
}
(3)異常處理
原始碼中只有c的err異常,java是沒有辦法在控制檯輸出這些異常資訊的,我們將它改造一下,利用JNIEnv的ThrowNew方法來丟擲IOException。在cn_sgwhp_patchdroid_PatchClient.c中新增throwIOException方法,並把env引數從Java_cn_sgwhp_patchdroid_PatchClient_applyPatchToOldApk傳入applypatch:
void throwIOException(JNIEnv* env,const char* msg)
{
//找到指定名稱的類的class型別
jclass cls=(*env)->FindClass(env,"java/io/IOException");
(*env)->ThrowNew(env,cls,msg);
(*env)->DeleteLocalRef(env,cls);
}
int applypatch(int argc,char * argv[],JNIEnv* env)
{ ...}
把applypatch方法中所有呼叫err方法的程式碼註釋掉,改成:
throwIOException(env, "can not open the patch file");
return 0
此時,Native方法類中的applyPatch和applyPatchToOwn要 申明丟擲IOException,避免程式因為異常而退出。(4)編譯
在jni目錄下建立Android.mk檔案,並複製以下程式碼:
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
# This is the target being built.
LOCAL_MODULE:= PatchDroid
# All of the source files that we will compile.
# 具體到底需要哪些c程式碼,沒有仔細研究過
LOCAL_SRC_FILES:= cn_sgwhp_patchdroid_PatchClient.c \
bzlib.c \
blocksort.c \
compress.c \
crctable.c \
decompress.c \
huffman.c \
randtable.c \
bzip2.c \
ifeq ($(HOST_OS),windows)
#NDK環境下
LOCAL_LDLIBS := -llog
else
#完整原始碼環境下
LOCAL_SHARED_LIBRARIES := libutils
endif
LOCAL_SHARED_LIBRARIES := \
libandroid_runtime
# No static libraries.
LOCAL_STATIC_LIBRARIES := \
libbz
# Also need the JNI headers.
LOCAL_C_INCLUDES += \
$(JNI_H_INCLUDE) external/bzip2
# No special compiler flags.
LOCAL_CFLAGS +=
include $(BUILD_SHARED_LIBRARY)
重新整理專案NDK就會自動幫我們編譯了。
4、打包
右擊專案,選擇properties,選擇Android,勾選Is Library,儲存之後Clean專案,在bin目錄下會生成一個jar包。其他專案需要用到時,把jar包匯入,並把libs目錄下的armeabi目錄複製到新專案的libs目錄下即可。
5、後話
有關applyPatchToOwn(Context context, String newApkPath, String patchPath)方法:程式安裝後,apk檔案會存在於/data/app目錄下,名字是“包名-數字.apk”,其中“數字”一般是1或2,不清楚規則是怎樣的。不過沒關係,我們可以通過ApplicationInfo的sourceDir來獲得目前版本的apk絕對路徑,這樣只要將增量包下載到sd卡就可以合成新版的apk了。記得要新增外部儲存裝置的讀寫許可權。