问一个关于 kotlin 协程 lifecycleScope 用法和内存泄漏的问题。 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
m30102
V2EX    Android

问一个关于 kotlin 协程 lifecycleScope 用法和内存泄漏的问题。

  •  
  •   m30102 2021-01-07 18:05:43 +08:00 13510 次点击
    这是一个创建于 1755 天前的主题,其中的信息可能已经有所发展或是发生改变。

    在随便一个 Activity 上启动此 Activity,然后迅速关闭,leakcanary 就会报内存泄漏。引用链包括了传入的第二个参数 lambda 对象和 Okhttpclient,这里泄漏的原因是什么呢? 一般 retrofit 或者 okhttpclient 对象全局只需要一就行了吧, 如果还是需要传参和传回调的方式访问网络,该如何正确修改下面的代码呢?

    class TestActivity:AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_test) requestBylifecycleCoroutine("https://www.baidu1.com/"){ Log.w("TestActivityTAG","result:"+it) } } val client = OkHttpClient.Builder().build() fun requestBylifecycleCoroutine(url: String, callBack: (s: String) -> Unit) { lifecycleScope.launch { val result = withContext(Dispatchers.IO) { suspendCoroutine<String> { continuation -> val request = Request.Builder().url(url).build() val newCall = client.newCall(request) newCall.enqueue(object : Callback { override fun onFailure(call: Call, e: IOException) { continuation.resume("fail") } override fun onResponse(call: Call, response: Response) { continuation.resume("success") } }) } } callBack(result) } } } 
    21 条回复    2021-01-13 10:25:26 +08:00
    lianyue13
        1
    lianyue13  
       2021-01-07 18:34:01 +08:00
    协程取消的时候,请求没取消吧。用 suspendCancellableCoroutine,在 cancel 里把请求取消试试
    m30102
        2
    m30102  
    OP
       2021-01-07 21:16:54 +08:00
    @lianyue13 我换了 suspendCancellableCoroutine, 也在 invokeOnCancellation 添加了 call.cancel(). 和之前一样,leakcanary 还是会有 x retained objects,tap to dump heap,不过很快通知就变成了 All retained objects were garbage collected . 这样是为什呢,我还需要担心吗?
    k10ndike
        3
    k10ndike  
       2021-01-08 10:11:57 +08:00
    @m30102 OKHttpClient 对象是 Activity 持有的么?
    hlayk
        4
    hlayk  
       2021-01-08 13:18:42 +08:00
    如果在 viewmodel 中使用对应的 viewModelScope 那么携程 launch 返回的 job 会由 viewmodel 的 onCleared 统一自动 cancel
    你在 activity 中这样使用 lifecycleScope 可以在 onDestory() 将 lifecycleScope.launch {} 返回的对象 job 主动取消下

    [Easy Coroutines in Android: viewModelScope]( https://medium.com/androiddevelopers/easy-coroutines-in-android-viewmodelscope-25bffb605471)
    m30102
        5
    m30102  
    OP
       2021-01-08 14:43:37 +08:00
    @k10ndike OKHttpClient 对象就算写在其他类中,同样是单例的话, 也会间接持有到 activity
    m30102
        6
    m30102  
    OP
       2021-01-08 14:51:24 +08:00
    @hlayk 如果还需要考虑 onDestory,那么 lifecycleScope 就不用叫 lifefcycleScope 了。实际上 lifefcycleScope 也确实自动 cancel 了,最终的 callBack 没有执行。但是网络请求不一定能成功 cancel,而且回调时间较长,leakcanary 不知为什么显示引用到了 activity 。 如果替换为 viewModelScope 也有效,有时候一个页面就一个网络请求懒得再写一个类直接在 activity 中请求网络,这样貌似 无解?
    k10ndike
        7
    k10ndike  
       2021-01-08 15:08:35 +08:00
    @m30102 呃,Activity 怎么间接持有到的?有测试过吗,创建一个类里面就一个静态的 OkHttpClient 实例
    vanxy
        8
    vanxy  
       2021-01-08 15:49:34 +08:00 via iPad
    你这个等于用了一半的协程,用错了
    应该同步地在协程里调用 okhttp,而不是通过回调 callback 的方式

    直接 newCall.execute() 拿到 response
    m30102
        9
    m30102  
    OP
       2021-01-08 16:58:58 +08:00
    @vanxy 同步的我试了,还是一样。同步的话只是自动取消协程,但是 call.execute()方法开始执行后并不会立即取消。
    m30102
        10
    m30102  
    OP
       2021-01-08 17:00:55 +08:00
    @k10ndike 测试过, static 的 OkHttpClient 最终会通过 activity 中传入的 lambda 回调,引用到 activity
    vanxy
        11
    vanxy  
       2021-01-08 17:22:26 +08:00 via iPad
    @m30102 你需要在 execute 外面包一个 withContext,这样当 activity finish 了,就不会执行接下来的代码了
    sankemao
        12
    sankemao  
       2021-01-08 18:06:37 +08:00
    我觉得应该是 callback 持有了 activity 的引用,可以试试把结果传给 livedata
    m30102
        13
    m30102  
    OP
       2021-01-08 18:59:30 +08:00
    @vanxy 是的,我有用 withContext. 无论是 execute 还是 enqueue ,activity finish 后传的 callback 不会执行,但是 okhttp 的 call 还是会执行的。
    m30102
        14
    m30102  
    OP
       2021-01-08 19:00:48 +08:00
    @sankemao liveData 一般配合 viewmodel 用吧,难道非得 mvp 或者 mvvm 把 activity 完全隔开才行吗。。。。
    sankemao
        15
    sankemao  
       2021-01-08 19:14:53 +08:00 via iPhone
    @m30102 你可以在 ac 里面用 livedata 验证下是否还有泄漏。你也可以用其他办法
    vanxy
        16
    vanxy  
       2021-01-08 21:02:40 +08:00
    @m30102 #13 那是自然地呀,call 是一整段的阻塞式调用。 不止是协程, 实际上没有任何方式可以取消它(除非强制停止线程),协程只是可以帮你在取消 withContext 之后的所有代码执行。

    因为不执行接下来的 callback 了, 所以 activity 也不会内存泄露了。 也不需要关心 execute 是否执行完成
    m30102
        17
    m30102  
    OP
       2021-01-08 21:57:39 +08:00
    @vanxy 我反编译了半天对比了下找到原因了, 是 okhttpclient 的原因,activity 虽然执行 destory 了,但是 okhttpclient 还在执行 call,所以延长了 activity 生命,报泄漏。如果把 okhttpclient 写在其他类中声明 static, 那么 activity 中调用协程方法传的 callBack 必须不能引用 activity 任何成员变量或者 view 等,不然还是会被延长生命,一般传回调就是为了改变 view 等,所以这个是无解的!
    vanxy
        18
    vanxy  
       2021-01-08 22:01:32 +08:00
    @m30102 #17
    哦哦, 因为 kotlin 的 lambda 持有了 activity 的引用。

    所以要把执行的放到 viewmodel 或者 presenter 里,activity 解除与 viewmodel 或者 presenter 之间的引用之后, 就不会造成泄露了
    k10ndike
        19
    k10ndike  
       2021-01-08 23:15:04 +08:00 via Android
    @m30102 你本地的代码应该有其他操作吧,帖子里那样打 Log 应该不会泄露。可以在请求 callback 里调用 livedata,就不用处理生命周期了
    xhpan10
        20
    xhpan10  
       2021-01-12 13:41:35 +08:00
    协程的代码没有 rxjava 的好看
    DiDiz
        21
    DiDiz  
       2021-01-13 10:25:26 +08:00
    @xhpan10 也要封装的,封装好了协程代码就和普通同步代码一样。相比之下 rxjava 就显得很嗦了
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1356 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 25ms UTC 23:55 PVG 07:55 LAX 16:55 JFK 19:55
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86