最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Flutter?Zone異常處理方法及基本原理

 更新時間:2023年01月18日 11:09:26   作者:開中斷  
這篇文章主要為大家介紹了Flutter?Zone異常處理方法及基本原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

1. 認識Zone

Zone像一個沙盒,是我們代碼執(zhí)行的一個環(huán)境。

我們的main函數默認就運行在Root Zone當中。

子Zone的構造有點像Linux中的進程,它支持從當前的Zone中Fork出一個子Zone:

Zone myZone = Zone.current.fork(...)

對于Zone而言,它有兩個構造函數:

  • ZoneSpecification
  • ZoneValues

ZoneSpecification:其實是Zone內部代碼行為的一個提取,我們可以通過它來為Zone設置一些監(jiān)聽。

ZoneValues:Zone的變量,私有變量。

類似Linux 通過Fork創(chuàng)建的 myZone默認也具有源Zone的ZoneSpecification和ZoneValues。

1.1 ZoneValues

和Linux類似地,當Zone做Fork的時候,會將父Zone所持有的ZoneSpecification、ZoneValues會繼承下來,可以直接使用。并且是支持追加的,secondZone在firstZone的基礎之上,又追加了extra_values屬性,不會因為secondZone的ZoneValues就導致name屬性被替換掉。

Zone firstZone = Zone.current
    .fork(specification: zoneSpecification, zoneValues: {"name": "bob"});
