目录
增量更新
介绍
效果
步骤
总结
增量更新
最近换了个新手机,号称2019年的android机皇一加7plus。面对这90z的屏幕,原本不玩游戏的我也入了王者农药的坑!不过很少玩,所以基本上每次玩都要下载补丁更新才能玩。今天我们就来讲一下Android应用的增量更新。
介绍
优点:
节省用户更新新版本的流量、时间、内存空间。
基本流程如下:
- app端开发人员打包新版本上传到服务器,new_version.apk
- 服务器通过bsdiff(依赖bzip2)两个c库,与原来纯在服务器上面的旧版本进行差分,生成补丁包patch
- app下载补丁包patch,与手机上安装的旧版本old_version.apk合成新的apk
- 安装新的apk
效果
步骤
1、准备bsdiff和bzip2(生成补丁,合成新版本必须用到的工具)
mac/linux系统下:
下载bsdiff库:http://www.daemonology.net/bsdiff/
由上图可知,bsdiff库和bspatch依赖bzip库,所以我们还要下载一个bzip库:https://sourceforge.net/projects/bzip2/
下载下来的bsdiff压缩包解压出来,进入到解压后到目录,尝试执行命令“make”,发现无法执行,报错如下:
Makefile:13: *** missing separator. Stop.
解决:
vim命令编辑MakeFile
按下键盘的“i”进入编辑模式
如上图给这两行加上tab缩进,然后依次点击
键盘的“ESC”进入行末模式
输入“:wq”保存并退出vim
重新执行make命令,如果你出现了以下的错误,
error: unknown type name 'u_char'; did you mean 'char'?
成功生成了bsdiff可执行文件,但是在生成bspathc的时候,却出现错误提示,原因是:找不到u_char的声明,所以需要引入#include <sys/types.h>到bspatch.c文件中,再次执行make。
出现以上结果表示执行成功,在原来基础下增加了以下两个文件
二、引入库
配置NDK环境
配好NDK环境后,下面引入bsdiff和bspatch
1、cmakefile
2、将前面准备好的资源复制进来,有bspatch.c和bzip压缩包的解压文件
编译后会出现问题找不到bzlib.h的问题,这是因为版本的问题
解决:把错误的#include <bzlib.h>那行删除掉,让代码自动导入库即可!
2、配置Android项目ndk环境
这里不详细展开来说,提供一个比较便捷的方法,新创建一个ndk的项目,把一下重要文件复制进来即可!
module的build.gradle文件
//...
android {
//...
defaultConfig {
//...
externalNativeBuild {
cmake {
cppFlags ""
}
}
}
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
version "3.10.2"
}
}
}
3、引入项目bspatch.c和bzip2的库
如下图,引入bspatch.c库,用于将旧版本apk与补丁patch合成新版本apk文件。
bspatch.c所依赖的bzip2库也要复制进来。
4、c库代码(至关重要)
修改CMakeLists.txt配置文件
# 设置构建本机库所需的CMake最低版本。
cmake_minimum_required(VERSION 3.4.1)
# 声明一个文件夹所有的c文件
file(GLOB bzip2_resource bzip2-1.0.6/*.c)
add_library( # 设置库的名称。
native-lib
#将库设置为共享库。
SHARED
# 提供源文件的相对路径。
native-lib.cpp
bspatch.c
${bzip2_resource}
)
# 引入一个目录
include_directories(bzip2-1.0.6)
find_library(
log-lib
log )
target_link_libraries(
native-lib
${log-lib} )
重新编译可能无法通过,会有异常,作以下修改即可
重新编译即可。
Activity中声明一个native函数
/**
* 合成新的apk安装包
* @param oldApk 旧版本
* @param patch 补丁
* @param newApk 新版本
* @return
*/
public native String increaseUpdate(String oldApk,String patch,String newApk);
编辑native-lib.cpp,声明函数与之关联,注意这个函数的命名是有规律的,Java_{类名路径,分割用“_”}_{方法名}
5、编写更新的代码
涉及到文件读写,需要配置申请权限,并兼容高版本
AndroidManifest.xml
<manifest >
<!--存储-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!--位未知来源权限,适配8.0-->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<application
>
<!--四大组件之一 内容提供者 可以跨进程使用,7.0安装必须使用它-->
<provider
android:authorities="com.bluetree.myapplication.fileprovider"
android:name="android.support.v4.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
</application>
</manifest>
file_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<resource xmlns:android="http://schemas.android.com/apk/res/android">
<paths>
<external-path name="haah" path="." />
</paths>
</resource>
合成新版本apk,
new AsyncTask<Void, Void, File>() {
@Override
protected File doInBackground(Void... voids) {
createNewFile(newApkPath);
increaseUpdate(oldApkPath, patchPath, newApkPath);
return new File(newApkPath);
}
@Override
protected void onPostExecute(File file) {
super.onPostExecute(file);
if(file == null) return;
installApk(file);
}
}.execute();
/**
* 创建一个文件
* @param path
* @return
*/
private File createNewFile(String path) {
File file = new File(path);
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
return file;
}
/**
* 安装应用
* @param file
*/
private void installApk(File file) {
Intent intent = new Intent(Intent.ACTION_VIEW);
//兼容7.0
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);
// 第二个参数,即第一步中配置的authorities
String packageName = getApplication().getPackageName();
Uri contentUri = FileProvider.getUriForFile(MainActivity.this, packageName + ".fileprovider", file);
intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
}
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
6、制作差分包
制作两个版本,必须用相同的证书,否者不成功
unix下:bsdiff/. {旧版本} {新版本} {补丁名字}
win下命令:bsdiff {旧版本} {新版本} {补丁名字}
7、测试
标准情况下,补丁包的是在服务器上生成的,手机客户端从服务器上下载补丁patch,与手机上存在的旧版本,合成新的版本(通过调用bspatch.c中的main方法进行合成),然后安装!所以合成后,手机硬盘上会产生新的安装包。这个版本实际上与之前打包出来的新版本大小是一致的。
- 手机上安装app_old.apk
- 把补丁patch复制到手机储存根目录下(模拟从服务器下载补丁)
- 点击“update”(增量更新)
注意事项:
1、mac或者Linux系统中,需要在bsdff文件夹中进行make指令编译
2、bspatch.c文件记需要重新导入bzlib.h
3、正确编写CMakeList文件
4、记得调用bspatch.c中的main()方法所传进去的数组元素顺序
总结
实际上增量更新的核心就是两部分,
- 会使用工具将新版本与旧版本进行差分,生成补丁包
- 会配置项目的NDK环境,调用bspatch.c库中的代码,将下载的补丁包+旧版本合并成新版本。
建议先下载完整的项目下来体验一下,然后看着源码一步一步学习!否则很多坑,最好能够下载这篇文章里的库,导入你的项目,这样可以省去折腾因为版本差异导致的问题!