Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android apk增量升級

Android apk增量升級

編輯:關於Android編程

前言

別看本文看上去很簡單,實際在實驗過程中遇到了很多問題,比如andorid studio下ndk編譯報錯,而本文呈現給大家的都是最終可行的方法.

所需資源

bzip2 bsdiff ndk 兩個不同版本的測試apk

原理

在服務器端,生成最新版與之前所有版本的差分包,為什麼是所有版本,因為我們無法知道用戶當前版本是什麼版本 在手機客戶端更新程序時,下載差分包,使用已安裝的舊版apk與這個差分包,合成為一個新版apk 校驗新合成的apk文件是否完整,檢驗合成版本的簽名是否和已安裝客戶端一致,如一致則提示用戶安裝

生成差分包

下載生成差分包所需依賴文件

bsdiff bzip2

新建項目並調用native代碼

新建java層類

package cn.edu.zafu.util;

public class DiffAndPatch {
    static {
        System.loadLibrary(DiffAndPatch);
    }

    public static native int generateDiff(String oldApkPath, String newApkPath,
            String patchPath);

    public static native int applyPatch(String oldApkPath, String newApkPath,
            String patchPath);

}

進入項目bin目錄,使用javah命令創建頭文件

javah cn.edu.zafu.util.DiffAndPatch

將頭文件復制到jni文件夾下(自己創建),復制一份修改後綴名為.c
將裡面的內容清除,復制下面的代碼進去

#include cn_edu_zafu_util_DiffAndPatch.h
#include 
#ifdef __cplusplus
extern C {
#endif
/*
 * Class:     cn_edu_zafu_util_DiffAndPatch
 * Method:    generateDiff
 * Signature: (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I
 */
JNIEXPORT jint JNICALL Java_cn_edu_zafu_util_DiffAndPatch_generateDiff
  (JNIEnv *env, jclass arg, jstring old, jstring new, jstring patch){
    printf(-----generateDiff start-----
);

    clock_t start, finish;
    double duration;
    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 
, argv[1]);
    printf(new apk = %s 
, argv[2]);
    printf(patch = %s 
, argv[3]);

    printf(start to generate patch file
);
    start = clock(); 
    int ret = generatePatch(argc, argv);
    finish = clock();
    printf(finish to generate patch file
);

    duration = (double)(finish - start) / CLOCKS_PER_SEC;
    printf(generateDiff result = %d 
, ret);
    printf(time spended %f s 
,duration);

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

    printf(-----generateDiff end-----
);
    return ret;
}
/*
 * Class:     cn_edu_zafu_util_PatchUtil
 * Method:    applyPatch
 * Signature: (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I
 */
JNIEXPORT jint JNICALL Java_cn_edu_zafu_util_DiffAndPatch_applyPatch
  (JNIEnv *env, jclass arg, jstring old, jstring new, jstring patch){
    printf(-----applyPatch start-----
);

    clock_t start, finish;
    double duration;
    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 
, argv[1]);
    printf(patch = %s 
, argv[3]);
    printf(new apk = %s 
, argv[2]);

    printf(start to apply patch file
);
    start = clock();
    int ret = applyPatch(argc, argv);
    finish = clock();
    printf(finish to apply patch file
);

    duration = (double)(finish - start) / CLOCKS_PER_SEC;
    printf(applyPatch result = %d 
, ret);
    printf(time spended %f s 
,duration);

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

    printf(-----applyPatch end-----
);
    return ret;
}

#ifdef __cplusplus
}
#endif

將相關依賴文件復制到jni目錄下,對應的文件從下載下來的bsdiff和bzip2中尋找,文件清單如圖
這裡寫圖片描述

並在jni文件下新建Makefile文件,內容如下,對應的jdk目錄修改為自己電腦的目錄.

CC      := gcc
CFLAGS  := -I /opt/java/jdk1.8.0_25/include -I /opt/java/jdk1.8.0_25/include/linux -I /usr/include
LDFLAGS := -fPIC -shared 
SOURCE  := $(wildcard *.c *.h)
libDiffAndPatch.so: $(SOURCE)  
    $(CC) $(LDFLAGS) $(CFLAGS) $^ -o libDiffAndPatch.so
clean:
    rm libDiffAndPatch.so

增加native library location.項目文件右鍵Propreties->Java Build Path->Libraryies->JRE System Library->Native Library Location雙擊後指向jni目錄
這裡寫圖片描述
這裡寫圖片描述