Zone secondZone = firstZone.fork(zoneValues: {"extra_values": 12345});
secondZone.run(() {
  print(secondZone["name"]); // bob
  print(secondZone["extra_values"]); // 12345
}

我們可以使用Zone.current,訪問當前的代碼執(zhí)行在哪一個Zone當中,默認情況下,代碼執(zhí)行在Root Zone當中,后續(xù)會根據需求分化出多個Zone,也可以使用Zone.root訪問到RootZone的實例。

1.2 ZoneSpecification

和ZoneValues不同,ZoneValues支持追加不同的屬性,而ZoneSpecification只支持重寫,并且RootZone已經預設好了一系列的Zone中運行的規(guī)則,一旦我們重寫了ZoneSpecification的一些方法回調,之前的一些功能可能會消失。

這種基于配置對象的擴展方法和基于繼承的子類的重寫是不一樣的,該方法具有更強的擴展性,但是在類似于特性保留的機制上就明顯不如繼承來的方便,一旦重寫某個方法,該方法原有的特性需要重新實現一遍,否則原有的功能會消失。

如果你只重寫了其中的一個方法,那么其他方法不會被覆蓋,依然采用默認配置。

ZoneSpecification的構造方法中,包含非常多的參數,其中絕大多數都是以回Callback形式出現,首先來看看run系列的方法:

RunHandler? run,
RunUnaryHandler? runUnary,
RunBinaryHandler? runBinary,

其實這三個方法的區(qū)別在于參數,我們看看RunHandler、RunUnaryHandlerRunBinaryHandler的具體定義:

typedef RunHandler = R Function<R>(
    Zone self, ZoneDelegate parent, Zone zone, R Function() f);?
typedef RunUnaryHandler = R Function<R, T>(
    Zone self, ZoneDelegate parent, Zone zone, R Function(T arg) f, T arg);?
typedef RunBinaryHandler = R Function<R, T1, T2>(Zone self, ZoneDelegate parent,
    Zone zone, R Function(T1 arg1, T2 arg2) f, T1 arg1, T2 arg2);

不難發(fā)現,三者除了固定的:self、parent、zone之外,區(qū)別就在于

UnaryHandlerBinaryHandler提供了分別提供了一個參數、兩個參數的選項。這個參數的作用是提供給另外一個參數:f,類型是一個Function,顯然它是我們調用Zone.run方法傳進來的body參數,以RunHandler為例,我們對run做出如下的定義:

Zone secondZone = firstZone.fork(
    zoneValues: {"extra_values": 12345},
    specification: ZoneSpecification(
      run: <int>(self, parent, zone, f) {
        int output = f();
        return output;
      },
    ));

我們在外部調用secondZone.run(()=>...)時,就可以在run方法的開始、結尾做一些其他的事情了:

secondZone.run(body);// 執(zhí)行
run: <int>(self, parent, zone, f) {
    // 1.
    print("before");
    int output = f();// 這里的f就是body,它是可執(zhí)行的
    print("after");
    return output;
    // 2.
},

直覺告訴我,1/2之間的代碼應該是在Second Zone中執(zhí)行的,但是打印一下Zone.root,我們發(fā)現實際上是在Root Zone中執(zhí)行的,二者的HashCode相同。

// 在body內部打印的
body internal Zone:195048515 // 
Root Zone:195048515 // 
first Zone:700091970
second Zone:707932504

大致上去跟了一下代碼,發(fā)現默認的run方法的實現,被我們新編寫的run參數覆蓋掉了,所以會導致本該在secondZone中執(zhí)行的body結果在Root Zone中執(zhí)行。然后再run參數的注釋里,發(fā)現了這么一段話:

Since the root zone is the only zone that can modify the value of [current], custom zones intercepting run should always delegate to their parent zone. They may take actions before and after the call.

大致上的意思是:

因為Root Zone是唯一能夠修改Zone.current參數的Zone,所以自定義的Zone攔截run方法必須總是將方法交給它們的父Zone去代為處理。而run自己可以在run調用之前或者之后采取一些行動。

也就是說,我們不能直接return f();,而要把f()委托給parent來執(zhí)行,像這樣:

secondZone.run(body);// 執(zhí)行
?
run: <int>(self, parent, zone, f) {
    // 1.這里執(zhí)行在Root Zone
    print("before");
    Function output = parent.run(self, () {
      // 這里執(zhí)行在second Zone
      return f(); 
    });
    print("after");
    return output;
    // 2.
},

委托之后,由Root Zone去做統(tǒng)一的調度、Zone的切換。這樣,我們再去打印一下執(zhí)行的Zone,發(fā)現正常了,secondZone.run方法(其實是被ZoneSpecification中的run指定的方法)的Zone仍然是Root Zone,而我們傳遞過去的任務被執(zhí)行在了self之中,也就是SecondZone 當中,符合我們的預期:

current zone:692810917
body internal Zone:558922284
Root Zone:692810917
firstZone Zone:380051056
second Zone:558922284

額外地,可以牽出ZoneDelegate是做什么的,它允許子Zone,訪問父Zone的一些方法,與此同時保留自己額外的一些行為:綠框表示額外的行為,當Zone A調用Zone B的run時,它通常執(zhí)行在調用者的Zone當中,也就是ZoneA。

1.3 通過runZoned快速創(chuàng)建Zone

Dart提供了runZoned方法,支持Zone的快速創(chuàng)建:

R runZoned<R>(R body(),
    {Map<Object?, Object?>? zoneValues,
    ZoneSpecification? zoneSpecification,
    @Deprecated("Use runZonedGuarded instead") Function? onError}) {

其中body、zoneValues、zoneSpecification都是老熟人了,關鍵在于它對于run方法的處理:

/// Runs [body] in a new zone based on [zoneValues] and [specification].
R _runZoned<R>(R body(), Map<Object?, Object?>? zoneValues,
        ZoneSpecification? specification) =>
    Zone.current
        .fork(specification: specification, zoneValues: zoneValues)
        .run<R>(body);

如果我們不顯式地傳遞一個ZoneSpecififation進來,fork時傳進去的是null,自然不會導致Specification被我們重寫,因此代碼能按照Dart默認的實現方式,運行在一個新的、Fork出來的Zone當中(至少能看出不是Root Zone):

runZoned(() {
 &nbsp;print("body internal Zone:" + Zone.current.hashCode.toString());
 &nbsp;print("Root Zone:" + Zone.root.hashCode.toString());
});
?
// 打印結果
body internal Zone:253994638
Root Zone:1004225004

但是如果你像之前手動fork一樣,指定它的ZoneSpecification,又不把f委托給上層Zone處理,那么就會:

body internal Zone:44766141
Root Zone:44766141

2. 異步基本原理和異常捕獲

默認大家已經知道什么事單線程模型,以及Future的執(zhí)行機制了,Dart的單線程模型和事件循環(huán)機制。

來看看這段簡單的代碼:

void asyncFunction() {
  print('1');
  Future((){
    print('2');
  }).then((e) {
    print('3');
  });
  print('4');
}

大家都知道,這段代碼的輸出的順序是:1423,它的大致流程是:

print 1
創(chuàng)建一個Future,并扔到Event Queue末尾
print 4
// 從Event Queue中取出,并執(zhí)行下一個消息......
執(zhí)行Future構造函數中的方法:->  print 2
print 2執(zhí)行完成,即Future完成,回調它的then: ->  print 3

我們?yōu)樗由蟖wait和async,并稍作改造,寫成async、await的同步形式,同時刪掉4

void asyncFunction() async {
  print('1');
  await Future(() {
    print('2');
  });
  print('3');
  print('4');
}

它的輸出是:1234,他所做的是:

print 1;
創(chuàng)建一個Future@1,并扔到Event Queue末尾;
// 從Event Queue中取出,并執(zhí)行下一個消息......
取出Future@1,立刻執(zhí)行它構造中的方法: -> print 2;
并將之后的代碼打包,重新放到Event Queue的末尾(這里一般會等待IO完成,之后就會去執(zhí)行和這個回調)
執(zhí)行完成之后,執(zhí)行之后的代碼:
print 3;
print 4;

今天我們不是討論Async和Await的,就不再展開。

但是大家可以比較一下這兩次調用,發(fā)現第二種和第一種相比,第二種調用的代碼是會 “回來” 繼續(xù)執(zhí)行的,而第一種的Future創(chuàng)建不搭配await/async的就好比脫韁的野馬,這種代碼我們并不關心它的結果,自然也不要求代碼在此await,執(zhí)行起來就無法控制,但在Dart中我們也無法通過try/catch捕獲異常。

關鍵點在于:async + await是會回到異步阻塞的代碼處(await處)執(zhí)行的。既然回來了,那么try/catch自然而然是能夠繼續(xù)監(jiān)聽是否有異常拋出的。

而第一種的Future,即使我們在外面包裹上了try/catch,而Future的代碼卻是在未來的某個時間內,在Event Queue的末尾的某個位置解包執(zhí)行的,上下文和try/catch所在的代碼并沒什么關聯,自然不能攔截到異常。我們可以從Stack Trace中看看這兩種代碼拋出異常時的執(zhí)行棧:

左側是一種方法的執(zhí)行棧,throwExceptionFunction()項相關的棧幀已經消失了,異常自然沒有辦法通過throwExceptionFunction()中的try/catch進行捕獲。

問題就出在這了, 對于這種錯誤我們是否有辦法去捕獲呢?

答案仍然還是是今天的主題 : Zone。

3. HandleUncaughtErrorHandler

雖然異步代碼的執(zhí)行,可能會橫跨多個Event,讓代碼前后的上下文失去聯系,導致異常無法被正常捕獲,但是它仍然在一個Zone之內。

就像仙劍奇?zhèn)b傳三中,李逍遙對景天說“邪劍仙(Exception)雖身處六界(Event)之外卻是在道(Zone)之內”。

