Flutter6 異常、非同步、包和庫

RainDou發表於2019-09-12

本文對應github地址Flutter6,如果由於github調整導致資源找不到,請訪問github

異常

  • throw 丟擲異常用

  • Exception 異常(內建異常和自定義異常)

  • try {} 包裝可能丟擲異常的程式碼,後面必須有on/catch/finally中一個或多個

  • on exceptionOfKnown{} 捕獲指定異常

  • catch(e){} 捕獲異常(如果前面有指定的則排除指定的後的異常)

  • finally{} 是否異常都執行

    testCatchException() {
      // try...on 方式捕獲某些型別異常(知道異常型別)
      try {
        testThrowException(0);
      } on IntegerDivisionByZeroException {
        print('捕獲除0異常');
      }
      // 不確定甚至不知道異常型別 catch
      try {
        testThrowException(-1);
      } on IntegerDivisionByZeroException {
        print('捕獲除0異常');
      } catch (e) {
        print(e);
      } finally {
        print('是否異常都執行finally');
      }
    }
    
    testThrowException(int exceptionIndex) {
      if (exceptionIndex == 0) {
        throw new IntegerDivisionByZeroException(); // 丟擲內建異常
      } else if (exceptionIndex < 0) {
        throw new Exception('Index need more then 0'); // 拋一個自定義異常
      } else {
        print(exceptionIndex);
      }
    }
    複製程式碼
  • 常見內建異常型別

    • DeferredLoadException 延遲庫無法載入時丟擲。
    • FormatException 資料沒有預期格式且無法解析或處理
    • IntegerDivisionByZeroException 當數字除以零時丟擲
    • IOException 所有與輸入輸出相關的異常的基類
    • IsolateSpawnException 無法建立隔離時丟擲
    • Timeout 在等待非同步結果時發生計劃超時時丟擲
  • zone

    • 一般try{}catch{}能捕獲到異常,但有時候無效
    • Dart語言中有個zone概念,類似沙盒(sandbox),不同zone程式碼互不影響,zone還能建立子zone。zone可以重新定義自己的print,timer,microtasks等,還可以解決未捕獲的異常
testZone() {
  // 未捕獲的異常
  try {
    Future.error('asynchronous error');
  } catch(e) {
    print(e);
  }

  // 將上面程式碼註釋後用下面程式碼捕獲異常	
  runZoned((){
    Future.error('asynchronous error');
  }, onError: (Object obj, StackTrace stack){
    // 這裡可以呼叫日誌上報函式
    print(obj); // asynchronous error
    print(stack); // null
  });
}
複製程式碼
* 可以給runZoned註冊方法,在需要時回撥

```
handleData(result) {
  print('handleData $result'); // handleData 2
}
var onData = Zone.current.registerUnaryCallback<dynamic, int>(handleData);
Zone.current.runUnary(onData, 2);

// 非同步邏輯
Zone.current.scheduleMicrotask((){
  print('Todo something'); // Todo something
});
```
複製程式碼

