对于RN的内存占用问题我都做了哪些优化?

一、前言

在使用RN的时候,我们并不是单独的使用RN开发一整个app,而更多的是将 rn 作为内部的一些独立模块/页面。熟悉 rn 的同学应该都清楚,想要 rn 页面在 app 中启动需要内置其一堆底层框架,并且初始化操作才可以。然后随着操作RN的页面,假如在页面中存在大量图片就会发现内存一直居高不下,返回到原生页面后会有内存溢出的现象存在。

二、图片占用内存如何优化?

在测试过程中我们发现随着页面上的图片不断的增加,app的内存也随之增加。首先我们想到的是把所有的图片都采用压缩服务压缩一遍,我们也确实这么做了,内存确实下去了不少。但是和要求的还相差甚远,这个时候我们考虑替换掉RN的原生Image标签,采用那就是 react-native-fast-image这个组件,这个组件的底层在安卓端是通过Glide来管理图片的,在iOS使用SDWebImage来处理图片的。react-native-fast-image 具体文档参考这里。替换过后内存确实下降了一点点,但是还达不到要求。通过对安卓内存的分析发现它被占用的大部分是Bitmap数据,也就是图片,为什么一直增加不释放呢?通过网上查找发现了一个方式去解决这个问题。在github上找到解决方案,调整cache大小。How can i set cache size

参考代码如下:

java 复制代码
public class BitmapMemoryCacheParamsSupplier implements Supplier<MemoryCacheParams> {

    private static final int CACHE_DIVISION = 300; // cache size will be 1/8 of the max allocated app memory
    private static final int MAX_CACHE_ENTRIES = 10;
    private static final int MAX_EVICTION_QUEUE_SIZE = Integer.MAX_VALUE;
    private static final int MAX_EVICTION_QUEUE_ENTRIES = Integer.MAX_VALUE;
//    private static final int MAX_CACHE_ENTRY_SIZE = Integer.MAX_VALUE;
//
//    private static final int MAX_CACHE_ENTRIES = 56;
    private static final int MAX_CACHE_ASHM_ENTRIES = 128;
    private static final int MAX_CACHE_EVICTION_SIZE = 5;
    private static final int MAX_CACHE_EVICTION_ENTRIES = 5;
    private final ActivityManager mActivityManager;

//    private final ActivityManager mActivityManager;

    public CustomBitmapMemoryCacheParamsSupplier(Context context) {
        mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
    }

    @Override
    public MemoryCacheParams get() {
        return new MemoryCacheParams(
                getMaxCacheSize(),
                MAX_CACHE_ENTRIES,
//                MAX_EVICTION_QUEUE_SIZE,
//                MAX_EVICTION_QUEUE_ENTRIES, MAX_CACHE_ENTRY_SIZE
                MAX_CACHE_ASHM_ENTRIES,
                MAX_CACHE_EVICTION_SIZE,
                MAX_CACHE_EVICTION_ENTRIES);
    }

    private int getMaxCacheSize() {
        final int maxMemory =
                Math.min(mActivityManager.getMemoryClass() * ByteConstants.MB, Integer.MAX_VALUE);
        if (maxMemory < 32 * ByteConstants.MB) {
            return 4 * ByteConstants.MB;
        } else if (maxMemory < 64 * ByteConstants.MB) {
            return 6 * ByteConstants.MB;
        } else {
            // We don't want to use more ashmem on Gingerbread for now, since it doesn't respond well to
            // native memory pressure (doesn't throw exceptions, crashes app, crashes phone)
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
                return 8 * ByteConstants.MB;
            } else {
                return maxMemory / CACHE_DIVISION;
            }
        }
    }
}

在iOS端使用SDWebImage这个依赖包对图片进行压缩,代码如下:

objective-c 复制代码
// 根据 aSize 返回一个新的image
- (UIImage *)setUIImage:(UIImage *)image drawImageBySize:(CGSize)size; {
    UIGraphicsBeginImageContextWithOptions(size, NO, [[UIScreen mainScreen] scale]);
    // 
    [image drawInRect:CGRectMake(0, 0, size.width, size.height)];
    // 
    UIImage * scaledImage = UIGraphicsGetImageFromCurrentImageContext();
    // 使当前的context出堆栈
    UIGraphicsEndImageContext();
    // 返回新的改变大小后的图片
    return scaledImage;
}

/**
 * 压缩图片到指定文件大小
 * @param image 目标图片
 * @param size 目标大小(最大值),例如最大1M,就传1024
 * @return 返回的图片文件
 */
