Android應用增量更新

jopen 8年前發布 | 9K 次閱讀 Android開發 移動開發

來源:https://github.com/cundong/SmartAppUpdates 

介紹

你所看到的,是一個用于Android應用程序增量更新的庫。

包括客戶端、服務端兩部分代碼。

原理

自從 Android 4.1 開始, Google Play 引入了應用程序的增量更新功能,App使用該升級方式,可節省約2/3的流量。

Smart app updates is a new feature of Google Play that introduces a better way of delivering app updates to devices. When developers publish an update, Google Play now delivers only the bits that have changed to devices, rather than the entire APK. This makes the updates much lighter-weight in most cases, so they are faster to download, save the device’s battery, and conserve bandwidth usage on users’ mobile data plan. On average, a smart app update is about 1/3 the sizeof a full APK update.

現在國內主流的應用市場也都支持應用的增量更新了。

增量更新的原理,就是將手機上已安裝apk與服務器端最新apk進行二進制對比,得到差分包,用戶更新程序時,只需要下載差分包,并在本地使用差分包與已安裝apk,合成新版apk。

例如,當前手機中已安裝微博V1,大小為12.8MB,現在微博發布了最新版V2,大小為15.4MB,我們對兩個版本的apk文件差分比對之后,發現差異只有3M,那么用戶就只需要要下載一個3M的差分包,使用舊版apk與這個差分包,合成得到一個新版本apk,提醒用戶安裝即可,不需要整包下載15.4M的微博V2版apk。

apk文件的差分、合成,可以通過 開源的二進制比較工具 bsdiff 來實現,又因為bsdiff依賴bzip2,所以我們還需要用到 bzip2

bsdiff中,bsdiff.c 用于生成差分包,bspatch.c 用于合成文件。

弄清楚原理之后,我們想實現增量更新,共需要做3件事:

  • 在服務器端,生成兩個版本apk的差分包;

  • 在手機客戶端,使用已安裝的apk與這個差分包進行合成,得到新版的微博apk;

  • 校驗新合成的apk文件是否完整,MD5或SHA1是否正確,如正確,則引導用戶安裝;

過程分析

1 生成差分包

這一步需要在服務器端來實現,一般來說,每當apk有新版本需要提示用戶升級,都需要運營人員在后臺管理端上傳新apk,上傳時就應該由程序生成與之前所有舊版本們與最新版的差分包。

例如: 你的apk已經發布了3個版,V1.0、V2.0、V3.0,這時候你要在后臺發布V4.0,那么,當你在服務器上傳最新的V4.0包時,服務器端就應該立即生成以下差分包:

  1. V1.0 ――> V4.0的差分包;

  2. V2.0 ――> V4.0的差分包;

  3. V3.0 ――> V4.0的差分包;

ApkPatchLibraryServer 工程即為 Java 語言實現的服務器端差分程序。

下面對ApkPatchLibraryServer做一些簡單說明:

1.1 C部分

ApkPatchLibraryServer/jni 中,除了以下4個:

com_cundong_utils_DiffUtils.c

com_cundong_utils_DiffUtils.h

com_cundong_utils_PatchUtils.c

com_cundong_utils_PatchUtils.h

jni/bzip2目錄中的文件,全部來自bzip2項目。

com_cundong_utils_DiffUtils.c

com_cundong_utils_DiffUtils.h

用于生成差分包。

com_cundong_utils_PatchUtils.c

com_cundong_utils_PatchUtils.h

用于合成新apk文件。

com_cundong_utils_DiffUtils.c 修改自 bsdiff/bsdiff.ccom_cundong_utils_PatchUtils.c修改自bsdiff/bspatch.c

我們在需要將jni中的C文件,build輸出為動態鏈接庫,以供Java調用(Window環境下生成的文件名為libApkPatchLibraryServer.dll,Unix-like系統下為libApkPatchLibraryServer.so,OSX下為libApkPatchLibraryServer.dylib)。

Build成功后,將該動態鏈接庫文件,加入環境變量,供Java語言調用。

com_cundong_utils_DiffUtils.cJava_com_cundong_utils_DiffUtils_genDiff() 方法,用于生成差分包的:

