Flutter - UIKit開發相關指南 - 線程和異步

線程和異步

編寫異步代碼

Dart采用單線程執行模型,支持Isolates(在另一個線程上運行Dart代碼)、事件循環和異步編程。除非生成一個Isolates,否則Dart代碼將在主UI線程中運行,并由事件循環驅動。Flutter的事件循環相當于iOS的主線程上的RunLoop。

Dart的單線程模型,不代表阻塞型的操作都會導致UI卡頓。實際上可以采用Dart語言提供的異步功能比如async/await來執行異步的操作。

因為要請求網絡,所以添加http模塊

$ fltter pub add http
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';void main() {runApp(const MainApp());
}class MainApp extends StatelessWidget {const MainApp({super.key});Widget build(BuildContext context) {return const MaterialApp(home: ThreadSample());}
}class ThreadSample extends StatefulWidget {const ThreadSample({super.key});State<ThreadSample> createState() => _ThreadSampleState();
}class _ThreadSampleState extends State<ThreadSample> {List<Map<String, Object?>> data = [];/// 1. 初始化_ThreadSampleState Widget的狀態void initState() {super.initState();/// 2.加載數據loadData();}Future<void> loadData() async {/// 3. 發起異步請求final Uri dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts');final http.Response response = await http.get(dataURL);/// 4. 等響應結束后調用setState() 更新data 觸發build方法setState(() {data = (jsonDecode(response.body) as List).cast<Map<String, Object?>>();});}Widget getRow(int index) {return Padding(padding: const EdgeInsets.all(10),child: Text('Row ${data[index]['title']}'),);}Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('線程與異步示例')),// 5. 顯示列表,長度為data.length,內容通過getRow方法返回data的子元素body: ListView.builder(itemCount: data.length,itemBuilder: (context, index) {return getRow(index);},),);}
}

2025-05-12 16.39.30.png

切到后臺線程

因為Flutter是單線程模型,不需要考慮線程管理相關的問題。在執行I/O密集型的操作時,比如訪問磁盤或網絡,可以使用async/await,但是當在執行CPU計算密集型的操作時,則應該將其移到獨立線程(Isolate)以避免阻塞事件循環。

Isolates 是獨立的執行線程,它們與主線程內存堆不共享任何內存。這意味著你無法訪問主線程中的變量,或通過調用 setState() 來更新用戶界面。

import 'dart:async';
import 'dart:convert';
import 'dart:isolate';import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;void main() {runApp(const SampleApp());
}class SampleApp extends StatelessWidget {const SampleApp({super.key});Widget build(BuildContext context) {return const MaterialApp(title: 'Sample App', home: SampleAppPage());}
}class SampleAppPage extends StatefulWidget {const SampleAppPage({super.key});State<SampleAppPage> createState() => _SampleAppPageState();
}class _SampleAppPageState extends State<SampleAppPage> {List<Map<String, Object?>> data = [];void initState() {super.initState();/// 主1. 加載數據loadData();}bool get showLoadingDialog => data.isEmpty;Future<void> loadData() async {/// Opens a long-lived port for receiving messages./// 打開端口用于接收數據final ReceivePort receivePort = ReceivePort();/// 主2.Isolate開啟子線程/// The [entryPoint] function must be able to be called with a single/// argument, that is, a function which accepts at least one positional/// parameter and has at most one required positional parameter.////// The entry-point function is invoked in the new isolate with [message]/// as the only argument./// 第一個參數:至少包含一個參數的函數指針,這里關聯的是dataLoader,參數是SendPort////// [message] must be sendable between isolates. Objects that cannot be sent/// include open files and sockets (see [SendPort.send] for details). Usually/// the initial [message] contains a [SendPort] so that the spawner and/// spawnee can communicate with each other./// 第二個參數: 不同Isolate之間傳遞的數據,通常初始化時傳的message包含一個SendPort////// receivePort.sendPort/// [SendPort]s are created from [ReceivePort]s./// Any message sent through a [SendPort] is delivered to its corresponding [ReceivePort]./// There might be many [SendPort]s for the same [ReceivePort]./// 通過SendPort發送的消息會傳送給關聯的ReceivePortawait Isolate.spawn(dataLoader, receivePort.sendPort);/// 主3. first是一個Future,它會在接收到第一個消息時完成/// 一旦收到第一個消息,它就會關閉ReceivePort,并且不再監聽其它消息/// 適用于只接收單個消息的情況final SendPort sendPort = await receivePort.first as SendPort;try {/// 主4. 使用await調用sendReceivefinal List<Map<String, dynamic>> msg = await sendReceive(sendPort,'https://jsonplaceholder.typicode.com/posts',);/// 主5.設置數據,通知Flutter刷新UIsetState(() {data = msg;});} catch (e) {print('Error in loadData:$e');}}// 子1. 執行子線程上的函數static Future<void> dataLoader(SendPort sendPort) async {// 子2.打開端口接收數據final ReceivePort port = ReceivePort();/// 子3. 發送自己的接收端口sendPort.send(port.sendPort);/// 子4:等待消息await for (final dynamic msg in port) {/// 子5: 接收到url + 主線程的接收端口final String url = msg[0] as String;final SendPort replyTo = msg[1] as SendPort;/// 子6: 發起網絡請求final Uri dataURL = Uri.parse(url);final http.Response response = await http.get(dataURL);/// 下面這種寫法在sendReceive會報/// Unhandled/// Exception: type 'Future<dynamic>' is not a subtype of type/// 'Future<List<Map<String, dynamic>>>'////// replyTo.send(jsonDecode(response.body) as List<Map<String, dynamic>>);/// 因為Dart在運行時無法檢查Future<T>中的T,直接轉換Future的泛型參數會失敗/// 強制類型轉換final data = jsonDecode(response.body) as List;final typedata = data.cast<Map<String, dynamic>>();/// 子7: 將網絡請求的結果發送到主線程replyTo.send(typedata);}}Future<dynamic> sendReceive(SendPort port, String msg) {// 主5.創建接收數據的端口final ReceivePort response = ReceivePort();// Sends an asynchronous [message] through this send port, to its corresponding [ReceivePort].// 主6. 主線程異步發送url + 通知其它線程接收端口port.send(<dynamic>[msg, response.sendPort]);return response.first;}Widget getBody() {/// 數據為空顯示進度條bool showLoadingDialog = data.isEmpty;if (showLoadingDialog) {return getProgressDialog();} else {return getListView();}}Widget getProgressDialog() {return const Center(child: CircularProgressIndicator());}ListView getListView() {return ListView.builder(itemCount: data.length,itemBuilder: (context, position) {return getRow(position);},);}Widget getRow(int i) {return Padding(padding: const EdgeInsets.all(10),child: Text("Row ${data[i]["title"]}"),);}Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('Sample App')),body: getBody(),);}
}

202505131636-w400

錯誤

[ERROR:flutter/runtime/dart_vm_initializer.cc(40)] Unhandled Exception: ClientException with SocketException: Failed host lookup: 'jsonplaceholder.typicode.com'

[ERROR:flutter/runtime/dart_vm_initializer.cc(40)] Unhandled Exception: ClientException with SocketException: Failed host lookup: ‘jsonplaceholder.typicode.com’ (OS Error: nodename nor servname provided, or not known, errno = 8), uri=https://jsonplaceholder.typicode.com/posts

首次啟動需要同意網絡權限,看報錯是DNS找不到域名,所以還是網絡問題,在手機上授權后再重新用flutter運行工程能恢復

參考

1.給 UIKit 開發者的 Flutter 指南
2.flutter 中 ReceivePort 的 first 和 listen

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/905445.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/905445.shtml
英文地址,請注明出處:http://en.pswp.cn/news/905445.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【愚公系列】《Manus極簡入門》038-數字孿生設計師:“虛實映射師”

&#x1f31f;【技術大咖愚公搬代碼&#xff1a;全棧專家的成長之路&#xff0c;你關注的寶藏博主在這里&#xff01;】&#x1f31f; &#x1f4e3;開發者圈持續輸出高質量干貨的"愚公精神"踐行者——全網百萬開發者都在追更的頂級技術博主&#xff01; &#x1f…

西門子WinCC Unified PC的GraphQL使用手冊

TIA V20版本&#xff1a;添加用戶 添加角色&#xff0c;并充分授權&#xff0c;尤其是GraphQL的讀寫權限。 通過SIMATIC Runtime Manager啟動wincc unifi工程。 打開瀏覽器&#xff0c;訪問本地的https://localhost/graphql/&#xff0c;運行正常如圖&#xff1a; 連接外…

開源長期主義:淺談DeepSeek技術主張與早期論文

開源、長期主義與DeepSeek的技術愿景 ©作者|格林 來源|神州問學 導入&#xff1a;Deepseek在早期就開源了許多優秀的指令模型與對話模型&#xff0c;并發布了多篇論文。以下&#xff0c;我們將基于Deepseek在早期發布的6篇論文&#xff0c;來梳理Deepseek公司的技術路徑與…

TTS-Web-Vue系列:Vue3實現內嵌iframe文檔顯示功能

&#x1f5bc;? 本文是TTS-Web-Vue系列的新篇章&#xff0c;重點介紹如何在Vue3項目中優雅地實現內嵌iframe功能&#xff0c;用于加載外部文檔內容。通過Vue3的響應式系統和組件化設計&#xff0c;我們實現了一個功能完善、用戶體驗友好的文檔嵌入方案&#xff0c;包括加載狀態…

Elasticsearch索引設計與調優

一、分片策略設計 1.?分片容量規劃 單分片容量建議30GB(日志場景可放寬至100GB),避免超大分片引發查詢延遲。分片總數計算公式:總數據量 / 30GB 1.2(20%余量應對未來增長)。主分片數創建后不可修改,副本分片數支持動態調整。2.?分片分布優化 PUT logs-2025 { &qu…

Spring AI 集成 Mistral AI:構建高效多語言對話助手的實戰指南

Spring AI 集成 Mistral AI&#xff1a;構建高效多語言對話助手的實戰指南 前言 在人工智能應用開發領域&#xff0c;選擇合適的大語言模型&#xff08;LLM&#xff09;與開發框架至關重要。Mistral AI 憑借其高效的多語言模型&#xff08;如 Mistral-7B、Mixtral-8x7B 等&am…

從新手到高手:全面解析 AI 時代的「魔法咒語」——Prompt

引言&#xff1a;AI 時代的「語言煉金術」 在人工智能技術突飛猛進的今天&#xff0c;我們正在經歷一場堪比工業革命的生產力變革。從聊天機器人到圖像生成&#xff0c;從數據分析到自動化寫作&#xff0c;AI 模型正在重塑人類與信息交互的方式。而在這一切背后&#xff0c;隱…

MySQL 8.0安裝(壓縮包方式)

MySQL 8.0安裝(壓縮包方式) 下載安裝包并解壓 下載 https://dev.mysql.com/downloads/mysql/可關注“后端碼匠”回復“MySQL8”關鍵字獲取 解壓&#xff08;我解壓到D:\dev\mysql-8.4.5-winx64目錄下&#xff09; 創建mysql服務 注意&#xff0c;這步之前一定要保證自己電…

免費Ollama大模型集成系統——Golang

Ollama Free V2 Web 功能實現&#xff1a;界面交互與后端邏輯 一、Web 界面概述 Ollama Free V2 的 Web 界面提供了豐富的交互功能&#xff0c;包括模型選擇、圖片上傳、歷史記錄查看等。界面使用 Bootstrap 進行布局&#xff0c;結合 JavaScript 實現動態交互。 二、前端界…

【AI】人工智能數據標注細分和商業機會

一、數據標注的常見方法 數據標注是為人工智能模型訓練提供高質量標簽的過程&#xff0c;根據數據類型&#xff08;圖像、文本、音頻、視頻等&#xff09;的不同&#xff0c;標注方法也有所差異&#xff1a; 1. 圖像標注 分類標注&#xff1a;為圖像分配類別標簽&#xff08…

lanqiaoOJ 652:一步之遙 ← 擴展歐幾里得定理

【題目來源】 https://www.lanqiao.cn/problems/652/learning/ 【題目背景】 本題為填空題&#xff0c;只需要算出結果后&#xff0c;在代碼中使用輸出語句將所填結果輸出即可。 【題目描述】 從昏迷中醒來&#xff0c;小明發現自己被關在X星球的廢礦車里。礦車停在平直的廢棄…

HTTP / HTTPS 協議

目錄 一、前言&#xff1a; 二、Fiddler 抓包工具&#xff1a; 三、http 協議&#xff1a; 1、http 請求&#xff1a; 1.&#xff08;1&#xff09;請求行&#xff1a; 1、(2) 請求頭&#xff1a; 1、(3) 請求正文: 2、http 響應&#xff1a; 2、(1) 狀態碼&#x…

使用泛型加載保存數據

文章速覽 泛型泛型概述定義優點 實例加載數據保存數據 一個贊&#xff0c;專屬于你的足跡&#xff01; 泛型 泛型概述 泛型&#xff08;Generics&#xff09;是 C# 中一種重要的編程特性&#xff0c;它允許程序員編寫靈活且類型安全的代碼。通過使用泛型&#xff0c;可以創建…

Redis內存淘汰策略和過期鍵刪除策略有哪些?

Redis 提供 8 種內存淘汰策略&#xff0c;以下是詳細解析及場景建議&#xff1a; 一、核心策略解析 noeviction (默認策略) 機制&#xff1a;內存滿時拒絕新寫入操作&#xff0c;返回錯誤優勢&#xff1a;絕對數據安全場景&#xff1a;金融交易系統、醫療數據存儲 allkeys-lr…

【C/C++】自定義類型:結構體

文章目錄 前言自定義類型&#xff1a;結構體1.結構體類型的聲明1.1 結構體回顧1.1.1 結構的聲明 1.1.2 結構體變量的創建和初始化1.2 結構的特殊聲明1.3 結構的自引用 2.結構體內存對齊2.1 對?規則2.2 為什么存在內存對齊&#xff1f;2.3 修改默認對?數 3. 結構體傳參4.結構體…

PPO算法:一種先進的強化學習策略

什么是PPO算法&#xff1f; PPO&#xff08;Proximal Policy Optimization&#xff09;是一種增強學習算法&#xff0c;主要應用于解決連續控制任務。PPO算法在2017年由OpenAI提出&#xff0c;旨在解決傳統策略梯度方法在連續控制任務中面臨的挑戰。PPO算法通過引入一個近似目…

OpenCV實現數字水印的相關函數和示例代碼

OpenCV計算機視覺開發實踐&#xff1a;基于Qt C - 商品搜索 - 京東 實現數字水印的相關函數 用OpenCV來實現數字水印功能&#xff0c;需要使用一些位操作函數&#xff0c;我們需要先了解一下這些函數。 1. bitwise_and函數 bitwise_and函數是OpenCV中的位運算函數之一&…

基于Python的計算機科學研究話題管理系統的設計與實現 - 爬蟲

標題:基于Python的計算機科學研究話題管理系統的設計與實現 - 爬蟲 內容:1.摘要 本文聚焦于基于Python的計算機科學研究話題管理系統的爬蟲部分。背景是隨著計算機科學研究的快速發展&#xff0c;相關話題數據海量且分散&#xff0c;人工管理效率低。目的是設計并實現一個能高…

告別手動解析!借助 CodeBuddy 快速開發網頁源碼提取工具

作為一名長期從事 Web 開發的程序員&#xff0c;我們在日常工作中&#xff0c;時不時會需要查看網頁的源代碼。這么做的目的通常是為了排查前端渲染的問題、分析接口返回的數據結構&#xff0c;或者就是單純地想快速提取頁面中的某些信息&#xff0c;比如文章鏈接、圖片地址&am…

為什么要在 input() 后加 .strip()?

strip() 是 Python 字符串的一個方法&#xff0c;用于去除字符串開頭和結尾的空白字符&#xff08;包括空格、制表符 \t、換行符 \n 等&#xff09;。 為什么要在 input() 后加 .strip()&#xff1f; 用戶在輸入時&#xff0c;可能會不小心在開頭或結尾輸入空格&#xff0c;例…