探究react-native 源码的图片缓存问题


Posted in Javascript onAugust 24, 2017

本文为xcode模拟器测试,rn版本0.44.3

突然想学习下RN是如何封装ios中的UIImage的,看着看着发现图片的缓存问题是个坑。。。

先看js端图片使用的三种方式,依次排序1、2、3

<Image source={{uri:url}} style={{width:200,height:200}}/> // 1、 加载远程图片
 <Image source={{uri:'1.png'}} style={{width:50,height:50}}/> //2、加载xcode中图片
 <Image source={require('../../../Resources/Images/Contact/conact_searchIcon@3x.png')}/> //3、加载js中图片

1、2必须设置图片宽高,3不需设置。

对应的ios原生端文件是RCTImageViewManager,暴露的属性

RCT_REMAP_VIEW_PROPERTY(source, imageSources, NSArray<RCTImageSource *>);

就是js中Image组件的属性source,在js中设置source会触发该属性的setter方法。进入RCTImageView的

- (void)setImageSources:(NSArray<RCTImageSource *> *)imageSources
 {
   if (![imageSources isEqual:_imageSources]) {
    _imageSources = [imageSources copy];
    [self reloadImage];
   }
 }

通过此方法中断点打印imageSources,依次得到下面结果:

探究react-native 源码的图片缓存问题

可见,Image组件加载图片都是采用URL的形式,将图片当作网络资源。不同的是URL的类型:

加载网络上图片   : http://
 加载xcode资源   : file://
 加载js中图片   : http://localhost:8081

追踪setter方法,到RCTImageLoader.m中的如下方法

- (RCTImageLoaderCancellationBlock)loadImageWithURLRequest:(NSURLRequest *)imageURLRequest
              size:(CGSize)size
              scale:(CGFloat)scale
             clipped:(BOOL)clipped
            resizeMode:(RCTResizeMode)resizeMode
            progressBlock:(RCTImageLoaderProgressBlock)progressBlock
           partialLoadBlock:(RCTImageLoaderPartialLoadBlock)partialLoadBlock
           completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
{
 __block volatile uint32_t cancelled = 0;
 __block dispatch_block_t cancelLoad = nil;
 dispatch_block_t cancellationBlock = ^{
 dispatch_block_t cancelLoadLocal = cancelLoad;
 if (cancelLoadLocal && !cancelled) {
  cancelLoadLocal();
 }
 OSAtomicOr32Barrier(1, &cancelled);
 };
 // 下载图片完成后回调
 __weak RCTImageLoader *weakSelf = self;
 void (^completionHandler)(NSError *, id, BOOL, NSString *) = ^(NSError *error, id imageOrData, BOOL cacheResult, NSString *fetchDate) {
 __typeof(self) strongSelf = weakSelf;
 if (cancelled || !strongSelf) {
  return;
 }
  // 如果imageOrData是图片类型,则直接回调
  // 此处,如果是第二种情况,则会满足,其他情况继续走下面方法
 if (!imageOrData || [imageOrData isKindOfClass:[UIImage class]]) {
  cancelLoad = nil;
  completionBlock(error, imageOrData);
  return;
 }
 
 // 在内存中查看是否存在该url对应的字节码图片
 if (cacheResult) {
  UIImage *image = [[strongSelf imageCache] imageForUrl:imageURLRequest.URL.absoluteString
              size:size
              scale:scale
             resizeMode:resizeMode
            responseDate:fetchDate];
  if (image) {
  cancelLoad = nil;
  completionBlock(nil, image);
  return;
  }
 }

  // 若没有缓存,则将图片解压,再将解压后图片缓存block
 RCTImageLoaderCompletionBlock decodeCompletionHandler = ^(NSError *error_, UIImage *image) {
  if (cacheResult && image) {
  // Store decoded image in cache
  [[strongSelf imageCache] addImageToCache:image
            URL:imageURLRequest.URL.absoluteString
           size:size
           scale:scale
          resizeMode:resizeMode
         responseDate:fetchDate];
  }

  cancelLoad = nil;
  completionBlock(error_, image);
 };
  // 具体的解压过程
 cancelLoad = [strongSelf decodeImageData:imageOrData
          size:size
          scale:scale
         clipped:clipped
        resizeMode:resizeMode
       completionBlock:decodeCompletionHandler];
 };
 // 走具体的方法加载图片,1、3种情况用网络请求下载,2情况加载本地文件
 cancelLoad = [self _loadImageOrDataWithURLRequest:imageURLRequest
            size:size
            scale:scale
           resizeMode:resizeMode
          progressBlock:progressBlock
         partialLoadBlock:partialLoadBlock
         completionBlock:completionHandler];
 return cancellationBlock;
}

具体的缓存类是RCTImageCache,采用NSCache缓存,方法

- (void)addImageToCache:(UIImage *)image
     forKey:(NSString *)cacheKey
{
 if (!image) {
 return;
 }
 CGFloat bytes = image.size.width * image.size.height * image.scale * image.scale * 4;
 if (bytes <= RCTMaxCachableDecodedImageSizeInBytes) {
 [self->_decodedImageCache setObject:image
         forKey:cacheKey
         cost:bytes];
 }
}

RCTMaxCachableDecodedImageSizeInBytes是个常量,为1048576,也就是只缓存小于1MB的图片。

问题出在cacheKey,查看缓存key的方法

static NSString *RCTCacheKeyForImage(NSString *imageTag, CGSize size, CGFloat scale,
          RCTResizeMode resizeMode, NSString *responseDate)
{
 return [NSString stringWithFormat:@"%@|%g|%g|%g|%zd|%@",
   imageTag, size.width, size.height, scale, resizeMode, responseDate];
}

