对于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

参考代码如下:

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这个依赖包对图片进行压缩,代码如下:

// 根据 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>

    在合适的地方调用

     [self.bridge invalidate];
    

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

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

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

  2. Android 端

    Instrumentation instrumentation = new Instrumentation();
    instrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
    myView.getReactDelegate().onHostDestroy(mContext);
    myView.getReactDelegate().destroy();
    

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

原文链接:https://juejin.cn/post/7324501285624610879 作者:VisuperviReborn

(0)
上一篇 2024年1月17日 上午10:16
下一篇 2024年1月17日 上午10:26

相关推荐

发表回复

登录后才能评论