客户端合并差分文件实现更新(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
-
对Android项目进行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目录下。
5.去bsdiff/bspatch官网下载源码,bsdiff/bspatch源码下载地址。下载完成后,解压,将bspatch.c文件拷贝至项目的jni目录下。
6.进入bsdiff4.3-win32-src目录,用Visual Studio打开bspatch.dsp文件。
7.在项目的jni目录下新建一个bzip2目录,将该bspatch项目所有的头文件和源文件(除bspatch.cpp外)拷贝至该目录下。
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文件。客户端的代码也可以优化,比如一般在后台服务中下在差分文件,下载差分文件时,应该检查当前客户端的版本号,下载对应的差分包。