對於上線的應用,數據統計方面最基本的要求就是異常和錯誤信息的上報,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 編程開發」微信公衆號 。