非同步

  • 簡介

    • Dart是單執行緒模型,預設單執行緒處理任務。
    • Dart中有個介於程式和執行緒之間的概念被稱為Worker-Isolate(實際更偏向程式概念),但一般理解成帶有獨立記憶體的執行緒,防止歧義,統一稱作Isolate、隔離。
    • Dart中Isolate不等同於執行緒,Isolate有執行緒和獨立記憶體構成,又比程式低階
    • Dart中的非同步機制涉及到的關鍵字有Future、async、await、async、sync、Iterator、Iterable、Stream、Timer等
    • Dart非同步呼叫中async和await要配套使用,且await可以多個。
  • Isolate

    • 定義在 import 'dart:isolate';
    • 每個Isolate包含一個事件迴圈(event loop)和兩個事件佇列(event queue和microtask queue)
    • event queue負責I/O、繪製、手勢等事件或接收其他Isolate訊息的外部事件。
    • microtask queue則可以自己向Isolate內部新增事件,事件優先順序高於event queue。
    • Isolate開始執行後優先microtask queue,處理完才處理event queue,且處理microtask queue時阻塞event queue,所以儘量把耗時操作放event queue。
    • Isolate擁有獨立記憶體,執行緒之間不共享,所以不存在搶奪資源,即不需要鎖。
    • Isolate之間通過port來通訊,且port訊息傳遞過程非同步。
    • Isolate例項化過程其實是例項化Isolate+堆內分配記憶體+配置port等過程。
    • Isolate物件允許其他Isolate監聽、控制它代表的事件迴圈,如當這個Isolate發生未捕獲錯誤時,可以暫停(pause)此Isolate或獲取錯誤資訊(addErrorListener)
    • controlPort識別並授予Isolate控制許可權,pauseCapability和terminateCapability會對某些控制操作進行許可權保護。如暫停一個無pauseCapability的Isolate物件是不生效的。
    • 由spawn操作建立的Isolate物件具有控制介面和控制該物件的能力。當然,Isolate構造方法建立的Isolate物件可以不必帶這些能力。
    • Isolate物件不能用SendPort傳送給另一個Isolate物件,但是控制介面和能力capability是可以傳送的,並且可以在另一個Isolate物件中用傳送的介面和capability建立一個新Isolate物件。
    • SendPort和ReceivePort是Isolate隔離物件唯一通訊方式
    • 呼叫需要頂級函式(類似main這樣不被包裹的函式)或靜態函式
    // 可以在頂級函式main()中呼叫 startInsolate(); 
    Isolate isolate;
    int i = 0;
    Capability capability = new Capability();
    
    runTimer(SendPort port) {
      int counter = 0;
      Timer.periodic(const Duration(seconds: 1), (_){
        counter++;
        i++;
        var msg = 'Notification $counter';
        print('Send message:$msg i:$i');
        port.send(msg);
      });
    }
    
    startIsolate() async {
      final receivePort = ReceivePort();
    
      isolate = await Isolate.spawn(runTimer, receivePort.sendPort);
      receivePort.listen((data){
        print('Receive message:$data i:$i');
      });
    }
    
    resumeIsolate() {
      isolate.resume(capability);
    }
    
    pauseIsolate() {
      isolate.pause(capability);
    }
    
    stopInsolate() {
      isolate.kill(priority: Isolate.immediate);
      isolate = null;
    }
    複製程式碼
    • Compute函式對isolate的建立和底層的訊息傳遞進行了封裝,使得我們不必關係底層的實現,只需要關注功能實現
    void main() async{
      //呼叫compute函式,compute函式的引數就是想要在isolate裡執行的函式,和這個函式需要的引數
      print( await compute(syncFibonacci, 20)); // 6765
      runApp(MyApp());
    }
    // 計算斐波那契數列
    int syncFibonacci(int n){
      return n < 2 ? n : syncFibonacci(n-2) + syncFibonacci(n-1);
    }
    複製程式碼
  • async await

    • Dart中可通過async和await進行非同步操作
    • async開啟非同步操作,返回一個Future結果(沒有返回值則返回null的Future,耗時操作要執行一段時間但Future卻立即返回)。
    • await表示式通常返回Future,若不是Future則Dart把該值放到Future中返回,await會阻塞當前執行直到物件返回
    • await可以多次使用(只能在async包裝內)
    getChatMessage1(String userName) {
      print('111: ${queryDatebase(userName)}');
    }
    
    getChatMessage2(String userName) async {
      var msg1 = await queryDatebase(userName);
      print('222: $msg1');
    }
    
    // 範型
    Future<String> queryDatebase(String userName) {
      return Future.delayed(Duration(seconds: 3), () => '20190808 $userName');
    }
    
    // 呼叫
    getChatMessage1('LiLei'); // 111: Instance of 'Future<String>'
    getChatMessage2('LiBai'); // 3秒後列印  333: 20190808 LiBai
    複製程式碼
  • Future

    • Future 簡單了說就是對 Zone 的封裝使用,如Future.microtask主要執行了Zone的 scheduleMicrotask,而result._complete最後呼叫的是 _zone.runUnary等
    • 定義在 dart:async中,基於觀察者模式。
    • 支援範型
    • Future 需要 import 'dart:io';('dart:async';)
    • Future中常用方法then(),whenComplete(),wait(),catchError()
    • 一般來說,如果需要監聽“完畢”這個狀態,那麼用whenComplete,需要監聽“成功”這個狀態,用then,需要監聽“失敗”這個狀態,用catchError
    class _TestFutureFunction {
    
      // then()回撥中帶引數,此引數為Future物件中包含的值
      testThen() {
        getNumber1().then((aNum) {
          print('a = $aNum');
          return getNumber2(aNum);
        }).then((_) {
          print('_ = $_');
          getResult(_);
        });
      }
    
      // whenComplete()中不帶引數,只指定了順序,此方法丟擲異常後使用相當於finally
      testWhenComplete() {
        getNumber1().whenComplete((){
          getNumber2(9).whenComplete((){
            getResult(7).then((_){
              print('TestWhenComplete');
            });
          });
        });
      }
    
      // 使用async await
      testAwait() async {
        var number1 = await getNumber1();
        var number2 = await getNumber2(number1);
        print(await getResult(number2));
      }
      
      // wait()方法, wait()方法內的非同步方法,要有await表示式
      testWait() {
        Future.wait([getNumber1(), getNumber2(2), getResult(4)]).then((List result){
          result.forEach((num) => print(num));
        });
      }
    
      Future<int> getNumber1() async {
        await print('getNumber1 10086');
        return 10086;
      }
    
      Future getNumber2(int num) async {
        await print('getNumber2 ${num + 2}');
        return num + 2;
      }
    
      Future getResult(int num) async {
        await print('getRusult');
      }
    }
    複製程式碼

    下面是一個then()和whenComplete()結合使用的例子

    var client = new http.Client();
    client.post(
        "http://example.com/fruit",
        body: {"name": "apple", "color": "red"})
      .then((response) => client.get(response.bodyFields['uri']))
      .then((response) => print(response.body))
      .whenComplete(client.close);
    複製程式碼
  • Stream

    • Stream中也涉及到Zone
    • Dart另一種非同步操作 async*/yield 或 Stream非同步(async*/yield是語法糖,最終被編譯器轉為Stream)
    • Stream也支援同步操作
    • Stream中有Stream、StreamController、StreamSink、StreamSubscription四個關鍵物件
    • Stream:事件源本身,可用於監聽事件或轉換事件,如listen、where
    • StreamController:用於整個Stream過程的控制,提供各類介面用於建立各種事件流
    • StreamSink:一般作為事件入口,提供如add、addStream等
    • StreamSubscription:事件訂閱後物件,管理訂閱等各類操作,如cancel、pause,同時在內部也是事件中專的關鍵
    • 一般通過StreamController建立Steam,通過SteamSink新增事件,通過Stream監聽事件,通過StreamSubscription管理訂閱
    • Stream中支援各種變化,比如map、expand、where、take等操作,同時支援轉換為Future
  • 訂閱

    • Stream單播只能有一個訂閱者
    Duration interval = Duration(seconds: 1);
    Stream<int> stream1 = Stream.periodic(interval, (data) => data);
    stream1.listen((_){
    print("訂閱者01");
    });
    // Stream has already been listened to
    //    stream.listen((_){
    //      print("訂閱者2");
    //    });
    複製程式碼
    • Stream廣播可以多個訂閱者
    var stream2 = Stream.fromIterable([1,2,3,4]);
    var broadcastStream = stream2.asBroadcastStream();
    broadcastStream.listen((i){
    	print("訂閱者11:${i}");
    });
    broadcastStream.listen((i){
    	print("訂閱者12:${i}");
    });
    複製程式碼
  • delayed 延時函式

    Future.delayed(Duration(seconds: 3), () => print('1234'));
    複製程式碼
  • Timer 定時器

    Timer _countdownTimer;
      String _codeCountdownStr = '獲取驗證碼';
      int _countdownNum = 59;
    
      void reGetCountdown() {
        setState(() {
          if (_countdownTimer != null) {
              return;
          }
          // Timer的第一秒倒數計時是有一點延遲的,為了立刻顯示效果可以新增下一行。
          _codeCountdownStr = '${_countdownNum--}重新獲取';
          _countdownTimer =
              new Timer.periodic(new Duration(seconds: 1), (timer) {
            setState(() {
              if (_countdownNum > 0) {
                _codeCountdownStr = '${_countdownNum--}重新獲取';
              } else {
                _codeCountdownStr = '獲取驗證碼';
                _countdownNum = 59;
                _countdownTimer.cancel();
                _countdownTimer = null;
              }
            });
          });
        });
      }
    
     // 不要忘記在這裡釋放掉Timer
     @override
      void dispose() {
        _countdownTimer?.cancel();
        _countdownTimer = null;
        super.dispose();
      }
    複製程式碼

