最近公司项目中需要集成热更新功能, 由于刚开始接入的时候踩了很多坑, 所以现在记录一下集成的过程.
集成过程参考了 Bugly 官方文档热更新使用指南
https://bugly.qq.com/docs/user-guide/instruction-manual-android-hotfix/?v=20170815114059
热更新是 Bugly 为解决开发者紧急修复线上 bug,而无需重新发版让用户无感知就能把问题修复的一项能力。Bugly 目前采用微信 Tinker 的开源方案,开发者只需要集成我们提供的 SDK 就可以实现自动下载补丁包、合成、并应用补丁的功能,提供了热更新管理后台让开发者对每个版本补丁进行管理。
为什么使用 Bugly 热更新?无需关注 Tinker 是如何合成补丁的
无需自己搭建补丁管理后台
无需考虑后台下发补丁策略的任何事情
无需考虑补丁下载合成的时机,处理后台下发的策略
提供了更加方便集成 Tinker 的方式
通过 HTTPS 及签名校验等机制保障补丁下发的安全性
丰富的下发维度控制,有效控制补丁影响范围
提供了应用升级一站式解决方案
第一步:添加插件依赖
工程根目录下“build.gradle”文件中添加:
buildscript {
repositories {jcenter()
}
dependencies {
// tinkersupport 插件, 其中 lastest.release 指拉取最新版本,也可以指定明确版本号,例如 1.0.4
classpath"com.tencent.bugly:tinker-support:latest.release"// 拉取的是最新版本
}
}
这里写代码片
第二步:集成 SDK
gradle 配置
在 app module 的“build.gradle”文件中添加:
android {
defaultConfig {
ndk {
// 设置支持的 SO 库架构
abiFilters 'armeabi' //, 'x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a'
}
}
}
dependencies {
compile "com.android.support:multidex:1.0.1"
// 其中 latest.release 指代最新版本号,也可以指定明确的版本号,例如 1.2.0
compile'com.tencent.bugly:crashreport_upgrade:latest.release'
// 其中 latest.release 指代最新版本号,也可以指定明确的版本号,例如 2.2.0
compile'com.tencent.bugly:nativecrashreport:latest.release'
}
在 app module 的“build.gradle”文件中添加:
// 依赖插件脚本
apply from: 'tinker-support.gradle'
tinker-support.gradle 内容如下所示:
注:需要在 app module 下创建 tinker-support.gradle 这个文件
apply plugin: 'com.tencent.bugly.tinker-support'
def bakPath = file("${buildDir}/bakApk/")
/**
* 此处填写每次构建生成的基准包目录
*/
def baseApkDir = "app-0908-17-42-31"
/**
* 对于插件各参数的详细解析请参考
*/
tinkerSupport {
// 开启 tinker-support 插件,默认值 true
enable = true
// 指定归档目录,默认值当前 module 的子目录 tinker
autoBackupApkDir = "${bakPath}"
// 编译补丁包时,必需指定基线版本的 apk,默认值为空
// 如果为空,则表示不是进行补丁包的编译
// @{link tinkerPatch.oldApk}
baseApk = "${bakPath}/${baseApkDir}/app-release.apk"
// 对应 tinker 插件 applyMapping
baseApkProguardMapping = "${bakPath}/${baseApkDir}/app-release-mapping.txt"
// 对应 tinker 插件 applyResourceMapping
baseApkResourceMapping = "${bakPath}/${baseApkDir}/app-release-R.txt"
// 构建基准包和补丁包都要指定不同的 tinkerId,并且必须保证唯一性
// tinkerId = "base-1.0.4"
tinkerId = "patch-1.0.4"
// 是否启用覆盖 tinkerPatch 配置功能,默认值 false
// 开启后 tinkerPatch 配置不生效,即无需添加 tinkerPatch
overrideTinkerPatchConfiguration = true
// 构建多渠道补丁时使用
// buildAllFlavorsDir = "${bakPath}/${baseApkDir}"
// 支持加固需要添加的属性
isProtectedApp = true
// 是否开启反射 Application 模式
enableProxyApplication = true
}
/**
* 一般来说, 我们无需对下面的参数做任何的修改
* 对于各参数的详细介绍请参考:
* https://github.com/Tencent/tinker/wiki/Tinker-%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97
*/
tinkerPatch {oldApk = "${bakPath}/${baseApkDir}/app-release.apk"
ignoreWarning = false
useSign = true
dex {
dexMode = "jar"
pattern = ["classes*.dex"]
loader = []}
lib {pattern = ["lib/*/*.so"]
}
res {pattern = ["res/*", "r/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]
ignoreChange = []
largeModSize = 100
}
packageConfig { }
sevenZip {
zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
// path = "/usr/local/bin/7za"
}
buildConfig {
keepDexApply = false
//tinkerId = "1.0.1-base"
//applyMapping = "${bakPath}/${appName}/app-release-mapping.txt" // 可选,设置 mapping 文件,建议保持旧 apk 的 proguard 混淆方式
//applyResourceMapping = "${bakPath}/${appName}/app-release-R.txt" // 可选,设置 R.txt 文件,通过旧 apk 文件保持 ResId 的分配
}
}
第三步:初始化 SDK
enableProxyApplication = false 的情况(亲测这种)
这是 Tinker 推荐的接入方式,一定程度上会增加接入成本,但具有更好的兼容性。集成 Bugly 升级 SDK 之后,我们需要按照以下方式自定义 ApplicationLike 来实现 Application 的代码
自定义 Application
public class SampleApplication extends TinkerApplication {
/* 注意:这个类集成 TinkerApplication 类,这里面不做任何操作,所有 Application 的代码都会放到 ApplicationLike 继承类当中
参数解析
参数 1:tinkerFlags 表示 Tinker 支持的类型 dex only、library only or all suuport,default: TINKER_ENABLE_ALL
参数 2:delegateClassName Application 代理类 这里填写你自定义的 ApplicationLike
参数 3:loaderClassName Tinker 的加载器,使用默认即可
参数 4:tinkerLoadVerifyFlag 加载 dex 或者 lib 是否验证 md5,默认为 false*/
public SampleApplication() {super(TINKER_ENABLE_ALL, "包名.weixinTinker.SampleApplicationLike", "com.tencent.tinker.loader.TinkerLoader", false);
}
}
自定义 SampleApplicationLike
/**
* 自定义 ApplicationLike 类.
* 注意:这个类是 Application 的代理类,以前所有在 Application 的实现必须要全部拷贝到这里 <br/>
*/
public class SampleApplicationLike extends DefaultApplicationLike {
public static final String TAG = "Tinker.SampleApplicationLike";
public SampleApplicationLike(Application application, int tinkerFlags,
boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime,
long applicationStartMillisTime, Intent tinkerResultIntent) {
super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime,
applicationStartMillisTime, tinkerResultIntent);
}
@Override
public void onCreate() {super.onCreate();
// 设置是否开启热更新能力,默认为 true
Beta.enableHotfix = true;
// 设置是否自动下载补丁,默认为 true
Beta.canAutoDownloadPatch = false;
// 设置是否自动合成补丁,默认为 true
Beta.canAutoPatch = false;
// 设置是否提示用户重启,默认为 false
Beta.canNotifyUserRestart = true;
// 补丁回调接口
Beta.betaPatchListener = new BetaPatchListener() {
@Override
public void onPatchReceived(String patchFile) {//Toast.makeText(getApplication(), "补丁下载地址" + patchFile, Toast.LENGTH_SHORT).show();}
@Override
public void onDownloadReceived(long savedLength, long totalLength) {Toast.makeText(getApplication(),
String.format(Locale.getDefault(), "%s %d%%",
"资源正在下载中",
(int) (totalLength == 0 ? 0 : savedLength * 100 / totalLength)),
Toast.LENGTH_SHORT).show();}
@Override
public void onDownloadSuccess(String msg) {Toast.makeText(getApplication(), "资源下载成功,正在准备解压", Toast.LENGTH_SHORT).show();
Beta.applyDownloadedPatch();}
@Override
public void onDownloadFailure(String msg) {Toast.makeText(getApplication(), "资源下载失败,请重新启动应用尝试", Toast.LENGTH_SHORT).show();}
@Override
public void onApplySuccess(String msg) {Toast.makeText(getApplication(), "资源解压完成,请关闭应用,点击桌面图标启动", Toast.LENGTH_SHORT).show();}
@Override
public void onApplyFailure(String msg) {Toast.makeText(getApplication(), "资源解压失败,请稍后尝试", Toast.LENGTH_SHORT).show();}
@Override
public void onPatchRollback() {}
};
// 设置开发设备,默认为 false,上传补丁如果下发范围指定为“开发设备”,需要调用此接口来标识开发设备
Bugly.setIsDevelopmentDevice(getApplication(), true);
// 这里实现 SDK 初始化,appId 替换成你的在 Bugly 平台申请的 appId
Bugly.init(getApplication(), appId, true);
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@Override
public void onBaseContextAttached(Context base) {super.onBaseContextAttached(base);
// you must install multiDex whatever tinker is installed!
MultiDex.install(base);
// TODO: 安装 tinker
Beta.installTinker(this);
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public void registerActivityLifecycleCallback(Application.ActivityLifecycleCallbacks callbacks) {getApplication().registerActivityLifecycleCallbacks(callbacks);
}
}
注意:tinker 需要你开启 MultiDex, 你需要在 dependencies 中进行配置 compile "com.android.support:multidex:1.0.1" 才可以使用 MultiDex.install 方法;SampleApplicationLike 这个类是 Application 的代理类,以前所有在 Application 的实现必须要全部拷贝到这里,在 onCreate 方法调用 SDK 的初始化方法,在 onBaseContextAttached 中调用 Beta.installTinker(this);。
enableProxyApplication = true 的情况(没测试这种)
public class MyApplication extends Application {
@Override
public void onCreate() {super.onCreate();
// 这里实现 SDK 初始化,appId 替换成你的在 Bugly 平台申请的 appId
// 调试时,将第三个参数改为 true
Bugly.init(this, "900029763", false);
}
@Override
protected void attachBaseContext(Context base) {super.attachBaseContext(base);
// you must install multiDex whatever tinker is installed!
MultiDex.install(base);
// 安装 tinker
Beta.installTinker();}
}
注:无须你改造 Application,主要是为了降低接入成本,我们插件会动态替换 AndroidMinifest 文件中的 Application 为我们定义好用于反射真实 Application 的类(需要您接入 SDK 1.2.2 版本 和 插件版本 1.0.3 以上)
第四步:AndroidManifest.xml 配置
在 AndroidMainfest.xml 中进行以下配置:
- 权限配置
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_LOGS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
- Activity 配置
<activity
android:name="com.tencent.bugly.beta.ui.BetaActivity"
android:configChanges="keyboardHidden|orientation|screenSize|locale"
android:theme="@android:style/Theme.Translucent" />
第五步:混淆配置
为了避免混淆 SDK,在 Proguard 混淆文件中增加以下配置:
-dontwarn com.tencent.bugly.**
-keep public class com.tencent.bugly.**{*;}
如果你使用了 support-v4 包,你还需要配置以下混淆规则:
-keep class android.support.**{*;}
第六步:测试
热更新:生成加固后的差异包的步骤:
1. 用签名文件进行打包处理,会在 build/outputs/bakApk 路径下生成生成三个文件:apk 文件,mapping 文件,R 文件(建议:另保存这三个文件,防止丢失)2. 在 tinker-support.gradle 文件中修改以下内容:baseApkDir 基准包目录,baseApk 生成的 apk 文件,baseApkProguardMapping 生成的 mapping 文件,baseApkResourceMapping 生成的 R 文件
3. 将生成的 apk 文件,进行加固,重新签名(加固时需要操作)
4. 将重新签名后的文件,复制放在 app module 项目下(加固是需要的操作)
5. 在 AS 右上角打开 Gradle,点击 app->Tasks->tinker-support, 双击 buildTinkerPatchRelease 脚本,执行完成后,会在 build->outputs->patch->release 下生成 patch_signed_7zip.apk 差异包
6. 将差异包 patch_signed_7zip.apk 给后台配置
上传补丁包到平台
上传补丁包到平台并下发编辑规则
总结
官方文档的帮助下, 集成了 Bugly 热更新. 当然在此之前也集成过原始的 Tinker 热更新, 比这个的集成比较复杂, 接入的时候微信 Tinker 还不支持加固, 不满足公司的需求, 过了一周后 Tinker 发布新版本, 新版本支持加固, 考虑到用原始的 Tinker 集成后, 需要后台的配置才能测试, 这样感觉会浪费很多的时间, 所以就用 Bugly 来集成, 这样测试的时候, 自己去 Bugly 后台配置就可以测试.
以下是个人公众号(longxuanzhigu),之后发布的文章会同步到该公众号,方便交流学习 Android 知识及分享个人爱好文章: