1. 程式人生 > 其它 >App快取優化篇(react-native)

App快取優化篇(react-native)

技術標籤: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);
      });
  });
}