1. 程式人生 > >增量升級(省流量更新)的Android客戶端實現

增量升級(省流量更新)的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環境請參考:

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);
    }
}
重點在程式碼的第三行,其他幾個方法在後面介紹。編譯之後,會在專案的bin/classes下生成.class檔案,開啟cmd,切到該目錄,輸入
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了。記得要新增外部儲存裝置的讀寫許可權。