概述
在应用中做增量更新,一般都会这样做,提示用户升级(也就是下载差分包),当用户下载了差分包后,将当前版本和差分包合并成最新版本,然后提醒用户安装新版本的apk。
上一篇中通过bsdiff的win版已经可以将两个版本的apk差分出差分包,然后将差分包放到服务器中,提示用户下载即可。
下载好差分包后,需要将旧版本的apk和差分包合并起来,这就用到了bsdiff,而bsdiff依赖于bzip2。直接到网上下载bsdiff和bzip2就行。
步骤
1.将bsdiff和bzip下的文件拷贝到项目中
新建android项目,然后下载 bsdiff解压后,将其中的bspatch.c复制到项目中的jni目录下,由于bsdiff是依赖bzip2的,所以需要下载bzip2,解压并将其包下的所有的c文件和.h文件复制到jni的bzip2目录下.
2.修改bspatch.c
修改bspatch.c的头文件引用,将bzip2下的所有c文件都引用过来。这里引用的是是.c文件而不是.h文件,如果要引用.h文件也是可以的,但需要在Android.mk下将所有的c文件配置,否则编译通不过。
//将bzip2中的所有c都引入到文件中
#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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <err.h>
#include <unistd.h>
#include <fcntl.h>
仔细阅读 bspatch.c 的源码
int bspatch_main(int argc,char * argv[])
{
...
if(argc!=4) errx(1,"usage: %s oldfile newfile patchfile\n",argv[0]);
...
}
在bspatch.c中可以做合并的方法,就是int main方法,可以看到其中的参数,和差分时的差不多,这就可以看出,如果你上回的差分做了,这次合并做法是同样的,将main修改为bspatch_main。
3.编写native方法
根据bspatch中的方法,java中可以提供这样的native方法
public class PatchUtil {
/**
* 合并
* @param oldApkPath 老apk路径
* @param newApkPath 合并成新的apk路径
* @param diffPath 差分包
*/
public native static void diff(String oldApkPath,String newApkPath,String diffPath);
//加载.so库
static {
System.loadLibrary("patch");
}
}
使用javah生成相应的.h文件,并将其移动到jni目录下
在bspatch.c中引入生成的.h文件,并实现其函数
#include "com_example_ndk_03_update_PatchUtil.h"
//diff函数的实现
JNIEXPORT void JNICALL Java_com_example_ndk_103_1update_PatchUtil_diff
(JNIEnv *env, jclass cls, jstring old_apk_path_str, jstring new_apk_path_str, jstring patch_path_str){
//将jstring转成char*
char* old_apk_path=(char*)(*env)->GetStringUTFChars(env,old_apk_path_str,NULL);
char* new_apk_path=(char*)(*env)->GetStringUTFChars(env,new_apk_path_str,NULL);
char* patch_path=(char*)(*env)->GetStringUTFChars(env,patch_path_str,NULL);
//bspatch_main的参数
int argc=4;
char * argv[4];
argv[0]="bspatch";
argv[1]=old_apk_path;
argv[2]=new_apk_path;
argv[3]=patch_path;
//调用bspatch的函数进行合并
bspatch_main(argc,argv);
//释放
(*env)->ReleaseStringUTFChars(env,old_apk_path_str,old_apk_path);
(*env)->ReleaseStringUTFChars(env,new_apk_path_str,new_apk_path);
(*env)->ReleaseStringUTFChars(env,patch_path_str,patch_path);
}
4.修改Android.mk文件
如果要在bspatch.c中实现.h文件,并生成bspatch库,需要修改Androi.mk文件中的c文件为bspatch.c
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := patch
LOCAL_SRC_FILES := bspatch.c
include $(BUILD_SHARED_LIBRARY)
上面的LOCAL_SRC_FILES 的值只写了bspatch.c,这是由于在bspatch.c中直接引用了bzip2中的c文件,如果引用的是.h文件则在这后面追加相应的.c文件。
5.在MainActiviyt中实现差分包的下载和合并
public class MainActivity extends Activity {
private Handler mHandle = new Handler() {
public void handleMessage(android.os.Message msg) {
// 3.提示安装新apk
Toast.makeText(MainActivity.this, "您正在进行无流量更新", Toast.LENGTH_SHORT).show();
ApkUtil.installAPK(MainActivity.this,new File(Constant.new_apk_path));
};
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
/**
* 更新版本
*/
public void update(View view) {
Toast.makeText(MainActivity.this, "开始更新", Toast.LENGTH_SHORT).show();
// 1.下载差分包
downDiffPatch(Constant.diff_patch_url);
}
private void downDiffPatch(String url) {
Request request = new Request.Builder().get().url(url).build();
OkHttpClient client = new OkHttpClient();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onResponse(Call arg0, Response response) throws IOException {
InputStream inputStream = response.body().byteStream();
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(new File(Constant.dif_patch_path));
byte[] buffer = new byte[2048];
int len = 0;
while ((len = inputStream.read(buffer)) != -1) {
fileOutputStream.write(buffer, 0, len);
}
fileOutputStream.flush();
} catch (IOException e) {
Log.i("gxl", "IOException");
e.printStackTrace();
}
System.out.println("下载完成");
// 2.合并差分包
File patchFile = new File(Constant.dif_patch_path);
// apk文件
String oldApkPath = ApkUtil.getSourceApkPath(MainActivity.this, getPackageName());
if (patchFile.exists()) {
PatchUtil.patch(oldApkPath, Constant.new_apk_path, Constant.dif_patch_path);
System.out.println("合并完成");
} else {
System.out.println("文件不存在");
}
mHandle.sendEmptyMessage(0);
}
@Override
public void onFailure(Call arg0, IOException arg1) {
Log.i("gxl", "下载失败");
}
});
}
}
ApkUtil
public class ApkUtil {
/**
* 获取已安装Apk文件的源Apk文件
* 如:/data/app/my.apk
* @param context
* @param packageName
* @return
*/
public static String getSourceApkPath(Context context, String packageName) {
if (TextUtils.isEmpty(packageName))
return null;
try {
ApplicationInfo appInfo = context.getPackageManager()
.getApplicationInfo(packageName, 0);
return appInfo.sourceDir;
} catch (NameNotFoundException e) {
e.printStackTrace();
}
return null;
}
/**
* 安装apk
* @param context 上下文
* @param file apk文件
*/
public static void installAPK(Context context, File file) {
Intent intent = new Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file),
"application/vnd.android.package-archive");
context.startActivity(intent);
}
}
Constant
public class Constant {
//查分包的下载路径
public static final String diff_patch_url="http://192.168.200.101:8080/apk.patch";
//sd卡路径
public static final String SD_PATH=Environment.getExternalStorageDirectory().getAbsolutePath();
//差分 包路径
public static final String dif_patch_path=SD_PATH+File.separator+"apk.patch";
//合成后的apk路径
public static final String new_apk_path=SD_PATH+File.separator+"new.apk";
}
PatchUtil
public class PatchUtil {
/**
* 合并
* @param oldApkPath 老apk路径
* @param newApkPath 合并成新的apk路径
* @param diffPath 差分包
*/
public native static void patch(String oldApkPath,String newApkPath,String diffPath);
//加载.so库
static {
System.loadLibrary("patch");
}
}