App快取優化篇(react-native)
阿新 • • 發佈:2020-12-22
技術標籤:react-native
備註:react-native中,Image元件僅支援本地快取的圖片的base64形式顯示圖片(uri: http/base64/localasset url)非http,也非靜態資源圖片路徑,除非有辦法直接呼叫原生android或者Ios圖片快取模組進行顯示,否則base64圖片很大時,載入慢這個問題無法解決
APP中的快取分為兩種:
一、離線快取,沒有網路時,顯示快取到記憶體或者本地磁碟的資料
邏輯是在請求前,優先獲取快取,其步驟又分為(1)先獲取記憶體中的快取(2)獲取不到記憶體中的快取時,獲取本地磁碟的快取(3)獲取不到記憶體和磁碟中的快取時,發起請求獲取介面的資料
這種快取主要用來優化圖片渲染速度,但也可以支援快取其他型別的介面資料
_getCacheImage = (url) => { // 載入失敗可重試3次 if (this._loadErrorTimes > 3) return; // 傳入md5直接用,沒傳入則先獲取md5 (userId必傳) getCacheImage(url) .then((r) => { this.setState({ picture: r }); this._loadErrorTimes = 0; }) .catch((e) => { console.warn('_getCacheImage err:', e, url); this._loadErrorTimes++; setTimeout(() => { this._getCacheImage(url); }, 300); }); };
/** * @author huangzhixin */ import { STORAGE_KEY_AVATAR } from './commonConstants'; import RNFSHelper, { USER_FILES_PATH } from './RNFSHelper'; import { downloadImageBase64 } from './AvatarUtil'; import RNFS from 'react-native-fs'; // RNFS持久化 + 記憶體快取 + 介面快取, key: portraitsMD5 let memoryCache = {}; // 記憶體快取使用者頭像md5,避免頻繁呼叫portraitsMD5獲取介面 {oaCode: portraitsMD5} let md5MemCache = {}; let keys = []; let MAX_MC_NUM = 50; // 頭像記憶體快取最大數量 // 根據快取策略獲取URL圖片 export function getCacheImage(url: tring) { return new Promise((resolve, reject) => { if (!url || !/^https?:\/\//.test(url)) { reject('invalid image url: ' + url); return; } const key = global.md5(url); // 優先讀記憶體 var val = memoryCache[key]; if (val) { console.log('記憶體中的快取', val, memoryCache) resolve(val); return; } // 存本地檔案方式 // const filePath = USER_FILES_PATH + STORAGE_KEY_AVATAR + '/' + key; // RNFSHelper.existsFile(filePath).then((dirExists) => { // MOALog.log('getCacheImage existsFile:', dirExists, filePath); // if (!dirExists) { // // 發起請求 // downloadImageBase64(url) // .then((webAvt) => { // if (webAvt) { // resolve(webAvt); // } else { // MOALog.warn('downImgBase64 獲取圖片異常 webAvt:', webAvt); // reject({ error: '獲取圖片異常' }); // } // }) // .catch((e) => { // reject(e); // }); // } else { // resolve(filePath); // } // }); // return; // 讀本地快取 getLocalStorage(key) .then((localAvt) => { console.log('本地中的快取', localAvt) if (localAvt) { // 記憶體快取 saveMemory(key, localAvt); resolve(localAvt); } else { throw new Error('getLocalAvatar null'); } }) .catch(() => { // 發起請求 downloadImageBase64(url) .then((webAvt) => { if (webAvt) { // 記憶體快取+本地快取 saveMemory(key, webAvt); saveLocalStorage(key, webAvt); resolve(webAvt); } else { MOALog.warn('downImgBase64 獲取圖片異常 webAvt:', webAvt); reject({ error: '獲取圖片異常' }); } }) .catch((e) => { reject(e); }); }); }); } export function getMD5Cache(oaCode: string) { return md5MemCache[oaCode]; } export function clearAvatarMemory() { memoryCache = {}; keys = []; md5MemCache = {}; } export function clearAvatarsCache() { return RNFSHelper.deleteDir(STORAGE_KEY_AVATAR); } function saveMemory(key: string, val: string) { if (!memoryCache[key]) { memoryCache[key] = val; keys.push(key); // 控制記憶體儲存量 if (keys.length > MAX_MC_NUM) { var _del = keys.shift(); delete memoryCache[_del]; } } } function saveLocalStorage(key: string, val: string) { // key 檔名, STORAGE_KEY_AVATAR: 目錄名 RNFSHelper.writeFile(STORAGE_KEY_AVATAR + '/' + key, val) .then((r) => { MOALog.log('writeFile success:', r, STORAGE_KEY_AVATAR + '/' + key); }) .catch((e) => { MOALog.log('writeFile error:', e, STORAGE_KEY_AVATAR + '/' + key); }); } function getLocalStorage(key: string) { return RNFSHelper.readFile(STORAGE_KEY_AVATAR + '/' + key); } export default { clearMemory: clearAvatarMemory, clearCache: clearAvatarsCache, };
二、線上資料快取
這種快取存在的目的是由於介面請求的資料再到渲染顯示,會有一段空白期,這段時間使用者體驗很不好,除了使用者登入後,APP第一次載入(沒有快取)時,才會出現這種空資料狀態
為了提高使用者體驗,我們需要快取上一次的介面快取,在主要的場景頁面請求列表或者輪播圖等資料時,優先獲取快取的資料進行顯示,之後用請求到的資料去覆蓋掉快取的資料,並且更新快取資料
例如:
// 從本地快取獲取資料佔位展示
_getCachedData = () => {
const { storageKey, storageId } = this.props;
if (!storageKey || !storageId) {
return;
}
let tasks = [
new Promise((resolve, reject) => {
mb.storage
.load({
key: storageKey + '@' + mb.getUserId(),
id: storageId,
})
.then((list) => {
resolve(list);
})
.catch((e) => {
MOALog.warn('storage refreshPageList catch:', e);
reject(e);
});
}),
];
Promise.all(tasks.map(function (promiseItem) {
return promiseItem.catch((err) => {
MOALog.log('Promise.all catch', err);
return err;
});
})).then((resArr) => {
this.setState({
items: resArr[0] && Array.isArray(resArr[0]) ? resArr[0] : [],
});
}).catch((err) => {
console.warn('Promise.all catch:', err);
});
};
PS:除此之外,還有一種情況,就是圖片沒有無論是快取還是磁碟還是介面都沒有獲取到,需要一張預設圖片進行佔位顯示,例如輪播圖(否則一片空白),這就需要判斷圖片載入成功與否了onLoad
補充所需檔案:
downloadImageBase64
export function downloadImageBase64(url, option = {}) {
return new Promise((resolve, reject) => {
// TODO: 新增請求池過濾重複頭像請求
if (RequestPool.checkCancel(url, resolve)) return;
// 覆蓋resolve來處理請求池結果回撥
resolve = (arg) => {
RequestPool.handleResultInPool(url, arg);
};
const { auth = true } = option;
let headers = {
'Content-Type': 'image/*',
};
if (auth) {
headers = {
...headers,
'msg.callback': '',
'auth.sysid': Config.BUSINESS_SYSID,
'auth.permit': Config.BUSINESS_PERMIT,
'auth.token': Zqmb.businessToken,
};
}
if (auth && !Zqmb.businessToken) {
reject('請先登入再進行操作');
return;
}
// // 儲存為本地路徑
// const key = global.md5(url);
// const filePath = STORAGE_KEY_AVATAR + '/' + key;
// download(url, filePath, { headers }).then((res) => {
// MOALog.log('download res:', res);
// resolve(filePath);
// }).catch(err => {
// MOALog.warn('download catch err:', err);
// reject(err);
// });
// return;
MOALog.log('downImgBase64 url:', url);
// 注:Debug下頭像base64編碼會出現問題,打包應用未現異常
fetch(url, {
method: 'GET',
headers: headers,
})
.then((r) => {
if (
r.headers.map &&
r.headers.map['content-type'] &&
!/image/.test(r.headers.map['content-type']) &&
!/jpg|jpeg|gif|png/.test(r.headers.map['content-type'])
) {
throw new Error(r._bodyText);
}
return r.blob();
})
.then((blob) => {
const reader = new FileReader();
reader.onload = (e) => {
let data = e.target.result;
MOALog.log('downImgBase64', url, data && data.length);
// android下字首問題
// data = data.replace('application/octet-stream', 'image/png')
var rst = data.split('base64,')[1];
resolve(rst);
};
reader.readAsDataURL(blob);
MOALog.log('downImgBase64 readAsDataURL', url, blob);
})
.catch((e) => {
MOALog.warn('downImgBase64 catch err:', e);
reject(e);
});
});
}