原创不易,尊重作者,转载请注明出处,谢谢您
前言
集成Bugly的崩溃收集和应用升级是很简单的,但是如果要集成热更新,那就需要仔细阅读文档了,况且官方的文档随着SDK版本的更新有些地方是不太准确的,官方文档还是很贴心,还有视频教程,当然视频比较老了,更多情况是开发者集成后出现很多问题不知道如何解决,基于这些情况,从实际出发,我将根据官方集成文档一步步讲解Bugly的热更新集成方式,要是还学不会,我倒地喝水
目录
- 快速的集成过程(相信我,真的很快,省略掉官方文档的废话)
- 集成完成后的测试
- 基准包的补丁更新
- 基准包多次修复BUG后的补丁更新
- 多渠道打包
- 基准包加固后
一、快速的集成过程
以下所有步骤都极度精简官方文档,并且依赖库版本使用新的,请认真看添加注释的代码。
步骤1:在Project的build.gradle文件中添加bugly的tinker插件
dependencies {
classpath 'com.android.tools.build:gradle:3.3.2'
// 添加tinker插件支持
classpath "com.tencent.bugly:tinker-support:1.1.5"
}
步骤2:在Module的build.gradle文件中添加需要使用的依赖包
dependencies {
···
// 解决65536方法数
implementation 'com.android.support:multidex:1.0.3'
// bugly Java崩溃收集和版本更新
implementation 'com.tencent.bugly:crashreport_upgrade:1.4.0'
// bugly native 崩溃收集
implementation 'com.tencent.bugly:nativecrashreport:3.7.1'
// tinker
implementation 'com.tencent.tinker:tinker-android-lib:1.9.9'
}
以上依赖大家一看注释就知道是做什么的,就不多阐述了
步骤3:在Module的build.gradle文件中开启multiDex、配置ndk、配置app的签名信息
release的签名直接使用了官方demo的签名文件
android {
···
defaultConfig {
···
// 配置ndk,根据自身情况来配置(粗略简单来讲,armeabi是真机,x86是模拟器)
ndk {
abiFilters 'armeabi', 'x86'//,'x86_64' ,'armeabi-v7a' , 'arm64-v8a'
}
// 开启multiDex,解决65536方法数
multiDexEnabled true
}
// 签名配置
signingConfigs {
// 正式包
release {
try {
storeFile file("./keystore/release.keystore")
storePassword "testres"
keyAlias "testres"
keyPassword "testres"
} catch (ex) {
throw new InvalidUserDataException(ex.toString())
}
}
// 测试包
debug {
storeFile file("./keystore/debug.keystore")
}
}
buildTypes {
release {
minifyEnabled true // 开启了混淆(也可以不用开启,默认就是false)
signingConfig signingConfigs.release // 设置签名文件
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
如果你开启了混淆,在proguard-rules.pro添加代码如下:
-dontwarn com.tencent.bugly.**
-keep public class com.tencent.bugly.**{*;}
# tinker混淆规则
-dontwarn com.tencent.tinker.**
-keep class com.tencent.tinker.** { *; }
步骤4:创建一个tinker-support.gradle文件,添加插件参数
参数如下,内容全部复制即可,一律不管···
apply plugin: 'com.tencent.bugly.tinker-support'
def bakPath = file("${buildDir}/bakApk/")
/**
* 此处填写每次构建生成的基准包目录
*/
def baseApkDir = "app-0525-15-05-57"
/**
* 对于插件各参数的详细解析请参考
*/
tinkerSupport {
// 开启tinker-support插件,默认值true
enable = true
// 指定归档目录,默认值当前module的子目录tinker
autoBackupApkDir = "${bakPath}"
// 是否启用覆盖tinkerPatch配置功能,默认值false
// 开启后tinkerPatch配置不生效,即无需添加tinkerPatch
overrideTinkerPatchConfiguration = true
// 编译补丁包时,必需指定基线版本的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 = "patch-1.0.3"
// 构建多渠道补丁时使用
// buildAllFlavorsDir = "${bakPath}/${baseApkDir}"
// 是否启用加固模式,默认为false.(tinker-spport 1.0.7起支持)
// isProtectedApp = true
// 是否开启反射Application模式
enableProxyApplication = false
// 是否支持新增非export的Activity(注意:设置为true才能修改AndroidManifest文件)
supportHotplugComponent = true
}
/**
* 一般来说,我们无需对下面的参数做任何的修改
* 对于各参数的详细介绍请参考:
* https://github.com/Tencent/tinker/wiki/Tinker-%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97
*/
tinkerPatch {
//oldApk ="${bakPath}/${appName}/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的分配
}
}
supportHotplugComponent 在官方文档是false,我设置成了true,注释写的很情况了。简单说一下export的作用就是“是否允许外部来启动该activity”,默认创建的activity的export其实就是false。
这里只讲最重要的两个参数:
baseApkDir : 填写需要打补丁的基准包的文件夹名称(只能填写基准包的,后续详情说明)。
tinkerId :构建基准包和补丁包的唯一id,必须保证唯一,补丁包就是根据基准包的tinkerId 来找到对应的包进行补丁更新的。
其他参数真的可以不用管,如果对其他参数有疑问,点这里https://bugly.qq.com/docs/utility-tools/plugin-gradle-hotfix/
步骤5:在Module的build.gradle中依赖步骤4的插件
apply plugin: 'com.android.application'
// 依赖插件
apply from: 'tinker-support.gradle'
步骤6:创建自定义的ApplicationLike类和Application类
自定义的ApplicationLike代码如下:
public class MyApplicationLike extends DefaultApplicationLike {
private static final String TAG = "MyApplicationLike";
public MyApplicationLike(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();
// 第二个参数为bugly申请的应用appId,调试时,将第三个参数改为true
Bugly.init(getApplication(), "1fc5a53101", 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);
// 安装tinker
// TinkerManager.installTinker(this); 替换成下面Bugly提供的方法
Beta.installTinker(this);
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public void registerActivityLifecycleCallback(Application.ActivityLifecycleCallbacks callbacks) {
getApplication().registerActivityLifecycleCallbacks(callbacks);
}
}
自定义的Application代码如下:
public class MyApplication extends TinkerApplication {
public MyApplication() {
super(ShareConstants.TINKER_ENABLE_ALL, "app.fynnjason.tinkerdemo.MyApplicationLike",
"com.tencent.tinker.loader.TinkerLoader", false);
}
}
在MyApplicationLike 类中,只需要关注onCreate()方法,将所有需要在Application中初始化的代码都放到这儿来。MyApplication 类是固定写法了,只需要替换super的第二个参数为MyApplicationLike 的路径即可,代码和注释已经非常清楚了,相信大家都看的懂。其他的可以一律不用管。
最后一步:在AndroidManifest.xml中添加Application
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="app.fynnjason.tinkerdemo">
<application
android:name=".MyApplication" // 这里
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
到此,集成就算完成了。有人可能要问了,官方文档中还需要在AndroidManifest中配置权限、特定的Activity、Android N的共享文件,为啥这里没有。因为在SDK1.3.1及以上版本,可以不用进行以上配置,aar已经在AndroidManifest配置了,并且包含了对应的资源文件,所以我这里也不用配置了。好了,集成完毕,接下来是热更新的重头戏了。
二、集成完成后的测试
首先我们需要打一个基准包,基准包相当于就是我们的一个正式包,在tinker-support.gradle文件中,我们修改tinkerId的参数为:
tinkerId = "base-1.0.1"
tinkerId 的命名是自由的,但是为了我们规范,建议是填写有规范性的命名(这里用base来代表基准包,用patch代表补丁包),baseApkDir不用修改,这个值在打基准包时是没有作用的。然后我们打开Android Studio的右侧边栏(一般都是右边),找到Gradle工具-assembleRelease,注意不同Android Studio版本的assembleRelease位置稍有不同,例如官方文档是2.2版本,在build目录下,而我使用的是3.3.2,在other目录下。
可以看到相似的还有assembleDebug,就是打debug包的,很好理解。执行assembleRelease,等待一会儿,我们就可以得到创建好的基准包
这个就相当于是我们用来上线的正式包了,记住此时的文件包名(app-0525-15-05-57,app-0525-15-05-57,app-0525-15-05-57,重要事情说三遍),把里面的app-release.apk扔到手机跑起来吧!这里需要说一下,基准包必须联网一次,也就是在手机上安装一次并且打开访问一次网络,这样Bugly后台才会记录这个基准包,以便后续补丁包的下发。如果我们继承没有任何问题,那么我们就可以在AndroidStudio日志中找到Bugly成功的日志。
可以看到tinkerId参数,app版本号等信息,接下来是热更新的步骤,也是最最关键的。
三、基准包的补丁更新
现在我们去修改demo中的信息,修改TextView的文字(比如这是修改好了一个bug)。然后回到tinker-support.gradle文件中,我们修改tinkerId,此时就是补丁包的名称,然后再修改baseApkDir,baseApkDir填写基准包的包名,这就是上一步中三遍说的那个包名称。如图:
然后打开右侧栏的Gradle工具栏,找到如图所示:
双击成功编译后,我们可以在如图所示查看到补丁包:
上图中第一个红框是每次编译都会生成的apk目录,除了基准包的,其他生成的可以不用管,这里只是圈出来说明一下。第二个框是重点了!只使用xxxx_7zip.apk,将这个apk上传到Bugly的热更新管理后台即可,具体看下图:
上传补丁文件后,会显示目标版本,也就是基准包的版本,点击立即下发即可,下发后等待大概十分钟、下发后等待大概十分钟、下发后等待大概十分钟,重要事情说三遍,因为Bugly下发有延迟,接下来就是验证补丁是否下载成功了,以下执行流程一定不能错。
1.首先杀掉我们app的进程
2.打开我们的app,等待几秒,查看log日志,如图(图片来自官方文档)
如果出现上面这些信息,那么你的补丁就下发成功了
3.再次杀掉我们的app进程
4.再次打开我们的app,查看我们修改的内容是否成功
这就是基本的热更新了~如果有疑问,请评论提出
四、基准包多次修复BUG后的补丁更新
上面我们只讲到了一次修复bug,如果我们基准包出现了一次bug,我们用热更新修复了,然后我们又发现了一个bug,这时候我们就需要再打一次补丁包了,那这时候我们应该怎么做呢?我会告诉你非常简单,接着往下看:
首先我们修复好代码,继续来到tinker-support.gradle,这里我们只需要修改tinkerId的参数,第一次我们修复代码,这里的参数为patch-1.0.1,那么这一次我们就改成patch-1.0.2,只要保证每次唯一就行。baseApkDir不改动,依然是基准包的包名,具体如图所示:
然后我们使用Gradle工具栏,执行
最后我们上传生成好的xxx_7zip.apk到管理后台即可,流程和之前一样,等待十分钟后,我们再来测试是否成功!
五、多渠道打包
修修改改多次后,还是觉得官方文档已经写的很好了,所以推荐大家直接阅读官方文档
https://buglydevteam.github.io/2017/05/15/solution-of-multiple-channel-hotpatch/#%E6%80%BB%E7%BB%93
同一版本,不同渠道中代码不同的热更新
有这么一种情况,同一版本下,不同渠道的一部分代码不同,比如我们开发的APP,在应用宝、小米、华为平台叫微信,在OPPO、VIVO平台叫微信聊天,或者部分代码有区别(比如刺激战场游戏针对不同厂商优化不同,但版本号是相同的)等等情况,这时候,我们怎么实现同一个BUG的热更新呢?
解决方案:
1.首先是基准包的生成,我们需要将渠道分组,完全相同代码的为一组,在tinker-support.gradle文件中,设置该组唯一的tinkerId,例如我们把
应用宝、小米、华为平台设置为base-1.0.1-a,将OPPO、VIVO平台设置为base-1.0.1-b,然后进行多渠道打包,最后我们可以得到每组的bakApk中的基准包的内容,需要说的是,美团多渠道打包的命令每次执行都会删掉上次多渠道打包的内容,所以大家要每次记得要备份一下该组的基准包!一定要备份!记得备份!
然后就可以将多渠道打包的apk上线了。
2.现在我们来生成每组的补丁,将bug修复后,我们依旧回到tinker-support.gradle文件中,针对不同组的补丁,需要设置不同的tinkerId,例如应用宝、小米、华为平台设置为patch-1.0.1-a,将OPPO、VIVO平台设置为patch-1.0.1-b,其中bakApk的参数就填写该组的包名即可,相信大家应该明白这个意思。接着我们生成应用宝、小米、华为平台的补丁后,我们将补丁上传到Bugly后台管理上发布,然后继续生成OPPO、VIVO平台的补丁包,再上传到Bugly后台管理上发布。这样,我们就实现了同一版本下,部分代码不同下的热更新效果了。
PS:只能共存最多5组补丁,只能共存最多5组补丁,只能共存最多5组补丁!也就是只能分成最多5组哟
六、基准包的加固
确实很简单,毕竟大家都使用的第三方加固工具,官方文档也没有特意去写文档,这里就摘录一下,实际效果也确实如此。
需要集成升级SDK版本1.3.0以上版本才支持加固。
经过测试的加固产品:
- 腾讯乐固
- 爱加密
- 梆梆加固
- 360加固(SDK 1.3.1之后版本支持)
其他产品需要大家进行验证。
1.设置isProtectedApp参数
在tinker-support配置当中设置isProtectedApp = true,表示你要打加固的的apk。 是否使用加固模式,仅仅将变更的类合成补丁。注意,这种模式仅仅可以用于加固应用中。
2.将基准包进行加固
如果你的app需要进行加固,你需要将你打出的基准包上传到具体的加固平台进行加固,例如乐固,加固完成之后需要对apk进行重签名:
jarsigner -verbose -keystore <YOUR_KEYSTORE> -signedjar <SIGNED_APK> <UNSIGNED_APK> <KEY_ALIAS>
以上命令说明:
-verbose:指定生成详细输出
-keystore:指定证书存储路径
-signedjar:改选项的三个参数分别为签名后的apk包、未签名的apk包、数字证书别名
示例:
jarsigner -verbose -keystore keystore/release.keystore -signedjar app-release-signed.apk app-release.encrypted.apk testres
如果你使用的加固产品提供了GUI的加固和签名工具,可以通过工具来进行加固和签名。
3.根据未加固的基准包生成补丁包
打patch包的操作跟普通打包方式一致。
注意:这里指定的基线版本是未加固的版本。
4.测试验证
测试验证的版本是经过加固并且已经重签名后的apk,安装到测试设备并上报联网,其他操作与普通打包无异。
其他问题
美团多渠道包使用加固工具后,渠道信息null的解决办法
https://github.com/Jay-Goo/ProtectedApkResignerForWalle
多渠道加固的新方式(乐固篇)
大多数情况我们的APP都是需要多渠道打包和加固的,如果按照之前的方式使用美团打多渠道包并且加固再解决加固后的渠道信息,这是一个挺麻烦的事,下面介绍一种更简单的方法,不妨看看:
1.使用普通方式打一个基准包
2.将基准包直接使用乐固的签名加固和多渠道输出
3.运行乐固生成的多渠道apk后打一个基准包的补丁包
4.上传补丁包,完成
是不是觉得很像目录三的热更新方式?其他加固工具方法类似,大家可以自行测试~
结论
如果文章对你有帮助,点个赞支持一下~
附录:
官方文档:https://bugly.qq.com/docs/user-guide/instruction-manual-android-hotfix/?v=20181014122344
官方文档中热更新的常见问题:https://bugly.qq.com/docs/user-guide/faq-android-hotfix/?v=20181014122344
官方文档中Bugly的常见问题:https://bugly.qq.com/docs/user-guide/faq-android/?v=20181014122344