包與庫

    • 包是一種封裝程式設計單元機制
    • Dart的包管理器是Pub(Cocoapods for iOS, Gradle for Java)
    • Pub有助於在儲存庫中安裝包。
    • 包後設資料在pubsec.yaml中定義(Yet Another Markup Language)
    • pub get(獲取應用程式所依賴的所有包)
    • pub upgrade(將所有依賴項升級到較新版本)
    • pub build(用於構建Web應用程式,建立一個包含所有相關指令碼的構建資料夾)
    • pub help(將提供所有pub命令的幫助。)
  • Dart2中可以用library宣告庫名(也可以不用)
  • 不同型別庫的引入方式
    • Dart語言內部庫,import 'dart:html'
    • Flutter庫檔案,import 'path/name.dart'
    • 三方庫,import 'package:path/name.dart'
    • URI檔案,import 'https://xxx/name.dart'
  • 引入的庫可以用as起別名,防止庫變數名或函式名衝突(呼叫時用newName.methodName)
  • 引入時用show/hide關鍵字可以單獨引入或剔除庫中某Api
  • export重新匯入庫
    • 當匯入的庫過多或要重新整合庫,可以通過export重新匯入,把部分庫或全部庫來組合或重新打包庫
    • export也有show和hide,但沒有as庫字首
    • 使用export重新匯入多個庫,同名成員(類,方法,變數)會衝突
    • export重新匯入的庫相當於把庫內程式碼複製到當前檔案,但在當前檔案並不能使用,因為Dart不存在過載機制,所以會出現衝突
    • 部分衝突可以用hide來將衝突部分隱藏來解決
  • part和part of關聯檔案和庫
  1. 庫的拆分
  • 我們可以將較大的庫檔案拆成幾個比較小的檔案,通過part關聯
  • part關聯的檔案,共享所有的名稱空間,共享所有物件(包括私有物件)
  • 如果使用part拆分,則必須用library宣告
  • part引入的檔案可以使用url但不推薦,如part "https://xxx/xx.dart";
  • part優先順序高於import,所以當前庫可以直接用part中的內容
  1. 延遲載入
  • 使用deferred as 可以實現庫的延遲載入。
  • 使用loadLibrary()進行載入可以多次呼叫但只執行一次,返回Future物件
  • 延遲載入能1.減少App啟動時間,2.延遲載入那些較少使用的功能
  1. 依賴第三方庫
  • 在pubspec.yaml檔案內dependencies:標籤下新增依賴如cupertino_icons: ^0.1.2
  • 新增依賴後可以用flutter packages get命令或工具欄packages get 按鈕 下載包
  • pubspec.lock檔案可檢視匯入依賴的相容版本
  • ^version 表示version<=libVersion < big version
  • 常規依賴:dependencies:此標籤下依賴在除錯和發版後都會生效
  • Dev依賴:dev_dependencies:此標籤下的依賴均在除錯時生效
  • 重寫依賴:dependency_overrides:強制下載依賴包,不管是否相容,不推薦使用