命令行進入jni目錄進行編譯

這裡寫圖片描述

編寫測試代碼,將apk文件置於對應文件夾下,這裡是/home/lizhangqu/apk/這個目錄下

package cn.edu.zafu;

import cn.edu.zafu.util.DiffAndPatch;

public class Test {
    // 舊版本
    public static final String OLD_APK = /home/lizhangqu/apk/ApkPatchTestVersion_1.apk;
    // 新版本
    public static final String NEW_APK = /home/lizhangqu/apk/ApkPatchTestVersion_2.apk;
    // patch存儲路徑
    public static final String PATCH_FILE = /home/lizhangqu/apk/version_1_to_version_2_.patch;
    // 使用舊版本+patch包,合成的新版本
    public static final String OLD_TO_NEW_APK = /home/lizhangqu/apk/generated.apk;

    public static void main(String[] args) {
        System.out.println(正在生成文件,可能需要一段時間,請稍後...);
        DiffAndPatch.generateDiff(OLD_APK, NEW_APK, PATCH_FILE);

    }

}

最終會輸出以下信息

正在生成文件,可能需要一段時間,請稍後...
-----generateDiff start-----
old apk = /home/lizhangqu/apk/ApkPatchTestVersion_1.apk 
new apk = /home/lizhangqu/apk/ApkPatchTestVersion_2.apk 
patch = /home/lizhangqu/apk/version_1_to_version_2_.patch 
start to generate patch file
finish to generate patch file
generateDiff result = 0 
time spended 2.595576 s 
-----generateDiff end-----

將舊版本安裝在手機上,將生成的patch拷到手機上.這裡拷到手機內存卡根路徑下apk文件夾裡

手機端合成apk並檢驗

使用android studio新建項目,創建java層調用native層代碼

package cn.edu.zafu.util;

/**
 * 類說明:  apk 合成工具類
 *
 * @author  lizhangqu
 * @date    2015-5-29
 * @version 1.0
 */
public class PatchUtil {
    static {
        System.loadLibrary(Patch);
    }

    /**
     * @param oldApkPath 舊包路徑
     * @param newApkPath 生成的包路徑
     * @param patchPath patch路徑
     * @return
     */
    public static native int applyPatch(String oldApkPath, String newApkPath,
                                        String patchPath);
}

build後打開android studio裡的終端使用javah命令生成頭文件

cd app/src/main
javah -d jni -classpath /media/lizhangqu/windows/sdk/platforms/android-22/android.jar:../../build/intermediates/classes/debug/ cn.edu.zafu.util.PatchUtil

之後再jni目錄裡會有頭文件生成,復制一份改後綴為.c,裡面實現的內容如下

#include 
#include 
JNIEXPORT jint JNICALL Java_cn_edu_zafu_util_PatchUtil_applyPatch
(JNIEnv *env, jclass arg, jstring oldApk, jstring newApk, jstring patch){
    printf(-----applyPatch start-----
);

    clock_t start, finish;
    double duration;
    int argc = 4;
    char * argv[argc];
    argv[0] = bspatch;
    argv[1] = (char*) ((*env)->GetStringUTFChars(env, oldApk, 0));
    argv[2] = (char*) ((*env)->GetStringUTFChars(env, newApk, 0));
    argv[3] = (char*) ((*env)->GetStringUTFChars(env, patch, 0));

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

    printf(start to apply patch file
);
    start = clock();
    int ret = applyPatch(argc, argv);
    finish = clock();
    printf(finish to apply patch file
);

    duration = (double)(finish - start) / CLOCKS_PER_SEC;
    printf(applyPatch result = %d 
, ret);
    printf(time spended %f s 
,duration);

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

    printf(-----applyPatch end-----
);
    return ret;
}

將之前的生成差分包的項目jni目錄下除了Makefile文件和自己生成的兩個文件外,其余文件都拷到android studio 的jni目錄下.

在jni文件下創建Android.mk,內容如下

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE := Patch
LOCAL_SRC_FILES := 
    blocksort.c
    bspatch.c
    bzip2.c
    bzlib.c
    bzlib.h
    bzlib_private.h
    cn_edu_zafu_util_PatchUtil.c
    cn_edu_zafu_util_PatchUtil.h
    compress.c
    crctable.c
    decompress.c
    huffman.c
    randtable.c

include $(BUILD_SHARED_LIBRARY)

