美文网首页
记一次Rxjava导致的内存泄漏

记一次Rxjava导致的内存泄漏

作者: 设计失 | 来源:发表于2019-12-15 21:48 被阅读0次

    昨天检测出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
    分析

    相关文章

      网友评论

          本文标题:记一次Rxjava导致的内存泄漏

          本文链接:https://www.haomeiwen.com/subject/egdqnctx.html