<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                在上一篇文章中,我帶你一起學習了 Dart 中異步與并發的機制及實現原理。與其他語言類似,Dart 的異步是通過事件循環與隊列實現的,我們可以使用 Future 來封裝異步任務。而另一方面,盡管 Dart 是基于單線程模型的,但也提供了 Isolate 這樣的“多線程”能力,這使得我們可以充分利用系統資源,在并發 Isolate 中搞定 CPU 密集型的任務,并通過消息機制通知主 Isolate 運行結果。 異步與并發的一個典型應用場景,就是網絡編程。一個好的移動應用,不僅需要有良好的界面和易用的交互體驗,也需要具備和外界進行信息交互的能力。而通過網絡,信息隔離的客戶端與服務端間可以建立一個雙向的通信通道,從而實現資源訪問、接口數據請求和提交、上傳下載文件等操作。 為了便于我們快速實現基于網絡通道的信息交換實時更新 App 數據,Flutter 也提供了一系列的網絡編程類庫和工具。因此在今天的分享中,我會通過一些小例子與你講述在 Flutter 應用中,如何實現與服務端的數據交互,以及如何將交互響應的數據格式化。 ## Http 網絡編程 我們在通過網絡與服務端數據交互時,不可避免地需要用到三個概念:定位、傳輸與應用。 其中,**定位**,定義了如何準確地找到網絡上的一臺或者多臺主機(即 IP 地址);**傳輸**,則主要負責在找到主機后如何高效且可靠地進行數據通信(即 TCP、UDP 協議);而**應用**,則負責識別雙方通信的內容(即 HTTP 協議)。 我們在進行數據通信時,可以只使用傳輸層協議。但傳輸層傳遞的數據是二進制流,如果沒有應用層,我們無法識別數據內容。如果想要使傳輸的數據有意義,則必須要用到應用層協議。移動應用通常使用 HTTP 協議作應用層協議,來封裝 HTTP 信息。 在編程框架中,一次 HTTP 網絡調用通常可以拆解為以下步驟: 1. 創建網絡調用實例 client,設置通用請求行為(如超時時間); 2. 構造 URI,設置請求 header、body; 3. 發起請求, 等待響應; 4. 解碼響應的內容。 當然,Flutter 也不例外。在 Flutter 中,Http 網絡編程的實現方式主要分為三種:dart:io 里的 HttpClient 實現、Dart 原生 http 請求庫實現、第三方庫 dio 實現。接下來,我依次為你講解這三種方式。 ### HttpClient HttpClient 是 dart:io 庫中提供的網絡請求類,實現了基本的網絡編程功能。 接下來,我將和你分享一個實例,對照著上面提到的網絡調用步驟,來演示 HttpClient 如何使用。 在下面的代碼中,我們創建了一個 HttpClien 網絡調用實例,設置了其超時時間為 5 秒。隨后構造了 Flutter 官網的 URI,并設置了請求 Header 的 user-agent 為 Custom-UA。然后發起請求,等待 Flutter 官網響應。最后在收到響應后,打印出返回結果: ~~~ get() async { // 創建網絡調用示例,設置通用請求行為 (超時時間) var httpClient = HttpClient(); httpClient.idleTimeout = Duration(seconds: 5); // 構造 URI,設置 user-agent 為 "Custom-UA" var uri = Uri.parse("https://flutter.dev"); var request = await httpClient.getUrl(uri); request.headers.add("user-agent", "Custom-UA"); // 發起請求,等待響應 var response = await request.close(); // 收到響應,打印結果 if (response.statusCode == HttpStatus.ok) { print(await response.transform(utf8.decoder).join()); } else { print('Error: \nHttp status ${response.statusCode}'); } } ~~~ 可以看到,使用 HttpClient 來發起網絡調用還是相對比較簡單的。 這里需要注意的是,由于網絡請求是異步行為,因此**在 Flutter 中,所有網絡編程框架都是以 Future 作為異步請求的包裝**,所以我們需要使用 await 與 async 進行非阻塞的等待。當然,你也可以注冊 then,以回調的方式進行相應的事件處理。 ### http HttpClient 使用方式雖然簡單,但其接口卻暴露了不少內部實現細節。比如,異步調用拆分得過細,鏈接需要調用方主動關閉,請求結果是字符串但卻需要手動解碼等。 http 是 Dart 官方提供的另一個網絡請求類,相比于 HttpClient,易用性提升了不少。同樣,我們以一個例子來介紹 http 的使用方法。 首先,我們需要將 http 加入到 pubspec 中的依賴里: ~~~ dependencies: http: '>=0.11.3+12' ~~~ 在下面的代碼中,與 HttpClient 的例子類似的,我們也是先后構造了 http 網絡調用實例和 Flutter 官網 URI,在設置 user-agent 為 Custom-UA 后,發出請求,最后打印請求結果: ~~~ httpGet() async { // 創建網絡調用示例 var client = http.Client(); // 構造 URI var uri = Uri.parse("https://flutter.dev"); // 設置 user-agent 為 "Custom-UA",隨后立即發出請求 http.Response response = await client.get(uri, headers : {"user-agent" : "Custom-UA"}); // 打印請求結果 if(response.statusCode == HttpStatus.ok) { print(response.body); } else { print("Error: ${response.statusCode}"); } } ~~~ 可以看到,相比于 HttpClient,http 的使用方式更加簡單,僅需一次異步調用就可以實現基本的網絡通信。 ### dio HttpClient 和 http 使用方式雖然簡單,但其暴露的定制化能力都相對較弱,很多常用的功能都不支持(或者實現異常繁瑣),比如取消請求、定制攔截器、Cookie 管理等。因此對于復雜的網絡請求行為,我推薦使用目前在 Dart 社區人氣較高的第三方 dio 來發起網絡請求。 接下來,我通過幾個例子來和你介紹 dio 的使用方法。與 http 類似的,我們首先需要把 dio 加到 pubspec 中的依賴里: ~~~ dependencies: dio: '>2.1.3' ~~~ 在下面的代碼中,與前面 HttpClient 與 http 例子類似的,我們也是先后創建了 dio 網絡調用實例、創建 URI、設置 Header、發出請求,最后等待請求結果: ~~~ void getRequest() async { // 創建網絡調用示例 Dio dio = new Dio(); // 設置 URI 及請求 user-agent 后發起請求 var response = await dio.get("https://flutter.dev", options:Options(headers: {"user-agent" : "Custom-UA"})); // 打印請求結果 if(response.statusCode == HttpStatus.ok) { print(response.data.toString()); } else { print("Error: ${response.statusCode}"); } } ~~~ > 這里需要注意的是,創建 URI、設置 Header 及發出請求的行為,都是通過 dio.get 方法實現的。這個方法的 options 參數提供了精細化控制網絡請求的能力,可以支持設置 Header、超時時間、Cookie、請求方法等。這部分內容不是今天分享的重點,如果你想深入理解的話,可以訪問其[API 文檔](https://github.com/flutterchina/dio#dio-apis)學習具體使用方法。 對于常見的上傳及下載文件需求,dio 也提供了良好的支持:文件上傳可以通過構建表單 FormData 實現,而文件下載則可以使用 download 方法搞定。 在下面的代碼中,我們通過 FormData 創建了兩個待上傳的文件,通過 post 方法發送至服務端。download 的使用方法則更為簡單,我們直接在請求參數中,把待下載的文件地址和本地文件名提供給 dio 即可。如果我們需要感知下載進度,可以增加 onReceiveProgress 回調函數: ~~~ // 使用 FormData 表單構建待上傳文件 FormData formData = FormData.from({ "file1": UploadFileInfo(File("./file1.txt"), "file1.txt"), "file2": UploadFileInfo(File("./file2.txt"), "file1.txt"), }); // 通過 post 方法發送至服務端 var responseY = await dio.post("https://xxx.com/upload", data: formData); print(responseY.toString()); // 使用 download 方法下載文件 dio.download("https://xxx.com/file1", "xx1.zip"); // 增加下載進度回調函數 dio.download("https://xxx.com/file1", "xx2.zip", onReceiveProgress: (count, total) { //do something }); ~~~ 有時,我們的頁面由多個并行的請求響應結果構成,這就需要等待這些請求都返回后才能刷新界面。在 dio 中,我們可以結合 Future.wait 方法輕松實現: ~~~ // 同時發起兩個并行請求 List<Response> responseX= await Future.wait([dio.get("https://flutter.dev"),dio.get("https://pub.dev/packages/dio")]); // 打印請求 1 響應結果 print("Response1: ${responseX[0].toString()}"); // 打印請求 2 響應結果 print("Response2: ${responseX[1].toString()}"); ~~~ 此外,與 Android 的 okHttp 一樣,dio 還提供了請求攔截器,通過攔截器,我們可以在請求之前,或響應之后做一些特殊的操作。比如可以為請求 option 統一增加一個 header,或是返回緩存數據,或是增加本地校驗處理等等。 在下面的例子中,我們為 dio 增加了一個攔截器。在請求發送之前,不僅為每個請求頭都加上了自定義的 user-agent,還實現了基本的 token 認證信息檢查功能。而對于本地已經緩存了請求 uri 資源的場景,我們可以直接返回緩存數據,避免再次下載: ~~~ // 增加攔截器 dio.interceptors.add(InterceptorsWrapper( onRequest: (RequestOptions options){ // 為每個請求頭都增加 user-agent options.headers["user-agent"] = "Custom-UA"; // 檢查是否有 token,沒有則直接報錯 if(options.headers['token'] == null) { return dio.reject("Error: 請先登錄 "); } // 檢查緩存是否有數據 if(options.uri == Uri.parse('http://xxx.com/file1')) { return dio.resolve(" 返回緩存數據 "); } // 放行請求 return options; } )); // 增加 try catch,防止請求報錯 try { var response = await dio.get("https://xxx.com/xxx.zip"); print(response.data.toString()); }catch(e) { print(e); } ~~~ 需要注意的是,由于網絡通信期間有可能會出現異常(比如,域名無法解析、超時等),因此我們需要使用 try-catch 來捕獲這些未知錯誤,防止程序出現異常。 除了這些基本的用法,dio 還支持請求取消、設置代理,證書校驗等功能。不過,這些高級特性不屬于本次分享的重點,故不再贅述,詳情可以參考 dio 的[GitHub 主頁](https://github.com/flutterchina/dio/blob/master/README-ZH.md)了解具體用法。 ## JSON 解析 移動應用與 Web 服務器建立好了連接之后,接下來的兩個重要工作分別是:服務器如何結構化地去描述返回的通信信息,以及移動應用如何解析這些格式化的信息。 ### 如何結構化地描述返回的通信信息? 在如何結構化地去表達信息上,我們需要用到 JSON。JSON 是一種輕量級的、用于表達由屬性值和字面量組成對象的數據交換語言。 一個簡單的表示學生成績的 JSON 結構,如下所示: ~~~ String jsonString = ''' { "id":"123", "name":" 張三 ", "score" : 95 } '''; ~~~ 需要注意的是,由于 Flutter 不支持運行時反射,因此并沒有提供像 Gson、Mantle 這樣自動解析 JSON 的庫來降低解析成本。在 Flutter 中,JSON 解析完全是手動的,開發者要做的事情多了一些,但使用起來倒也相對靈活。 接下來,我們就看看 Flutter 應用是如何解析這些格式化的信息。 ### 如何解析格式化的信息? 所謂手動解析,是指使用 dart:convert 庫中內置的 JSON 解碼器,將 JSON 字符串解析成自定義對象的過程。使用這種方式,我們需要先將 JSON 字符串傳遞給 JSON.decode 方法解析成一個 Map,然后把這個 Map 傳給自定義的類,進行相關屬性的賦值。 以上面表示學生成績的 JSON 結構為例,我來和你演示手動解析的使用方法。 首先,我們根據 JSON 結構定義 Student 類,并創建一個工廠類,來處理 Student 類屬性成員與 JSON 字典對象的值之間的映射關系: ~~~ class Student{ // 屬性 id,名字與成績 String id; String name; int score; // 構造方法 Student({ this.id, this.name, this.score }); //JSON 解析工廠類,使用字典數據為對象初始化賦值 factory Student.fromJson(Map<String, dynamic> parsedJson){ return Student( id: parsedJson['id'], name : parsedJson['name'], score : parsedJson ['score'] ); } } ~~~ 數據解析類創建好了,剩下的事情就相對簡單了,我們只需要把 JSON 文本通過 JSON.decode 方法轉換成 Map,然后把它交給 Student 的工廠類 fromJson 方法,即可完成 Student 對象的解析: ~~~ loadStudent() { //jsonString 為 JSON 文本 final jsonResponse = json.decode(jsonString); Student student = Student.fromJson(jsonResponse); print(student.name); } ~~~ 在上面的例子中,JSON 文本所有的屬性都是基本類型,因此我們直接從 JSON 字典取出相應的元素為對象賦值即可。而如果 JSON 下面還有嵌套對象屬性,比如下面的例子中,Student 還有一個 teacher 的屬性,我們又該如何解析呢? ~~~ String jsonString = ''' { "id":"123", "name":" 張三 ", "score" : 95, "teacher": { "name": " 李四 ", "age" : 40 } } '''; ~~~ 這里,teacher 不再是一個基本類型,而是一個對象。面對這種情況,我們需要為每一個非基本類型屬性創建一個解析類。與 Student 類似,我們也需要為它的屬性 teacher 創建一個解析類 Teacher: ~~~ class Teacher { //Teacher 的名字與年齡 String name; int age; // 構造方法 Teacher({this.name,this.age}); //JSON 解析工廠類,使用字典數據為對象初始化賦值 factory Teacher.fromJson(Map<String, dynamic> parsedJson){ return Teacher( name : parsedJson['name'], age : parsedJson ['age'] ); } } ~~~ 然后,我們只需要在 Student 類中,增加 teacher 屬性及對應的 JSON 映射規則即可: ~~~ class Student{ ... // 增加 teacher 屬性 Teacher teacher; // 構造函數增加 teacher Student({ ... this.teacher }); factory Student.fromJson(Map<String, dynamic> parsedJson){ return Student( ... // 增加映射規則 teacher: Teacher.fromJson(parsedJson ['teacher']) ); } } ~~~ 完成了 teacher 屬性的映射規則添加之后,我們就可以繼續使用 Student 來解析上述的 JSON 文本了: ~~~ final jsonResponse = json.decode(jsonString);// 將字符串解碼成 Map 對象 Student student = Student.fromJson(jsonResponse);// 手動解析 print(student.teacher.name); ~~~ 可以看到,通過這種方法,無論對象有多復雜的非基本類型屬性,我們都可以創建對應的解析類進行處理。 不過到現在為止,我們的 JSON 數據解析還是在主 Isolate 中完成。如果 JSON 的數據格式比較復雜,數據量又大,這種解析方式可能會造成短期 UI 無法響應。對于這類 CPU 密集型的操作,我們可以使用上一篇文章中提到的 compute 函數,將解析工作放到新的 Isolate 中完成: ~~~ static Student parseStudent(String content) { final jsonResponse = json.decode(content); Student student = Student.fromJson(jsonResponse); return student; } doSth() { ... // 用 compute 函數將 json 解析放到新 Isolate compute(parseStudent,jsonString).then((student)=>print(student.teacher.name)); } ~~~ 通過 compute 的改造,我們就不用擔心 JSON 解析時間過長阻塞 UI 響應了。 ## 總結 好了,今天的分享就到這里了,我們簡單回顧一下主要內容。 首先,我帶你學習了實現 Flutter 應用與服務端通信的三種方式,即 HttpClient、http 與 dio。其中 dio 提供的功能更為強大,可以支持請求攔截、文件上傳下載、請求合并等高級能力。因此,我推薦你在實際項目中使用 dio 的方式。 然后,我和你分享了 JSON 解析的相關內容。JSON 解析在 Flutter 中相對比較簡單,但由于不支持反射,所以我們只能手動解析,即:先將 JSON 字符串轉換成 Map,然后再把這個 Map 給到自定義類,進行相關屬性的賦值。 如果你有原生 Android、iOS 開發經驗的話,可能會覺得 Flutter 提供的 JSON 手動解析方案并不好用。在 Flutter 中,沒有像原生開發那樣提供了 Gson 或 Mantle 等庫,用于將 JSON 字符串直接轉換為對應的實體類。而這些能力無一例外都需要用到運行時反射,這是 Flutter 從設計之初就不支持的,理由如下: 1. 運行時反射破壞了類的封裝性和安全性,會帶來安全風險。就在前段時間,Fastjson 框架就爆出了一個巨大的安全漏洞。這個漏洞使得精心構造的字符串文本,可以在反序列化時讓服務器執行任意代碼,直接導致業務機器被遠程控制、內網滲透、竊取敏感信息等操作。 2. 運行時反射會增加二進制文件大小。因為搞不清楚哪些代碼可能會在運行時用到,因此使用反射后,會默認使用所有代碼構建應用程序,這就導致編譯器無法優化編譯期間未使用的代碼,應用安裝包體積無法進一步壓縮,這對于自帶 Dart 虛擬機的 Flutter 應用程序是難以接受的。 反射給開發者編程帶來了方便,但也帶來了很多難以解決的新問題,因此 Flutter 并不支持反射。而我們要做的就是,老老實實地手動解析 JSON 吧。 我把今天分享所涉及到的知識點打包到了[GitHub](https://github.com/cyndibaby905/24_network_demo)中,你可以下載下來,反復運行幾次,加深理解與記憶。 ## 思考題 最后,我給你留兩道思考題吧。 1. 請使用 dio 實現一個自定義攔截器,攔截器內檢查 header 中的 token:如果沒有 token,需要暫停本次請求,同時訪問"[http://xxxx.com/token](http://xxxx.com/token)",在獲取新 token 后繼續本次請求。 2. 為以下 Student JSON 寫相應的解析類: ~~~ String jsonString = ''' { "id":"123", "name":" 張三 ", "score" : 95, "teachers": [ { "name": " 李四 ", "age" : 40 }, { "name": " 王五 ", "age" : 45 } ] } '''; ~~~
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看