缓存key的生成方法中包含了responseDate,responseDate是网络请求时返回来的

responseDate = ((NSHTTPURLResponse *)response).allHeaderFields[@"Date"];

1、3方式每次加载都是一个网络请求,那么网络请求的时间总是变化的,于是responseDate是变化的,cacheKey不唯一,所以虽然系统做了图片的缓存,但是每次取出的都为nil,缓存无效。

2方式加载具体方法在RCTLocalAssetImageLoader.m中,其调用的是RCTUtils的RCTImageFromLocalAssetURL方法

UIImage *__nullable RCTImageFromLocalAssetURL(NSURL *imageURL)
{
// .....省略各种处理
 UIImage *image = nil;
 if (bundle) {
 image = [UIImage imageNamed:imageName inBundle:bundle compatibleWithTraitCollection:nil];
 } else {
 image = [UIImage imageNamed:imageName];
 }
// .....省略各种处理
 return image;
}

可见是采用[UIImage imageNamed:imageName]的方式加载xcode自带的图片,这个是有内存缓存的。

综上,对react-native图片加载

1、3情况,没有内存缓存

2情况有系统默认的内存缓存

所有情况都没有磁盘缓存

想让内存缓存生效,只需要改变cacheKey的生成规则即可。

补充:沙盒下面的Library/Caches/项目bunderId号/fsCachedData文件夹里面会磁盘缓存大于一定值(测试约为5kb)的图片和文件,这个是NSURLSession网络请求系统默认的缓存类NSURLCache自动生成的,非图片的磁盘缓存。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
Jquery Validation插件防止重复提交表单的解决方法
Mar 05 Javascript
jQuery ajax serialize()方法的使用以及常见问题解决
Jan 27 Javascript
JavaScript AOP编程实例
Jun 16 Javascript
JavaScript文本框脚本编写的注意事项
Jan 25 Javascript
jQuery+css实现的切换图片功能代码
Jan 27 Javascript
BootStrap智能表单实战系列(七)验证的支持
Jun 13 Javascript
jQuery仿写百度百科的目录树
Jan 03 Javascript
jQuery实现广告条滚动效果
Aug 22 jQuery
js实现延迟加载的几种方法详解
Jan 19 Javascript
微信小程序上传图片到php服务器的方法
May 23 Javascript
node.js基础知识汇总
Aug 25 Javascript
解决ant Design中this.props.form.validateFields未执行的问题
Oct 27 Javascript
详解vue.js之绑定class和style的示例代码
Aug 24 #Javascript
10个最优秀的Node.js MVC框架
Aug 24 #Javascript
Vue.js实现输入框绑定的实例代码
Aug 24 #Javascript
Vue.js实现价格计算器功能
Mar 30 #Javascript
js微信分享实现代码
Oct 11 #Javascript
Vue.js实现实例搜索应用功能详细代码
Aug 24 #Javascript
实例详解JSON取值(key是中文或者数字)方式
Aug 24 #Javascript
You might like
利用Ffmpeg获得flv视频缩略图和视频时间的代码
2011/09/15 PHP
php实现基于openssl的加密解密方法
2016/09/30 PHP
Zend Framework入门应用实例详解
2016/12/11 PHP
PHP正则删除HTML代码中宽高样式的方法
2017/06/12 PHP
Alliance vs AM BO3 第二场2.13
2021/03/10 DOTA
Js表格万条数据瞬间加载实现代码
2014/02/20 Javascript
js的2种继承方式详解
2014/03/04 Javascript
JQuery给元素绑定click事件多次执行的解决方法
2014/05/29 Javascript
超链接的禁用属性Disabled使用示例
2014/07/31 Javascript
采用自执行的匿名函数解决for循环使用闭包的问题
2014/09/11 Javascript
Nodejs极简入门教程(三):进程
2014/10/27 NodeJs
Node.js 学习笔记之简介、安装及配置
2015/03/03 Javascript
浅谈js常用内置方法和对象
2016/09/24 Javascript
JS button按钮实现submit按钮提交效果
2016/11/01 Javascript
Bootstrap CSS组件之面包屑导航(breadcrumb)
2016/12/17 Javascript
JavaScript定时器制作弹窗小广告
2017/02/05 Javascript
vue离开当前页面触发的函数代码
2020/09/01 Javascript
[02:16]卖萌的僵尸 DOTA2神话信使飞僵小宝来袭
2014/03/24 DOTA
python从入门到精通(DAY 1)
2015/12/20 Python
基于Python实现文件大小输出
2016/01/11 Python
Flask框架的学习指南之制作简单blog系统
2016/11/20 Python
详解Python自建logging模块
2018/01/29 Python
Python实现新型冠状病毒传播模型及预测代码实例
2020/02/05 Python
2020版Python学习路线图(附学习资料)
2020/09/15 Python
有关HTML5 Video对象的ontimeupdate事件(Chrome上无效)的问题
2013/07/19 HTML / CSS
澳大利亚制造的蜡烛和扩散器:Glasshouse Fragrances
2018/05/20 全球购物
澳大利亚最大的百货公司:Myer
2018/12/21 全球购物
Jdbc数据访问技术面试题
2012/03/30 面试题
农田水利实习自我鉴定
2013/09/19 职场文书
乡党委干部党的群众路线教育实践活动个人对照检查材料思想汇报
2014/10/01 职场文书
2014年体育部工作总结
2014/11/13 职场文书
酒店前台接待岗位职责
2015/04/02 职场文书
校园歌手大赛主持词
2015/07/03 职场文书
mysql 8.0.24版本安装配置方法图文教程
2021/05/12 MySQL
pytorch 权重weight 与 梯度grad 可视化操作
2021/06/05 Python
vue实现在data里引入相对路径
2022/06/05 Vue.js