java bsdiff_Android增量更新從入坑到成功

流程step1 使用bsdiff生成差異包PATCH.patch

step2 在行動電話上合併base包和差異包,生成新版本的安裝包

step3 安裝新的安裝包

準備bsdiff-4.3 (用於生成差異包,合併新包)

bzip2 (bsdiff要使用到)

試驗step1 解壓bsdiff4.3的壓縮包

 

 

step2 修改Makefile檔案,將.ifndef和.endif縮進,要麼無法進行後面的操作CFLAGS += -O3 -lbz2

PREFIX ?= /usr/localINSTALL_PROGRAM ?= ${INSTALL} -c -s -m 555

INSTALL_MAN ?= ${INSTALL} -c -m 444

all: bsdiff bspatch

bsdiff: bsdiff.c

 

 

bspatch: bspatch.c

install: ${INSTALL_PROGRAM} bsdiff bspatch ${PREFIX}/bin

.ifndef WITHOUT_MAN ${INSTALL_MAN} bsdiff.1 bspatch.1 ${PREFIX}/man/man1

.endifstep3 在該資料夾的命令列里執行make命令,會生成bsdiff和bspatch兩個可執行的檔案。如果是從上面的地址下載的bsdiff的話,會爆出bspatch.c:39:21: error: unknown type name 'u_char'; did you mean 'char'?這樣的錯誤。這是由於bspatch.c中缺少了#include ,加上即可。然後執行make命令。bogon:bsdiff-4.3 Tony$ make

cc -O3 -lbz2 bsdiff.c -o bsdiff

cc -O3 -lbz2 bspatch.c -o bspatchstep4 準備兩個同一簽名的apk,old.apk和.apk,放在bsdiff-4.3解壓後的資料夾里。

step5 使用bsdiff生成差異檔案PATCH.patch,命令格式:bsdiff oldfile newfile patchfile(這一步操作可以放在伺服端來執行)bogon:bsdiff-4.3 Tony$ ./bsdiff old.apk new.apk PATCH.patch

-step6 使用bspatch合成新的apk,dest.apk。命令格式:bspatch oldfile newfile patchfilebogon:bsdiff-4.3 Tony$ ./bspatch old.apk dest.apk PATCH.patchstep7 驗證生成的dest.apk和之前的new.apk使用一樣,驗證兩個apk的md5值,即可。bogon:bsdiff-4.3 Tony$ md5 new.apk

 

 

MD5 (new.apk) = 55005cec2de8ad3668a0fd5bd8746f43

bogon:bsdiff-4.3 Tony$ md5 dest.apk

MD5 (dest.apk) = 55005cec2de8ad3668a0fd5bd8746f43

bogon:bsdiff-4.3 Tony$ md5 old.apk

MD5 (old.apk) = 822cc0938694008089ea5523f86585d7

bogon:bsdiff-4.3 Tony$

在Android中使用增量更新

