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環境請參考:NDK配置
3、編碼實現
(1)創建Native方法類先看代碼:
- 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;
- }
源碼中只有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)
- { ...}
- throwIOException(env, "can not open the patch file");
- return 0
(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)
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了。記得要添加外部存儲設備的讀寫權限。