For online applications, the most basic requirement of data statistics is the reporting of exceptions and error information. There are also special tools and platforms on flutter to do these things. However, for domestic applications, Bugly is not often used.
For integration, exception reporting, and hotfix, the official website is the most important. Next, I will demonstrate how to integrate Bugly with the Flutter application for exception reporting and thermal repair.
I. Bugly integration
1. Add dependencies
Android Studio opens the Android native projects inside Flutter and adds bugly and Tinker plug-ins to the build.gradle project.
dependencies {
classpath 'com. Android. Tools. Build: gradle: 3.4.1 track'// TinkerSupport (you do not need to configure tinker after 1.0.3) classpath"Com. Tencent. Bugly: tinker - support: 1.1.5." "
classpath 'com. Tencent. Bugly: symtabfileuploader: 2.2.1'
}
Copy the code
Then add a plugin to your app’s build.gardle
// Apply plugin for bug management:'bugly'
Copy the code
Then add a dependency to your app’s build.gradle
implementation "Com. Android. Support: multidex: 1.0.1." "//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'// Specify tinker dependent version (note: tinker is no longer built in as of version 1.3.5) implementation'com. Tencent. Tinker: tinker - android - lib: 1.9.6'
implementation 'com.tencent.bugly:nativecrashreport:latest.release'Latest. release indicates the latest version number, or you can specify an explicit version number, such as 2.2.0 implementation'com. Android. Support: support - v4:28.0.0'
implementation 'com. Android. Support: appcompat - v7:28.0.0'
Copy the code
2. Package the configuration
After adding dependencies, configure the information needed for packaging. First put your signature file in app\keystore.
Then set the configuration information in your app’s 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') // Obfuscation switch minifyEnabledfalse// Whether zip is aligned with zipAlignEnabledtrue// Remove the useless resource file shrinkResourcesfalse// Whether to enable the DEBUggable functionfalse// Whether to enable jniDebuggable jniDebuggablefalse// Mix up the proguardFile configuration file'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'}}}Copy the code
So now you can type the Release package.
Pro = progrard-rules.pro = progrard-rules.pro = progrard-rules.pro = progrard-rules.pro = progrard-rules.pro = progrard-rules.pro
# Bugly obfuscate rules-dontwarn com.tencent.bugly.** -keep public class com.tencent.bugly.**{*; }# To avoid affecting the upgrade function, you need to keep the support package classes-keep class android.support.**{*; }Copy the code
If there are other obliquity rules that need to be added on the native side, add them so that the release package is not abnormal.
3. Create the tinker-support.gradle file
To use tinker hotfix, you need to create a tinker related script file. This file is the same as the example on the official website. You need to create this file in the same directory as build.gradle in the app. The content of the document is as follows:
apply plugin: 'com.tencent.bugly.tinker-support'
def bakPath = file("${buildDir}/bakApk/") /** * Enter the base package directory generated by each build */ def baseApkDir ="app-1123-18-38-49"/** * For details about plug-in parameters, see */ tinkerSupport {// Enable tinker-support. The default value is tinker-supporttrue
enable = true
tinkerEnable = true// Specify the archive directory. The default is the current module subdirectory tinker autoBackupApkDir ="${bakPath}"// Whether to enable overwriting tinkerPatch configuration. The default valuefalse/ / open tinkerPatch configuration is not effective after that without adding tinkerPatch overrideTinkerPatchConfiguration =trueWhen compiling the patch package, you must specify the apK of the baseline version. The default value is null. If this value is null, it indicates that the patch package is not compiled"${bakPath}/${baseApkDir}/app-release.apk"// Corresponding tinker plug-in applyMapping baseApkProguardMapping ="${bakPath}/${baseApkDir}/app-release-mapping.txt"// applyResourceMapping baseApkResourceMapping ="${bakPath}/${baseApkDir}/app-release-R.txt"// Base and patch packages are built with different Tinkerids and must be unique tinkerId ="Patch - 20.0 - the test"// buildAllFlavorsDir ="${bakPath}/${baseApkDir}"// Whether to enable the hardening mode. The default value is false.(supported from tinker-spport 1.0.7) // isProtectedApp =true// Whether to enable reflection Application modeenableProxyApplication = false// Whether new non is supportedexportThe Activity (note: set totrueFile supportHotplugComponent =false} /** * Generally speaking, we do not need to make any changes to the following parameters * For detailed description of each parameter please refer to: * 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"// Optional, set the mapping file. It is recommended to keep the proGuard obfuscation mode of the old APK //applyResourceMapping ="${bakPath}/${appName}/app-release-R.txt"// Optional, set r.txt file to keep ResId allocation through old APK file}}Copy the code
Then import the script file in your app’s build.gradle.
// Apply plugin for bug management:'bugly'
apply from: 'tinker-support.gradle'
Copy the code
This allows you to use tinker’s subcontracting capabilities.
Note: after the script is modified, you need to Sync to download the code, etc.
4. Androidmanifest.xml configuration
According to the requirements of bugly’s official website, the relevant configuration is also required in androidmanifest.xml.
Configure permissions
<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"/>
Copy the code
Configure the provider and activity
<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>
Copy the code
Create a new provider_paths.xml file under res/ 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>
Copy the code
5. Application configuration
As recommended by the official documentation, do not use the Application reflection mode:
// Whether to enable reflection Application modeenableProxyApplication = false
Copy the code
1. Create MyApplication
public class MyApplication extends TinkerApplication {
public MyApplication() {
super(ShareConstants.TINKER_ENABLE_ALL, "com.hc.flutter_hotfix_notkt.SampleApplicationLike"."com.tencent.tinker.loader.TinkerLoader".false); }}Copy the code
SampleApplicationLike is called directly.
2. Create SampleApplicationLike
Here is the real Applicaion implementation class, where hot fixes and Tinker initialization are done
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(); } // The flutter load succeeds in this way. public voidload_library_hack( ) {
Log.i(TAG, "load_library_hack: "); String CPU_ABI = Build.CPU_ABI; // Register the SO of the CPU_ABI schema in tinker Library into the system library path. try { /// Toast.makeText(mContext,Start loading so, ABI:+ CPU_ABI,Toast.LENGTH_SHORT).show(); // TinkerLoadLibrary.installNavitveLibraryABI(this, CPU_ABI); // The path is blocked, i.e., the CPU_ABI is not allowed. TinkerLoadLibrary.installNavitveLibraryABI(mContext,"armeabi-v7a");
// TinkerLoadLibrary.loadLibraryFromTinker(MainActivity.this, "lib/armeabi"."app");
Toast.makeText(mContext,"Loading so completed",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: Start setting tinker path"); }}else {
Log.i("hcc"."Load_library_hack: so library file path does not exist... ");
}
}catch (Exception e){
Toast.makeText(mContext,e.toString(),Toast.LENGTH_SHORT).show();
}
}
private void configTinker() {
Log.i(TAG, "configTinker: "); // Set whether to enable hot updatetrue
Beta.enableHotfix = true; // Set whether to automatically download patches. The default value istrue
Beta.canAutoDownloadPatch = true; // Set whether to automatically compose patches. Defaulttrue
Beta.canAutoPatch = true; // Sets whether to prompt the user to restartfalse
Beta.canNotifyUserRestart = true; // Patch callback interface beta. betaPatchListener = newBetaPatchListener() {
@Override
public void onPatchReceived(String patchFile) {
Toast.makeText(mContext, "Patch download address" + 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, "Patch downloaded successfully", Toast.LENGTH_SHORT).show();
}
@Override
public void onDownloadFailure(String msg) {
Toast.makeText(mContext, "Patch download failed", Toast.LENGTH_SHORT).show();
}
@Override
public void onApplySuccess(String msg) {
Toast.makeText(mContext, "Patch applied successfully", Toast.LENGTH_SHORT).show();
}
@Override
public void onApplyFailure(String msg) {
Toast.makeText(mContext, "Patch application failed", Toast.LENGTH_SHORT).show();
}
@Override
public void onPatchRollback() {}}; // Set the development device, default isfalse, upload the patch if the range is specified for the issuance of the "development" equipment, you need to call this interface to identify development equipment Bugly. SetIsDevelopmentDevice (mContext,false);
Bugly.init(mContext, "Your own AppID".true); / / multi-channel requirement into / / the String channel. = WalleChannelReader getChannel (getApplication ()); // Bugly.setAppChannel(getApplication(), channel); // Here is the SDK initialization, Replace appId with your Bugly application 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); // installTinker beta.installtinker (this); } @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) public void registerActivityLifecycleCallback(Application.ActivityLifecycleCallbacks callbacks) { getApplication().registerActivityLifecycleCallbacks(callbacks); }}Copy the code
3. Specify application in the Manifest
<application
android:name=".MyApplication"
android:label="flutter_hotfix_notkt"
android:icon="@mipmap/ic_launcher">... </application>Copy the code
Abnormal flutter is reported
Bugly cannot collect information about the collapse of Flutter, so we need to manually report it. Flutter provides this function:
CrashReport.postCatchedException(new Throwable(msg));
Copy the code
So we just need to manually report errors on the Flutter side to Bugly via native.
1. Manual call
You can manually report an error message to the flutter end:
RaisedButton(
child: Text("Manual reporting"),
onPressed: (){
platform.invokeMethod('report'."Manual error reporting"); },),Copy the code
The native receives the message via the methodChannel and reports it to Bugly:
if(call.method.equals( "report")){
String msg = call.arguments.toString();
CrashReport.postCatchedException(new Throwable(msg));
}
Copy the code
2. Automatically capture and report exceptions
To catch and report an error with runZoned:
const platform = const MethodChannel('com.hc.flutter'); Void collectLog(String line){// Collect logs} void ReporandLog (FlutterErrorDetails details){// Report errors and log logic platform.invokeMethod('report',details.toString()); } FlutterErrorDetails makeDetails(Object obj, StackTrace stack){// Build error message} voidmain() {
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); }); }Copy the code
This will capture abnormalities on the flutter side and report them to Bugly.
Note: The error captured in this way is not crash information and needs to be viewed in error analysis
3. Thermal repair through Bugly
For more information on the principles and code related to the hot repair of Flutter Android, see the previous article hot repair practice of Flutter Android
1. Make the Release package as the base package
Before release package, need to configure the tinker – support. Gradle tinkerId inside, this is the release package for a logo.
// Base and patch packages are built with different Tinkerids and must be unique tinkerId ="Base - 20.0 - the test"
Copy the code
Gradle Task executes the Task with assembleRelease, which can also assemble, except this will also have a debug package, or run the command directly from the Android project directory:
gradlew assembleRelease
Copy the code
After getting the release installation package, install it and run it once in the networking state. Only when the network is connected can the information be reported to Bugly, so that the patch package can be issued dynamically for hot repair.
2. Modify code (bug) through Tinker subcontracting
Gradle must have a reference. The reference is the base package you just made. Specify this base package in tinker-support.gradle.
Also don’t forget to change the tinkerId. Bugly will match the tinkerId of the fix pack to the base pack so that it matches the specific version.
// Base and patch packages are built with different Tinkerids and must be unique tinkerId ="Patch - 20.0 - the test"
Copy the code
Once you’re done, execute buildTinkerPatchRelease to subcontract
Note: This task is created by tinker-support.
After executing the command, can be in the build/app/outputs/patch/release find poor sub-contract compression.
Note: must be in the build/app/outpus/patch/release is right under the subcontract, not in the app/outputs/apk rinkerPatch under that.
Android Studio opens this file directly and takes a look
3. Bugly uploads the patch for hot repair
On the Back of Bugly, new patches are released by applying upgrades — hot updates — with the option to choose between full or development devices. Once the upload is successful, the phone can wait for the heat repair effect, which may take a few minutes.
Before fixing:
If an updated patch is received and Tinker is configured to prompt the user to restart:
// Sets whether to prompt the user to restartfalse
Beta.canNotifyUserRestart = true;
Copy the code
Repair completed after reboot:
Four, notes
There are a few considerations when using Bugly thermal fixes:
- Don’t forget permissions in manifest, at the same time
- If obfuscation is enabled, add the corresponding obfuscation rule
- TinkerId and tinkerId subcontracted each time need to be unique, do not repeat packaging upload
- Tinker hotfixes so, don’t specify CPU_ABI incorrectly, because most apps use Armeabi-v7A. If you need something else, you can change it in build.gradle and code accordingly.
ndk {
abiFilters 'armeabi-v7a'/ /,'armeabi-v7a'.'x86_64'.'arm64-v8a'.'mips'.'mips64'
}
Copy the code
- Because Flutter supports both debug hot reload and Release AOT modes, code is different. You can specify the code to run with Flutter in the following way. This allows you to debug and release packages
if(BuildConfig.DEBUG){
FlutterMain.startInitialization(mContext);
}else {
MyFlutterMain.startInitialization(mContext);
}
Copy the code
- If bugly hotfixes fail or just want to experience Tinker hotfixes, I also wrote a native demo that integrates Tinker hotfixes. Refer to github. Once this works, Bugly should work as well.
See Github for more details
Welcome to follow the wechat public account “Flutter Programming and Development”.