ndk部分step1 新建一個PatchUtils的類,用於呼叫native的方法public class PatchUtils { static PatchUtils instance; public static PatchUtils getInstance() { if (instance == null) {

instance = new PatchUtils();

} return instance;

} static {

System.loadLibrary("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 bspatch(String oldApkPath, String newApkPath, String patchPath);

}step2 使用javah命令生成PatchUtils的標頭檔,或者使用ndk-build里的javah生成標頭檔。會在jni資料夾下生成一個com_bigademo_updatedemo_PatchUtils.h的檔案,其中com_bigademo_updatedemo是我的包名。

step3 將bsdiff4.3資料夾中的bspatch.c檔案匯入到jni資料夾下。

step4 將bzip2資料夾匯入到jni資料夾下

step5 刪除bzip2中的除了以c和h結尾的其他檔案。

如圖:

AAffA0nNPuCLAAAAAElFTkSuQmCC

jni資料夾目錄

 

 

step6 修改bspatch.c。引入bzip的檔案,引入com_bigademo_updatedemo_PatchUtils.h,在bspatch.c中重寫JNIEXPORT jint JNICALL Java_com_bigademo_updatedemo_PatchUtils_bspatch方法。程式碼如下#if 0__FBSDID("$FreeBSD: src/usr.bin/bsdiff/bspatch/bspatch.c,v 1.1 2005/08/06 01:59:06 cperciva Exp $");#endif#include #include #include #include #include #include #include "bzip2/bzlib.c"#include "bzip2/crctable.c"#include "bzip2/compress.c"#include "bzip2/decompress.c"#include "bzip2/randtable.c"#include "bzip2/blocksort.c"#include "bzip2/huffman.c"#include JNIEXPORT jint JNICALL Java_com_bigademo_updatedemo_PatchUtils_bspatch(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
", argv[1]); printf("new apk = %s
", argv[2]); printf("patch = %s
", 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;

}static off_t offtin(u_char *buf){ off_t y;

y=buf[7]&0x7F;

y=y*256;y+=buf[6];

y=y*256;y+=buf[5];

y=y*256;y+=buf[4];

y=y*256;y+=buf[3];

y=y*256;y+=buf[2];

y=y*256;y+=buf[1];

y=y*256;y+=buf[0]; if(buf[7]&0x80) y=-y; return y;

}int genpatch(int argc,char * argv[]){

FILE * f, * cpf, * dpf, * epf;

BZFILE * cpfbz2, * dpfbz2, * epfbz2; int cbz2err, dbz2err, ebz2err; int fd; ssize_t oldsize,newsize; ssize_t bzctrllen,bzdatalen;

u_char header[32],buf[8];

u_char *old, *new; off_t oldpos,newpos; off_t ctrl[3]; off_t lenread; off_t i; if(argc!=4) errx(1,"usage: %s oldfile newfile patchfile
",argv[0]); /* Open patch file */

if ((f = fopen(argv[3], "r")) == NULL)

err(1, "fopen(%s)", argv[3]); /*

File format:

0 8 "BSDIFF40"

8 8 X

16 8 Y

24 8 sizeof(newfile)

32 X bzip2(control block)

32+X Y bzip2(diff block)

32+X+Y ??? bzip2(extra block)

with control block a set of triples (x,y,z) meaning "add x bytes

from oldfile to x bytes from the diff block; copy y bytes from the

extra block; seek forwards in oldfile by z bytes".

*/

/* Read header */

if (fread(header, 1, 32, f)

errx(1, "Corrupt patch
");

 

 

err(1, "fread(%s)", argv[3]);

} /* Check for appropriate magic */

if (memcmp(header, "BSDIFF40", 8) != 0)

errx(1, "Corrupt patch
"); /* Read lengths from header */

bzctrllen=offtin(header+8);

bzdatalen=offtin(header+16);

newsize=offtin(header+24); if((bzctrllen<0) || (bzdatalen<0) || (newsize<0))

errx(1,"Corrupt patch
"); /* Close patch file and re-open it via libbzip2 at the right places */

if (fclose(f))

err(1, "fclose(%s)", argv[3]); if ((cpf = fopen(argv[3], "r")) == NULL)

err(1, "fopen(%s)", argv[3]); if (fseeko(cpf, 32, SEEK_SET))

err(1, "fseeko(%s, %lld)", argv[3],

(long long)32); if ((cpfbz2 = BZ2_bzReadOpen(&cbz2err, cpf, 0, 0, NULL, 0)) == NULL)

errx(1, "BZ2_bzReadOpen, bz2err = %d", cbz2err); if ((dpf = fopen(argv[3], "r")) == NULL)

err(1, "fopen(%s)", argv[3]); if (fseeko(dpf, 32 + bzctrllen, SEEK_SET))

err(1, "fseeko(%s, %lld)", argv[3],

(long long)(32 + bzctrllen)); if ((dpfbz2 = BZ2_bzReadOpen(&dbz2err, dpf, 0, 0, NULL, 0)) == NULL)

errx(1, "BZ2_bzReadOpen, bz2err = %d", dbz2err); if ((epf = fopen(argv[3], "r")) == NULL)

err(1, "fopen(%s)", argv[3]); if (fseeko(epf, 32 + bzctrllen + bzdatalen, SEEK_SET))

err(1, "fseeko(%s, %lld)", argv[3],

(long long)(32 + bzctrllen + bzdatalen)); if ((epfbz2 = BZ2_bzReadOpen(&ebz2err, epf, 0, 0, NULL, 0)) == NULL)

errx(1, "BZ2_bzReadOpen, bz2err = %d", ebz2err); if(((fd=open(argv[1],O_RDONLY,0))<0) ||

((oldsize=lseek(fd,0,SEEK_END))==-1) ||

((old=malloc(oldsize+1))==NULL) ||

(lseek(fd,0,SEEK_SET)!=0) ||

(read(fd,old,oldsize)!=oldsize) ||

(close(fd)==-1)) err(1,"%s",argv[1]); if((new=malloc(newsize+1))==NULL) err(1,NULL);

oldpos=0;newpos=0; while(newpos

for(i=0;i<=2;i++) {

lenread = BZ2_bzRead(&cbz2err, cpfbz2, buf, 8); if ((lenread

(cbz2err != BZ_STREAM_END)))

errx(1, "Corrupt patch
");

ctrl[i]=offtin(buf);

}; /* Sanity-check */

if(newpos+ctrl[0]>newsize)

errx(1,"Corrupt patch
"); /* Read diff string */

lenread = BZ2_bzRead(&dbz2err, dpfbz2, new + newpos, ctrl[0]); if ((lenread

((dbz2err != BZ_OK) && (dbz2err != BZ_STREAM_END)))

errx(1, "Corrupt patch
"); /* Add old data to diff string */

for(i=0;i=0) && (oldpos+i

newpos+=ctrl[0];

oldpos+=ctrl[0]; /* Sanity-check */

if(newpos+ctrl[1]>newsize)

errx(1,"Corrupt patch
"); /* Read extra string */

lenread = BZ2_bzRead(&ebz2err, epfbz2, new + newpos, ctrl[1]); if ((lenread

((ebz2err != BZ_OK) && (ebz2err != BZ_STREAM_END)))

errx(1, "Corrupt patch
"); /* Adjust pointers */

newpos+=ctrl[1];

oldpos+=ctrl[2];

}; /* Clean up the bzip2 reads */

BZ2_bzReadClose(&cbz2err, cpfbz2);

BZ2_bzReadClose(&dbz2err, dpfbz2);

BZ2_bzReadClose(&ebz2err, epfbz2); if (fclose(cpf) || fclose(dpf) || fclose(epf))

err(1, "fclose(%s)", argv[3]); /* Write the new file */

if(((fd=open(argv[2],O_CREAT|O_TRUNC|O_WRONLY,0666))<0) ||

(write(fd,new,newsize)!=newsize) || (close(fd)==-1))

err(1,"%s",argv[2]); free(new); free(old); return 0;

}step7 在jni下建立Android.mk和Application.mk兩個檔案

Android.mk# 構建系統提供的巨集函式 my-dir 將回傳當前目錄(包含 Android.mk 檔案本身的目錄)的路徑,基本上是固定的,不需要去動LOCAL_PATH := $(call my-dir)# 會清除很多 LOCAL_XXX 變數,不會清除 LOCAL_PATH,基本上是固定的,不需要去動include $(CLEAR_VARS)# 需要構建模組的名稱,會自動生成相應的 libNDKSample.so 檔案,每個模組名稱必須唯一,且不含任何空白LOCAL_MODULE := patchutils# 包含要構建到模組中的 C 或 C++ 源檔案串列LOCAL_SRC_FILES := bspatch.c

LOCAL_C_INCLUDES := /Users/Tony/Code/android/AndroidStudioProjects/demo/updatedemo/app/src/main/jni/bzip2# 幫助系統將所有內容連接到一起,固定的,不需要去動include $(BUILD_SHARED_LIBRARY)

Application.mkAPP_PLATFORM := android-16# 選擇不同的 ABI,多個使用空白作為分隔符,全部是allAPP_ABI := allstep8 在module中的build.gradle中配置ndk。android{

...

defaultConfig{

ndk{ //這個名字就是在PatchUtils中loadLibrary的名字,同時和Android.mk中的LOCAL_MODULE的名字一樣,也和生成的so的檔案的名字類似。

moduleName "patchutils"

}

}

externalNativeBuild {

ndkBuild {

path "src/main/jni/Android.mk"

}

}

}

java部分

建立一個ApkExtract類,用於取得當前app的apk和安裝新的apk。因為涉及到Android 7.0的,需要處理Provider的問題,同時在AndroidManifest.xml增加一些程式碼public class ApkExtract { public static String extract(Context context) {

context = context.getApplicationContext();

ApplicationInfo applicationInfo = context.getApplicationInfo();

String apkPath = applicationInfo.sourceDir;

Log.d("info", apkPath); return apkPath;

} public static void install(Context context, String apkPath) {

Intent intent = new Intent(Intent.ACTION_VIEW); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {

intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

Uri contentUri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileProvider", new File(apkPath));

intent.setDataAndType(contentUri, "application/vnd.android.package-archive");

} else {

intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

intent.setDataAndType(Uri.fromFile(new File(apkPath)), "application/vnd.android.package-archive");

}

context.startActivity(intent);

}

}

androidmanifest.xml

android:allowBackup="true"

android:icon="@mipmap/ic_launcher"

android:label="@string/app_name"

android:roundIcon="@mipmap/ic_launcher_round"

android:supportsRtl="true"

android:theme="@style/AppTheme">

android:name="android.support.v4.content.FileProvider"

android:authorities="${applicationId}.fileProvider"

android:grantUriPermissions="true"

android:exported="false">

android:name="android.support.FILE_PROVIDER_PATHS"

android:resource="@xml/file_paths" />

...

最後在需要更新的時候呼叫下面這個方法,當然先做一下動態呼叫讀寫許可權的操作。if (ContextCompat.checkSelfPermission(MainActivity.this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {

ActivityCompat.requestPermissions(MainActivity.this, new String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE}, 2);

} else {

doBspatch();// startActivity(new Intent(MainActivity.this, SecondActivity.class));

} private void doBspatch() { final File destApk = new File(Environment.getExternalStorageDirectory(), "dest.apk"); final File patch = new File(Environment.getExternalStorageDirectory(), "PATCH.patch"); //一定要檢查檔案都存在

int result= PatchUtils.bspatch(ApkExtract.extract(this),

destApk.getAbsolutePath(),

patch.getAbsolutePath());

Log.e("info","patch result "+result); if (destApk.exists()) {

ApkExtract.install(this, destApk.getAbsolutePath());

}

}

作者:Coding小學生

連結:https://www.jianshu.com/p/472d0453656a

posted @ 2022-08-05 14:41  leo21sun  阅读(80)  评论(0编辑  收藏  举报