Zone提供了一些特殊的編程接口,讓我們能夠對當前這個Zone沙盒內的未捕獲的異常進行集中處理。

它就是HandleUncaughtErrorHandler。作為ZoneSpecification的一個參數,它支持將Zone當中未被處理的錯誤統(tǒng)一歸到這里進行處理(Dart和Java不一樣,Dart的異常本身通常不會導致程序的退出),因此,常使用HandleUncaughtErrorHandler來做異常的統(tǒng)計、上報等等。

另外,因為Dart執(zhí)行環(huán)境的單線程 + 事件隊列機制本身,Dart的try/catch對于異步代碼是無法處理的,如下的代碼異常會穿透(或者說根本不經過)try/catch后拋出,會在控制臺中留下紅色的報錯。

// Zone.run(()=>throwExceptionFunctino());
void throwExceptionFunction() {
  try {
    Future.delayed(const Duration(seconds: 1))
        .then((e) => throw("This is an Exception"));
  } catch (e) {
    print("an Exception has been Captured: ${e.toString()}");
  }
}

顯然,異步的異常并沒有被捕獲:

Unhandled exception:
This is an Exception
#0      throwExceptionFunction.<anonymous closure> (file:///Users/rEd/IdeaProjects/dartProjs/zone/bin/zone.dart:140:22)
#1      _rootRunUnary (dart:async/zone.dart:1434:47)
#2      _CustomZone.runUnary (dart:async/zone.dart:1335:19)
<asynchronous suspension>

但是我們改成這樣呢?

void throwExceptionFunction() async{
  try {
    await Future.delayed(const Duration(seconds: 1))
        .then((e) => throw ("This is an Exception"));
  } catch (e) {
    print("an Exception has been Captured: ${e.toString()}");
  }
}

我們對異步的方法throwExceptionFunction()加了await/async關鍵字。我們會發(fā)現異常,又能被捕獲了:

an Exception has been Captured: This is an Exception
Process finished with exit code 0

其實上文已經提到了是異步時Dart代碼上下文切換的原因,這里也不做過多的贅述了,我們像這樣,將我們的App包裹在一個額外的Zone里面,并在它的HandleUncaughtErrorHandler相關方法做如下定義:

void main() {
  runZoned(() => runApp(const MyExceptionApp()),
      zoneSpecification: ZoneSpecification(
          // print: (self, parent, zone, line) {},
          handleUncaughtError: (Zone self, ZoneDelegate parent, Zone zone,
              Object error, StackTrace stackTrace) {
            // 同樣將print代理給上層Zone,這樣就可以在上層捕獲到這些異常了。
            parent.print(self," ### \n $stackTrace \n ### ");
          }));
}

