preface

Similar to the Java language in Android, Dart allows you to catch code block exceptions using try/catch/finally. The difference is that flutter APP does not crash when an anomaly occurs in Dart. In my practice, Dart exceptions in the Debug version show a red screen with an exception message, while the Release version shows a blank white screen. Here we trace Flutter exceptions and capture from source code

Abnormalities captured by Flutter

Flutter provides us with partial anomaly capture. In the development of Flutter, you have no doubt encountered red screen programming with error messages, even when the Widget’s width is out of bounds. The code error does not cause the APP to crash, and Flutter helps us catch the exception. As for the principle, we need to look at the source code. Take the StatelessWidget as an example. The Widget creates a corresponding Element, with the following code:

abstract class StatelessWidget extends Widget {
  /// Initializes [key] for subclasses.
  const StatelessWidget({ Key key }) : super(key: key);

  /// Creates a [StatelessElement] to manage this widget's location in the tree. /// /// It is uncommon for subclasses to override this method. @override StatelessElement createElement() => StatelessElement(this); . }Copy the code

Trace the StatelessElement parent class to ComponentElement and find the following code:

/// Calls the [StatelessWidget.build] method of the [StatelessWidget] object
/// (for stateless widgets) or the [State.build] method of the [State] object
/// (for stateful widgets) and then updates the widget tree.
@override
void performRebuild() {
 if(! kReleaseMode && debugProfileBuildsEnabled) ... try { built = build(); debugWidgetBuilderValue(widget, built); } catch (e, stack) { built = ErrorWidget.builder(_debugReportException(ErrorDescription('building $this'), e, stack)); } finally { // We delay marking the element as clean until after calling build() so // that attempts to markNeedsBuild()  during build() will be ignored. _dirty =false;
      assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(false)); } try { _child = updateChild(_child, built, slot); assert(_child ! = null); } catch (e, stack) { built = ErrorWidget.builder(_debugReportException(ErrorDescription('building $this'), e, stack)); _child = updateChild(null, built, slot); }}Copy the code

As you can see from the comments, this method is used to call the build method of StatelessWidget or State and update the Widget tree. Built = build() is wrapped in a try/catch; That is, the method that wraps build execution. Use the ErrorWidget to pop up an error message when an exception is caught. The ErrorWidget. Builder accepts a FlutterErrorDetails returned by the _debugReportException method and displays the exception — that’s the red screen we mentioned above. Go to the _debugReportException method to continue tracing the exception information we need:

FlutterErrorDetails _debugReportException(
  DiagnosticsNode context,
  dynamic exception,
  StackTrace stack, {
  InformationCollector informationCollector,
}) {
  final FlutterErrorDetails details = FlutterErrorDetails(
    exception: exception,
    stack: stack,
    library: 'widgets library',
    context: context,
    informationCollector: informationCollector,
  );
  FlutterError.reportError(details);
  return details;
}
Copy the code

You can see that the exception information is reported by FlutterError. ReportError. Enter the method:

/// Calls [onError] with the given details, unless it is null. static void reportError(FlutterErrorDetails details) { assert(details ! = null); assert(details.exception ! = null);if(onError ! = null) onError(details); }Copy the code

Finally call onError method, trace source:

static FlutterExceptionHandler onError = dumpErrorToConsole;
Copy the code

OnError is a static property of FlutterError, which is handled by dumpErrorToConsole by default. If we change the handling of the exception, we can provide a custom error callback.

// Configure the custom exception report callback (customerReport) void in the entry of the Flutter appmain(){
  FlutterError.onError = (FlutterErrorDetails details) {
    customerReport(details);
  };
  runApp(MyApp());
}
Copy the code

Configure the custom exception reporting callback customerReport in the entry of the Flutter app. We will not discuss whether to report exceptions directly to the network or to interact with native Flutter. At this point, we can collect and process the anomalies that Flutter captures for us.

Flutter does not catch exceptions (concurrent exceptions)

Although Flutter does catch exceptions for us in many critical ways, it unfortunately does not catch concurrent exceptions for us. Synchronous exceptions can be caught by try/catch, while asynchronous exceptions are not:

try{
    Future.delayed(Duration(seconds: 1)).then((e) => Future.error("xxx"));
}catch (e){
    print(e)
}
Copy the code

The code above does not catch Future exceptions. How to do? Fortunately, the concept of Flutter is different from that of a Zone, and Dart can represent the environment in which code is executed by a Zone. Different Zone code contexts are independent. Similar to the concept of a sandbox, different sandboxes are isolated from each other. Sandboxes can capture, intercept, or modify some code behavior, and we can capture all the exceptions that have not been handled in this sandbox. Take a look at the source code for runZoned:

R runZoned<R>(R body(),{
    Map zoneValues,
    ZoneSpecification zoneSpecification, 
    Function onError
    }
)
Copy the code
  • ZoneValues: Private data of a Zone. You can obtain the private data of each sandbox by using Zone [key].
  • ZoneSpecification: Indicates the Zone configuration. You can customize code behaviors, such as blocking log output
  • OnError: No exception handling callbacks are caught in the Zone if the developer provides onError callbacks or passes

We can catch exceptions with onError as follows:

runZoned(
      () {
        Future.error("error"); }, onError: (dynamic e, StackTrace stack) { reportError(e, stack); });Copy the code

We can make runApp run in the Zone and catch all errors in our Flutter application!

runZoned(() {
    runApp(MyApp());
}, onError: (Object obj, StackTrace stack) {
    reportError(e, stack);
});
Copy the code

ZoneSpecification gives us a greater possibility by providing a specification for ForkedZones, using the strength given in this class as the default behavior for callbacks to override zones. A method on a handler that has the same naming method. For example, intercept all calls to print in an application:

runZoned(
    () => runApp(MyApp()),
    zoneSpecification: ZoneSpecification(
      print: (Zone self, ZoneDelegate parent, Zone zone, String line) {
            report(line)
      },
    ),
  );
Copy the code

In this way, onError and zoneSpecification can be used to record logs and phone crash information:

void main() {
  runZoned(
    () => runApp(MyApp()),
    zoneSpecification: ZoneSpecification(
      print: (Zone self, ZoneDelegate parent, Zone zone, String line) { report(line) }, ), onError: (Object obj, StackTrace stack) { customerReport(e, stack); }); }Copy the code

Exception capture of the Flutter engine

The exception of the Flutter engine, for example Android, is mainly caused by libfutter.so. Exceptions that occur in this part are caught in the same way as those that occur natively, which can be implemented by using Bugly et al.

conclusion

According to the above introduction, abnormalities of Flutter are captured and sorted as follows:

Abnormal collection

void main() {
  FlutterError.onError = (FlutterErrorDetails details) {
    customerReport(details);
  };
  runZoned(
    () => runApp(MyApp()),
    zoneSpecification: ZoneSpecification(
      print: (Zone self, ZoneDelegate parent, Zone zone, String line) { report(line) }, ), onError: (Object obj, StackTrace stack) { customerReport(e, stack); }); }Copy the code

Exception reporting

The exception reporting can be passed to Native using MethodChannel, which is implemented in a slightly different way. Communication between Flutter and Android In my project, I used Bugly to collect abnormal information. We could submit the Flutter abnormalities received by Native to Bugly for report. The collected crash information requires configuring the Symbol table (Symbols) to restore the stack to determine the crash information. For details, see building system to add Flutter symbol table