客戶端合并差分文件實現更新(Android環境下)
1. 下載差分文件
編寫下載文件工具類DownloadUtils
package com.skyward.increment_update.util;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import android.os.Environment;
public class DownloadUtils {
/**
* 下載差分文件
* @param url
* @return
* @throws Exception
*/
public static File download(String url){
File file = null;
InputStream is = null;
FileOutputStream os = null;
try {
file = new File(Environment.getExternalStorageDirectory(),Constants.PATCH_FILE);
if (file.exists()) {
file.delete();
}
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setDoInput(true);
is = conn.getInputStream();
os = new FileOutputStream(file);
byte[] buffer = new byte[1024];
int len = 0;
while((len = is.read(buffer)) != -1){
os.write(buffer, 0, len);
}
} catch(Exception e){
e.printStackTrace();
}finally{
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return file;
}
}
2.將舊版本的apk文件和差分文件合并生成新版本的apk文件
-
添加本地支持,右擊項目,點擊Android Tools-> Add Native Support
NDK開發配置 -
對Android項目進行NDK開發所需的相關配置
NDK開發配置 編寫Java本地方法
package com.skyward.increment_update.util;
public class BsPatch {
/**
* 合并差分文件
*
* @param oldfile
* @param newfile
* @param patchfile
*/
public native static void patch(String oldfile, String newfile, String patchfile);
static{
System.loadLibrary("bspatch");
}
}
4.使用javah命令生成頭文件,并將其拷貝至項目的jni目錄下。
javah命令生成頭文件
5.去bsdiff/bspatch官網下載源碼,bsdiff/bspatch源碼下載地址。下載完成后,解壓,將bspatch.c文件拷貝至項目的jni目錄下。
6.進入bsdiff4.3-win32-src目錄,用Visual Studio打開bspatch.dsp文件。
bspatch項目目錄結構
7.在項目的jni目錄下新建一個bzip2目錄,將該bspatch項目所有的頭文件和源文件(除bspatch.cpp外)拷貝至該目錄下。
Java項目目錄結構
8.修改bspatch.c文件,將其中的main函數修改為bspatch_main。用C語言實現Java本地方法調用bspatch_main方法。
#include "com_skyward_increment_update_util_BsPatch.h"
//可以將bzip2目錄下所有的源文件都引入進來,也可以在Android.mk文件中編譯所有源文件
//#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 "bzip2/bzlib.h"
#include "bzip2/bzlib_private.h"
JNIEXPORT void JNICALL Java_com_skyward_increment_1update_util_BsPatch_patch
(JNIEnv *env, jclass jcls, jstring oldfile_jstr, jstring newfile_jstr, jstring patchfile_jstr){
int argc = 4;
char* oldfile = (char*)(*env)->GetStringUTFChars(env,oldfile_jstr, NULL);
char* newfile = (char*)(*env)->GetStringUTFChars(env,newfile_jstr, NULL);
char* patchfile = (char*)(*env)->GetStringUTFChars(env,patchfile_jstr, NULL);
char *argv[4];
argv[0] = "bspatch";
argv[1] = oldfile;
argv[2] = newfile;
argv[3] = patchfile;
bspatch_main(argc,argv);
(*env)->ReleaseStringUTFChars(env,oldfile_jstr, oldfile);
(*env)->ReleaseStringUTFChars(env,newfile_jstr, newfile);
(*env)->ReleaseStringUTFChars(env,patchfile_jstr, patchfile);
}
9.編寫Android.mk文件
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := bspatch
LOCAL_SRC_FILES := bspatch.c bzip2/blocksort.c bzip2/bzlib.c bzip2/compress.c bzip2/crctable.c bzip2/decompress.c bzip2/huffman.c bzip2/randtable.c
include $(BUILD_SHARED_LIBRARY)
10.編寫AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
3.安裝新版本的apk文件
package com.skyward.increment_update;
import java.io.File;
import com.skyward.increment_update.util.ApkUtils;
import com.skyward.increment_update.util.BsPatch;
import com.skyward.increment_update.util.Constants;
import com.skyward.increment_update.util.DownloadUtils;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new ApkUpdateTask().execute();
}
class ApkUpdateTask extends AsyncTask<Void, Void, Boolean> {
@Override
protected Boolean doInBackground(Void... params) {
try {
// 1. 下載差分文件
File patch_file = DownloadUtils.download(Constants.URL_PATCH_DOWNLOAD_WINDOWS);
Log.d("skyward", "下載差分文件");
// 2. 合并差分文件
String oldfile = ApkUtils.getSourceApkPath(MainActivity.this, getPackageName());
BsPatch.patch(oldfile, Constants.NEW_APK_PATH, patch_file.getAbsolutePath());
Log.d("skyward", "合并差分文件");
return true;
} catch (Exception e) {
return false;
}
}
protected void onPostExecute(Boolean result) {
// 3. 安裝apk
if(result){
Toast.makeText(MainActivity.this, "您正在進行無流量更新", Toast.LENGTH_SHORT).show();
ApkUtils.installApk(MainActivity.this, Constants.NEW_APK_PATH);
}
};
}
}
測試
至于測試在這里就不再贅述。簡述一下步驟:
- 運行服務端的程序,生成差分文件
- 將生成的差分文件部署到服務端,供客戶端下載
-
在手機端安裝舊版本的apk文件
安裝新版本
新版本apk
要注意:新舊版本的apk包名要一致
總結
本文只是簡單介紹了一下增量更新的基本原理和步驟,代碼還有很多地方可以完善的,比如服務端應該對各個apk版本進行版本控制,生成各個不同版本之間的apk文件。客戶端的代碼也可以優化,比如一般在后臺服務中下在差分文件,下載差分文件時,應該檢查當前客戶端的版本號,下載對應的差分包。