对于上线的应用,数据统计方面最基本的要求就是异常和错误信息的上报,flutter 端也有专门的工具和平台来作这些事情,可是对于国内应用来讲应该用的不是不少,用的比较多的应该仍是 bugly,同时集成 bugly 还能实现热修复功能。android
对于集成、异常上报和热修复,最主要的仍是以官网为准。接下来演示一下如何在 flutter 应用上集成 bugly 来进行异常上报和热修复。git
Android Studio 打开 flutter 里面的 android 原生工程,在 project 的 build.gradle 里面添加 bugly 和 tinker 的插件。github
dependencies {
classpath 'com.android.tools.build:gradle:3.4.1'
// tinkersupport插件(1.0.3以上无须再配置tinker插件)
classpath "com.tencent.bugly:tinker-support:1.1.5"
classpath 'com.tencent.bugly:symtabfileuploader:2.2.1'
}
复制代码
而后在 app 的 build.gardle 里面引入插件编程
//腾讯bug管理插件
apply plugin: 'bugly'
复制代码
接着在 app 的 build.gradle 里面添加依赖bash
implementation "com.android.support:multidex:1.0.1" // 多dex配置
//implementation 'com.tencent.bugly:crashreport_upgrade:1.3.4'// 远程仓库集成方式(推荐)
//implementation("com.tencent.tinker:tinker-android-lib:1.9.1") { changing = true }
implementation 'com.tencent.bugly:crashreport_upgrade:1.3.5'
// 指定tinker依赖版本(注:应用升级1.3.5版本起,再也不内置tinker)
implementation 'com.tencent.tinker:tinker-android-lib:1.9.6'
implementation 'com.tencent.bugly:nativecrashreport:latest.release' //其中latest.release指代最新版本号,也能够指定明确的版本号,例如2.2.0
implementation 'com.android.support:support-v4:28.0.0'
implementation 'com.android.support:appcompat-v7:28.0.0'
复制代码
添加完成依赖以后,配置一下打包须要的信息。 首先将本身的签名文件放到 app\keystore 下。微信
接着在 app 的 build.gardle 里面设置配置信息。架构
signingConfigs {
release {
try {
storeFile file("./keystore/hcc.jks")
storePassword "hcc007"
keyAlias "hcc"
keyPassword "hcc007"
} catch (ex) {
throw new InvalidUserDataException(ex.toString())
}
}
debug {
storeFile file("./keystore/debug.keystore")
}
}
buildTypes {
release {
// minifyEnabled true
signingConfig signingConfigs.release
// proguardFiles getDefaultProguardFile('proguard-android.txt'), project.file('proguard-rules.pro')
// 混淆开关
minifyEnabled false
// 是否zip对齐
zipAlignEnabled true
// 移除无用的resource文件
shrinkResources false
// 是否打开debuggable开关
debuggable false
// 是否打开jniDebuggable开关
jniDebuggable false
// 混淆配置文件
proguardFile 'proguard-rules.pro'
//
//
ndk {
abiFilters 'armeabi-v7a' //, 'armeabi-v7a', 'x86_64', 'arm64-v8a', 'mips', 'mips64'
}
}
debug {
debuggable true
minifyEnabled false
signingConfig signingConfigs.release
ndk {
abiFilters 'armeabi' , 'armeabi-v7a', 'x86_64', 'arm64-v8a', 'mips', 'mips64'
}
}
}
复制代码
这样就能够打 release 包了。app
注意: 若是配置了使用混淆文件,则须要在 progrard-rules.pro 文件里面添加对应的混淆规则,好比 bugly 的就是:ide
# Bugly混淆规则
-dontwarn com.tencent.bugly.**
-keep public class com.tencent.bugly.**{*;}
# 避免影响升级功能,须要keep住support包的类
-keep class android.support.**{*;}
复制代码
若是在原生端还有其余的须要添加混淆规则的,都加上,以避免打出的 release 包出现异常。工具
要使用 tinker 热修复的功能,首先须要新建一个 tinker 相关的脚本文件,这个文件就是官网示例的那个文件,须要在 app 里面的 build.gradle 的同级目录下新建这个文件。 文件内容以下:
apply plugin: 'com.tencent.bugly.tinker-support'
def bakPath = file("${buildDir}/bakApk/")
/**
* 此处填写每次构建生成的基准包目录
*/
def baseApkDir = "app-1123-18-38-49"
/**
* 对于插件各参数的详细解析请参考
*/
tinkerSupport {
// 开启tinker-support插件,默认值true
enable = true
tinkerEnable = 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-20.0-test"
// 构建多渠道补丁时用
// buildAllFlavorsDir = "${bakPath}/${baseApkDir}"
// 是否启用加固模式,默认为false.(tinker-spport 1.0.7起支持)
// isProtectedApp = true
// 是否开启反射Application模式
enableProxyApplication = false
// 是否支持新增非export的Activity(注意:设置为true才能修改AndroidManifest文件)
supportHotplugComponent = false
}
/**
* 通常来讲,咱们无需对下面的参数作任何的修改
* 对于各参数的详细介绍请参考:
* 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 的 build.gradle 中引入这个脚本文件。
//腾讯bug管理插件
apply plugin: 'bugly'
apply from: 'tinker-support.gradle'
复制代码
这样就可使用 tinker 提供的打差分包的功能了。
注意:
脚本都修改完成以后,须要 Sync 一下下载代码等。
按照 bugly 官网的要求,还须要在 AndroidManifest.xml 进行相关的配置。
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE">
</uses-permission>
<uses-permission android:name="android.permission.ACCESS_NETWORK_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"/>
复制代码
<activity
android:name="com.tencent.bugly.beta.ui.BetaActivity"
android:configChanges="keyboardHidden|orientation|screenSize|locale"
android:theme="@android:style/Theme.Translucent"/>
<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>
复制代码
<?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>
复制代码
按照官方文档建议的,不采用 Application 反射的模式:
// 是否开启反射Application模式
enableProxyApplication = false
复制代码
public class MyApplication extends TinkerApplication {
public MyApplication() {
super(ShareConstants.TINKER_ENABLE_ALL, "com.hc.flutter_hotfix_notkt.SampleApplicationLike",
"com.tencent.tinker.loader.TinkerLoader", false);
}
}
复制代码
这里会直接回调 SampleApplicationLike 。
这里是真正的 Applicaion 实现类,这里进行热修复和 Tinker 等初始化配置
public class SampleApplicationLike extends DefaultApplicationLike {
public static final String TAG = "hcc";
private Application mContext;
@SuppressLint("LongLogTag")
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();
mContext = getApplication();
load_library_hack();
if(BuildConfig.DEBUG){
FlutterMain.startInitialization(mContext);
}else {
MyFlutterMain.startInitialization(mContext);
}
configTinker();
//
}
// 使用Hack的方式(测试成功),flutter 加载,也是经过这种方式成功的。
public void load_library_hack( ) {
Log.i(TAG, "load_library_hack: ");
String CPU_ABI = Build.CPU_ABI;
// 将tinker library中的 CPU_ABI架构的so 注册到系统的library path中。
try {
///
Toast.makeText(mContext,"开始加载 so,abi:" + CPU_ABI,Toast.LENGTH_SHORT).show();
// TinkerLoadLibrary.installNavitveLibraryABI(this, CPU_ABI);
//这个路径写死试一下,也就是,获取的 CPU_ABI,不许。
TinkerLoadLibrary.installNavitveLibraryABI(mContext, "armeabi-v7a");
// TinkerLoadLibrary.loadLibraryFromTinker(MainActivity.this, "lib/armeabi", "app");
Toast.makeText(mContext,"加载 so 完成",Toast.LENGTH_SHORT).show();
///data/data/${package_name}/tinker/lib
Tinker tinker = Tinker.with(mContext);
TinkerLoadResult loadResult = tinker.getTinkerLoadResultIfPresent();
if (loadResult.libs == null) {
return;
}
File soDir = new File(loadResult.libraryDirectory, "lib/" + "armeabi-v7a/libapp.so");
if (soDir.exists()){
if(!BuildConfig.DEBUG){
Log.i(TAG, "load_library_hack: 开始设置 tinker 路径");
}
}else {
Log.i("hcc", "load_library_hack: so 库文件路径不存在。。。 ");
}
}catch (Exception e){
Toast.makeText(mContext,e.toString(),Toast.LENGTH_SHORT).show();
}
}
private void configTinker() {
Log.i(TAG, "configTinker: ");
// 设置是否开启热更新能力,默认为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(mContext, "补丁下载地址" + patchFile, Toast.LENGTH_SHORT).show();
}
@Override
public void onDownloadReceived(long savedLength, long totalLength) {
Toast.makeText(mContext,
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(mContext, "补丁下载成功", Toast.LENGTH_SHORT).show();
}
@Override
public void onDownloadFailure(String msg) {
Toast.makeText(mContext, "补丁下载失败", Toast.LENGTH_SHORT).show();
}
@Override
public void onApplySuccess(String msg) {
Toast.makeText(mContext, "补丁应用成功", Toast.LENGTH_SHORT).show();
}
@Override
public void onApplyFailure(String msg) {
Toast.makeText(mContext, "补丁应用失败", Toast.LENGTH_SHORT).show();
}
@Override
public void onPatchRollback() {
}
};
// 设置开发设备,默认为false,上传补丁若是下发范围指定为“开发设备”,须要调用此接口来标识开发设备
Bugly.setIsDevelopmentDevice(mContext, false);
Bugly.init(mContext, "你本身的appid", true);
// 多渠道需求塞入
// String channel = WalleChannelReader.getChannel(getApplication());
// Bugly.setAppChannel(getApplication(), channel);
// 这里实现SDK初始化,appId替换成你的在Bugly平台申请的appId
}
@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
Beta.installTinker(this);
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public void registerActivityLifecycleCallback(Application.ActivityLifecycleCallbacks callbacks) {
getApplication().registerActivityLifecycleCallbacks(callbacks);
}
}
复制代码
<application
android:name=".MyApplication"
android:label="flutter_hotfix_notkt"
android:icon="@mipmap/ic_launcher">
...
</application>
复制代码
bugly 是不能收集到 flutter 的崩溃信息的,所以须要咱们本身手动上报,flutter 提供了手动上报的功能:
CrashReport.postCatchedException(new Throwable(msg));
复制代码
所以咱们只要把 flutter 端的错误,经过原生手动上报给 bugly 就好了。
能够手动上报一条错误信息,flutter 端:
RaisedButton(
child: Text("手动上报"),
onPressed: (){
platform.invokeMethod('report',"手动上报错误");
},
),
复制代码
原生经过 methodChannel 接收到消息以后上报给 bugly:
if(call.method.equals( "report")){
String msg = call.arguments.toString();
CrashReport.postCatchedException(new Throwable(msg));
}
复制代码
经过 runZoned 来捕获和上报错误,至关于 try/catch:
const platform = const MethodChannel('com.hc.flutter');
void collectLog(String line){
//收集日志
}
void reportErrorAndLog(FlutterErrorDetails details){
//上报错误和日志逻辑
platform.invokeMethod('report',details.toString());
}
FlutterErrorDetails makeDetails(Object obj, StackTrace stack){
// 构建错误信息
}
void main() {
FlutterError.onError = (FlutterErrorDetails details) {
reportErrorAndLog(details);
};
runZoned(
() => runApp(MyApp()),
zoneSpecification: ZoneSpecification(
print: (Zone self, ZoneDelegate parent, Zone zone, String line) {
collectLog(line); //手机日志
},
),
onError: (Object obj, StackTrace stack) {
var details = makeDetails(obj, stack);
reportErrorAndLog(details);
},
);
}
复制代码
这样就能够捕获到 flutter 这边也异常上报给 bugly。
注意: 这样捕获到的错误不是崩溃信息,须要在错误分析中查看
关于热修复原理和代码相关的,参考上一篇文章Flutter Android 端热修复(热更新)实践
打 release 包以前,须要配置在 tinker-support.gradle 里面的 tinkerId,这个是这个 release 包的一个标识。
// 构建基准包和补丁包都要指定不一样的tinkerId,而且必须保证惟一性
tinkerId = "base-20.0-test"
复制代码
Gradle Task 面板执行 assembleRelease 的 task,也能够执行 assemble ,只不过这个也会打 debug 包,或者直接在 android 项目目录下运行 命令:
gradlew assembleRelease
复制代码
拿到 release 安装包以后,安装并在联网状态下运行一次,只有在联网时才能上报信息给 bugly, 这样才能热修复动态下发补丁包。
打差分包确定须要有一个对照,对照的就是刚才打的基准包,在 tinker-support.gradle 中指定这个基准包。
同时别忘了修改 tinkerId,bugly 会把这个修复包的 tinkerId 和 打的基准包对应上,这样才能匹配到具体的版本。
// 构建基准包和补丁包都要指定不一样的tinkerId,而且必须保证惟一性
tinkerId = "patch-20.0-test"
复制代码
都修改完成以后,执行 buildTinkerPatchRelease 打差分包
注意: 是 tinker-support 下的,不是 tinker 下的 task.
执行完命令以后,能够在 build/app/outputs/patch/release 下找到已经压缩好的差分包。
注意: 必须是 build/app/outpus/patch/release 下的才是正确的差分包,不是在 app/outputs/apk/rinkerPatch 下的那个。
Android Studio 直接打开这个文件,看一下
在 bugly 的后台上,经过 应用升级——热更新来发布新补丁,同时能够选择全量设备仍是开发设备。若是不出意外的话,上传成功以后,手机端就能够等待热修复效果了,可能须要几分钟的时间。
在修复以前:
若是收到了更新补丁,而且 tinker 配置了提示用户重启:
// 设置是否提示用户重启,默认为false
Beta.canNotifyUserRestart = true;
复制代码
重启以后完成修复:
在使用 bugly 热修复的时候有几个注意事项:
ndk {
abiFilters 'armeabi-v7a' //, 'armeabi-v7a', 'x86_64', 'arm64-v8a', 'mips', 'mips64'
}
复制代码
if(BuildConfig.DEBUG){
FlutterMain.startInitialization(mContext);
}else {
MyFlutterMain.startInitialization(mContext);
}
复制代码
更详细的代码请参考 github
欢迎关注「Flutter 编程开发」微信公众号 。