WebSocket和Handler引发的内存泄漏处理

2018-08-01  本文已影响0人  刺客的幻影
image.png
基于Android Studio3.1.3和Kotlin开发,使用到Android Profiler和LeakCanary工具

最新在项目中使用到websocket和服务器交互,发现项目的主要页面存在严重的内存泄漏问题,下面通过示例记录排查、分析的解决步骤:

class LeakTestActivity : BaseActivity() {
    private lateinit var mSocketClient: WebSocketClient

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_leak_test)
        getData()
    }

    val handler = @SuppressLint("HandlerLeak")
    object : Handler() {
        override fun handleMessage(msg: Message?) {
            when (msg?.what) {
            //todo
            }
        }
    }

    private fun getData() {
        mSocketClient = object : WebSocketClient(URI(ApiConstants.SOCKET_ADDRESS)) {
            override fun onError(ex: Exception?) {
            }

            override fun onMessage(message: String?) {
            }

            override fun onMessage(bytes: ByteBuffer?) {
                super.onMessage(bytes)
                val msg = handler.obtainMessage()
                msg.what = 1
                msg.obj = bytes
                handler.sendMessage(msg)
            }

            override fun onClose(code: Int, reason: String?, remote: Boolean) {
            }

            override fun onOpen(handshakedata: ServerHandshake?) {
            }
        }
        mSocketClient.connect()
        if (mSocketClient.isOpen) {
            mSocketClient.send(getUnSubUrl("etcbtc"))
        }
    }


    override fun onDestroy() {
        super.onDestroy()
        handler.removeCallbacksAndMessages(null)
        mSocketClient.close()
    }

}
进入被检测的Activity然后退出,点击垃圾桶执行GC操作,继续运行程序,点击向下箭头,可查看内存分配结果: leak_img1.jpeg leak_img2.jpeg leak_img3.png

从上图可以看到内存泄漏的引用链,由于该类实例被QuotesDetailActivity的getData方法中的0所引用,导致无法回收,而 0就是代表匿名内部类的写法,这里就是指我们创建的WebSocketClient匿名内部类

因为WebSocketClient与网络的交互是一个耗时操作,当页面关闭时操作还没结束,而匿名内部类又默认持有外部类的引用,导致了这个Activity不能被GC掉

使用静态类代替匿名内部类,由于静态类不能直接调用外部类成员,所以构造WebSocketClient对象的时候传入Activity对象的弱引用,具体代码如下:

class LeakTestActivity : BaseActivity() {
    private lateinit var mSocketClient: WebSocketClient

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_leak_test)
        getData()


    }

    val handler = @SuppressLint("HandlerLeak")
    object : Handler() {
        override fun handleMessage(msg: Message?) {
            when (msg?.what) {
            //todo
            }
        }
    }

    private fun getData() {
        mSocketClient = MyWebSocketClient()
        mSocketClient.connect()
        if (mSocketClient.isOpen) {
            mSocketClient.send(getUnSubUrl("etcbtc"))
        }
    }


    class MyWebSocketClient(activity: LeakTestActivity) :WebSocketClient(URI(ApiConstants.SOCKET_ADDRESS)){
        val weakReference = WeakReference<LeakTestActivity>(activity)
        override fun onError(ex: Exception?) {
        }

        override fun onMessage(message: String?) {
            val mActivity = weakReference.get()?:return
            // do sth 调用mActivity的成员
            
        }

        override fun onClose(code: Int, reason: String?, remote: Boolean) {
        }

        override fun onOpen(handshakedata: ServerHandshake?) {
        }

    }

    override fun onDestroy() {
        super.onDestroy()
        handler.removeCallbacksAndMessages(null)
        mSocketClient.close()
    }

}

再次使用Android Porfiler和LeakCanary检测,问题得到解决,handler的泄漏情况可用同种方式处理,因为当MessageQueue里的消息未处理完,而handler又持有外部类的引用也会导致页面关闭而内存不能及时回收

匿名内部类中不要做耗时操作,应该使用静态类和弱引用
tips : Kotlin中class修饰的类默认为静态类,inner class修饰的类为普通的内部类

上一篇下一篇

猜你喜欢

热点阅读