版權聲明:本文為作者原創書籍。轉載請注明作者和出處,未經授權,嚴禁私自轉載,侵權必究?。?!
情感語錄: 不要去追一匹馬,用追馬的時間種草,待到春暖花開時,就會有一群駿馬任你挑選;豐富自己,比取悅他人更有力量!
歡迎來到本章節,上一章節介紹了常用對話框和進度條
的使用,知識點回顧 戳這里 Flutter基礎第十一章
本章知識點主要介紹 Flutter 中的數據共享和持久化,很顯然,本章知識點相當重要,數據共享和持久化操作基本是每個應用的必備操作。比如:一個應用里有很多界面都會用到我的的用戶信息,而我的用戶信息一開始就在登錄時就返回了,我們把用戶的基本信息存儲在一個全局的變量里即可實現數據共享,讓其他視圖直接引用該變量值即可 。但是這樣的存儲只能達到內存級別的,應用關閉后這些數據就被丟失了。要實現關閉應用后再次重啟應用我的用戶信息而不丟失那該怎么辦呢? 這就是本章節的重點了------ 持久化
本章簡要:
1、Flutter 中單例介紹
2、shared_preferences 實現數據持久化
3、IO 文件讀寫操作數據持久化
一、全局靜態變量
Flutter 中的 Dart 語言如同 Java 語言一樣,存在很多地方的相似性。在通過使用 static
關鍵字 為一個變量開辟內存地址后,而其他需要引用到該變量值時, 只需要將內存地址指向該變量即可實現數據共享。
如: 我定義了一個 Global 類來管理全局的的靜態資源:
class Global{
//定義一個靜態屬性 name
static String name = "張三";
}
在需要使用到該變量值的地方 通過 Global.name
方式獲取該變量的值。
二、單例
單例模式是日常開發中最常用的設計模式之一。Dart 是單線程模型,因此在Flutter 中實現單例 不需要像 Java 中加雙重檢查鎖去考慮多線程的問題。
Dart中的單例也有兩種模式:
1、餓漢式
餓漢式比較好理解:在類加載時,就進行實例的創建。加載時獲取實例速度較慢,運行時速度較快。通俗的講:"我管你吃不吃包子,反正我先把這坨包子蒸好放在這兒"
實例代碼:
class UserHelper{
// 單例公開訪問點
factory UserHelper() =>_userInstance();
static UserHelper get instance => _userInstance();
// 靜態私有成員,沒有初始化
static UserHelper _instance = UserHelper._();
// 私有構造函數
UserHelper._() {
// 具體初始化代碼
print("------>初始化");
}
// 靜態、同步、私有訪問點
static UserHelper _userInstance() {
return _instance;
}
String getUserName(){
return "張三";
}
}
餓漢式單例測試:
void main(){
var userHelper = UserHelper();
var userHelper1 = UserHelper();
var userHelper2 = UserHelper.instance;
print("------------> 對象:'${userHelper.hashCode} 相等 '+${identical(userHelper, userHelper1)}"); //true
print("------------> 對象:'${userHelper1.hashCode} 相等 '+${identical(userHelper, userHelper2)}"); //true
print("------------> 對象:'${userHelper2.hashCode} 相等 '+${identical(userHelper1, userHelper2)}"); //true
}
控制臺輸出:
I/flutter: ------>初始化
I/flutter: ------------> 對象:'1070008279 相等 '+true
I/flutter: ------------> 對象:'1070008279 相等 '+true
I/flutter: ------------> 對象:'1070008279 相等 '+true
可以看出 UserHelper 類有且只初始化了一次,且 hashCode 值也都相等,證明后面的無論 new UserHelper() 多少次拿到的都是同一對象。
2、懶漢式
在類加載時,不創建實例。加載時速度較快,運行時獲取實例速度較慢。通俗的講:“我管你吃不吃包子,等你問我了我才開始給你做包子”
實例代碼:
class UserHelper{
// 單例公開訪問點
factory UserHelper() =>_userInstance();
static UserHelper get instance => _userInstance();
// 靜態私有成員,沒有初始化
static UserHelper _instance;
// 私有構造函數
UserHelper._() {
// 具體初始化代碼
print("------>初始化");
}
// 靜態、同步、私有訪問點
static UserHelper _userInstance() {
if (_instance == null) {
_instance = UserHelper._();
}
return _instance;
}
}
懶漢式單例測試:
void main(){
var userHelper = UserHelper();
var userHelper1 = UserHelper();
var userHelper2 = UserHelper.instance;
print("------------> 對象:'${userHelper.hashCode} 相等 '+${identical(userHelper, userHelper1)}"); //true
print("------------> 對象:'${userHelper1.hashCode} 相等 '+${identical(userHelper, userHelper2)}"); //true
print("------------> 對象:'${userHelper2.hashCode} 相等 '+${identical(userHelper1, userHelper2)}"); //true
}
控制臺輸出:
I/flutter ( 8120): ------>初始化
I/flutter ( 8120): ------------> 對象:'537698073 相等 '+true
I/flutter ( 8120): ------------> 對象:'537698073 相等 '+true
I/flutter ( 8120): ------------> 對象:'537698073 相等 '+true
無論是通過餓漢式 還是懶漢式方式實現的單例始終拿到的都是同一對象,而同一對象中的屬性值肯定是相等的,那么上面介紹的第一點通過靜態實現全局共享 也可以換成單例的方式實現。
下面列舉下使用單例的場景和好處:
1、對象需要頻繁的實例化和銷毀,此時考慮使用單例可以大幅度提高性能。
2、控制資源的使用。
3、控制實例產生的數量,達到節約資源的目的。
4、作為通信媒介使用,也就是數據共享。
三、shared_preferences 數據持久化
shared_preferences 是 Flutter 提供的 key-value 存儲插件,它通過Android和iOS平臺提供的機制來實現數據持久化到磁盤中。在 iOS 上封裝的是 NSUserDefault(后綴 .plist
的文件中), 在 android 上封裝的是 SharedPreferences(后綴.xml
文件中)。在使用上也是如同原生一樣簡單。
為工程添加 shared_preferences 插件:
1、在pubspec.yaml
文件中添加依賴
dependencies:
fluttertoast: ^3.0.3
flutter:
sdk: flutter
#添加持久化插件 sp
shared_preferences: ^0.5.3+1
在 pubspec.yaml 添加依賴時特別需要注意縮進,多一個或少一個空格可能都將添加不上。
2、安裝依賴庫
執行 flutter packages get
命令;AS 開發工具直接右上角 packages get
也可。
3、在使用的文件中導入該庫
import 'package:shared_preferences/shared_preferences.dart';
shared_preferences 源碼分析:
class SharedPreferences {
SharedPreferences._(this._preferenceCache);
static const String _prefix = 'flutter.';
static SharedPreferences _instance;
static Future<SharedPreferences> getInstance() async {
if (_instance == null) {
final Map<String, Object> preferencesMap =
await _getSharedPreferencesMap();
_instance = SharedPreferences._(preferencesMap);
}
return _instance;
}
/// The cache that holds all preferences.
///
/// It is instantiated to the current state of the SharedPreferences or
/// NSUserDefaults object and then kept in sync via setter methods in this
/// class.
///
/// It is NOT guaranteed that this cache and the device prefs will remain
/// in sync since the setter method might fail for any reason.
final Map<String, Object> _preferenceCache;
/// Returns all keys in the persistent storage.
Set<String> getKeys() => Set<String>.from(_preferenceCache.keys);
/// Reads a value of any type from persistent storage.
dynamic get(String key) => _preferenceCache[key];
/// Reads a value from persistent storage, throwing an exception if it's not a
/// bool.
bool getBool(String key) => _preferenceCache[key];
/// Reads a value from persistent storage, throwing an exception if it's not
/// an int.
int getInt(String key) => _preferenceCache[key];
/// Reads a value from persistent storage, throwing an exception if it's not a
/// double.
double getDouble(String key) => _preferenceCache[key];
/// Reads a value from persistent storage, throwing an exception if it's not a
/// String.
String getString(String key) => _preferenceCache[key];
/// Returns true if persistent storage the contains the given [key].
bool containsKey(String key) => _preferenceCache.containsKey(key);
/// Reads a set of string values from persistent storage, throwing an
/// exception if it's not a string set.
List<String> getStringList(String key) {
List<Object> list = _preferenceCache[key];
if (list != null && list is! List<String>) {
list = list.cast<String>().toList();
_preferenceCache[key] = list;
}
// Make a copy of the list so that later mutations won't propagate
return list?.toList();
}
/// Saves a boolean [value] to persistent storage in the background.
///
/// If [value] is null, this is equivalent to calling [remove()] on the [key].
Future<bool> setBool(String key, bool value) => _setValue('Bool', key, value);
/// Saves an integer [value] to persistent storage in the background.
///
/// If [value] is null, this is equivalent to calling [remove()] on the [key].
Future<bool> setInt(String key, int value) => _setValue('Int', key, value);
/// Saves a double [value] to persistent storage in the background.
///
/// Android doesn't support storing doubles, so it will be stored as a float.
///
/// If [value] is null, this is equivalent to calling [remove()] on the [key].
Future<bool> setDouble(String key, double value) =>
_setValue('Double', key, value);
/// Saves a string [value] to persistent storage in the background.
///
/// If [value] is null, this is equivalent to calling [remove()] on the [key].
Future<bool> setString(String key, String value) =>
_setValue('String', key, value);
/// Saves a list of strings [value] to persistent storage in the background.
///
/// If [value] is null, this is equivalent to calling [remove()] on the [key].
Future<bool> setStringList(String key, List<String> value) =>
_setValue('StringList', key, value);
/// Removes an entry from persistent storage.
Future<bool> remove(String key) => _setValue(null, key, null);
Future<bool> _setValue(String valueType, String key, Object value) {
final Map<String, dynamic> params = <String, dynamic>{
'key': '$_prefix$key',
};
if (value == null) {
_preferenceCache.remove(key);
return _kChannel
.invokeMethod<bool>('remove', params)
.then<bool>((dynamic result) => result);
} else {
if (value is List<String>) {
// Make a copy of the list so that later mutations won't propagate
_preferenceCache[key] = value.toList();
} else {
_preferenceCache[key] = value;
}
params['value'] = value;
return _kChannel
.invokeMethod<bool>('set$valueType', params)
.then<bool>((dynamic result) => result);
}
}
/// Always returns true.
/// On iOS, synchronize is marked deprecated. On Android, we commit every set.
@deprecated
Future<bool> commit() async => await _kChannel.invokeMethod<bool>('commit');
/// Completes with true once the user preferences for the app has been cleared.
Future<bool> clear() async {
_preferenceCache.clear();
return await _kChannel.invokeMethod<bool>('clear');
}
/// Fetches the latest values from the host platform.
///
/// Use this method to observe modifications that were made in native code
/// (without using the plugin) while the app is running.
Future<void> reload() async {
final Map<String, Object> preferences =
await SharedPreferences._getSharedPreferencesMap();
_preferenceCache.clear();
_preferenceCache.addAll(preferences);
}
static Future<Map<String, Object>> _getSharedPreferencesMap() async {
final Map<String, Object> fromSystem =
await _kChannel.invokeMapMethod<String, Object>('getAll');
assert(fromSystem != null);
// Strip the flutter. prefix from the returned preferences.
final Map<String, Object> preferencesMap = <String, Object>{};
for (String key in fromSystem.keys) {
assert(key.startsWith(_prefix));
preferencesMap[key.substring(_prefix.length)] = fromSystem[key];
}
return preferencesMap;
}
/// Initializes the shared preferences with mock values for testing.
///
/// If the singleton instance has been initialized already, it is automatically reloaded.
@visibleForTesting
static void setMockInitialValues(Map<String, dynamic> values) {
_kChannel.setMockMethodCallHandler((MethodCall methodCall) async {
if (methodCall.method == 'getAll') {
return values;
}
return null;
});
_instance?.reload();
}
}
從源碼上看非常簡單,首先 SharedPreferences 使用的一個單例模式,且是異步的。它在數據存儲上提供了:setInt、setBool、setString 和 setStringList
方法來設置特定類型的數據。在數據讀取上提供了:getString 、getInt 、 getDouble、getStringList
方法來獲取數據。此外還提供了如下幾個工具API:
1、是否包含有該 key 值從存儲:containsKey(String key)
2、移除指定的 key 值存儲:remove(String key)
3、清除全部的持久化數據:clear()
4、提交存儲數據:commit()
,該方法已被廢棄。
綜合運用:
首先我們在 全局類 Global 中初始化 SharedPreferences ,使用靜態方式方便其他地方使用。
import 'package:shared_preferences/shared_preferences.dart';
class Global{
//定義一個全局的 sp
static SharedPreferences preferences;
//初始化
static void initPreferences() async{
preferences= await SharedPreferences.getInstance();
}
}
特別注意:SharedPreferences單例的獲取是一個異步方法所以需要 await 、async
關鍵字。
其次:在程序啟動時就初始化
void main(){
runApp(MyApp());
//初始化 sp
Global.initPreferences();
}
下面就可以在使用的地方直接引用即可,例子:
import 'package:flutter/material.dart';
import 'package:flutter_learn/util/Global.dart';
import 'package:flutter_learn/util/ToastUtil.dart';
import 'package:shared_preferences/shared_preferences.dart';
//使用初始化好的靜態資源
SharedPreferences sharedPreferences = Global.preferences;
//定義一個內存級別的變量
int count = 0;
class PersistenPage extends StatefulWidget {
PersistenPage({Key key}) : super(key: key);
_PersistenPageState createState() => _PersistenPageState();
}
class _PersistenPageState extends State<PersistenPage> {
@override
Widget build(BuildContext context) {
count = sharedPreferences.getInt("count") == null
? 0
: sharedPreferences.getInt("count");
return Scaffold(
appBar: AppBar(
title: Text("持久化"),
),
body: Container(
color: Colors.blue,
width: double.infinity,
child: Column(
children: <Widget>[
SizedBox(height: 20),
Text('ShardPreferences方式持久化',
style: TextStyle(color: Colors.white)),
SizedBox(height: 20),
Text("當前累計數據:$count"),
Row(
children: <Widget>[
SizedBox(width: 5),
RaisedButton(
color: Colors.deepPurple,
elevation: 20,
focusElevation: 40,
child: Text('自增', style: TextStyle(color: Colors.white)),
onPressed: () {
setState(() {
count++;
sharedPreferences?.setInt("count", count);
});
},
),
SizedBox(width: 5),
RaisedButton(
color: Colors.deepPurple,
elevation: 20,
focusElevation: 40,
child: Text('清除', style: TextStyle(color: Colors.white)),
onPressed: () {
setState(() {
count = 0;
sharedPreferences.clear();
// sharedPreferences.remove("count");
});
},
),
SizedBox(width: 5),
RaisedButton(
color: Colors.deepPurple,
elevation: 20,
focusElevation: 40,
child: Text('是否包含', style: TextStyle(color: Colors.white)),
onPressed: () {
setState(() {
count = 0;
bool flag = sharedPreferences.containsKey("count");
ToastUtil.show("是否包含: $flag");
});
},
),
SizedBox(width: 5),
],
),
SizedBox(height: 10),
],
),
));
}
}
案例效果圖:
從上面可以看出,無論是退出當前界面,還是關閉應用后,再次回到持久化界面任然可以讀取到存儲到文件中的值,也就是實現了數據本地持久化。
四、IO 文件讀寫操作數據持久化
Flutter 同原生一樣支持對文件的讀和寫。Flutter 中 IO 其實是 Dart 中的一部分,由于Dart VM是運行在PC或服務器操作系統下,而Flutter是運行在移動操作系統中,他們的文件系統所以會有一些差異。為此 PathProvider 插件提供了一種平臺透明的方式來訪問設備文件系統上的常用位置。該類當前支持訪問兩個文件系統位置:
1、臨時文件夾
:可以使用 getTemporaryDirectory() 來獲取臨時目錄;在 iOS 上對用的是 NSCachesDirectory 在 Android 對用的是 getCacheDir()。
2、應用的Documents 目錄
:可以使用 getApplicationDocumentsDirectory 來獲取 Documents 目錄;在 iOS 對應的是 NSDocumentDirectory ,在 Android 上對應的是 AppData 目錄。
【特別注意:】臨時文件夾在執行系統的清空緩存時會清空該文件夾,documents 目錄只有在刪除應用時才會清空。
PathProvider和 SharedPreferences 一樣需要引入;具體步驟如下:
1、在pubspec.yaml
文件中添加聲明:
dependencies:
fluttertoast: ^3.0.3
flutter:
sdk: flutter
#添加持久化插件 sp
shared_preferences: ^0.5.3+1
#添加文件庫
path_provider: ^1.2.0
2、安裝依賴庫
執行 flutter packages get
命令;AS 開發工具直接右上角 packages get
也可。
3、在使用的文件中導入該庫
import 'package:path_provider/path_provider.dart';
File類的代碼比較多,這里就不貼內部源碼了,下面只列出開發中常用的 API :
1、create()
創建文件,多級目錄 recursive 為真則遞歸創建
2、createSync()
同步方式創建文件
3、rename()
文件重命名
4、renameSync()
同步設置文件重命名
5、copy()
復制文件
6、copySync()
同步復制文件
7、length()
獲取文件長度
8、lengthSync()
同步獲取文件長度
9、absolute
獲取絕對路徑文件
10、lastAccessed()
獲取最后一次的訪問時間
11、lastAccessedSync()
同步獲取最后一次的訪問時間
12、setLastAccessed()
設置最后一次的訪問時間
13、setLastAccessedSync()
同步設置最后一次的訪問時間
14、lastModified()
獲取最后一次修改時間
15、lastModifiedSync()
同步獲取最后一次修改時間
16、setLastModified()
設置最后一次修改時間
17、setLastModifiedSync()
同步設置最后一次修改時間
18、open()
打開文件有多種方式和文件的訪問模式,詳情參閱源碼
19、readAsBytes()
讀取字節
20、readAsBytesSync()
同步讀取字節
21、readAsString()
讀取字符串
22、readAsStringSync()
同步讀取字符串
23、readAsLines()
讀取一行
24、readAsLinesSync()
同步讀取一行
25、writeAsBytes()
寫出字節數組
26、writeAsBytesSync()
同步寫出字節數組
27、writeAsString()
寫出字符串
28、writeAsStringSync()
同步寫出字符串
對 File 的操作有太多 API 了 ,更多內容只有自己去查閱源碼了。下面我們來將之前的計數器實例來改造下,使用文件的方式。
首先我們在 全局類 Global 中創建文件 ,使用靜態方式方便其他地方使用。
import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
class Global{
//定義一個全局的 sp
static SharedPreferences preferences;
static File file;
//初始化
static void initPreferences() async{
preferences= await SharedPreferences.getInstance();
}
//初始化一個文件,方便使用
static void initFile() async{
final directory = await getApplicationDocumentsDirectory();
if (!(file is File)) {
final String path = directory.path;
file = File('$path/myInfo.txt');
if( !file.existsSync()){
// 不存在則創建文件
file.createSync(recursive: true);
}
}
}
}
其次:在程序啟動時就初始化
void main(){
runApp(MyApp());
//初始化 sp
Global.initPreferences();
//初始化文件
Global.initFile();
}
下面就可以在使用的地方直接引用即可,例子:
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_learn/util/Global.dart';
import 'package:flutter_learn/util/ToastUtil.dart';
import 'package:shared_preferences/shared_preferences.dart';
//使用初始化好的靜態資源
SharedPreferences sharedPreferences = Global.preferences;
//使用初始化好的本地文件
File localFile = Global.file;
class PersistenPage extends StatefulWidget {
PersistenPage({Key key}) : super(key: key);
_PersistenPageState createState() => _PersistenPageState();
}
class _PersistenPageState extends State<PersistenPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("持久化"),
),
body: SingleChildScrollView(
child: Container(
color: Colors.blue,
height: 1000,
child: Column(
children: <Widget>[
// shardPreferences(),
IoReadWirte()
],
),
),
));
}
//文件的讀寫
Widget IoReadWirte(){
//定義一個內存級別的變量
String info = localFile.readAsStringSync();
int count =int.parse(info??0);
return Column(
children: <Widget>[
SizedBox(height: 20),
Text('文件操作方式持久化',
style: TextStyle(color: Colors.white)),
SizedBox(height: 20),
Text("當前累計數據:$count"),
Row(
children: <Widget>[
SizedBox(width: 5),
RaisedButton(
color: Colors.deepPurple,
elevation: 20,
focusElevation: 40,
child: Text('自增', style: TextStyle(color: Colors.white)),
onPressed: () {
setState(() {
count++;
localFile.writeAsStringSync(count.toString());
});
},
),
SizedBox(width: 5),
RaisedButton(
color: Colors.deepPurple,
elevation: 20,
focusElevation: 40,
child: Text('自減', style: TextStyle(color: Colors.white)),
onPressed: () {
setState(() {
count--;
localFile.writeAsStringSync(count.toString());
});
},
),
],
),
SizedBox(height: 10),
],
);
}
}
為了方便起見和減少文章篇幅,這里只貼出了 File 操作的全部代碼,shardPreferences 中的代碼暫時刪除,文末的工程實例中擁有全部代碼。
效果如下:
無論是用 SharedPreferences 還是文件操作 方式實現數據持久化都是非常簡單,在開發中 如果只是存儲一些簡單數據 其實用 SharedPreferences 就非常好了,如果涉及到一些文件(如:圖片,文檔等)才會去使用 File。
上面兩點都只是對一些單一數據的持久化,而要持久化關系型數據就顯現的捉衿見肘了,對于有關系的數據我們首先想到的肯定是通過數據庫去存儲,而數據庫
在開發中也是重中之中,涉及到篇幅問題這里就不介紹了,這也是為下一篇文章做鋪墊吧
本章中很多都是通過靜態的方式去存值,其實在開發中應該減少這種方式的使用 即在需要使用的時候才去獲取,避免不必要的內存消耗!我這里只是為了方便演示才這樣寫。
好了本章節到此結束,又到了說再見的時候了,如果你喜歡請留下你的小紅星,創作真心也不容易;你們的支持才是創作的動力,如有錯誤,請熱心的你留言指正, 謝謝大家觀看,下章再會 O(∩_∩)O