在jni目錄下創建Application.mk,內容如下

APP_ABI := armeabi
APP_PLATFORM :=android-15

APP_PLATFORM一定要,否則編譯的時候會報錯

禁用android studio自帶的ndk build並使用自己的Android.mk和Application.mk文件

在local.properties裡加入ndk目錄

ndk.dir=/media/lizhangqu/windows/sdk/android-ndk-r10e

修改app目錄下的build.gradle文件,最終內容如下

import org.apache.tools.ant.taskdefs.condition.Os
apply plugin: 'com.android.application'

android {
    compileSdkVersion 22
    buildToolsVersion 22.0.1

    defaultConfig {
        applicationId cn.edu.zafu.patch
        minSdkVersion 15
        targetSdkVersion 22
        versionCode 1
        versionName 1.0

    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    sourceSets.main.jni.srcDirs = [] // disable automatic ndk-build call, which ignore our Android.mk
    sourceSets.main.jniLibs.srcDir 'src/main/libs'

    // call regular ndk-build(.cmd) script from app directory
    task ndkBuild(type: Exec) {
        workingDir file('src/main')
        commandLine getNdkBuildCmd()
    }

    tasks.withType(JavaCompile) {
        compileTask -> compileTask.dependsOn ndkBuild
    }

    task cleanNative(type: Exec) {
        workingDir file('src/main')
        commandLine getNdkBuildCmd(), 'clean'
    }

    clean.dependsOn cleanNative
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.1.1'
}
def getNdkDir() {
    if (System.env.ANDROID_NDK_ROOT != null)
        return System.env.ANDROID_NDK_ROOT

    Properties properties = new Properties()
    properties.load(project.rootProject.file('local.properties').newDataInputStream())
    def ndkdir = properties.getProperty('ndk.dir', null)
    if (ndkdir == null)
        throw new GradleException(NDK location not found. Define location with ndk.dir in the local.properties file or with an ANDROID_NDK_ROOT environment variable.)

    return ndkdir
}

def getNdkBuildCmd() {
    def ndkbuild = getNdkDir() + /ndk-build
    if (Os.isFamily(Os.FAMILY_WINDOWS))
        ndkbuild += .cmd

    return ndkbuild
}

編寫更新代碼,假設已經將patch下載到本地

package cn.edu.zafu;

import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;




import cn.edu.zafu.util.ApkUtil;
import cn.edu.zafu.util.PatchUtil;
import cn.edu.zafu.util.SignUtil;


public class MainActivity extends AppCompatActivity {
    private static final String  ROOT_PATH= Environment.getExternalStorageDirectory().getAbsolutePath();

    private  static final String PACKAGE_NAME=com.sina.weibo;
    // patch存儲路徑
    public static final String PATCH_FILE = /apk/weibo.patch;
    // 使用舊版本+patch包,合成的新版本
    public static final String OLD_TO_NEW_APK = /apk/generated.apk;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        boolean isInstalled=ApkUtil.isInstalled(getApplicationContext(), PACKAGE_NAME);
        if(isInstalled){

            String source=ApkUtil.getSourceApkPath(getApplicationContext(),PACKAGE_NAME);
            Log.d(TAG,source);
            Log.d(TAG,ROOT_PATH+OLD_TO_NEW_APK);
            Log.d(TAG,ROOT_PATH+PATCH_FILE);

            applyPatch(source,ROOT_PATH+OLD_TO_NEW_APK,ROOT_PATH+PATCH_FILE);
        }

    }

    private void applyPatch(final String oldApk,final String newApk,final String patch) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, 正在合成文件,可能需要一段時間,請稍後...);
                PatchUtil.applyPatch(oldApk, newApk, patch);
                String oldSign=SignUtil.getInstalledApkSignature(getApplicationContext(), PACKAGE_NAME);
                String newSign=SignUtil.getUnInstalledApkSignature(newApk);
                if(oldSign.equals(newSign)){
                    Log.d(TAG, 簽名相同);
                    ApkUtil.installApk(MainActivity.this, newApk);

                }else{
                    Log.d(TAG,簽名錯誤);
                }
            }
        }).start();
    }

}

工具類代碼就不貼了,其作用就跟名字一樣.還有注意的就是記得加讀取內存卡的權限.

這時候build一下,再運行程序,記得把patch文件放在對應的文件目錄下,不出意外,幾秒鐘之後就會合成新包並進行安裝.

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved