增量更新就是通过某种算法找出新版本和旧版本不一样的地方,然后将不一样的地方抽取出来形成更新补丁(patch),也称之为差分包。客户端在检测到更新的时候,只需要下载差分包到本地,然后将差分包合并至本地的安装包安装。
由此就可以看出增量更新主要分为两步:
1、服务端拿新版本Apk和旧版本Apk做差分,生成差分包(patch)
2、客户端检测到可增量更新的差分包,下载差分包(patch)之后,和本地旧版本做合成,生成新版本安装。
使用开源库bsdiff来实现,由于bsdiff进行差分合并时依赖bzip2,所以还需要bzip2源码
<1> 将bsdiff解压,可以看到bsdiff.c(差分) 和 bspatch.c (合并)
<2> 将bzip2解压,可以看到好多.c文件、.h头文件以及一些其他文件(包括Makefile文件)
打开Makefile文件,可以看到:
OBJS= blocksort.o \
huffman.o \
crctable.o \
randtable.o \
compress.o \
decompress.o \
bzlib.o
这些就是需要的文件,将对应的.c文件和.h头文件拿出来
<1> 新建一个Library项目,勾选支持C语言
<2> 将解压出来的bspatch.c (合并)放到src/main/cpp目录下,然后在src/main/cpp新建一个bzip目录,将
bzip2解压出来对应的.c文件和.h头文件放进去
<3> 打开CMakeLists.txt文件
cmake_minimum_required(VERSION 3.4.1)
file(GLOB bzip_source src/main/cpp/bzip/*.c)
add_library( # Sets the name of the library.
update-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
src/main/cpp/update-lib.cpp
src/main/cpp/bspatch.c
${bzip_source})
include_directories(src/main/cpp/bzip)
<4> 新建一个类TestUpdate
public class TestUpdate {
static {
//加载动态库
System.loadLibrary("update-lib");
}
//oldApk是当前APP的路径 outApk将要输出的apk路径 patch差分包路径
public static native void testPatch(String oldApk,String outApk,String patch);
}
<5> 打开bspatch.c文件,找到main函数,将mian修改为bsPatch_main(随意),防止和其他c文件中的main函数重名
<6> 打开src/main/cpp目录下的update-lib.cpp文件
#include
#include
#include
extern "C" {
//bspatch.c中在执行合成方法就是其中bsPatch_main(int argc,char * argv[])
//所以在这里需要引入该方法
extern int bsPatch_main(int argc,char * argv[]);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_test_updatelibrary_TestUpdate_testPatch(JNIEnv *env, jclass type, jstring oldApk_,
jstring outApk_, jstring patch_) {
const char *oldApk = env->GetStringUTFChars(oldApk_, 0);
const char *outApk = env->GetStringUTFChars(outApk_, 0);
const char *patch = env->GetStringUTFChars(patch_, 0);
//打印路径
__android_log_print(ANDROID_LOG_ERROR,"HelloJni","oldfile*%s",oldApk);
__android_log_print(ANDROID_LOG_ERROR,"HelloJni","oldfile*%s",outApk);
__android_log_print(ANDROID_LOG_ERROR,"HelloJni","oldfile*%s",patch);
char * arvg[4] = {"", const_cast(oldApk), const_cast(outApk),
const_cast(patch)};
//调用bspatch.c中bsPatch_main函数
int i = bsPatch_main(4, arvg);
//打印返回值,返回0表示执行完成
__android_log_print(ANDROID_LOG_ERROR,"HelloJni","oldfile*%i",i);
env->ReleaseStringUTFChars(oldApk_, oldApk);
env->ReleaseStringUTFChars(outApk_, outApk);
env->ReleaseStringUTFChars(patch_, patch);
}
<7> 测试 7.0的手机安装需要用到FileProvider
新建一个主项目,引入上面的Library模块,
public class TestUpdateActivity extends AppCompatActivity {
TextView textView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_update);
textView = (TextView) findViewById(R.id.test_update_version_tv);
textView.setText(getVersionName());
}
///通过PackageInfo得到应用的版本号
private String getVersionName() {
PackageInfo pInfo = null;
try {
//通过PackageManager可以得到PackageInfo
PackageManager pManager = getPackageManager();
pInfo = pManager.getPackageInfo(getPackageName(),
PackageManager.GET_CONFIGURATIONS);
return pInfo.versionName;
} catch (Exception e) {
e.printStackTrace();
}
return pInfo.versionName;
}
//点击按钮进行合并
public void testUpdate(View view) {
//模拟网络下载patch包
new MyAsyncTask().execute();
}
class MyAsyncTask extends AsyncTask {
@Override
protected File doInBackground(Void... voids) {
//获取当前APP的路径
String oldPath = getApplication().getApplicationInfo().sourceDir;
//合成后输出的apk路径
String outPath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "out.apk";
//差分包的路径(一般是从服务端下载得到,这里演示就用本地路径)
String patch = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "patch";
File file = new File(outPath);
if(!file.exists()){
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
//调用testPatch进行合成
TestUpdate.testPatch(oldPath,outPath,patch);
return file;
}
@Override
protected void onPostExecute(File file) {
super.onPostExecute(file);
if(file == null){
return;
}
//调用系统进行安装
Intent intent = new Intent(Intent.ACTION_VIEW);
//判断是否大于android7.0,7.0的手机安装需要FileProvider
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
//不是就执行安装
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
} else {
//是的话 需要声明临时权限
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
//获取包名
String packageName = getApplication().getPackageName();
// 第二个参数,即清单文件中配置的authorities,第三个参数就是要执行的文件
Uri contentUri = FileProvider.getUriForFile(TestUpdateActivity.this, packageName + ".fileprovider", file);
intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
}
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
}
}
配置清单文件
在 中添加provider
在res下创建xml目录,新建一个file_paths.xml文件
这样就完成了,可以运行测试了。由于是测试,所以需要在本地生成patch差分包。
这里是windows所以需要使用bsdiff的windows版本,将bsdiff-win解压,使用cmd进入到
解压目录中,将上面的项目打包成两个apk(版本号不同),放到解压目录中使用bsdiff命令将
两个apk进行差分,得到patch文件。命令如下:
bsdiff old.apk new.apk patch
将patch文件放到手机的根目录下,安装old.apk到手机中,点击按钮进行更新。
在实际的项目中差分是由服务端完成的,客户端只是将旧版本与差分包进行合并成新的版本。
开源库bsdiff中的bsdiff.c就是用于差分用的,用法和合并是一样的,里面也是一个main方法