记一次Rxjava导致的内存泄漏
昨天检测出Rxjava
导致的内存泄漏之后,一时无从下手;想了一晚上还是无果,于是今天早上搜谷歌的时候换了另一个关键词rxjava ObservableOnSubscribe内存泄漏
, 于是乎在网上找到了一篇大佬的文章,果断读之并加以收藏:
首先,安卓中内存泄漏无外乎一点:生命周期长的对象持有生命周期短的对象,导致GC
无法到达从而引起内存泄漏的问题;那什么对象生命周期会长呢?
一般生命周期比较长的对象:
单例
、匿名内部类
、Handler
、Static局部变量
等等。
事情的起因:
某一天闲暇的下午,运营人员在线上直播间举办了一场轰轰烈烈的K歌
活动;在某个时刻,在线人数一度达到五百人以上,公司各部高管进入直播间送礼物的送礼物~上麦位聊天的聊天,一开始的场面十分和谐;过了不到半小时,主持人那边反映说,客户端卡顿,消息数一直不停的刷;一些人就提议说把消息显示的公屏直接关了,聊天什么的都不显示;这样能够保证直播间不再那么卡顿,活动也能将就着进行!
分析:
直播间卡顿,一个是直播间人数过多,导致推流与拉流会很频繁,网络请求也会在回调的时候更新UI
从而使得在16ms中处理过多的逻辑;同样的,在直播间人数过多的情况下,里面的观众会和麦位上的主播们互动,会不断的发送消息,不断的赠送礼物,所以在公屏下面会一直有消息;
当时我观察到在一秒钟内可以达到几百条消息,这是很恐怖的,也是很正常的,但是我们的界面就会很卡,而关闭公屏之后,界面就不会出现卡顿,所以我可以断定出现的问题在公屏上面的RecyclerView
上,因为来一条消息会去notify
一下,来一条消息就会刷新界面,这是代码逻辑处理不当导致的,所以我们得从公屏消息优化着手;
优化开始:
既然来一条消息就会导致界面的更新,我们完全可以做一个缓冲池的操作,把一段时间内进入的消息缓存起来,然后达到了这时间就一次性去刷新界面,这样就不会有在16ms内做过多的处理问题了;那问题是:这个缓冲池怎么写呢?
第一种方法
我们可以使用Handler
去延时处理,然后将数据保存到LinkedBlockingQueue
中,等到达一定时间后就从队列中取数据, LinkedBlockingQueue
有一个特性是同步和从中取就删掉了元素,所以我们不需要管理数据的增删操作,这样的做法缺陷比较明显,使用Handler
比较容易出现内存泄漏,而且代码维护起来也不太方便,这里不做演示~
另外一种方法
因为项目中我们大量都使用到了RxJava
,所以当有一些特别复杂的逻辑的时候,我会优先考虑使用到RxJava
,其中RxJava
就有一个操作符buffer
, 关于buffer
的操作这里不做过多的解释,意思和我们优化前分析的一样:先将一段时间内的数据保存,然后过一段时间再一次性刷新界面;代码如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
adapter = new CurrentListAdapter(null);
recyclerView.setAdapter(adapter);
// 在oncreate() 方法中创建方法
compositeDisposable.add(
Observable
.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> emitter) throws Exception {
if (!emitter.isDisposed()) {
mEmitter = emitter;
}
}
})
.buffer(1_000, TimeUnit.MILLISECONDS)
.subscribeOn(Schedulers.io())
.observerOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<List<String>>() {
@Override
public void accept(List<String> strings) throws Exception {
// TODO
Log.d(TAG, "接受数据");
adapter.addDatas(strings);
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
}
})
);
}
方法很简单,使用create
创建操作符,在onCreate
的时候创建一次发射器,然后拿到发射器之后可以在当前页面发送数据了,然后buffer
操作符传入时间间隔,这里我们模拟在一秒钟内获取, 我们写一个按钮来发送数据:
int index = 0;
/**
* 添加数据
*
* @param view
*/
public void addData(View view) {
for (int i = index; i < index + 5; i++) {
mEmitter.onNext(i + "");
Log.d(TAG, "发送数据");
}
index += 5;
}
运行代码, 点击按钮,我们每次发送5个数据,这样在accept
方法中就一次性更新了界面;很完美,这样的处理方式我们完全能接受, 当我们点击按钮:
// 初始化走一次
2019-12-15 18:47:31.442 21352-21352/com.example.rxjavamemoryleak D/MainActivity: 接受数据
// 点击按钮发送数据
2019-12-15 18:47:31.636 21352-21352/com.example.rxjavamemoryleak D/MainActivity: 发送数据
2019-12-15 18:47:31.636 21352-21352/com.example.rxjavamemoryleak D/MainActivity: 发送数据
// 沦陷获取数据
2019-12-15 18:47:32.443 21352-21352/com.example.rxjavamemoryleak D/MainActivity: 接受数据
2019-12-15 18:47:33.443 21352-21352/com.example.rxjavamemoryleak D/MainActivity: 接受数据
2019-12-15 18:47:34.442 21352-21352/com.example.rxjavamemoryleak D/MainActivity: 接受数据
2019-12-15 18:47:35.443 21352-21352/com.example.rxjavamemoryleak D/MainActivity: 接受数据
2019-12-15 18:47:36.442 21352-21352/com.example.rxjavamemoryleak D/MainActivity: 接受数据
2019-12-15 18:47:37.443 21352-21352/com.example.rxjavamemoryleak D/MainActivity: 接受数据
2019-12-15 18:47:40.443 21352-21352/com.example.rxjavamemoryleak D/MainActivity: 接受数据
2019-12-15 18:47:41.444 21352-21352/com.example.rxjavamemoryleak D/MainActivity: 接受数据
2019-12-15 18:47:56.445 21352-21352/com.example.rxjavamemoryleak D/MainActivity: 接受数据
// 再次点击按钮发送数据
2019-12-15 18:47:56.938 21352-21352/com.example.rxjavamemoryleak D/MainActivity: 发送数据
2019-12-15 18:47:56.938 21352-21352/com.example.rxjavamemoryleak D/MainActivity: 发送数据
2019-12-15 18:47:57.443 21352-21352/com.example.rxjavamemoryleak D/MainActivity: 接受数据
2019-12-15 18:47:58.443 21352-21352/com.example.rxjavamemoryleak D/MainActivity: 接受数据
2019-12-15 18:47:59.443 21352-21352/com.example.rxjavamemoryleak D/MainActivity: 接受数据
2019-12-15 18:48:00.443 21352-21352/com.example.rxjavamemoryleak D/MainActivity: 接受数据
2019-12-15 18:48:01.445 21352-21352/com.example.rxjavamemoryleak D/MainActivity: 接受数据
2019-12-15 18:48:02.443 21352-21352/com.example.rxjavamemoryleak D/MainActivity: 接受数据
2019-12-15 18:48:03.445 21352-21352/com.example.rxjavamemoryleak D/MainActivity: 接受数据
2019-12-15 18:48:04.444 21352-21352/com.example.rxjavamemoryleak D/MainActivity: 接受数据
界面显示:
rxjava_buffer.gif
可以看到,每次点击按钮增加数据,会一次性添加五个数据,而且顺序和数据都没改变。
问题出现:
当我兴致勃勃的提交代码,为自己的【机智】感到高兴的时候,页面突然报出leakcanary
内存检测的弹窗,当时没有太注意,就提交了代码。
相信大家对这张图很熟悉了,通过profile
我们也能看到,Activity
结束之后,内存根本没有释放,反复的进入到这列表页面之后,内存会持续的增加: