备注:react-native中,Image组件仅支持本地缓存的图片的base64形式显示图片(uri: http/base64/local asset url)非http,也非静态资源图片路径,除非有办法直接调用原生android或者Ios图片缓存模块进行显示,否则base64图片很大时,加载慢这个问题无法解决
关于离线缓存,我们需要区分一件事,APP资源在有网络的情况下加载完成了,然后突然没网络,但是又没有杀掉app时,这个时候用的是React-Native Image或者List相关组件自带的内存缓存数据
我们这边提的离线缓存,是指,在没有网络的情况下,直接进的APP时所需要展示的数据(图片、列表等),这种情况下,RN的内存缓存数据是会随着上一次App杀掉而释放掉的
有人可能会疑惑,既然没有网络,那么怎么绕过登录呢?
其实,APP一般具有快捷登录的,比如指纹,手势密码等。这个是不需要再次调用登录接口就可以绕过登录的
下面进入正文
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
补充所需文件:
PS:在这里贴一下接口返回的文件响应体数据:
downloadImageBase64:
export function downloadImageBase64(url, option = {}) {
return new Promise((resolve, reject) => {
// TODO: 添加请求池过滤重复头像请求
// 首页是先取缓存数据再取接口请求数据,而缓存数据获取是慢于token的,由于没有携带token会报错取无法下载图片到本地供离线时使用,同时还会占据请求池相同的apiKey,而后的真实api就会被请求池返回true,这边针对首页进行开放限制
if (RequestPool.checkCancel(url, resolve) && !isHome && !global.isAlreadyAppLogout) 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) => {
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);
// 如果是RN 不能在React Navtive Debugger的Enable Network模式下测试 该模式下无法得到文件
return r.blob();
// return r._bodyBlob;
.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);
// 请求池:避免同时刻重复请求
export const RequestPool = {
_pool: {}, // {key: [result1, result2 ...]}
checkCancel: function(key, rstCb) {
// 检查已有进行中请求则需取消后面相同请求
if (this._pool[key]) {
this._pool[key].push(rstCb);
return true;
} else {
this._pool[key] = [];
this._pool[key].push(rstCb);
return false;
handleResultInPool: function(key, ...args) {
// 结束请求同时对请求池中请求统一回调
if (this._pool[key]) {
this._pool[key].forEach(cb => {
cb && cb(...args);
delete this._pool[key];
global.RequestPool = RequestPool;
文件转babase64
function getImgToBase64(url,callback){//将图片转换为Base64
var canvas = document.createElement('canvas'),
ctx = canvas.getContext('2d'),
img = new Image;
img.crossOrigin = 'Anonymous';
img.onload = function(){
canvas.height = img.height;
canvas.width = img.width;
ctx.drawImage(img,0,0);
var dataURL = canvas.toDataURL('image/png');
callback(dataURL);
canvas = null;
img.src = url;
* 获取指定文件的base64编码
* @param object File Blob 或 File 对象 这里是file对象
* @param Function callback 返回数据的回调函数
* @return string 返回base64编码
function getBase64(File,callback){
var reader = new FileReader(); //IE10+
var AllowImgFileSize = 2100000; //上传图片最大值(单位字节)( 2 M = 2097152 B )超过2M上传失败
var File = File||$("#file").get(0).files[0]; //获取上传的文件对象
FileList {0: File, 1: File, length: 2} 多个文件
File:{name: "fan.jpg", lastModified: 1559019043288, lastModifiedDate: Tue May 28 2019 12:50:43 GMT+0800 (中国标准时间), webkitRelativePath: "", size: 3346145, type: "image/jpeg"}
FileList {0: File, 1: File, length: 2} 单个文件
if (File) {
//读取指定的 Blob 或 File 对象 触发loadend 事件 并将图片的base64编码赋值给result
reader.readAsDataURL(File);
//reader.readAsText(File)
//异步通信 回调函数返回
reader.onload = function (e) {
//var ImgFileSize = reader.result.substring(reader.result.indexOf(",") + 1).length;//截取base64码部分(可选可不选,需要与后台沟通)
if (AllowImgFileSize != 0 && AllowImgFileSize < reader.result.length) {
alert( '上传失败,请上传不大于2M的图片!');
return;
}else{
var base64Data=reader.result;
//返回base64编码
callback(base64Data);
base64转换为file对象
function Base64toFile(dataurl, filename) {//将base64转换为文件
var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
while(n--){
u8arr[n] = bstr.charCodeAt(n);
return new File([u8arr], filename, {type:mime});
base64转blob
function blobToBase64(blob, callback) {
let a = new FileReader();
a.onload = function (e) { callback(e.target.result); }
a.readAsDataURL(blob);
APP中的缓存分为两种:一、离线缓存,没有网络时,显示缓存到内存或者本地磁盘的数据逻辑是在请求前,优先获取缓存,其步骤又分为(1)先获取内存中的缓存(2)获取不到内存中的缓存时,获取本地磁盘的缓存(3)获取不到内存和磁盘中的缓存时,发起请求获取接口的数据这种缓存主要用来优化图片渲染速度,但也可以支持缓存其他类型的接口数据/** * @author huangzhixin */import { STORAGE_KEY_AVATAR } from './commonConstants';
npm install --save react-native-clear-app-cache
Then link with:
react-native link react-native-clear-app-cache
文章目录一、函数(1)图片绝对路径转文件流File1. 主函数2. 获取图片base643. base64转file二、页面结构(最终图片以file格式提交给后端)即formdata(1)html页面结构显示1. 主页面2. 子页面(2) 判断浏览器是否自带复原函数(3)图片旋转函数三、页面使用(1)不涉及图片回显(2)涉及图片回显(回显格式为路径)四、后端接口(1)后端上传接口(2)axios封装http
(1)图片绝对路径转文件流File
路径就是类似下面这种
"/pic/userpic/4
配置的血泪史
一、0.49版本后没有了index.android.js和index.ios.js,统一为index.js。android目录下assets必须有index.android.bundle文件。
java.lang.RuntimeException: Unable to load script from assets 'index.android.bundle'...
本文均为RN开发过程中遇到的问题、坑点的分析及解决方案,各问题点之间无关联,希望能帮助读者少走弯路,持续更新中... (2019年3月29日更新)
原文链接:www.kovli.com/2018/06/25/…
作者:Kovli
- 如何在原生端(iOS和android两个平台)使用ReactNative里的本地图片(路径类似require('./xxximage.png'))。
在ReactNa...
仅介绍需要动态修改数据的本地缓存
第一步:定义两个方法,方法1,设置本地缓存键值对,方法2,读取本地缓存键值对进行判断,未存值发出请求,否则读取本地缓存数据设置state
setItem = async (key,item) => {
try {
await AsyncStorage.setItem(key, JSON.stringify(i...
1、npm install react-native-file-selector --save
2、react-native link react-native-file-selector
3、/app/build.gradle:
增加<...
React Native 错误:EMFILE:打开的文件太多,请注意
React Native Error: EMFILE: too many open files, watch
Error: EMFILE: too many open files, watch
at FSEvent.FSWatcher._handle.onchange (internal/fs/watchers.js:127:28)
Emitted 'error' event on NodeWatcher instance at:
很多使用RN里需要使用mobx来做全局状态管理,一般变量的更新比较好做,比如string,number类型,但是涉及到list,有时候更新后其他observer并不会实时刷新。
究其原因,可以参考这个https://github.com/mobxjs/mobx/issues/476#issuecomment-250969418
代码如下:
注意dataSource的部分,list使用的部分也要用dataSource,不要直接用privateList。
import { observable, co