bugly的简单介绍
1、热更新能力是Bugly为解决开发者紧急修复线上bug,而无需重新发版让用户无感知就能把问题修复的一项能力。Bugly目前采用微信Tinker的开源方案,开发者只需要集成我们提供的SDK就可以实现自动下载补丁包、合成、并应用补丁的功能,我们也提供了热更新管理后台让开发者对每个版本补丁进行管理。
详细文档:https://bugly.qq.com/docs/user-guide/instruction-manual-android-hotfix/?v=20170912151050
2、bugly的有点:
无需关注Tinker是如何合成补丁的
无需自己搭建补丁管理后台
无需考虑后台下发补丁策略的任何事情
无需考虑补丁下载合成的时机,处理后台下发的策略
我们提供了更加方便集成Tinker的方式
我们通过HTTPS及签名校验等机制保障补丁下发的安全性
丰富的下发维度控制,有效控制补丁影响范围
我们提供了应用升级一站式解决方案
3、bugly的缺点:
无法即时生效,一定要冷启动
只支持部分加固(360)
升级和补丁同时下发,优先处理升级
接入流程
1、首先登陆地址:https://bugly.qq.com/v2/index, 注册登陆bugly控制台,创建自己的产品,获取appid (这个id后面会用到)。根据官方文档接入,基本上没什么大问题,我接入的是改造application,因为官方介绍说这种兼容性比较好,如果文档看的迷迷糊糊的话,里面也贴出视频地址,可以看着视频来接入更直观。
2、在app目录下创建tinker-support.gradle,里面需要注意的是打补丁包需要改动的是def baseApkDir = “基准包的目录”, tinkerId = “”;不过初试者的话建议看下视频,会更了解里面的参数含义。
apply plugin: 'com.tencent.bugly.tinker-support'
// 基准包目录
def bakPath = file("${buildDir}/bakApk/")
/**
* 此处填写每次构建生成的基准包目录
*/
def baseApkDir = "app-1102-17-29-08"
/**
* 对于插件各参数的详细解析请参考
*/
tinkerSupport {
// 开启tinker-support插件,默认值true
enable = true
// 自动生成tinkerId, 你无须关注tinkerId,默认为false
autoGenerateTinkerId = false
// 指定归档目录,默认值当前module的子目录tinker
autoBackupApkDir = "${bakPath}"
// 是否启用覆盖tinkerPatch配置功能,默认值false
// 开启后tinkerPatch配置不生效,即无需添加tinkerPatch
overrideTinkerPatchConfiguration = true
// 编译补丁包时,必需指定基线版本的apk,默认值为空
// 如果为空,则表示不是进行补丁包的编译,指定是debug apk 还是release apk
// @{link tinkerPatch.oldApk }
baseApk = "${bakPath}/${baseApkDir}/app-debug.apk"
// 对应tinker插件applyMapping, 映射文件需要开启混淆才能生成
// app gradle 里面的 minifyEnabled 设置true才能生成mapping混淆
baseApkProguardMapping = "${bakPath}/${baseApkDir}/app-debug-mapping.txt"
// 对应tinker插件applyResourceMapping
baseApkResourceMapping = "${bakPath}/${baseApkDir}/app-debug-R.txt"
// 构建基准包跟补丁包都要修改tinkerId,主要用于区分, 基准包:1.0.1-base, 补丁包:1.0.1-patch
tinkerId = "patch-1.0.1"
// 打多渠道补丁时指定目录
// buildAllFlavorsDir = "${bakPath}/${baseApkDir}"
// 是否使用加固模式,默认为false
// isProtectedApp = true
// 是否采用反射Application的方式集成,无须改造Application, 反射兼容性没有改造的好
// 改造设置false, 试用反射设置true
enableProxyApplication = false
}
/**
* 一般来说,我们无需对下面的参数做任何的修改
* tinkerPatch 主要是用来当enableProxyApplication = true的时候反射集成,如果false此段则不调用
* 对于各参数的详细介绍请参考:
* https://github.com/Tencent/tinker/wiki/Tinker-%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97
*/
tinkerPatch {
tinkerEnable = true
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 = "base-2.0.1"
}
}
3、配置项目下的gradle 和 app 目录下的gradle
项目下的gradle, 以来 classpath 'com.tencent.bugly:tinker-support:1.0.8',这个版本可以根据自己需要选择一个稳定版本,也可以设置latest.release版本
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
// 只需配置tinker-support插件依赖,无需再依赖tinker插件
classpath 'com.tencent.bugly:tinker-support:1.0.8'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
app gradle: 这里面主要是添加依赖 compile 'com.tencent.bugly:crashreport_upgrade:latest.release'(应用升级和热更新都是用这个sdk), 不要忘了依赖下插件脚本 apply from: 'tinker-support.gradle', 至于其他的配置ndk里一般设置 abiFilters 'armeabi' , 'x86'这两个兼容够用了。我这里的是debug测试的,所以没有配release版,一般上线都会配置release版的。
apply plugin: 'com.android.application'
android {
compileSdkVersion 26
buildToolsVersion "26.0.2"
defaultConfig {
applicationId "com.example.administrator.buglyupdatedemo"
minSdkVersion 15
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
ndk {
//设置支持的SO库架构
abiFilters 'armeabi' , 'x86' //, 'armeabi-v7a', 'x86_64', 'arm64-v8a'
}
multiDexEnabled true // 分dex包
}
buildTypes {
release {
minifyEnabled false // 开启混淆,生成mapping文件
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
debuggable true
minifyEnabled false
signingConfig signingConfigs.debug
}
}
// 编译选项
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
// recommend
dexOptions {
jumboMode = true
}
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
repositories {
flatDir {
dirs 'libs'
}
}
lintOptions {
checkReleaseBuilds false
abortOnError false
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:26.+'
compile 'com.android.support.constraint:constraint-layout:1.0.2'
testCompile 'junit:junit:4.12'
//注释掉原有bugly的仓库
//compile 'com.tencent.bugly:crashreport:latest.release'//其中latest.release指代最新版本号,也可以指定明确的版本号,例如2.3.2
compile 'com.tencent.bugly:crashreport_upgrade:latest.release'//其中latest.release指代最新版本号,也可以指定明确的版本号,例如1.2.0
// compile 'com.tencent.bugly:nativecrashreport:latest.release' //其中latest.release指代最新版本号,也可以指定明确的版本号,例如2.2.0
// 多dex配置
compile "com.android.support:multidex:1.0.1"
}
// 依赖插件脚本
apply from: 'tinker-support.gradle'
4、改造application:新建一个MyApplicationLike 集成 DefaultApplicationLike oncreate方法中初始化bugly, onBaseContextAttached方法中安装MultiDex和tinker
package com.example.administrator.buglyupdatedemo;
import android.annotation.TargetApi;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.support.multidex.MultiDex;
import android.widget.Toast;
import com.tencent.bugly.Bugly;
import com.tencent.bugly.beta.Beta;
import com.tencent.bugly.beta.interfaces.BetaPatchListener;
import com.tencent.tinker.loader.app.DefaultApplicationLike;
import java.util.Locale;
public class MyApplicationLike extends DefaultApplicationLike{
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();
Beta.autoCheckUpgrade = true;
// 设置是否开启热更新能力,默认为true
Beta.enableHotfix = true;
// 设置是否自动下载补丁,默认为true
Beta.canAutoDownloadPatch = true;
// 设置是否自动合成补丁,默认为true
Beta.canAutoPatch = true;
// 设置是否提示用户重启,默认为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%%",
Beta.strNotificationDownloading,
(int) (totalLength == 0 ? 0 : savedLength * 100 / totalLength)),
Toast.LENGTH_SHORT).show();
}
@Override
public void onDownloadSuccess(String msg) {
Toast.makeText(getApplication(), "补丁下载成功", Toast.LENGTH_SHORT).show();
}
@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);
// 多渠道需求塞入
// String channel = WalleChannelReader.getChannel(getApplication());
// Bugly.setAppChannel(getApplication(), channel);
// 这里实现SDK初始化,appId替换成你的在Bugly平台申请的appId
// 调试时,将第三个参数改为true
Bugly.init(getApplication(), "65663b32e8", true); // 第二个参数就是上面提到的APPID, 第三个参数是是否开启debuglog模式
}
@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中集成TinkerApplication, 主要是第二个参数上面刚刚创建的MyApplicationLike
package com.example.administrator.buglyupdatedemo;
import com.tencent.tinker.loader.app.TinkerApplication;
import com.tencent.tinker.loader.shareutil.ShareConstants;
/**
* Created by Administrator on 2017/10/30.
* 自定义Application.
*
* 注意:这个类集成TinkerApplication类,这里面不做任何操作,所有Application的代码都会放到ApplicationLike继承类当中
*
* 参数解析:
* 参数1:int tinkerFlags 表示Tinker支持的类型 dex only、library only or all suuport,default: TINKER_ENABLE_ALL
* 参数2:String delegateClassName Application代理类 这里填写你自定义的ApplicationLike
* 参数3:String loaderClassName Tinker的加载器,使用默认即可
* 参数4:boolean tinkerLoadVerifyFlag 加载dex或者lib是否验证md5,默认为false
*/
public class MyApplication extends TinkerApplication{
public MyApplication() {
super(ShareConstants.TINKER_ENABLE_ALL, "com.example.administrator.buglyupdatedemo.MyApplicationLike",
"com.tencent.tinker.loader.TinkerLoader", false);
}
}
最后把自己的application引用到我们的配置文件里, 配置下权限和bugly sdk,整个改造流程和配置就完成了
5、打基准包(base包)相关配置和步骤(打base包时候ef baseApkDir = "app-1103-13-41-46"这个目录值是可以任意的,用到的是打出来后的目录值)
打出来的基准包,我打的是debug包
6、接下来就是打补丁包了,这个补丁包是在patch目录下的,不是在tinkerpatch目录下的
a.先修改好你的bug(四大组件是无法更新的)
b.先将def baseApkDir = "app-1103-13-41-46"这个改为基准包目录
c.tinkerId = "patch-1.0.1"这个id改为patch-xxx, xxx对应基准包的xxx
7、最后就是在后台控制器上传补丁了
下发成功(要下载合成补丁的前提是,基准包要上报联网成功后才能去下载合成的)
坑点1:
我在试的时候踩了一个坑,下发成功后,app一直下载不了补丁包,后来发现是全量升级开启导致的,在文档里有提到,全量更新的下发的优先级是高于热更新的下发的,也就是说同时有全量更新和热更新的下发,app会优先去提示下载升级版本,如果用户一直不升级,那么补丁就一直无法下载合并的,如果用户选择升级了,在升级后会自动下载合并补丁的。
坑点二: 全量更新和热更新都是需要(冷启动)用户重新启动app才会触发的,而且热更新下载合并完补丁后还需要重新启动app才会显示修复后的状态。