腾讯Bugly的一个强大的线上crash收集工具,在微信的热修复框架Tinker开源后,Bugly也很快集成了这个功能,并且提供了升级补丁的后台管理功能。
第一步:集成SDK
工程根目录下build.gradle
文件中添加:
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
//tinkersupport插件,其中lastest.release指拉取最新版本,也可以指定明确版本号
classpath "com.tencent.bugly:tinker-support:1.1.1"
}
然后在app module的build.gradle
文件中添加:
dependencies {
// 多dex配置
compile "com.android.support:multidex:1.0.1"
//注释掉原有bugly的仓库,crashreport_upgrade已经有crash收集功能了
//compile 'com.tencent.bugly:crashreport:latest.release'
// 其中latest.release指代最新版本号,也可以指定明确的版本号
compile 'com.tencent.bugly:crashreport_upgrade:1.3.4'
}
接着在app module的build.gradle
同层目录下创建一个tinker-support.gradle
文件:
tinker-support.gradle
内容如下所示,这里有我的部分注释:
apply plugin: 'com.tencent.bugly.tinker-support'
def bakPath = file("${buildDir}/bakApk/")
/**
* 此处填写每次构建生成的基线包目录
* 如果没有,可以使用默认值tinkerfix-0216-18-42-47
* 生成的基准包在app->build->bakApk里面
* 生成基准包后,如需生成基线包补丁,则要填上基线包的包名,如:app-0250-17-06-10
*/
def baseApkDir = "tinkerfix-0216-18-42-47"
/**
* 对于插件各参数的详细解析请参考
*/
tinkerSupport {
// 开启tinker-support插件,默认值true
enable = true
// 指定归档目录,默认值当前module的子目录tinker
autoBackupApkDir = "${bakPath}"
//表示需要打加固的包
//isProtectedApp = true
// 是否启用覆盖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,并且必须保证线上的apk包里面的唯一性
* 这里建议如果是发布到线上的基线包就是使用base+当前软件版本号,如:base-1.0.1
* 如果是发布到线上的补丁包就是使用patch+当前软件版本号+第几个补丁包,如:patch-1.0.1-01
*/
tinkerId = "base-1.0.1"
// 构建多渠道补丁时使用
// buildAllFlavorsDir = "${bakPath}/${baseApkDir}"
/**
* 是否开启反射Application模式,true是开启
* 该模式仅在Bugly版本号在1.2.2以上和gradle插件版本在1.0.3以上可用
* 如果要兼容Bugly旧版本SDK的apk,需要关闭该模式并且自定义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}/${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的分配
}
}
这时在app module的build.gradle
文件中添加插件即可:
// 依赖插件脚本
apply from: 'tinker-support.gradle'
第二步:完成SDK初始化
1.enableProxyApplication =false的兼容模式
这个模式是为了兼容旧版本的SDK,需要我们去自定义Application:
public class SampleApplication extends TinkerApplication {
public SampleApplication() {
super(ShareConstants.TINKER_ENABLE_ALL, "xxx.xxx.SampleApplicationLike",
"com.tencent.tinker.loader.TinkerLoader", false);
}
}
这里的xxx.xxx.SampleApplicationLike
是你的SampleApplication
的文件路径,自己动手粘贴一下就好了,还有记得在清单文件添加该Application
:
SampleApplication
集成了TinkerApplication
类,这里面不需要做任何操作,所有Application
的代码都应该放到ApplicationLike
继承类当中:
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();
// 这里实现SDK初始化,appId替换成你的在Bugly平台申请的appId
// 调试时,将第三个参数改为true,就可以看到打印
Bugly.init(getApplication(), "XXXXXXXX", false);
}
@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);
// Bugly提供的安装tinker的方法
Beta.installTinker(this);
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public void registerActivityLifecycleCallback(Application.ActivityLifecycleCallbacks callbacks) {
getApplication().registerActivityLifecycleCallbacks(callbacks);
}
}
2.enableProxyApplication =true的简单模式
如果没有兼容旧版本的需求,建议直接使用这个模式,因为真的很简单,只需要在application
初始化就可以了:
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// 这里实现SDK初始化,appId替换成你的在Bugly平台申请的appId
// 调试时,将第三个参数改为true,就可以看到打印
Bugly.init(this, "XXXXXXXXXX", true);
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
//tinker要求必须分包
MultiDex.install(base);
// 安装tinker
Beta.installTinker();
}
}
第三步:清单文件的配置
1.权限的配置
<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" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
2.Activity的配置
<activity
android:name="com.tencent.bugly.beta.ui.BetaActivity"
android:configChanges="keyboardHidden|orientation|screenSize|locale"
android:theme="@android:style/Theme.Translucent" />
3.FileProvider的配置
这个配置是为了适配Android 7.0的:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.fileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider>
如果你的Bugly版本是1.3.1以上,那么就不用自己写provider_paths
这个文件了,因为SDK已经有了,如果不够高,就得在res目录新建xml文件夹,创建provider_paths.xml
:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<!-- /storage/emulated/0/Download/${applicationId}/.beta/apk-->
<external-path name="beta_external_path" path="Download/"/>
<!--/storage/emulated/0/Android/data/${applicationId}/files/apk/-->
<external-path name="beta_external_files_path" path="Android/data/"/>
</paths>
这里要注意一下,FileProvider
类是在support-v4
包中的,记得引入,如果和友盟之类的SDK产生了FileProvider
冲突,可以通过继承FileProvider
类来解决方法:
public class BuglyFileProvider extends FileProvider {
}
<provider
android:name=".utils.BuglyFileProvider"
..............
</provider>
ok,到这里前期的配置就已经结束了,我们可以去尝试测试热修复功能了。
第四步:热修复功能测试
在Activity里面调用一个空对象:
public class MainActivity extends AppCompatActivity {
private TextView mTvTest;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = (Button) findViewById(R.id.action);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mTvTest.setText("热修复测试成功了");
}
});
}
}
这里调用了一个空的TextView
,只要按下Button
,程序是必然奔溃的。现在到app module的build.gradle
里面配置签名设置:
android {
signingConfigs {
config {
keyAlias 'android'
keyPassword 'XXXXXXXXXX'
storeFile file('X:xx/xxx/keystore.jks')
storePassword 'XXXXXXXXXX'
}
}
...........
buildTypes {
release {
..............
signingConfig signingConfigs.config
}
}
}
现在开始执行Gradle
脚本打包一个基线包并安装到模拟器上面:
这样子就会在在app->build->bakApk生成一个基线包:
把这个apk安装到模拟器上面,点击按钮可以瞬间看到app奔溃了:
如果有在Bugly初始化开启打印功能的话,可以看到下面的输出:
这时候就可以启动热修复的功能了,修改Activity
的代码:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = (Button) findViewById(R.id.action);
mTvTest = (TextView) findViewById(R.id.show);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mTvTest.setText("热修复测试成功了");
}
});
}
好了,现在我们的TextView不再是null了,去修改一下tinker-support.gradle
:
//def baseApkDir = "tinkerfix-0216-18-42-47"
def baseApkDir = "app-0203-17-56-01"
tinkerSupport {
.........
/**
* 构建基准包和补丁包都要指定不同的tinkerId,并且必须保证线上的apk包里面的唯一性
* 这里建议如果是发布到线上的基线包就是使用base+当前软件版本号,如:base-1.0.1
* 如果是发布到线上的补丁包就是使用patch+当前软件版本号+第几个补丁包,如:patch-1.0.1-01
*/
tinkerId = "patch-1.0.1"
}
将baseApkDir
的路径修改为刚才那个基线包的文件夹路径,然后就可以开始打包了,这次打包执行的gradle
脚本就不一样了:
这次执行的是Tinkner
的脚本命令来生成补丁,不过补丁的所在路径就不一样了:
补丁在app->build->outputs->patch->release里面,是中间那个patch_signed_7zip.apk
。我们打开Bugly的后台来上传补丁:
如果出现下面这个情况:
一般有两个原因:
1.你补丁的tinkerId
和线上的apk的tinkerId
是重复,找不到应该升级的apk的记录
2.apk没有和Bugly后台进行交互,这时候需要检查是否给了上网的权限和测试手机能否联网。
补丁上传成功后就可以进行下发,不过在下发之前我们需要设置一下下发的规则,由于只是用模拟器进行测试,这里我的设置如下:
补丁下发后我们再重启App的话,如果开启了打印功能的话,就可以看到下面的Logcat:
这个说明已经匹配到目标版本,App在下载热修复补丁,下载完成就会开始后台合成补丁,合成成功后就会看到下面的打印:
这时候就需要用户退出App,下次启动时热补丁就会生效:
这里我选择了使用App奔溃的方法去退出,可以看到重启后Bug已经修复了。我们也可以在Bugly后台看到补丁的下发和激活的情况:
后续的一些问题
1.Instance Run的问题
由于Tinker暂时还不支持Instance Run,所以在调试的过程需要用到这个功能的话,就得暂时把Tinker关闭,关闭方法很简单的,在app module的build.gradle
文件中把Tinker插件注释掉就可以了:
apply plugin: 'com.android.application'
// 依赖插件脚本
//apply from: 'tinker-support.gradle'
这样子就可以了。
2.加固打包的处理
这个功能只在Bugly的1.3.0以上版本实现了,使用方法也很简单,在tinker-support.gradle
中开启加固处理即可:
tinkerSupport {
.........
//表示需要打加固的包
isProtectedApp = true
}
其他操作和未加固时都是一样的。
这里需要注意的一件事就是无法使用加固后的基线包来生产升级补丁,每次发版本请务必保管好未加固的基线包!
目前Tinker支持的加固软件如下:
加固软件 | 测试 |
---|---|
腾讯云·乐固 | OK |
爱加密 | OK |
梆梆加固 | OK |
360加固 | OK,需要17年5月8号之后的版本 |
结语
到此Bugly的热修复就基本说完了,不对的还望指点一二,欢迎留言。