Как предотвратить утечку памяти с помощью библиотеки обновлений в приложении - PullRequest
2 голосов
/ 17 октября 2019

Я хочу реализовать новую библиотеку In-App Update в моем приложении, но я заметил, что она вызывает утечку памяти в моей деятельности, когда она воссоздается / поворачивается.

Вот единственная деталь, которая у меня естьиз LeakCanary:

LeakCanary trace

Очевидно, у меня ничего нет, если я удаляю код из библиотеки обновлений в приложении, особенно addOnSuccessListener:

appUpdateManager.appUpdateInfo.addOnSuccessListener { appUpdateInfo ->
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
        && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)){
            updateInfo.value = appUpdateInfo
            updateAvailable.value = true
        }else{
            updateInfo.value = null
            updateAvailable.value = false
        }
    }

Согласно этой записи , я сначала использовал некоторые LiveData, но проблема была та же, поэтому я использовал полный класс для обработки обратного вызова с LiveData:

Мой класс обслуживания:

class AppUpdateService {

    val updateAvailable: MutableLiveData<Boolean> by lazy { MutableLiveData<Boolean>() }
    val updateDownloaded: MutableLiveData<Boolean> by lazy { MutableLiveData<Boolean>() }
    val updateInfo: MutableLiveData<AppUpdateInfo> by lazy { MutableLiveData<AppUpdateInfo>() }

    fun checkForUpdate(appUpdateManager: AppUpdateManager){
        appUpdateManager.appUpdateInfo.addOnSuccessListener { appUpdateInfo ->
            if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
                    && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)){
                updateInfo.value = appUpdateInfo
                updateAvailable.value = true
            }else{
                updateInfo.value = null
                updateAvailable.value = false
            }
        }
    }

    fun checkUpdateOnResume(appUpdateManager: AppUpdateManager){
        appUpdateManager.appUpdateInfo.addOnSuccessListener {
            updateDownloaded.value = (it.installStatus() == InstallStatus.DOWNLOADED)
        }
    }
}

Упрощение моей активности:

class MainActivity : BaseActivity(), InstallStateUpdatedListener {

    override fun contentViewID(): Int { return R.layout.activity_main }

    private val UPDATE_REQUEST_CODE = 8000

    private lateinit var appUpdateManager : AppUpdateManager

    private val appUpdateService = AppUpdateService()

    override fun onStateUpdate(state: InstallState?) {
        if(state?.installStatus() == InstallStatus.DOWNLOADED){ notifyUser() }
    }

    // Called in the onCreate()
    override fun setupView(){
        appUpdateManager = AppUpdateManagerFactory.create(this)
        appUpdateManager.registerListener(this)
        setupAppUpdateServiceObservers()
        // Check for Update
        appUpdateService.checkForUpdate(appUpdateManager)
    }

    private fun setupAppUpdateServiceObservers(){
        appUpdateService.updateAvailable.observe(this, Observer {
            if (it)
                requestUpdate(appUpdateService.updateInfo.value)
        })

        appUpdateService.updateDownloaded.observe(this, Observer {
            if (it)
                notifyUser()
        })
    }

    private fun requestUpdate(appUpdateInfo: AppUpdateInfo?){
        appUpdateManager.startUpdateFlowForResult(appUpdateInfo, AppUpdateType.FLEXIBLE, this, UPDATE_REQUEST_CODE)
    }

    private fun notifyUser(){
        showSnackbar(getString(R.string.updated_downloaded), getString(R.string.restart)) {
            appUpdateManager.completeUpdate()
            appUpdateManager.unregisterListener(this)
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == UPDATE_REQUEST_CODE) {
            if (resultCode != RESULT_OK) {
                Timber.d("Update flow failed! Result code: $resultCode")
            }
        }
    }

    override fun onDestroy() {
        appUpdateManager.unregisterListener(this)
        super.onDestroy()
    }

    override fun onResume() {
        super.onResume()
        appUpdateService.checkUpdateOnResume(appUpdateManager)
    }

}

Я не совсем понимаю, как избежать утечки памяти, поскольку appUpdateManager должен создаваться в контексте контекстаактивность, и это похоже на то, что вызывает утечку памяти при обратном вызове.

Кто-то уже реализует это без этой проблемы?

Ответы [ 2 ]

1 голос
/ 19 октября 2019

Благодаря @Sina Farahzadi я искал и пробовал много вещей и понял, что проблема была в вызове appUpdateManager.appUdateInfo с объектом Task.

Я нашел способ решить проблему утечки памяти:applicationContext вместо контекста действия. Я не уверен, что это лучшее решение, но это то, что я нашел на данный момент. Я экспортировал все в своем классе обслуживания, поэтому вот мой код:

AppUpdateService.kt:

class AppUpdateService : InstallStateUpdatedListener {

    val updateAvailable: MutableLiveData<Boolean> by lazy { MutableLiveData<Boolean>() }
    val updateDownloaded: MutableLiveData<Boolean> by lazy { MutableLiveData<Boolean>() }
    val notifyUser: MutableLiveData<Boolean> by lazy { MutableLiveData<Boolean>() }
    val updateInfo: MutableLiveData<AppUpdateInfo> by lazy { MutableLiveData<AppUpdateInfo>() }