JNIEXPORT jint JNICALL Java_com_cundong_utils_DiffUtils_genDiff(JNIEnv *env,
        jclass cls, jstring old, jstring new, jstring patch) {
    int argc = 4;
    char * argv[argc];
    argv[0] = "bsdiff";
    argv[1] = (char*) ((*env)->GetStringUTFChars(env, old, 0));
    argv[2] = (char*) ((*env)->GetStringUTFChars(env, new, 0));
    argv[3] = (char*) ((*env)->GetStringUTFChars(env, patch, 0));

    printf("old apk = %s \n", argv[1]);
    printf("new apk = %s \n", argv[2]);
    printf("patch = %s \n", argv[3]);

    int ret = genpatch(argc, argv);

    printf("genDiff result = %d ", ret);

    (*env)->ReleaseStringUTFChars(env, old, argv[1]);
    (*env)->ReleaseStringUTFChars(env, new, argv[2]);
    (*env)->ReleaseStringUTFChars(env, patch, argv[3]);

    return ret;
}

com_cundong_utils_PatchUtils.cJava_com_cundong_utils_PatchUtils_patch() 方法,用于合成新的APK;

JNIEXPORT jint JNICALL Java_com_cundong_utils_PatchUtils_patch
  (JNIEnv *env, jclass cls,
            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));

    printf("old apk = %s \n", argv[1]);
    printf("patch = %s \n", argv[3]);
    printf("new apk = %s \n", argv[2]);

    int ret = applypatch(argc, argv);

    printf("patch result = %d ", ret);

    (*env)->ReleaseStringUTFChars(env, old, argv[1]);
    (*env)->ReleaseStringUTFChars(env, new, argv[2]);
    (*env)->ReleaseStringUTFChars(env, patch, argv[3]);
    return ret;
}

1.2 Java部分

com.cundong.utils包,為調用C語言的Java實現; com.cundong.apkdiff包,為apk差分程序的Demo; com.cundong.apkpatch包,為apk合并程序的Demo;

調用,com.cundong.utils.DiffUtilsgenDiff()方法,可以通過傳入的新舊apk路徑,得到差分包。

/**
 * 類說明:     APK Diff工具類
 * 
 * @author     Cundong
 * @date          2013-9-6
 * @version  1.0
 */
public class DiffUtils {

    /**
     * native方法 比較路徑為oldPath的apk與newPath的apk之間差異,并生成patch包,存儲于patchPath
     * 
     * 返回:0,說明操作成功
     *  
     * @param oldApkPath 示例:/sdcard/old.apk
     * @param newApkPath 示例:/sdcard/new.apk
     * @param patchPath  示例:/sdcard/xx.patch
     * @return
     */
    public static native int genDiff(String oldApkPath, String newApkPath, String patchPath);
}

調用,com.cundong.utils.PatchUtilspatch()方法,可以通過舊apk與差分包,合成為新apk。

/**
 * 類說明:     APK Patch工具類
 * 
 * @author    Cundong
 * @date         2013-9-6
 * @version 1.0
 */
public class PatchUtils {

    /**
     * native方法 使用路徑為oldApkPath的apk與路徑為patchPath的補丁包,合成新的apk,并存儲于newApkPath
     * 
     * 返回:0,說明操作成功
     * 
     * @param oldApkPath 示例:/sdcard/old.apk
     * @param newApkPath 示例:/sdcard/new.apk
     * @param patchPath  示例:/sdcard/xx.patch
     * @return
     */
    public static native int patch(String oldApkPath, String newApkPath,
            String patchPath);
}

2.使用舊版apk與差分包,在客戶端合成新apk

需要在手機客戶端實現,ApkPatchLibrary 工程封裝了這個過程。

2.1 C部分

同ApkPatchLibraryServer工程一樣,ApkPatchLibrary/jni/bzip2 目錄中所有文件都來自bzip2項目。

ApkPatchLibrary/jni/com_cundong_utils_PatchUtils.cApkPatchLibrary/jni/com_cundong_utils_PatchUtils.c實現文件的合并過程,其中com_cundong_utils_PatchUtils.c修改自bsdiff/bspatch.c

我們需要用NDK編譯出一個libApkPatchLibrary.so文件,生成的so文件位于libs/armeabi/ 下,其他 Android 工程便可以使用該libApkPatchLibrary.so文件來合成apk(如果需要支持多種CPU架構需要自己配置)。

com_cundong_utils_PatchUtils.Java_com_cundong_utils_PatchUtils_patch()方法,即為生成差分包的代碼:

/*
 * Class:     com_cundong_utils_PatchUtils
 * Method:    patch
 * Signature: (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I
 */
