天天看點

c語言捕獲崩潰位置,Flutter異常捕獲和Crash崩潰日志收集

前言

和Android中的Java語言類似,Dart中也可以通過try/catch/finally來捕獲代碼塊異常。不同的是在Dart中發生異常的時候flutter APP并不會崩潰。在我的實踐中,debug版中的Dart異常會表現為紅屏加異常資訊,而release版則是空白的白屏。下面我們就從源碼追溯Flutter的異常和捕獲

Flutter捕獲的異常

Flutter為我們提供了部分異常捕獲。在flutter開發中大家肯定遇到過螢幕程式設計紅色并帶有錯誤資訊的情況,甚至在Widget寬度越界時也會出現這樣的錯誤提示界面。雖然代碼出現了錯誤,但是并不會導緻APP崩潰,Flutter會幫我們捕獲異常。至于其中的原理,就需要我們去看一下源碼了。 以StatelessWidget為例,Widget會建立對應的Element,(至于為什麼要進入Element裡面,請大家自行了解Widget的繪制原理)其代碼如下:

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);

....

}

複制代碼

追根溯源StatelessElement父類到ComponentElement,發現如下代碼:

/// 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);

}

}

複制代碼

根據注釋不難發現,這個方法用來調用StatelessWidget或者State的build方法,并更新Widget樹。不難發現,代碼使用了try/catch包裹了built = build();也就是包裹了build執行的方法。當捕獲到異常時使用ErrorWidget彈出錯誤提示。不難發現ErrorWidget.builder接受了一個_debugReportException方法傳回的FlutterErrorDetails,并展示異常——這就是我們上文提到的紅屏吧。進入_debugReportException方法内繼續追蹤我們需要的異常資訊:

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;

}

複制代碼

可以看到,異常資訊通過FlutterError.reportError上報。進入該方法:

/// 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);

}

複制代碼

最後調用了onError方法,追蹤源碼:

static FlutterExceptionHandler onError = dumpErrorToConsole;

複制代碼

onError是FlutterError的一個靜态屬性,它預設的處理方法是dumpErrorToConsole。如果我們更改異常的處理方式,提供一個自定的錯誤回調就可以了。

//在Flutter app入口配置自定義的異常上報回調(customerReport)

void main(){

FlutterError.onError = (FlutterErrorDetails details) {

customerReport(details);

};

runApp(MyApp());

}

複制代碼

在Flutter app入口配置自定義的異常上報回調customerReport,至于異常的上報,是在Flutter中直接請求網絡還是與原生互動,我們暫不進行讨論。至此,我們就可以收集和處理那些Flutter為我們捕獲的異常了。

Flutter沒有捕獲的異常(并發異常)

雖然Flutter為我們在很多關鍵的方法進行了異常捕獲,但遺憾的是,它并不能為我們捕獲并發異常。同步異常可以通過try/catch捕獲,而異步異常則不會被捕獲:

try{

Future.delayed(Duration(seconds: 1)).then((e) => Future.error("xxx"));

}catch (e){

print(e)

}

複制代碼

上面的代碼是捕獲不了Future異常的。怎麼辦? 幸運的是,Flutter中與一個Zone的概念,Dart中可通過Zone表示指定代碼執行的環境,不同的Zone代碼上下文是不同的互不影響。類似一個沙盒概念,不同沙箱的之間是隔離的,沙箱可以捕獲、攔截或修改一些代碼行為,我們可以再這裡面捕獲所有沒有被處理過的異常。 看一下runZoned的源碼:

R runZoned(R body(),{

Map zoneValues,

ZoneSpecification zoneSpecification,

Function onError

}

)

複制代碼

zoneValues: Zone 的私有資料,可以通過執行個體zone[key]擷取,可以了解為每個“沙箱”的私有資料。

zoneSpecification:Zone的一些配置,可以自定義一些代碼行為,比如攔截日志輸出行為等

onError:Zone中未捕獲異常處理回調,如果開發者提供了onError回調或者通

我們可以通過onError捕獲異常就像下面這樣:

runZoned(

() {

Future.error("error");

},

onError: (dynamic e, StackTrace stack) {

reportError(e, stack);

},

);

複制代碼

我們可以讓runApp運作在Zone中,這樣就可以捕獲我們Flutter應用中全部錯誤了!

runZoned(() {

runApp(MyApp());

}, onError: (Object obj, StackTrace stack) {

reportError(e, stack);

});

複制代碼

zoneSpecification則給了我們帶來了更大的可能性,它提供了forkedZone的規範,使用在此類中給出的實力作為回調覆寫Zone中的預設行為。處理程式上具有相同命名方法的方法。例如:攔截應用中所有調用print輸出日志的行為:

runZoned(

() => runApp(MyApp()),

zoneSpecification: ZoneSpecification(

print: (Zone self, ZoneDelegate parent, Zone zone, String line) {

report(line)

},

),

);

複制代碼

這樣使用onError和zoneSpecification搭配可以實作記錄日志和手機崩潰資訊:

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);

}

);

}

複制代碼

Flutter engine 異常捕獲

flutter engine部分的異常,以Android為例,主要為libfutter.so發生的錯誤。這部分發生的異常的捕獲方式和原生發生的異常的捕獲方式一樣,可以借用Bugly等實作。

總結

通過上述介紹,Flutter的異常捕獲整理如下:

異常收集

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);

}

);

}

複制代碼

異常上報

其中異常的上報可以使用MethodChannel傳遞給Native,實作方式略,有興趣的課參考:Flutter與android之間的通訊 而在我負責的項目中,我使用Bugly來收集異常資訊,我們可以将native接收到的Flutter異常交給Bugly上報。 而收集到的崩潰資訊則需要配置符号表(symbols)還原堆棧以确定崩潰資訊。詳情請參考:建構系統加入Flutter符号表

作者:白瑞德

連結:https://juejin.cn/post/6844903940497244167

來源:掘金

著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。