知識點

  • 獲取一些尺寸

    // 螢幕寬高
    var ScreenSize = MediaQuery.of(context).size;
    // 狀態列高度
    var StatusHeight = MediaQuery.of(context).padding.top;
    複製程式碼
  • 判斷裝置型別

    import 'dart:io';
    
    if ( Platform.isIOS) { } else if (Platform.isAndroid) { } 
    // Platform.isMacOS 
    // Platform.isWindows
    // Platform.isLinux
    // Platform.isFuchsia
    複製程式碼
  • 時間

    var today = DateTime.now();
    print('當前時間是:$today');
    
    var date1 = today.millisecondsSinceEpoch;
    print('當前時間戳:$date1');
    
    var date2 = DateTime.fromMillisecondsSinceEpoch(date1);
    print('時間戳轉日期:$date2');
    
    // 拼接成date
    var dentistAppointment = new DateTime(2019, 6, 20, 17, 30,20);
    print(dentistAppointment);
    
    // 字串轉date
    DateTime date3 = DateTime.parse("2019-06-20 15:32:41");
    print(date3);
    // DateTime轉formatString 
    // formatDate(DateTime ,[yyyy,'-',mm,'-',dd]);
    
    // 時間比較
    print(today.isBefore(date3));// 在之前
    print(today.isAfter(date3)); // 在之後
    print(date3.isAtSameMomentAs(date3));// 相同
    
    print(date3.compareTo(today));// 大於返回1;等於返回0;小於返回-1。
    // print(DateTime.now().toString());
    // print(DateTime.now().toIso8601String());
    
    // 時間增加
    var fiftyDaysFromNow = today.add(new Duration(days: 5));
    print('today加5天:$fiftyDaysFromNow');
    
    // 時間減少
    DateTime fiftyDaysAgo = today.subtract(new Duration(days: 5));
    print('today減5天:$fiftyDaysAgo');
    
    // 時間差 兩個時間相差 小時數
    print('比較兩個時間 差 小時數:${fiftyDaysFromNow.difference(fiftyDaysAgo)}');
    
    print('本地時區簡碼:${today.timeZoneName}');
    
    print('返回UTC與本地時差 小時數:${today.timeZoneOffset}');
    
    print('獲取年月日:${today.year}');//month、day、hour、minute、second、millisecond、microsecond
    
    print('星期:${today.weekday}');// 返回星期幾
    複製程式碼
  • 剪貼簿 ClipBoard

    testClipBoard() {
      // 賦值
      ClipboardData willSetData = new ClipboardData(text: "剪貼簿的文字");
      Clipboard.setData(willSetData);
    
      // 取值
      getClipboardData();
    }
    getClipboardData() async {
      var clipboardData = await Clipboard.getData(Clipboard.kTextPlain);
      if (clipboardData != null) {
        // 一些操作,如setState()中給控制元件賦值
        print(clipboardData.text); // 剪貼簿的文字
      }
    }
    複製程式碼
  • URI 編碼解碼與組裝拆分

    // <scheme>://<user>:<password>@<host>:<port>/<path>;<params>?<query>#<frag>
    // https://<host>:<埠>/<路徑>?<查詢>#<片段>
    var uri1 = 'https://github.com/starainDou?tab=repositories';
    var encodeFullURI = Uri.encodeFull(uri1);
    var encodeComponentURI = Uri.encodeComponent(uri1);
    
    var uri2 = Uri(scheme: 'https', host: 'google.com', path: '/News/today', fragment: 'frag');
    var uri3 = Uri.parse('htps://google.com/News/tody#frag');
    
    print('${Uri.decodeFull(encodeFullURI)}'); //  https://github.com/starainDou?tab=repositories
    print('${Uri.decodeComponent(encodeComponentURI)}'); //  https://github.com/starainDou?tab=repositories
    print('${uri2}'); //  https://google.com/News/today#frag
    print('${uri3}'); //  htps://google.com/News/tody#frag
    複製程式碼

蒐集一些三方庫

  • 網路請求http

    包含一組高階函式和類,可輕鬆使用HTTP資源。與平臺無關,可在命令列和瀏覽器上用

  • 網路請求dio

    一個強大的Http客戶端,支援攔截器、全域性配置、FormData、請求取消、檔案下載、超時等

  • 圖片選擇photo

    用於選擇影象,支援多選,而且這個是用Flutter做的UI,可以很方便的自定義修改

  • 圖片載入image

    提供以各種不同的檔案格式載入、儲存和操作影象的能力。該庫不依賴於dart:io

  • svg圖片flutter_svg 載入svg影象

  • 圖片檢視zoomable_image

    提供影象檢視和手勢縮放操作功能

  • 日曆flutter_calendar

參考

上一頁 Flutter5 類、範型和流程控制
下一頁 Flutter7 網路和資料解析

相關文章