基于Android Studio3.1.3和Kotlin开发,使用到Android Profiler和LeakCanary工具
最新在项目中使用到websocket和服务器交互,发现项目的主要页面存在严重的内存泄漏问题,下面通过示例记录排查、分析的解决步骤:
-
造成内存泄漏的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 = 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()
}
}
-
使用Android Profiler初步分析
-
使用LeakCanary精确定位
从上图可以看到内存泄漏的引用链,由于该类实例被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修饰的类为普通的内部类
网友评论