解决使用kotlin编写OkHttp的网络请求时遇到的OOM问题

一、背景介绍:
问题的出现是因为我的代码中需要进行网络请求,而网络请求又是在一个线程中,间隔几秒就会请求一次,为了方便,就是用的OkHttp进行网络访问,本来,是没问题的,谁知道,某次测试时间长了一点,然后apk就突然闪退了,看了下log,是因为大量创建了线程,栈被撑爆了,OOM,遂卒~,通过网上找资料,最终查明原因是OkHttp的client没有进行单实例封装,每发一次请求,就会创建一个线程池,最终OOM,下面就这个问题,单独写了一个demo进行问题模拟及解决。

二、模拟复现:
1.OkHttp的异步访问代码:

fun sendOkHttpRequest(url: String, callback: okhttp3.Callback){
    val client = OkHttpClient()
    val request = Request.Builder()
        .url(url)
        .build()
    client.newCall(request).enqueue(callback)
}

2.MainActivity中直接在onCreate中起一个线程,然后每过2s进行一次网络访问:

class MainActivity : AppCompatActivity() {

    private var threadRun = true
    val updateUi = 1
    val helloWorld = 2

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        /**
         *  每2s执行一次网络访问并更新UI
         */
        thread {
            var i: Int = 1
            while (threadRun){

                /* 进行网络访问 */
                sendOkHttpRequest("https://www.baidu.com", object: Callback{
                    override fun onResponse(call: Call, response: Response) {
                        /* 拿到响应数据 */
                        val responseData = response.body?.string()
                        /* 这里为了方便,拿到数据什么也不干,直接去更新UI */
                        runOnUiThread {
                            textView.text = if ((i++ % 2) == 1){
                                "Update UI!"
                            } else {
                                "Hello World!"
                            }
                        }
                    }

                    override fun onFailure(call: Call, e: IOException) {

                    }
                })

                Thread.sleep(2000)
            }

        }
    }
}

好了,从代码中我们可以看到,UI上的TextView控件会每隔两秒更新一下text,接着运行程序,打开Profilter,开始监控CPU项,为了快速复现OOM,我们注释掉代码中的两秒延时,很快apk就闪退了,我们先看下log:
在这里插入图片描述
然后是CPU的监视情况:
在这里插入图片描述
线程在短时间内急速上升,最后在达到1500+之后OOM闪退。

三、原因分析:
在网上查资料说是由于OkHttpClient不是单实例导致的,下面简单看下源码:
在这里插入图片描述
在实例化OkHttpClient的时候,其次构造函数会去进行各种资源申请,其中就有ConnectionPool,看下这个的构造:
在这里插入图片描述
可以看到,网络连接的线程池生命周期长达5分钟,也就是说,一个OkHttpClient创建成功,需要在5分钟之后才会被回收,如果我们的代码中,5分钟内持续创建多个OkHttpClient,不是特别频繁的话,那么系统在5分钟之后会维持一个线程峰值,之后一直都是这个状态,如果非常频繁,还不到5分钟的话就会OOM,因此,问题的关键在于我们要确保代码中尽量少的创建OkHttpClient。

四、单实例的实现:
我们将OkHttp的代码进行一下单实例封装:

object HttpUtils{
    private val client = OkHttpClient()
    fun sendOkHttpRequest(url: String, callback: okhttp3.Callback){
        val request = Request.Builder()
            .url(url)
            .build()
        client.newCall(request).enqueue(callback)
    }
}

接下来onCreate中做相应修改:

        /**
         *  每2s执行一次网络访问并更新UI
         */
        thread {
            var i: Int = 1
            while (threadRun){

                /* 进行网络访问 */
                HttpUtils.sendOkHttpRequest("https://www.baidu.com", object: Callback{
                    override fun onResponse(call: Call, response: Response) {
                        /* 拿到响应数据 */
                        val responseData = response.body?.string()
                        /* 这里为了方便,拿到数据什么也不干,直接去更新UI */
                        runOnUiThread {
                            textView.text = if ((i++ % 2) == 1){
                                "Update UI!"
                            } else {
                                "Hello World!"
                            }
                        }
                    }

                    override fun onFailure(call: Call, e: IOException) {

                    }
                })

                //Thread.sleep(2000)
            }

        }

这里多说一下,想要实现UI的更新,既可以使用runOnUiThread来实现(这种线程中再创建runOnUiThread会有什么缺点我不知道),也可以使用消息机制(使用bundle附带数据)来实现。
接下来验证一下:
在这里插入图片描述
可以看到开启应用之后,线程数一直保持平稳,这样就不会有OOM的情况发生了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值