    private var appUpdateManager : AppUpdateManager? = null
    private var appUpdateInfoTask: Task<AppUpdateInfo>? = null

    override fun onStateUpdate(state: InstallState?) {
        notifyUser.value =  (state?.installStatus() == InstallStatus.DOWNLOADED)
    }

    fun setupAppUpdateManager(context: Context){
        appUpdateManager = AppUpdateManagerFactory.create(context)
        appUpdateManager?.registerListener(this)
        checkForUpdate()
    }

    fun onStopCalled(){
        appUpdateManager?.unregisterListener(this)
        appUpdateInfoTask = null
        appUpdateManager = null
    }

    fun checkForUpdate(){
        appUpdateInfoTask = appUpdateManager?.appUpdateInfo
        appUpdateInfoTask?.addOnSuccessListener { appUpdateInfo ->
            if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
                    && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)){
                updateInfo.value = appUpdateInfo
                updateAvailable.value = true
            }else{
                updateInfo.value = null
                updateAvailable.value = false
            }
        }
    }

    fun startUpdate(activity: Activity, code: Int){
        appUpdateManager?.startUpdateFlowForResult(updateInfo.value, AppUpdateType.FLEXIBLE, activity, code)
    }

    fun updateComplete(){
        appUpdateManager?.completeUpdate()
        appUpdateManager?.unregisterListener(this)
    }

    fun checkUpdateOnResume(){
        appUpdateManager?.appUpdateInfo?.addOnSuccessListener {
            updateDownloaded.value = (it.installStatus() == InstallStatus.DOWNLOADED)
        }
    }

}

Упрощение MainActivity:

class MainActivity : BaseActivity(){

    override fun contentViewID(): Int { return R.layout.activity_main }

    private val UPDATE_REQUEST_CODE = 8000

    private var appUpdateService: AppUpdateService? = AppUpdateService()

    /**
     * Setup the view of the activity (navigation and menus)
     */
    override fun setupView(){
        val contextWeakReference = WeakReference<Context>(applicationContext)
        contextWeakReference.get()?.let {weakContext ->
            appUpdateService?.setupAppUpdateManager(weakContext)
        }
    }

    private fun setupAppUpdateServiceObservers(){
        appUpdateService?.updateAvailable?.observe(this, Observer {
            if (it)
                requestUpdate()
        })

        appUpdateService?.updateDownloaded?.observe(this, Observer {
            if (it)
                notifyUser()
        })

        appUpdateService?.notifyUser?.observe(this, Observer {
            if (it)
                notifyUser()
        })
    }

    private fun removeAppUpdateServiceObservers(){
        appUpdateService?.updateAvailable?.removeObservers(this)
        appUpdateService?.updateDownloaded?.removeObservers(this)
        appUpdateService?.notifyUser?.removeObservers(this)
    }

    private fun requestUpdate(){
        appUpdateService?.startUpdate(this, UPDATE_REQUEST_CODE)
    }

    private fun notifyUser(){
        showSnackbar(getString(R.string.updated_downloaded), getString(R.string.restart)) {
            appUpdateService?.updateComplete()
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == UPDATE_REQUEST_CODE) {
            if (resultCode != RESULT_OK) {
                Timber.d("Update flow failed! Result code: $resultCode")
            }
        }
    }

    override fun onStop() {
        appUpdateService?.onStopCalled()
        removeAppUpdateServiceObservers()
        appUpdateService = null
        super.onStop()
    }

    override fun onResume() {
        super.onResume()
        setupAppUpdateServiceObservers()
        appUpdateService?.checkUpdateOnResume()
    }

}

Пока я будупродолжайте в том же духе и продолжайте искать другой способ сделать это. Дайте мне знать, если у кого-то есть лучший способ сделать это.

1 голос
/ 17 октября 2019

Использование слабой ссылки на контекст, вероятно, решит проблему утечки памяти. Напишите это в своей деятельности:

WeakReference<Context> contextWeakReference = new WeakReference<Context>(this);

Context context = contextWeakReference.get();
if (context != null) {
   // Register using context here
}

Существует множество хороших статей о WeakReference, Сборке мусора и Утечках памяти, чтобы прочитать больше на эту тему.

Кроме того, onDestroy () не гарантируетсябыть вызванным. Когда вы запускаете другую операцию, вместо onDestroy () вызывается метод onPause () и onStop ().

onDestroy () вызывается, когда вы нажимаете кнопку назад или вызываете метод finish (). Итак, отмените регистрацию слушателя в onPause () или onStop (). Если вы отмените регистрацию в методе onDestroy (), это может вызвать утечку памяти.

Другая идея состоит в том, что, поскольку класс AppUpdateService не является подклассом ViewModel, он не распознает жизненный цикл. Я не уверен, но вам может понадобиться удалить наблюдателей в onstop / onDestroy действия и добавить их в onResume. (наблюдатели настоятельно ссылаются на LifecycleOwner, здесь - активность). Для этого вам необходимо определить наблюдателей, чтобы иметь возможность удалить их позже. Что-то вроде:

MutableLiveData<Boolean> someData = new MutableLiveData<>;

, а затем в onResume:

someData = appUpdateService.updateAvailable;
someData.observe()

и в onStop:

someData.removeObservers()

Это всего лишь предположение, но, надеюсь, это будетпомогите как-нибудь.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...