记一次Rxjava导致的内存泄漏

昨天检测出Rxjava导致的内存泄漏之后,一时无从下手;想了一晚上还是无果,于是今天早上搜谷歌的时候换了另一个关键词rxjava ObservableOnSubscribe内存泄漏, 于是乎在网上找到了一篇大佬的文章,果断读之并加以收藏:

一张图搞定-RxJava2的线程切换原理和内存泄露问题分析

首先,安卓中内存泄漏无外乎一点:生命周期长的对象持有生命周期短的对象,导致GC无法到达从而引起内存泄漏的问题;那什么对象生命周期会长呢?

一般生命周期比较长的对象:单例匿名内部类HandlerStatic局部变量等等。

事情的起因:

某一天闲暇的下午,运营人员在线上直播间举办了一场轰轰烈烈的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内存检测的弹窗,当时没有太注意,就提交了代码。

leak内存泄漏.png

相信大家对这张图很熟悉了,通过profile我们也能看到,Activity结束之后,内存根本没有释放,反复的进入到这列表页面之后,内存会持续的增加:

profile检测.png

分析
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,165评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,720评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,849评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,245评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,596评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,747评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,977评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,708评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,448评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,657评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,141评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,493评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,153评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,890评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,799评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,685评论 2 272

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,039评论 1 32
  • RxJava系列文章目录导读: 一、RxJava create操作符的用法和源码分析二、RxJava map操作符...
    Chiclaim阅读 1,863评论 0 1
  • 不可以,你的人生不可以再有一天像今天一样。 不可以再有一次这样的一天。 不可以再有一次一样的经历。 不可以再有这样...
    宗门君阅读 136评论 0 2
  • 但凡去西安旅游,回民小吃一条街是必去之地。 到达的当晚,跑马似地节奏,游览大雁塔、观赏音乐喷泉、迷人的钟鼓楼夜景…...
    烟雨縹湘阅读 570评论 3 5
  • 如何给儿童选择沐浴露 宝宝的肌肤构造与成人不同,由于宝宝的皮肤较为细嫩,如果反复使用一般的沐浴露,会对宝宝的皮肤造...
    美卡2019阅读 279评论 0 0