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

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                在上一篇文章中,我帶你一起學習了 Flutter 的網絡編程,即如何建立與 Web 服務器的通信連接,以實現數據交換,以及如何解析結構化后的通信信息。 其中,建立通信連接在 Flutter 中有三種基本方案,包括 HttpClient、http 與 dio。考慮到 HttpClient 與 http 并不支持復雜的網絡請求行為,因此我重點介紹了如何使用 dio 實現資源訪問、接口數據請求與提交、上傳及下載文件、網絡攔截等高級操作。 而關于如何解析信息,由于 Flutter 并不支持反射,因此只提供了手動解析 JSON 的方式:把 JSON 轉換成字典,然后給自定義的類屬性賦值即可。 正因為有了網絡,我們的 App 擁有了與外界進行信息交換的通道,也因此具備了更新數據的能力。不過,經過交換后的數據通常都保存在內存中,而應用一旦運行結束,內存就會被釋放,這些數據也就隨之消失了。 因此,我們需要把這些更新后的數據以一定的形式,通過一定的載體保存起來,這樣應用下次運行時,就可以把數據從存儲的載體中讀出來,也就實現了**數據的持久化**。 數據持久化的應用場景有很多。比如,用戶的賬號登錄信息需要保存,用于每次與 Web 服務驗證身份;又比如,下載后的圖片需要緩存,避免每次都要重新加載,浪費用戶流量。 由于 Flutter 僅接管了渲染層,真正涉及到存儲等操作系統底層行為時,還需要依托于原生 Android、iOS,因此與原生開發類似的,根據需要持久化數據的大小和方式不同,Flutter 提供了三種數據持久化方法,即文件、SharedPreferences 與數據庫。接下來,我將與你詳細講述這三種方式。 ## 文件 文件是存儲在某種介質(比如磁盤)上指定路徑的、具有文件名的一組有序信息的集合。從其定義看,要想以文件的方式實現數據持久化,我們首先需要確定一件事兒:數據放在哪兒?這,就意味著要定義文件的存儲路徑。 Flutter 提供了兩種文件存儲的目錄,即**臨時(Temporary)目錄與文檔(Documents)目錄**: * 臨時目錄是操作系統可以隨時清除的目錄,通常被用來存放一些不重要的臨時緩存數據。這個目錄在 iOS 上對應著 NSTemporaryDirectory 返回的值,而在 Android 上則對應著 getCacheDir 返回的值。 * 文檔目錄則是只有在刪除應用程序時才會被清除的目錄,通常被用來存放應用產生的重要數據文件。在 iOS 上,這個目錄對應著 NSDocumentDirectory,而在 Android 上則對應著 AppData 目錄。 接下來,我通過一個例子與你演示如何在 Flutter 中實現文件讀寫。 在下面的代碼中,我分別聲明了三個函數,即創建文件目錄函數、寫文件函數與讀文件函數。這里需要注意的是,由于文件讀寫是非常耗時的操作,所以這些操作都需要在異步環境下進行。另外,為了防止文件讀取過程中出現異常,我們也需要在外層包上 try-catch: ~~~ // 創建文件目錄 Future<File> get _localFile async { final directory = await getApplicationDocumentsDirectory(); final path = directory.path; return File('$path/content.txt'); } // 將字符串寫入文件 Future<File> writeContent(String content) async { final file = await _localFile; return file.writeAsString(content); } // 從文件讀出字符串 Future<String> readContent() async { try { final file = await _localFile; String contents = await file.readAsString(); return contents; } catch (e) { return ""; } } ~~~ 有了文件讀寫函數,我們就可以在代碼中對 content.txt 這個文件進行讀寫操作了。在下面的代碼中,我們往這個文件寫入了一段字符串后,隔了一會又把它讀了出來: ~~~ writeContent("Hello World!"); ... readContent().then((value)=>print(value)); ~~~ 除了字符串讀寫之外,Flutter 還提供了二進制流的讀寫能力,可以支持圖片、壓縮包等二進制文件的讀寫。這些內容不是本次分享的重點,如果你想要深入研究的話,可以查閱[官方文檔](https://api.flutter.dev/flutter/dart-io/File-class.html)。 ## SharedPreferences 文件比較適合大量的、有序的數據持久化,如果我們只是需要緩存少量的鍵值對信息(比如記錄用戶是否閱讀了公告,或是簡單的計數),則可以使用 SharedPreferences。 SharedPreferences 會以原生平臺相關的機制,為簡單的鍵值對數據提供持久化存儲,即在 iOS 上使用 NSUserDefaults,在 Android 使用 SharedPreferences。 接下來,我通過一個例子來演示在 Flutter 中如何通過 SharedPreferences 實現數據的讀寫。在下面的代碼中,我們將計數器持久化到了 SharedPreferences 中,并為它分別提供了讀方法和遞增寫入的方法。 這里需要注意的是,setter(setInt)方法會同步更新內存中的鍵值對,然后將數據保存至磁盤,因此我們無需再調用更新方法強制刷新緩存。同樣地,由于涉及到耗時的文件讀寫,因此我們必須以異步的方式對這些操作進行包裝: ~~~ // 讀取 SharedPreferences 中 key 為 counter 的值 Future<int>_loadCounter() async { SharedPreferences prefs = await SharedPreferences.getInstance(); int counter = (prefs.getInt('counter') ?? 0); return counter; } // 遞增寫入 SharedPreferences 中 key 為 counter 的值 Future<void>_incrementCounter() async { SharedPreferences prefs = await SharedPreferences.getInstance(); int counter = (prefs.getInt('counter') ?? 0) + 1; prefs.setInt('counter', counter); } ~~~ 在完成了計數器存取方法的封裝后,我們就可以在代碼中隨時更新并持久化計數器數據了。在下面的代碼中,我們先是讀取并打印了計數器數據,隨后將其遞增,并再次把它讀取打印: ~~~ // 讀出 counter 數據并打印 _loadCounter().then((value)=>print("before:$value")); // 遞增 counter 數據后,再次讀出并打印 _incrementCounter().then((_) { _loadCounter().then((value)=>print("after:$value")); }); ~~~ 可以看到,SharedPreferences 的使用方式非常簡單方便。不過需要注意的是,以鍵值對的方式只能存儲基本類型的數據,比如 int、double、bool 和 string。 ## 數據庫 SharedPrefernces 的使用固然方便,但這種方式只適用于持久化少量數據的場景,我們并不能用它來存儲大量數據,比如文件內容(文件路徑是可以的)。 如果我們需要持久化大量格式化后的數據,并且這些數據還會以較高的頻率更新,為了考慮進一步的擴展性,我們通常會選用 sqlite 數據庫來應對這樣的場景。與文件和 SharedPreferences 相比,數據庫在數據讀寫上可以提供更快、更靈活的解決方案。 接下來,我就以一個例子分別與你介紹數據庫的使用方法。 我們以上一篇文章中提到的 Student 類為例: ~~~ class Student{ 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 字典轉換成類對象的工廠類方法,我們也可以提供將類對象反過來轉換成 JSON 字典的實例方法。因為最終存入數據庫的并不是實體類對象,而是字符串、整型等基本類型組成的字典,所以我們可以通過這兩個方法,實現數據庫的讀寫。同時,我們還分別定義了 3 個 Student 對象,用于后續插入數據庫: ~~~ class Student{ ... // 將類對象轉換成 JSON 字典,方便插入數據庫 Map<String, dynamic> toJson() { return {'id': id, 'name': name, 'score': score,}; } } var student1 = Student(id: '123', name: '張三', score: 90); var student2 = Student(id: '456', name: '李四', score: 80); var student3 = Student(id: '789', name: '王五', score: 85); ~~~ 有了實體類作為數據庫存儲的對象,接下來就需要創建數據庫了。在下面的代碼中,我們通過 openDatabase 函數,給定了一個數據庫存儲地址,并通過數據庫表初始化語句,創建了一個用于存放 Student 對象的 students 表: ~~~ final Future<Database> database = openDatabase( join(await getDatabasesPath(), 'students_database.db'), onCreate: (db, version)=>db.execute("CREATE TABLE students(id TEXT PRIMARY KEY, name TEXT, score INTEGER)"), version: 1, ); ~~~ 以上代碼屬于通用的數據庫創建模板,有兩個地方需要注意: 1. 在設定數據庫存儲地址時,使用 join 方法對兩段地址進行拼接。join 方法在拼接時會使用操作系統的路徑分隔符,這樣我們就無需關心路徑分隔符究竟是“/”還是“\\”了。 2. 在創建數據庫時,傳入了一個參數 version:1,在 onCreate 方法的回調里面也有一個參數 version。前者代表當前版本的數據庫版本,后者代表用戶手機上的數據庫版本。 比如,我們的應用有 1.0、1.1 和 1.2 三個版本,在 1.1 把數據庫 version 升級到了 2。考慮到用戶的升級順序并不總是連續的,可能會直接從 1.0 升級到 1.2。因此我們可以在 onCreate 函數中,根據數據庫當前版本和用戶手機上的數據庫版本進行比較,制定數據庫升級方案。 數據庫創建好了之后,接下來我們就可以把之前創建的 3 個 Student 對象插入到數據庫中了。數據庫的插入需要調用 insert 方法,在下面的代碼中,我們將 Student 對象轉換成了 JSON,在指定了插入沖突策略(如果同樣的對象被插入兩次,則后者替換前者)和目標數據庫表后,完成了 Student 對象的插入: ~~~ Future<void> insertStudent(Student std) async { final Database db = await database; await db.insert( 'students', std.toJson(), // 插入沖突策略,新的替換舊的 conflictAlgorithm: ConflictAlgorithm.replace, ); } // 插入 3 個 Student 對象 await insertStudent(student1); await insertStudent(student2); await insertStudent(student3); ~~~ 數據完成插入之后,接下來我們就可以調用 query 方法把它們取出來了。需要注意的是,寫入的時候我們是一個接一個地有序插入,讀的時候我們則采用批量讀的方式(當然也可以指定查詢規則讀特定對象)。讀出來的數據是一個 JSON 字典數組,因此我們還需要把它轉換成 Student 數組: ~~~ Future<List<Student>> students() async { final Database db = await database; final List<Map<String, dynamic>> maps = await db.query('students'); return List.generate(maps.length, (i)=>Student.fromJson(maps[i])); } // 讀取出數據庫中插入的 Student 對象集合 students().then((list)=>list.forEach((s)=>print(s.name))); ~~~ 可以看到,在面對大量格式化的數據模型讀取時,數據庫提供了更快、更靈活的持久化解決方案。 除了基礎的數據庫讀寫操作之外,sqlite 還提供了更新、刪除以及事務等高級特性,這與原生 Android、iOS 上的 SQLite 或是 MySQL 并無不同,因此這里就不再贅述了。你可以參考 sqflite 插件的[API 文檔](https://pub.dev/documentation/sqflite/latest/),或是查閱[SQLite 教程](http://www.sqlitetutorial.net/)了解具體的使用方法。 ## 總結 好了,今天的分享就這里。我們簡單回顧下今天學習的內容吧。 首先,我帶你學習了文件,這種最常見的數據持久化方式。Flutter 提供了兩類目錄,即臨時目錄與文檔目錄。我們可以根據實際需求,通過寫入字符串或二進制流,實現數據的持久化。 然后,我通過一個小例子和你講述了 SharedPreferences,這種適用于持久化小型鍵值對的存儲方案。 最后,我們一起學習了數據庫。圍繞如何將一個對象持久化到數據庫,我與你介紹了數據庫的創建、寫入和讀取方法。可以看到,使用數據庫的方式雖然前期準備工作多了不少,但面對持續變更的需求,適配能力和靈活性都更強了。 數據持久化是 CPU 密集型運算,因此數據存取均會大量涉及到異步操作,所以請務必使用異步等待或注冊 then 回調,正確處理讀寫操作的時序關系。 我把今天分享所涉及到的知識點打包到了[GitHub](https://github.com/cyndibaby905/25_data_persistence)中,你可以下載下來,反復運行幾次,加深理解與記憶。 ## 思考題 最后,我給你留下兩道思考題吧。 1. 請你分別介紹一下文件、SharedPreferences 和數據庫,這三種持久化數據存儲方式的適用場景。 2. 我們的應用經歷了 1.0、1.1 和 1.2 三個版本。其中,1.0 版本新建了數據庫并創建了 Student 表,1.1 版本將 Student 表增加了一個字段 age(ALTER TABLE students ADD age INTEGER)。請你寫出 1.1 版本及 1.2 版本的數據庫創建代碼。 ~~~ //1.0 版本數據庫創建代碼 final Future<Database> database = openDatabase( join(await getDatabasesPath(), 'students_database.db'), onCreate: (db, version)=>db.execute("CREATE TABLE students(id TEXT PRIMARY KEY, name TEXT, score INTEGER)"), version: 1, ); ~~~
                  <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>

                              哎呀哎呀视频在线观看