本知识点只是个人见解,具体知识及使用请查阅官网,以免被误导,同时大家可以对此文发表自己的见解。
不得不说,接入第三方的时候,稍微不注意,坑就很多! 所以要注意很多细节在里面!
之前写了个适配8.0的方案 https://blog.csdn.net/yang1349day/article/details/80016607 后来适配的问题还是很多,于是接了腾讯的bugly.使app能够在线去修复bug.
不得不说bugly的接入文档写的非常好.
此篇文章只是个人接入过程中的坑.具体大部分的坑见 https://bugly.qq.com/docs/user-guide/faq-android-hotfix/?v=20170912151050
先来看总体的流程:
1 打好path包之后,上传到bugly后台显示如图:
首先你看下这个官方视频http://v.qq.com/boke/gplay/9f3b4b1232819f453becd2356a3493c4_bme000301803d13_5_e0385shcfzm.html 如果你看了,可以免过,继续看下面:
我总结了使用过程中的一些经验:
1 打基准包的时候,设置的tinkerId的时候,优先设置自己app版本号
2 记住设置的版本号一定要比用户正在使用的要大(如果你是第一次接入的话).
比如市场上的版本号是1.0.9,如果市场上的1.0.9版本设置过了基准包为base-1.0.9 那么release的patch包版本号也设置成patch-1.1.0 ,这里可以理解为基准版本已经线上有了
如果市场上的版本是1.0.9但是没有设置过基准包,则先生成基准包为base-1.1.0, 设置好了之后,把这个基准包在手机上面安装一下,然后仔细观察CrashReport的log日志(如果你连log日志都不会看,或者看不到后面是很难去接入的),如果联网成功,bugly后台就会知道你这个基准包的版本了. 然后生成补丁包,生成release的patch包版本号也设置成patch-1.1.0 (此处这么做是为了规范,免得不必要的低级错误)
以上步骤完成,基本上不会出现,基准包没有上传的问题了.
注意: 一定要观察log, 同时最好在application处设置监听,如下代码:
(timber是打印log日志的工具类,详情见 https://github.com/JakeWharton/timber)
private void initializeBugly() {
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();
Timber.tag("CrashReport");
Timber.e("onPatchReceived: "+"补丁下载地址" + patchFile);
}
@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();
Timber.tag("CrashReport");
Timber.e("onDownloadReceived: "+"补丁下载地址" + String.format(Locale.getDefault(), "%s %d%%",
Beta.strNotificationDownloading,
(int) (totalLength == 0 ? 0 : savedLength * 100 / totalLength)));
}
@Override
public void onDownloadSuccess(String msg) {
// Toast.makeText(getApplication(), "补丁下载成功", Toast.LENGTH_SHORT).show();
Timber.tag("CrashReport");
Timber.e("onDownloadSuccess: "+"补丁下载成功");
}
@Override
public void onDownloadFailure(String msg) {
// Toast.makeText(getApplication(), "补丁下载失败", Toast.LENGTH_SHORT).show();
Timber.tag("CrashReport");
Timber.e("onDownloadFailure: "+"补丁下载失败");
}
@Override
public void onApplySuccess(String msg) {
// Toast.makeText(getApplication(), "补丁应用成功", Toast.LENGTH_SHORT).show();
Timber.tag("CrashReport");
Timber.e("onApplySuccess: "+"补丁应用成功");
}
@Override
public void onApplyFailure(String msg) {
// Toast.makeText(getApplication(), "补丁应用失败", Toast.LENGTH_SHORT).show();
Timber.tag("CrashReport");
Timber.e("onApplyFailure: "+"补丁应用失败");
}
@Override
public void onPatchRollback() {
Timber.tag("CrashReport");
Timber.e("onPatchRollback: "+"补丁应用失败");
}
};
/**
* 全量升级状态回调
*/
Beta.upgradeStateListener = new UpgradeStateListener() {
@Override
public void onUpgradeFailed(boolean b) {
Timber.tag("CrashReport");
Timber.e("onUpgradeFailed: "+"全量升级状态回调"+b);
}
@Override
public void onUpgradeSuccess(boolean b) {
Timber.tag("CrashReport");
Timber.e("onUpgradeSuccess: "+"全量升级状态回调"+b);
}
@Override
public void onUpgradeNoVersion(boolean b) {
// Toast.makeText(DidiApplicationLike.this.getApplication(), "最新版本", Toast.LENGTH_SHORT).show();
Timber.tag("CrashReport");
Timber.e("onUpgradeNoVersion: "+"全量升级状态回调"+b);
}
@Override
public void onUpgrading(boolean b) {
// Toast.makeText(DidiApplicationLike.this.getApplication(), "onUpgrading", Toast.LENGTH_SHORT).show();
Timber.tag("CrashReport");
Timber.e("onUpgrading: "+"全量升级状态回调"+b);
}
@Override
public void onDownloadCompleted(boolean b) {
Timber.tag("CrashReport");
Timber.e("onDownloadCompleted: "+"全量升级状态回调"+b);
}
};
// 设置开发设备,默认为false,上传补丁如果下发范围指定为“开发设备”,需要调用此接口来标识开发设备
Bugly.setIsDevelopmentDevice(getApplication(), true); //在发布正式版本的时候需要关掉,不然发布新的patch包的时候,测试都没法去测试!
// 多渠道需求塞入
// String channel = WalleChannelReader.getChannel(getApplication());
// Bugly.setAppChannel(getApplication(), channel);
// 这里实现SDK初始化,appId替换成你的在Bugly平台申请的appId
// 调试时,将第三个参数改为true
Bugly.init(getApplication(), AppConfig.BuglyAppId, AppConfig.ADB);
}
在测试的时候最好把弹框打开,这样更直观些. 同时把bugly.init第三个参数设置为true,如果没有看到log,请先调试出log后再进行下一步.
当发布的时候,发Appconifg.adb定义为true,即切换到生产环境,而不是开发环境的api(有实际工作经验的都懂的)
2 官网上面展示的是很理想的情况下的bugly配置和修复.但是接入到实际项目可能会有些迷惑如下:
首先来观察下图:
1 目标版本和我们设置的patch版本实际上不一致的哦,不要太纠结.
2 已激活,已下发之类的其实是有延迟的,所以不要指望会立即生效
3 如果上传版本的时候还是出现未上传基准包,最好先刷新这个页面,让后台即时更新
3 bugly的下载及加载策略
bugly接入完毕,但是有个问题,用户进入app的时候立即闪退,导致bugly下载patch包和合并修复不及时,导致bug无法修改。
所以此处涉及到一个合适加载的问题:
1 参考手Q的策略,就是检测到有patch包就进入下载和加载patch页面,当进度条显示加载完毕则自动重启app进入启动页面,这种效果是非常好的。而官方提供给我的则是一个弹窗,提示用户重启,导致无法及时修复bug。那么怎么去仿手Q的加载页面呢? 那就必须要对回调有所理解,要不断的调试才行,调试的时候要把instans run 关掉才能热启动,不用一直用命令行启动哦。此处还在持续更新....
2 解决不停闪退的办法还有一个,就是使用uncathExceptionHandler,自定义发生异常之后的操作
可以使用的开源框架是---崩溃日志搜集
4 bugly的实践
1 实际应用中,请注意保存线上发布版本的基准apk包、mapping文件、R.txt文件,如果线上版本有bug,就可以借助我们tinker-support插件进行补丁包的生成。要是删除了基准包,就无法热更新了哦! 同时,发布新的patch包,调试开发者设备的时候需要设置为false
// 设置开发设备,默认为false,上传补丁如果下发范围指定为“开发设备”,需要调用此接口来标识开发设备 Bugly.setIsDevelopmentDevice(getApplication(), true); //在发布正式版本的时候需要关掉,不然发布新的patch包的时候,测试都没法去测试!!!
2 如下所示,一个模拟器不停的在刷接口,导致的很多的空指针和异常bug,所以直接可以将其拉黑,限制访问
5 配置生成的apk路径不一致的问题
Demo是直接在build下面生成的apk,不包含渠道路径,和渠道的命名,此处需要外面自己去配置.
这里是我在app目录下build.gradle的配置:
release { // 不显示Log buildConfigField "boolean", "LOG_DEBUG", "true" //混淆 minifyEnabled true //Zipalign优化 zipAlignEnabled true // 移除无用的resource文件 shrinkResources true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' //签名 signingConfig signingConfigs.release // android.applicationVariants.all { variant -> // variant.outputs.all { // outputFileName = "YeYouTuan_${variant.versionName}_${variant.productFlavors[0].name}.apk" // } // } }
恩,此处最好是注释掉,不然对于后续tinker-support.gradle 的生成路径是有问题的
然后是渠道的配置:
//渠道Flavors,配置不同风格的app,友盟渠道统计时用到 productFlavors { yingyongbao {} /* "offical" {} "360" {} yingyongbao {} wandoujia {} baidu {} "91" {} */ }
渠道和tinker-support.gradle 此处的配置是一致的
// 构建多渠道补丁时使用 buildAllFlavorsDir = "${bakPath}/${baseApkDir}" // 是否启用加固模式,默认为false.(tinker-spport 1.0.7起支持) isProtectedApp = true // 是否开启反射Application模式 enableProxyApplication = false
此处完成之后如下:
上面的bugly是我存储基准版本的地方,也上传到了svn
最后是我总忘记和混淆的一点
6 普通打包和生成补丁包的步骤
配置基准包的tinkerId
tinkerId最好是一个唯一标识,例如git版本号、versionName等等。 如果你要测试热更新,你需要对基线版本进行联网上报。
这里强调一下,基线版本配置一个唯一的tinkerId,而这个基线版本能够应用补丁的前提是集成过热更新SDK,并启动上报过联网,这样我们后台会将这个tinkerId对应到一个目标版本,例如tinkerId = "bugly_1.0.0" 对应了一个目标版本是1.0.0,基于这个版本打的补丁包就能匹配到目标版本。
执行assembleRelease
编译生成基准包:
这个会在build/outputs/bakApk路径下生成每次编译的基准包、混淆配置文件、资源Id文件,如下图所示:
实际应用中,请注意保存线上发布版本的基准apk包、mapping文件、R.txt文件,如果线上版本有bug,就可以借助我们tinker-support插件进行补丁包的生成。
启动apk,上报联网数据
我们每次冷启动都会请求补丁策略,会上报当前版本号和tinkerId,这样我们后台就能将这个唯一的tinkerId对应到一个版本,大家测试的时候可以打开logcat查看我们的日志,如下图所示:
如果看不到log,您需要将bugly初始化的第三个参数设置为true才能看到。
修改待修复apk路径、mapping文件路径、resId文件路径
执行构建补丁包的task
如果你要生成不同编译环境的补丁包,只需要执行TinkerSupport插件生成的task,比如buildTinkerPatchRelease
就能生成release编译环境的补丁包。 注:TinkerSupport插件版本低于1.0.4的,需要使用tinkerPatchRelease来生成补丁包 。
生成的补丁包在build/outputs/patch目录下:
大家这里可能会有一个疑问,补丁版本是怎么匹配到目标版本的,可以双击patch包,我们提供的插件会在tinker生成的patch包基础上插入一个MF文件:
最后,需要注意的是,视频一定要看!!! 而且不要边接入边看,要先看完,然后再接入,不动的再回过去看文档或者视频
.同时,入手的时候,最好先自己做个小demo,然后再接进去自己的项目,这是接入所有第三方的基本套路!