一、前言
在使用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对内存做了哪些优化?
-
某些不经常改动的组件使用memo包裹一下,memo 允许你的组件在 props 没有改变的情况下跳过重新渲染。但 React 仍可能会重新渲染它:记忆化是一种性能优化,而非保证。还有类似useCallback用于函数的缓存。
-
对于列表类的全部使用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
-
getItemLayout
如果 FlatList(VirtualizedList)的 ListLtem 高度是固定的,那么使用 getItemLayout 就非常的合算。
在源码中(#L1287、#L2046),如果不使用 getItemLayout,那么所有的 Cell 的高度,都要调用 View 的 onLayout 动态计算高度,这个运算是需要消耗时间的;如果我们使用了 getItemLayout,VirtualizedList 就直接知道了 Cell 的高度和偏移量,省去了计算,节省了这部分的开销。
具体你参考: React Native 性能优化指南
四、从RN页面返回到原生页面如何解决RN的内存泄漏?
-
iOS端
是因为原生代码里没释放
RCTBridge bridge
对象。在RCTRootView 中找到方法
首先导入头文件:#import <React/RCTRootView.h>
在合适的地方调用
[self.bridge invalidate];
如何找到bridge,然后确认是你在程序中定义的bridge对象。
经过这个方法后iOS内存得到释放。
-
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