隨便找個地方拋出個異常:

floatingActionButton: FloatingActionButton(
  onPressed: () => Future((){
    throw ("ERROR!");
  }),
),

我們可以發(fā)現,異常在此處被HandleUncaughtErrorHandler集中捕獲了。

或者我們也可以使用runZoned自帶的回調來處理,而不是去自己重寫ZoneSpecification:

// runZonedGuarded替換runZoned
runZonedGuarded(() => runApp(const MyExceptionApp()),
    (Object error, StackTrace stack) {
  print('stack: $stack');
});

不過,我們去它內部看看,其實它還是HandleUncaughtErrorHandler實現的。

注意:如果重寫了ZoneSpecification的run相關的方法,可能會導致當前的Zone無法捕獲到異常,就像1.中所說的那樣,基于配置類的重寫將原有特性覆蓋掉了,導致當前代碼并不一定在我們直覺認為的Zone中執(zhí)行。

這需要編寫者自己去解決這個問題,所以,如果沒有特殊的需求,一般不給Zone傳遞ZoneSpecification選項,如果要傳遞,需要去實現它,以保證相關的功能特性可用。

以上就是Flutter Zone異常處理方法及基本原理的詳細內容,更多關于Flutter Zone異常處理的資料請關注腳本之家其它相關文章!

相關文章

  • Android結合kotlin使用coroutine的方法實例

    Android結合kotlin使用coroutine的方法實例

    這篇文章主要給大家介紹了關于Android結合kotlin使用coroutine的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-12-12
  • 鴻蒙開源第三方組件之連續(xù)滾動圖像組件功能

    鴻蒙開源第三方組件之連續(xù)滾動圖像組件功能

    這篇文章主要介紹了鴻蒙開源第三方組件之連續(xù)滾動圖像組件功能,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-04-04
  • Android編程實現兩個Activity相互切換而不使用onCreate()的方法

    Android編程實現兩個Activity相互切換而不使用onCreate()的方法

    這篇文章主要介紹了Android編程實現兩個Activity相互切換而不使用onCreate()的方法,結合實例形式分析了多個Activity切換而不重新創(chuàng)建的操作技巧,需要的朋友可以參考下
    2017-01-01
  • Android popupWindow彈出窗體實現方法分析

    Android popupWindow彈出窗體實現方法分析

    這篇文章主要介紹了Android popupWindow彈出窗體實現方法,結合具體實例形式分析了Android彈出窗體的布局及popupwindow屬性設置、事件監(jiān)聽相關操作技巧,需要的朋友可以參考下
    2017-07-07
  • Android 百度地圖marker中圖片不顯示的解決方法(推薦)

    Android 百度地圖marker中圖片不顯示的解決方法(推薦)

    下面小編就為大家分享一篇Android 百度地圖marker中圖片不顯示的解決方法(推薦),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-01-01
  • Android自定義一個圖形單點移動縮小的效果

    Android自定義一個圖形單點移動縮小的效果

    本文通過實例代碼給大家介紹了android 自定義圖形單點移動縮小效果,代碼簡單易懂,非常不錯,具有參考借鑒價值,需要的的朋友參考下吧
    2017-08-08
  • Android實現圖片文字識別

    Android實現圖片文字識別

    這篇文章主要為大家詳細介紹了Android實現圖片文字識別,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-07-07
  • Android實現日期時間選擇對話框

    Android實現日期時間選擇對話框

    這篇文章主要為大家詳細介紹了Android實現日期以及時間選擇對話框,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-09-09
  • Android 模擬器的使用詳細介紹

    Android 模擬器的使用詳細介紹

    本文主要介紹Android 模擬器,在開發(fā)Android應用過程中大家都要使用模擬器對應用的測試,這里對模擬器的資料做了詳細整理,有興趣的小伙伴可以參考下
    2016-08-08
  • android自定義窗口標題示例分享

    android自定義窗口標題示例分享

    這篇文章主要介紹了android自定義窗口標題示例,需要的朋友可以參考下
    2014-03-03

最新評論

伽师县| 恩平市| 安福县| 宜丰县| 尼勒克县| 岢岚县| 安西县| 攀枝花市| 宝山区| 绥芬河市| 静安区| 高碑店市| 洪湖市| 班戈县| 定南县| 贵阳市| 民县| 潍坊市| 石林| 房产| 江西省| 漠河县| 和政县| 合江县| 龙江县| 息烽县| 满城县| 周至县| 北宁市| 象州县| 石首市| 凤翔县| 依兰县| 铜鼓县| 浦北县| 阿拉善左旗| 普兰县| 鄂伦春自治旗| 兴义市| 克东县| 晋江市|