xuexiangjys / XUpdate

🚀A lightweight, high availability Android version update framework.(一个轻量级、高可用性的Android版本更新框架)
https://github.com/xuexiangjys/XUpdate/wiki
Apache License 2.0
2.32k stars 406 forks source link

取消下载内存泄漏 #112

Closed mafanwei closed 3 years ago

mafanwei commented 3 years ago

问题描述(必填) 当在下载中取消下载,内存泄漏

使用的XUpdate版本(必填) 2.0.2

如何重现(必填) 重现的步骤: 在应用程序启动后检查更新

期望的效果 内存不泄漏

截图 日志:

References underlined with "~~~" are likely causes.
    Learn more at https://squ.re/leaks.

    415914 bytes retained by leaking objects
    Signature: 453e752cfbb380a711eb3eefef25dc6c7499fd2
    ┬───
    │ GC Root: Input or output parameters in native code
    │
    ├─ android.os.FileObserver$ObserverThread instance
    │    Leaking: NO (PathClassLoader↓ is not leaking)
    │    Thread name: 'FileObserver'
    │    ↓ FileObserver$ObserverThread.contextClassLoader
    ├─ dalvik.system.PathClassLoader instance
    │    Leaking: NO (XUpdate↓ is not leaking and A ClassLoader is never leaking)
    │    ↓ PathClassLoader.runtimeInternalObjects
    ├─ java.lang.Object[] array
    │    Leaking: NO (XUpdate↓ is not leaking)
    │    ↓ Object[].[1050]
    ├─ com.xuexiang.xupdate.XUpdate class
    │    Leaking: NO (a class is never leaking)
    │    ↓ static XUpdate.sInstance
    │                     ~~~~~~~~~
    ├─ com.xuexiang.xupdate.XUpdate instance
    │    Leaking: UNKNOWN
    │    ↓ XUpdate.mIUpdateDownloader
    │              ~~~~~~~~~~~~~~~~~~
    ├─ com.xuexiang.xupdate.proxy.impl.DefaultUpdateDownloader instance
    │    Leaking: UNKNOWN
    │    ↓ DefaultUpdateDownloader.mServiceConnection
    │                              ~~~~~~~~~~~~~~~~~~
    ├─ com.xuexiang.xupdate.proxy.impl.DefaultUpdateDownloader$1 instance
    │    Leaking: UNKNOWN
    │    Anonymous class implementing android.content.ServiceConnection
    │    ↓ DefaultUpdateDownloader$1.val$downloadListener
    │                                ~~~~~~~~~~~~~~~~~~~~
    ├─ com.xuexiang.xupdate.widget.UpdateDialogFragment$2 instance
    │    Leaking: UNKNOWN
    │    Anonymous class implementing com.xuexiang.xupdate.service.OnFileDownloadListener
    │    ↓ UpdateDialogFragment$2.this$0
    │                             ~~~~~~
    ╰→ com.xuexiang.xupdate.widget.UpdateDialogFragment instance
    ​     Leaking: YES (ObjectWatcher was watching this because com.xuexiang.xupdate.widget.UpdateDialogFragment received Fragment#onDestroy() callback and Fragment#mFragmentManager is null)

设备信息 任意设备

附加信息 我重定义了IUpdateHttpService,不知道是否与此有关。

class RetrofitHttpService() : IUpdateHttpService {

    private var call: Call<ResponseBody>?= null

    override fun asyncGet(url: String, params: MutableMap<String, Any>, callBack: IUpdateHttpService.Callback) {
        APIManager.apiService.checkUpdate(getVersion()).enqueue(object : Callback<BaseResponseBean> {
            override fun onFailure(call: Call<BaseResponseBean>, t: Throwable) {
                callBack.onError(t)
            }

            override fun onResponse(call: Call<BaseResponseBean>, response: Response<BaseResponseBean>) {
                val body = response.body()
                if (body == null || !body.isSuccess()) {
                    callBack.onError(Throwable(body?.msg ?: "网络错误"))
                } else {
                    callBack.onSuccess(body.data.toString())
                }
            }
        })
    }

    override fun asyncPost(url: String, params: MutableMap<String, Any>, callBack: IUpdateHttpService.Callback) {

    }

    override fun download(url: String, path: String, fileName: String, callback: IUpdateHttpService.DownloadCallback) {
        call = APIManager.apiService.download(url)
        call?.enqueue(
                object : Callback<ResponseBody> {
                    override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
                        callback.onError(t)
                    }

                    override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
                        callback.onStart()
                        GlobalScope.launch {
                            writeResponseToDisk(path + fileName, response, callback)
                        }
                    }
                })
    }

    override fun cancelDownload(url: String) {
        call?.cancel()
        call = null
        cancelHandler()
    }

    private val sBufferSize = 8192

    private fun writeResponseToDisk(path: String, responseBody: Response<ResponseBody>, callback: IUpdateHttpService.DownloadCallback) {
        val file = File(path)
        val inputStream = responseBody.body()!!.byteStream()
        val totalLength = responseBody.body()!!.contentLength()
        if (!file.exists()) {
            if (!file.parentFile.exists()) {
                file.parentFile.mkdir()
            }
            try {
                file.createNewFile()
            } catch (e: IOException) {
                runOnMainThread {
                    callback.onError(e)
                }
            }
        }

        var currentLength = 0
        try {
            val os = BufferedOutputStream(FileOutputStream(file))
            var data = ByteArray(8192)
            var len = 0
            while ((inputStream.read(data, 0, sBufferSize).also { len = it }) != -1) {
                os.write(data, 0, len)
                currentLength += len
                runOnMainThread {
                    callback.onProgress(currentLength / totalLength.toFloat(), totalLength)
                }
            }
            runOnMainThread {
                callback.onSuccess(file)
            }
            os.close()
        } catch (e: IOException) {
            callback.onError(e)
        } finally {
            try {
                inputStream.close()
            } catch (e: Exception) {
                runOnMainThread {
                    callback.onError(e)
                }
            }
        }
    }
}
xuexiangjys commented 3 years ago

确实存在内存泄漏的情况

xuexiangjys commented 3 years ago

已修复: 2a833646eac58b36028691e39acd8f1e67ee0e06