美文网首页
ReactNative FlatList Carsh Memor

ReactNative FlatList Carsh Memor

作者: 一本大书 | 来源:发表于2021-04-09 11:50 被阅读0次

    问题描述

    最近用户反馈iOS客户端进入餐厅首页后,过几秒钟后闪退了。

    分析解决

    原因内存泄漏导致。

    运营上架了新菜品,配置了高清图片,列表同时展示多张高清图片内存不足。
    首先要知道几点:
    1.RN上的列表是没有复用机制的,这就导致列表上的所有图片对象都会同时被持有。
    2.运营在后台配置了高清图片,因我司的餐厅模块依赖了哗啦啦平台(为了支持线下下单,购买了双屏机,餐品由双屏机操作录入),而哗啦啦提供的服务并不支持同一个菜品配置多张图片用于展示缩略图、高清图,同时也不支持动态获取指定尺寸的图片。
    3.我司开发人员无法从技术上约束运营只能配置小尺寸图片。
    解决方案:
    iOS Native 添加图片裁剪接口给 js 使用。

    对比前后

    优化前 优化后

    具体实现

    思路:
    1.先判断本地是否已经有裁剪好的图片可以使用,如果有就直接使用。
    2.依赖SDWebImage下载图片。
    3.根据指定尺寸裁剪图片。
    4.保存图片到本地。
    5.返回本地图片路径给js。

    要注意的是需要控制图片处理的并发量,如果同时处理多张图片同样有内存问题。
    一下为Native的代码,只要看 -getImage:resolve:rejecter 方法即可。

    #define QD_LOCK(lock) dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
    #define QD_UNLOCK(lock) dispatch_semaphore_signal(lock);
    
    @interface ImageDownloadModule() <RCTBridgeModule>
    
    /// 图片处理并发数
    @property (strong, nonatomic, nonnull) dispatch_semaphore_t imageHandleLock;
    
    @end
    
    @implementation ImageDownloadModule
    
    RCT_EXPORT_MODULE()
    
    - (instancetype)init
    {
      self = [super init];
      if (self) {
        self.imageHandleLock = dispatch_semaphore_create(3);
      }
      return self;
    }
    
    RCT_EXPORT_METHOD(getImage:(NSDictionary *)params
                      resolve:(RCTPromiseResolveBlock)resolve
                      rejecter:(RCTPromiseRejectBlock)reject)
    {
      NSString *url = params[@"url"];
      CGFloat width = [params[@"width"] integerValue];
      CGFloat height = [params[@"height"] integerValue];
      NSString *qdResizeMode = params[@"qdResizeMode"];
      // 先查找本地是否存在
      NSString *fileName = [self fileNameWithUrl:url width:width height:height resizeMode:qdResizeMode];
      NSString *path = [[self diskCachePath] stringByAppendingPathComponent:fileName];
      if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
        resolve(@{ @"path": path });
        return;
      }
      // 控制并发数
      QD_LOCK(self.imageHandleLock)
      
      // 依赖SDWebImage下载图片
      __weak typeof(self) weakSelf = self;
      [[SDWebImageManager sharedManager] loadImageWithURL:[NSURL URLWithString:url]
                                                  options:0
                                                 progress:^(NSInteger receivedSize,
                                                            NSInteger expectedSize,
                                                            NSURL * _Nullable targetURL)
      {
        // 下载过程
      } completed:^(UIImage * _Nullable image,
                    NSData * _Nullable data,
                    NSError * _Nullable error,
                    SDImageCacheType cacheType,
                    BOOL finished,
                    NSURL * _Nullable imageURL)
      {
        // 获取图片完成
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
          // 获取指定尺寸
          UIViewContentMode mode = [qdResizeMode isEqualToString:@"contain"] ? UIViewContentModeScaleAspectFit : UIViewContentModeScaleAspectFill;
          CGRect rect = [weakSelf aspectRectFromSize:image.size
                                          toSize:CGSizeMake(width, height)
                                     contentMode:mode];
          // 根据尺寸裁剪
          UIGraphicsBeginImageContextWithOptions(CGSizeMake(width, height), NO, 1.0f);
          [image drawInRect:rect];
          UIImage *imagez = UIGraphicsGetImageFromCurrentImageContext();
          UIGraphicsEndImageContext();
          // 内存优化
          NSString *cacheKey = [SDWebImageManager.sharedManager cacheKeyForURL:imageURL];
          [[SDImageCache sharedImageCache] removeImageFromMemoryForKey:cacheKey];
          // 保存到本地
          NSData *pngData = UIImagePNGRepresentation(imagez);
          BOOL result = [pngData writeToFile:path atomically:YES];
          if (result) {
            resolve(@{ @"path": path });
          } else {
            NSError *err = [NSError errorWithDomain:@"" code:1 userInfo:nil];
            reject(@"1", @"", err);
          }
          QD_UNLOCK(weakSelf.imageHandleLock)
        });
      }];
    }
    
    - (NSString *)fileNameWithUrl:(NSString *)url
                        width:(NSInteger)width
                       height:(NSInteger)height
                       resizeMode:(NSString *)resizeMode
    {
      @try {
        NSString *fileName = [NSString stringWithFormat:@"%@_%ld_%ld_%@", resizeMode, (long)width, (long)height, url.lastPathComponent];
        return fileName;
      } @catch (NSException *exception) {
        return @"";
      } @finally {
      }
    }
    
    - (NSString *)diskCachePath {
      NSString *folderName = @"tmp";
      NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
      NSString *path = [paths[0] stringByAppendingPathComponent:folderName];
      NSFileManager *manager = [NSFileManager defaultManager];
      BOOL isExist = [manager fileExistsAtPath:path];
      if (!isExist) {
          [manager createDirectoryAtPath:path
             withIntermediateDirectories:YES
                              attributes:nil
                                   error:nil];
      }
      return path;
    }
    
    - (CGRect)aspectRectFromSize:(CGSize)fromSize
                          toSize:(CGSize)toSize
                     contentMode:(UIViewContentMode)contentMode
    {
        CGRect rect = CGRectZero;
        
        // tosize 200x200
        CGFloat scaleW = fromSize.width / toSize.width;
        CGFloat scaleH = fromSize.height / toSize.height;
        CGFloat scale;
        if (contentMode == UIViewContentModeScaleAspectFit) {
            scale = MAX(scaleW, scaleH);
        } else if (contentMode == UIViewContentModeScaleAspectFill) {
            scale = MIN(scaleW, scaleH);
        } else {
            scale = MIN(scaleW, scaleH);
        }
        
        CGFloat width = fromSize.width / scale;
        CGFloat height = fromSize.height / scale;
        
        CGFloat x = (toSize.width - width) * 0.5;
        CGFloat y = (toSize.height - height) * 0.5;
        rect = CGRectMake(x, y, width, height);
        
        return rect;
    }
    
    @end
    

    对于js端,包装一个使用方法与Image相似的组件即可。

              // 具体使用
              <SizeImage
                style={styles.skuItemIcon}
                source={{ uri: imgePath || '' }}
                width={W(144) * 1.5}
                height={W(144) * 1.5}
                qdResizeMode='cover'
              />
    

    <SizeImage>内部实现

    export class SizeImage extends Component {
        static defaultProps = {
            width: 100,
            height: 100,
            /** 只支持 cover 、 contain */
            qdResizeMode: 'cover',
        };
    
        constructor(props) {
            super(props);
            const { source, width, height, qdResizeMode } = props || {}
            const { uri } = source || {}
    
            this.state = {
                localPath: ''
            }
    
            const { ImageDownloadModule } = NativeModules
            if (Platform.OS === 'ios' && ImageDownloadModule && uri) {
                // 调用Native,获取缩略图localPath
                ImageDownloadModule.getImage({ 
                    url: uri, 
                    width,
                    height,
                    qdResizeMode,
                }).then(res => {
                    const { path } = res || {}
                    this.setState({ localPath: path })
                })
            }
        }
    
        render() {
            const { localPath } = this.state
            const { style, source } = this.props || {}
            const { uri } = source || {}
            const { ImageDownloadModule } = NativeModules
            if (Platform.OS === 'ios' && ImageDownloadModule && uri) {
                return <Image style={style} source={localPath ? { uri: localPath } : IMAGE.defaultPlaceholder} />
            } else {
                return <Image style={style} source={source} />
            }
        }
    }
    
    

    总结

    这种场景很少遇到,但就是这种场景需要去思考解决成长,给平淡的工作加点料。

    相关文章

      网友评论

          本文标题:ReactNative FlatList Carsh Memor

          本文链接:https://www.haomeiwen.com/subject/aqjikltx.html