千家信息网

react-native源码图片缓存问题的示例分析

发表于:2024-09-24 作者:千家信息网编辑
千家信息网最后更新 2024年09月24日,小编给大家分享一下react-native源码图片缓存问题的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!为xco
千家信息网最后更新 2024年09月24日react-native源码图片缓存问题的示例分析

小编给大家分享一下react-native源码图片缓存问题的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!

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

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

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

  // 1、 加载远程图片  //2、加载xcode中图片  //3、加载js中图片

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

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

RCT_REMAP_VIEW_PROPERTY(source, imageSources, NSArray);

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

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

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

可见,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自动生成的,非图片的磁盘缓存。

以上是"react-native源码图片缓存问题的示例分析"这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注行业资讯频道!

0