由于項(xiàng)目中的頭像圖片加載極慢,需要對(duì)此進(jìn)行優(yōu)化。主要針對(duì)以下兩點(diǎn)做優(yōu)化:
- 由于后端沒有對(duì)圖片進(jìn)行壓縮,前端對(duì)上傳的圖片提前進(jìn)行裁剪壓縮上傳。
- 對(duì)未改變的圖片進(jìn)行本地持久化,減少二次加載圖片的延遲。
圖片的裁剪壓縮直接采用了第三方插件,這里不再過多贅述,主要了解一下flutter的圖片加載及緩存模式。
在flutter中ImageProvider作為一個(gè)抽象類,他除了定義了圖片數(shù)據(jù)獲取和加載的相關(guān)接口,還提供了圖片數(shù)據(jù)源及緩存圖片的作用。渲染一張圖片:
- 先判斷圖片數(shù)據(jù)有沒有緩存,如果有,則直接返回ImageStream。
- 如果沒有緩存,則調(diào)用load(T key)方法從數(shù)據(jù)源加載圖片數(shù)據(jù),加載成功后先緩存,然后返回ImageStream。
查看圖片是否緩存及生成緩存數(shù)據(jù)都與緩存key相關(guān),現(xiàn)在我們看一下緩存的key,因?yàn)镸ap中相同key的值會(huì)被覆蓋,也就是說key是圖片緩存的一個(gè)唯一標(biāo)識(shí),只要是不同key,那么圖片數(shù)據(jù)就會(huì)分別緩存。圖片緩存key是ImageProvider.obtainKey()方法的返回值,而此方法需要ImageProvider子類去重寫。接下來看一下NetworkImage為的obtainKey()的具體實(shí)現(xiàn):
@override
Future<NetworkImage> obtainKey(image_provider.ImageConfiguration configuration) {
return SynchronousFuture<NetworkImage>(this);
}
代碼創(chuàng)建了一個(gè)同步的future,然后直接將自身做為key返回。因?yàn)镸ap中在判斷key(此時(shí)是NetworkImage對(duì)象)是否相等時(shí)會(huì)使用“==”運(yùn)算符,那么定義key的邏輯就是NetworkImage的“==”運(yùn)算符:
@override
bool operator ==(dynamic other) {
... //省略無關(guān)代碼
final NetworkImage typedOther = other;
return url == typedOther.url
&& scale == typedOther.scale;
}
所以對(duì)于網(wǎng)絡(luò)圖片來說,會(huì)將其“url+縮放比例”作為緩存的key。也就是說如果兩張圖片的url或scale只要有一個(gè)不同,便會(huì)重新下載并分別緩存。
另外,圖片緩存是在內(nèi)存中,并沒有進(jìn)行本地文件持久化存儲(chǔ),所以網(wǎng)絡(luò)圖片在應(yīng)用重啟后需要重新聯(lián)網(wǎng)下載的原因。雖然Flutter中的ImageProvider本身是帶有內(nèi)存緩存機(jī)制的,但由于項(xiàng)目圖片存儲(chǔ)于云端,且每次圖片地址url都會(huì)有所變化,導(dǎo)致NetworkImage自帶的緩存機(jī)制也無效。
基于以上原因,最終決定將圖片以二進(jìn)制形式請(qǐng)求到本地,并以圖片主路徑(除其它附帶參數(shù))作為存儲(chǔ)key保存,若key不變,則采用Image.memory()讀取圖片數(shù)據(jù)流并渲染到頁面中。以下是部分關(guān)鍵代碼:
// Global.dart
import 'package:shared_preferences/shared_preferences.dart';
class Global {
static SharedPreferences _prefs;
static Profile profile = Profile();
// 持久化Profile信息
static saveProfile() =>
_prefs.setString("profile", jsonEncode(profile.toJson()));
}
// userModel.dart
class UserModel extends ChangeNotifier {
Profile get _profile => Global.profile; // 本地持久化
String get _headImageKey => _profile.headImageKey;
Uint8List get _headImageData {
if (_profile.HeadImageData != null) {
return new Uint8List.fromList(_profile.HeadImageData.codeUnits);
} else {
return null;
}
}
get headImageKey => _headImageKey;
get headImageData => _headImageData;
set _headImageKey(String key) {
Global.profile.headImageKey = key;
Global.saveProfile();
}
set _headImageData(Uint8List bytes) {
Global.profile.HeadImageData = new String.fromCharCodes(bytes);
Global.saveProfile();
}
Future _initHeadImage(String url) async {
String newImageKey = getImageKey(url);
if (_headImageKey != newImageKey) { // 判斷key是否相同
_headImageKey = newImageKey;
Response res = await dio.get(url); // 二進(jìn)制形式請(qǐng)求圖片
Uint8List bytes = res.data;
_headImageData = bytes;
notifyListeners();
} else {
print('利用緩存頭像!?。。?!');
}
}
}
調(diào)用Image.memory( Provider.of<UserModel>(context). headImageData, fit: BoxFit.cover, )