美文网首页
关于一个网络请求相关的内存泄露

关于一个网络请求相关的内存泄露

作者: Horps | 来源:发表于2023-03-28 17:25 被阅读0次
  • 概述

    基于Android Studio的Profiler和LeakCanary等工具对项目进行内存泄露问题的排查时,发现在使用RxJava结合RxLifecycle进行网络接口请求的时候,在网络请求返回之前关闭Activity后会出现内存泄露,在此记录排查思路。

  • dump hprof

    LeakCanary的dump记录如下:

hprof.jpg
  • 排查

    代码中使用了匿名类构造HttpObserver:

    object :
        HttpObserver<CaTripDetailDaysResponse, CaTripDetailDaysRequest>(
            request,
            RxApiManager.HttpTaskId.TASK_ID_CA_TRIP_DETAIL_DAYS,
            iViewModelService
        ) {
        override fun onSuccess(response: CaTripDetailDaysResponse) {
            daysData.postValue(response)
        }
    
        override fun onFailure(response: BaseResponse) {
            super.onFailure(response)
            daysData.postValue(CaTripDetailDaysResponse().apply {
                resultCode = response.resultCode
                resultMsg = response.resultMsg
            })
        }
    }
    
    constructor(request: K, httpTaskId: RxApiManager.HttpTaskId, mIViewModelService: IViewModelService?) {
        this.request = request
        this.mIViewModelService = mIViewModelService
        this.httpTaskId = httpTaskId
    }
    

    因为日志中出现了this$0,所以一度以为是匿名内部类的问题,以为是子线程未执行完导致未释放HttpObserver,在HttpObserver的构造方法里把mIViewModelService(在这里的架构里就是Activity本身),从而导致Activity和ViewModel没释放。

    但是经过在onError和onComplete等回调方法中把mIViewModelService置为null后,在网路请求迟迟没有完成前(可以通过外网访问内网api来进行模拟)关闭Activity还是会出现泄露,打断点显示,在这个场景下,这些回调方法并没有调用...

    再从别的信息分析,从日志上可以看出,有可能是RxApiManager的maps出现的问题,因为RxApiManager.mInstance是静态实例,又因为在HttpObserver的onSubscribe方法中:

    override fun onSubscribe(d: Disposable) {
        disposable = d;
        if (this.httpTaskId != null) {
            RxApiManager.get().add(httpTaskId, d)
        } else if (this.httpTaskStringId != null) {
            RxApiManager.get().addHttpStringTask(httpTaskStringId, d)
        }
        if (showLoading) {
            mIViewModelService?.preLoading()
        }
    }
    

    可以看到,这里会把RxJava网络请求链对象放在RxApiManager的maps中,所以它的maps会一直持有Activity实例,是不是它导致无法释放呢?

    但随后我们在HttpObserver中还发现了:

    override fun onComplete() {
        if (this.httpTaskId != null) {
            RxApiManager.get().cancel(this.httpTaskId)
        } else if (this.httpTaskStringId != null) {
            RxApiManager.get().cancelHttpStringTask(this.httpTaskStringId)
        }
        if (!manuelFinishLoading && showLoading && finishCount == 1) {
            mIViewModelService?.finishLoading()
        }
    }
    

    可以看到,这里会调用cancel方法进行取消(内部其实是remove掉RxJava网络请求链对象),那为什么还会出现RxApiManager的maps泄露呢?

  • 结论

    经过场景复现,发现在等待完接口请求正常返回后再关闭Activity的时候不会出现内存泄露,只有在请求尚未完成之前关闭Activity才会出现泄露。再结合RxLifecycle的原理,我们可以做出结论:

    RxLifecycle只是将调用链dispose掉,dispose之后调用传递会被终止,因此这就是onError和onComplete未调用的原因,所以在onSubscribe中add到RxApiManager的maps中的Disposable就得不到对应的remove,又因为Disposable调用链中持有了HttpObserver,HttpObserver又持有了Activity和ViewModel(this$0),所以就发生了泄露,解决办法就是在Activity或者ViewModel销毁的时候调用RxApiManager的cancel方法移除对应的调用链。

相关文章

网友评论

      本文标题:关于一个网络请求相关的内存泄露

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