JNIEXPORT jint JNICALL Java_com_cundong_utils_PatchUtils_patch(JNIEnv *env,
        jobject obj, jstring old, jstring new, jstring patch) {

    char * ch[4];
    ch[0] = "bspatch";
    ch[1] = (char*) ((*env)->GetStringUTFChars(env, old, 0));
    ch[2] = (char*) ((*env)->GetStringUTFChars(env, new, 0));
    ch[3] = (char*) ((*env)->GetStringUTFChars(env, patch, 0));

    __android_log_print(ANDROID_LOG_INFO, "ApkPatchLibrary", "old = %s ", ch[1]);
    __android_log_print(ANDROID_LOG_INFO, "ApkPatchLibrary", "new = %s ", ch[2]);
    __android_log_print(ANDROID_LOG_INFO, "ApkPatchLibrary", "patch = %s ", ch[3]);

    int ret = applypatch(4, ch);

    __android_log_print(ANDROID_LOG_INFO, "ApkPatchLibrary", "applypatch result = %d ", ret);

    (*env)->ReleaseStringUTFChars(env, old, ch[1]);
    (*env)->ReleaseStringUTFChars(env, new, ch[2]);
    (*env)->ReleaseStringUTFChars(env, patch, ch[3]);

    return ret;
}

2.2 Java部分

com.cundong.utils包,為調用C語言的Java實現;

調用,com.cundong.utils.PatchUtils中patch()方法,可以通過舊apk與差分包,合成為新apk。

/**
 * 類說明:     APK Patch工具類
 * 
 * @author   Cundong
 * @date      2013-9-6
 * @version 1.0
 */
public class PatchUtils {

    /**
     * native方法 使用路徑為oldApkPath的apk與路徑為patchPath的補丁包,合成新的apk,并存儲于     newApkPath
     * 
     * 返回:0,說明操作成功
     * 
     * @param oldApkPath 示例:/sdcard/old.apk
     * @param newApkPath 示例:/sdcard/new.apk
     * @param patchPath  示例:/sdcard/xx.patch
     * @return
     */
    public static native int patch(String oldApkPath, String newApkPath,
            String patchPath);
}

3.校驗新合成的apk文件

在執行patch之前,需要先讀取本地安裝舊版本APK的MD5或SHA1,判斷當前安裝的文件是否為合法版本,同樣,patch得到新包之后,也需要對它進行MD5或SHA1校驗,校驗失敗,說明合成過程有問題。

注意事項

增量更新的前提條件,是在手機客戶端能讓我們讀取到當前應用程序安裝后的源apk,如果獲取不到源apk,那么就無法進行增量更新了,另外,如果你的應用程序不是很大,比如只有2、3M,那么完全沒有必要使用增量更新,增量更新只適用于apk包比較大的情況,比如手機游戲客戶端。

一些說明

  • ApkPatchLibraryServer:服務器端生成差分包工程,使用Java實現;

  • ApkPatchLibrary:客戶端使用的apk合成庫,用于生成libApkPatchLibrary.so,使用Eclipse開發;

  • ApkPatchLibrarySample:一個Sample,手機上安裝 Weibo5.5.apk,通過與SD卡上預先存放的weibo.patch文件進行合并,得到Weibo5.6.apk,使用AndroidStudio開發。

另外, ApkPatchLibraryServer、ApkPatchLibrarySample 中用到的Weibo5.5.apk,Weibo5.6.apk,以及使用ApkPatchLibraryServer生成的差分包(Weibo5.5.apk->Weibo5.6.apk), 都通過云盤共享了

關于我

Update

1.目前的做法只是提供了一個例子,并沒有做成開源庫,打算這幾天改進一下,做成一個開源庫,push到GitHub上,開發ing..(2014年,8月31日)

2.已經大幅度重構原代碼,并將原來的Demo程序提取成為開源庫,歡迎所有人Watch、Star、Fork。(2014年,9月2日)

3.修改ReadMe.md,更加清晰的說明開源庫的使用,同時進一步重構代碼。(2014年,10月4日晚)

4.調整ApkPatchLibraryServer工程目錄。(2015年,4月24日)

5.上傳一個演示demo ApkPatchLibrarySample.apk。(2015-4-26)

6.ApkPatchLibrarySample重新使用AndroidStudio開發,修改文件MD5的對比邏輯。(2015-12-22)

License

Copyright 2015 Cundong

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

來自: http://www.jcodecraeer.com//a/anzhuokaifa/androidkaifa/2015/1222/3795.html

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!