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

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

作者: 就叫汉堡吧 | 来源:发表于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