- (NSData *)compressOriginalImage:(UIImage *)image toMaxDataSizeKBytes:(CGFloat)size {
    NSData * data = UIImageJPEGRepresentation(image, 0.5);
    
    CGFloat dataKBytes = data.length/1024.0;
    CGFloat maxQuality = 0.9f;
    CGFloat lastData = dataKBytes;
    while (dataKBytes > size && maxQuality > 0.01f) {
        maxQuality = maxQuality - 0.01f;
        data = UIImageJPEGRepresentation(image, maxQuality);
        dataKBytes = data.length / 1024.0;
        if (lastData == dataKBytes) {
            break;
        }
        else {
            lastData = dataKBytes;
        }
    }
    return data;
}

@end

三、利用react hooks和react native对内存做了哪些优化?

  1. 某些不经常改动的组件使用memo包裹一下,memo 允许你的组件在 props 没有改变的情况下跳过重新渲染。但 React 仍可能会重新渲染它:记忆化是一种性能优化,而非保证。还有类似useCallback用于函数的缓存。

  2. 对于列表类的全部使用FlatList组件

​ 参考文档:

​ 本组件实质是基于](https://reactnative.cn/docs/virtualizedlist)组件的封装,继承了其所有 props(也包括所有[)的 props),但在本文档中没有列出。此外还有下面这些需要 注意的事项:

  • 当某行滑出渲染区域之外后,其内部状态将不会保留。请确保你在行组件以外的地方保留了数据。

  • 本组件继承自PureComponent而非通常的Component,这意味着如果其props在浅比较中是相等的,则不会重新渲染。所以请先检查你的renderItem函数所依赖的props数据(包括data属性以及可能用到的父组件的 state),如果是一个引用类型(Object 或者数组都是引用类型),则需要先修改其引用地址(比如先复制到一个新的 Object 或者数组中),然后再修改其值,否则界面很可能不会刷新。(译注:这一段不了解的朋友建议先学习下js 中的基本类型和引用类型。)

  • 为了优化内存占用同时保持滑动的流畅,列表内容会在屏幕外异步绘制。这意味着如果用户滑动的速度超过渲染的速度,则会先看到空白的内容。这是为了优化不得不作出的妥协,你可以根据自己的需求调整相应的参数,而我们也在设法持续改进。

  • 默认情况下每行都需要提供一个不重复的 key 属性。你也可以提供一个keyExtractor函数来生成 key。

    如果这个组件你觉得还不够极致,建议使用: recyclerlistview 参考: React Native系列:使用Recyclerlistview

  1. getItemLayout

    如果 FlatList(VirtualizedList)的 ListLtem 高度是固定的,那么使用 getItemLayout 就非常的合算。

    在源码中(#L1287、#L2046),如果不使用 getItemLayout,那么所有的 Cell 的高度,都要调用 View 的 onLayout 动态计算高度,这个运算是需要消耗时间的;如果我们使用了 getItemLayout,VirtualizedList 就直接知道了 Cell 的高度和偏移量,省去了计算,节省了这部分的开销。

​ 具体你参考: React Native 性能优化指南

四、从RN页面返回到原生页面如何解决RN的内存泄漏?

  1. iOS端

    是因为原生代码里没释放 RCTBridge bridge 对象。

    在RCTRootView 中找到方法

    首先导入头文件:#import <React/RCTRootView.h>

    在合适的地方调用

    objective-c 复制代码
     [self.bridge invalidate];

    如何找到bridge,然后确认是你在程序中定义的bridge对象。

    经过这个方法后iOS内存得到释放。

    参考: Swift & React-Native 混编,内存泄漏问题解决

  2. Android 端

    java 复制代码
    Instrumentation instrumentation = new Instrumentation();
    instrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
    myView.getReactDelegate().onHostDestroy(mContext);
    myView.getReactDelegate().destroy();

    手动调用react native的销毁方法,把RN实例销毁。

相关推荐
卸任15 小时前
Vibe coding工具使用心得
前端·ai编程
brien916 小时前
为什么模型标称 1M,Google Antigravity 聊到 256K 却突然失忆?我们给它做了个原生行内监控圆环
前端
修炼的dance16 小时前
录屏关键帧间隔多少合适?六档实测,2秒到10秒小了75.6%
前端
Lank_M16 小时前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写16 小时前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李16 小时前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易16 小时前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
风骏时光牛马17 小时前
前沿视界
前端
kyriewen17 小时前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员
陈随易17 小时前
3000 万人在用的翻译插件,重写一遍就是一门生意
前端·后端·程序员