Browse Source

Minor fixes regarding leaks

inorichi 4 năm trước cách đây
mục cha
commit
36f81b4a62

+ 7 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt

@@ -12,6 +12,9 @@ import com.bluelinelabs.conductor.ControllerChangeHandler
 import com.bluelinelabs.conductor.ControllerChangeType
 import com.bluelinelabs.conductor.RestoreViewOnCreateController
 import kotlinx.android.extensions.LayoutContainer
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.MainScope
+import kotlinx.coroutines.cancel
 import timber.log.Timber
 
 abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) :
@@ -20,6 +23,8 @@ abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) :
 
     lateinit var binding: VB
 
+    lateinit var viewScope: CoroutineScope
+
     init {
         addLifecycleListener(
             object : LifecycleListener() {
@@ -28,6 +33,7 @@ abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) :
                 }
 
                 override fun preCreateView(controller: Controller) {
+                    viewScope = MainScope()
                     Timber.d("Create view for ${controller.instance()}")
                 }
 
@@ -40,6 +46,7 @@ abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) :
                 }
 
                 override fun preDestroyView(controller: Controller, view: View) {
+                    viewScope.cancel()
                     Timber.d("Destroy view for ${controller.instance()}")
                 }
             }

+ 0 - 5
app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/NucleusController.kt

@@ -4,9 +4,6 @@ import android.os.Bundle
 import androidx.viewbinding.ViewBinding
 import eu.kanade.tachiyomi.ui.base.presenter.NucleusConductorDelegate
 import eu.kanade.tachiyomi.ui.base.presenter.NucleusConductorLifecycleListener
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
 import nucleus.factory.PresenterFactory
 import nucleus.presenter.Presenter
 
@@ -17,8 +14,6 @@ abstract class NucleusController<VB : ViewBinding, P : Presenter<*>>(val bundle:
 
     private val delegate = NucleusConductorDelegate(this)
 
-    val scope = CoroutineScope(Job() + Dispatchers.Main)
-
     val presenter: P
         get() = delegate.presenter!!
 

+ 2 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionController.kt

@@ -67,7 +67,7 @@ open class ExtensionController :
         binding.swipeRefresh.isRefreshing = true
         binding.swipeRefresh.refreshes()
             .onEach { presenter.findAvailableExtensions() }
-            .launchIn(scope)
+            .launchIn(viewScope)
 
         // Initialize adapter, scroll listener and recycler views
         adapter = ExtensionAdapter(this)
@@ -142,7 +142,7 @@ open class ExtensionController :
                 query = it.toString()
                 drawExtensions()
             }
-            .launchIn(scope)
+            .launchIn(viewScope)
     }
 
     override fun onItemClick(view: View, position: Int): Boolean {

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/ExtensionDetailsController.kt

@@ -134,7 +134,7 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
                                             isChecked = enabled
                                             sourcePrefs.forEach { pref -> pref.isVisible = enabled }
                                         }
-                                        .launchIn(scope)
+                                        .launchIn(viewScope)
                                 }
 
                                 // Source enable/disable

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceController.kt

@@ -222,7 +222,7 @@ class SourceController :
         searchView.queryTextEvents()
             .filterIsInstance<QueryTextEvent.QuerySubmitted>()
             .onEach { performGlobalSearch(it.queryText.toString()) }
-            .launchIn(scope)
+            .launchIn(viewScope)
     }
 
     private fun performGlobalSearch(query: String) {

+ 3 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt

@@ -187,6 +187,7 @@ open class BrowseSourceController(bundle: Bundle) :
     }
 
     override fun cleanupFab(fab: ExtendedFloatingActionButton) {
+        fab.setOnClickListener(null)
         actionFabScrollListener?.let { recycler?.removeOnScrollListener(it) }
         actionFab = null
     }
@@ -224,7 +225,7 @@ open class BrowseSourceController(bundle: Bundle) :
                     .drop(1)
                     // Set again the adapter to recalculate the covers height
                     .onEach { adapter = [email protected] }
-                    .launchIn(scope)
+                    .launchIn(viewScope)
 
                 (layoutManager as GridLayoutManager).spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
                     override fun getSpanSize(position: Int): Int {
@@ -275,7 +276,7 @@ open class BrowseSourceController(bundle: Bundle) :
             .filter { router.backstack.lastOrNull()?.controller() == this@BrowseSourceController }
             .filterIsInstance<QueryTextEvent.QuerySubmitted>()
             .onEach { searchWithQuery(it.queryText.toString()) }
-            .launchIn(scope)
+            .launchIn(viewScope)
 
         searchItem.fixExpand(
             onExpand = { invalidateMenuOnExpand() },

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchController.kt

@@ -129,7 +129,7 @@ open class GlobalSearchController(
                 searchItem.collapseActionView()
                 setTitle() // Update toolbar title
             }
-            .launchIn(scope)
+            .launchIn(viewScope)
     }
 
     /**

+ 4 - 8
app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryController.kt

@@ -22,9 +22,6 @@ import eu.kanade.tachiyomi.ui.base.controller.NucleusController
 import eu.kanade.tachiyomi.ui.main.MainActivity
 import eu.kanade.tachiyomi.util.system.toast
 import eu.kanade.tachiyomi.util.view.shrinkOnScroll
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import reactivecircus.flowbinding.android.view.clicks
 
 /**
  * Controller to manage the categories for the users' library.
@@ -103,14 +100,13 @@ class CategoryController :
         actionFab = fab
         fab.setText(R.string.action_add)
         fab.setIconResource(R.drawable.ic_add_24dp)
-        fab.clicks()
-            .onEach {
-                CategoryCreateDialog(this@CategoryController).showDialog(router, null)
-            }
-            .launchIn(scope)
+        fab.setOnClickListener {
+            CategoryCreateDialog(this@CategoryController).showDialog(router, null)
+        }
     }
 
     override fun cleanupFab(fab: ExtendedFloatingActionButton) {
+        fab.setOnClickListener(null)
         actionFabScrollListener?.let { binding.recycler.removeOnScrollListener(it) }
         actionFab = null
     }

+ 11 - 15
app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadController.kt

@@ -18,9 +18,6 @@ import eu.kanade.tachiyomi.source.model.Page
 import eu.kanade.tachiyomi.ui.base.controller.FabController
 import eu.kanade.tachiyomi.ui.base.controller.NucleusController
 import eu.kanade.tachiyomi.util.view.shrinkOnScroll
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import reactivecircus.flowbinding.android.view.clicks
 import rx.Observable
 import rx.Subscription
 import rx.android.schedulers.AndroidSchedulers
@@ -104,23 +101,22 @@ class DownloadController :
 
     override fun configureFab(fab: ExtendedFloatingActionButton) {
         actionFab = fab
-        fab.clicks()
-            .onEach {
-                val context = applicationContext ?: return@onEach
+        fab.setOnClickListener {
+            val context = applicationContext ?: return@setOnClickListener
 
-                if (isRunning) {
-                    DownloadService.stop(context)
-                    presenter.pauseDownloads()
-                } else {
-                    DownloadService.start(context)
-                }
-
-                setInformationView()
+            if (isRunning) {
+                DownloadService.stop(context)
+                presenter.pauseDownloads()
+            } else {
+                DownloadService.start(context)
             }
-            .launchIn(scope)
+
+            setInformationView()
+        }
     }
 
     override fun cleanupFab(fab: ExtendedFloatingActionButton) {
+        fab.setOnClickListener(null)
         actionFabScrollListener?.let { binding.recycler.removeOnScrollListener(it) }
         actionFab = null
     }

+ 5 - 4
app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt

@@ -169,13 +169,13 @@ class LibraryController(
                 activeCategory = it
                 updateTitle()
             }
-            .launchIn(scope)
+            .launchIn(viewScope)
 
         getColumnsPreferenceForCurrentOrientation().asImmediateFlow { mangaPerRow = it }
             .drop(1)
             // Set again the adapter to recalculate the covers height
             .onEach { reattachAdapter() }
-            .launchIn(scope)
+            .launchIn(viewScope)
 
         if (selectedMangas.isNotEmpty()) {
             createActionModeIfNeeded()
@@ -197,7 +197,7 @@ class LibraryController(
                     GlobalSearchController(query).withFadeTransaction()
                 )
             }
-            .launchIn(scope)
+            .launchIn(viewScope)
 
         (activity!! as MainActivity).fixViewToBottom(binding.actionToolbar)
     }
@@ -212,6 +212,7 @@ class LibraryController(
 
     override fun onDestroyView(view: View) {
         destroyActionModeIfNeeded()
+        (activity!! as MainActivity).clearFixViewToBottom(binding.actionToolbar)
         binding.actionToolbar.destroy()
         adapter?.onDestroy()
         adapter = null
@@ -391,7 +392,7 @@ class LibraryController(
                 query = it.toString()
                 performSearch()
             }
-            .launchIn(scope)
+            .launchIn(viewScope)
     }
 
     private fun performSearch() {

+ 13 - 6
app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt

@@ -67,6 +67,8 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
     private var isConfirmingExit: Boolean = false
     private var isHandlingShortcut: Boolean = false
 
+    private var fixedViewsToBottom = mutableMapOf<View, AppBarLayout.OnOffsetChangedListener>()
+
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
@@ -401,12 +403,17 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
      * the collapsing AppBarLayout.
      */
     fun fixViewToBottom(view: View) {
-        binding.appbar.addOnOffsetChangedListener(
-            AppBarLayout.OnOffsetChangedListener { appBarLayout, verticalOffset ->
-                val maxAbsOffset = appBarLayout.measuredHeight - binding.tabs.measuredHeight
-                view.translationY = -maxAbsOffset - verticalOffset.toFloat()
-            }
-        )
+        val listener = AppBarLayout.OnOffsetChangedListener { appBarLayout, verticalOffset ->
+            val maxAbsOffset = appBarLayout.measuredHeight - binding.tabs.measuredHeight
+            view.translationY = -maxAbsOffset - verticalOffset.toFloat()
+        }
+        binding.appbar.addOnOffsetChangedListener(listener)
+        fixedViewsToBottom[view] = listener
+    }
+
+    fun clearFixViewToBottom(view: View) {
+        val listener = fixedViewsToBottom.remove(view)
+        binding.appbar.removeOnOffsetChangedListener(listener)
     }
 
     private fun setBottomNavBehaviorOnScroll() {

+ 24 - 25
app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt

@@ -76,7 +76,6 @@ import eu.kanade.tachiyomi.util.view.shrinkOnScroll
 import eu.kanade.tachiyomi.util.view.snack
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
-import reactivecircus.flowbinding.android.view.clicks
 import reactivecircus.flowbinding.recyclerview.scrollEvents
 import reactivecircus.flowbinding.swiperefreshlayout.refreshes
 import timber.log.Timber
@@ -228,14 +227,14 @@ class MangaController :
 
         binding.recycler.scrollEvents()
             .onEach { updateToolbarTitleAlpha() }
-            .launchIn(scope)
+            .launchIn(viewScope)
 
         binding.swipeRefresh.refreshes()
             .onEach {
                 fetchMangaInfoFromSource(manualFetch = true)
                 fetchChaptersFromSource(manualFetch = true)
             }
-            .launchIn(scope)
+            .launchIn(viewScope)
 
         (activity!! as MainActivity).fixViewToBottom(binding.actionToolbar)
 
@@ -279,42 +278,42 @@ class MangaController :
         actionFab = fab
         fab.setText(R.string.action_start)
         fab.setIconResource(R.drawable.ic_play_arrow_24dp)
-        fab.clicks()
-            .onEach {
-                val item = presenter.getNextUnreadChapter()
-                if (item != null) {
-                    // Create animation listener
-                    val revealAnimationListener: Animator.AnimatorListener = object : AnimatorListenerAdapter() {
-                        override fun onAnimationStart(animation: Animator?) {
-                            openChapter(item.chapter, true)
-                        }
+        fab.setOnClickListener {
+            val item = presenter.getNextUnreadChapter()
+            if (item != null) {
+                // Create animation listener
+                val revealAnimationListener: Animator.AnimatorListener = object : AnimatorListenerAdapter() {
+                    override fun onAnimationStart(animation: Animator?) {
+                        openChapter(item.chapter, true)
                     }
+                }
 
-                    // Get coordinates and start animation
-                    actionFab?.getCoordinates()?.let { coordinates ->
-                        if (!binding.revealView.showRevealEffect(
-                                coordinates.x,
-                                coordinates.y,
-                                revealAnimationListener
-                            )
-                        ) {
-                            openChapter(item.chapter)
-                        }
+                // Get coordinates and start animation
+                actionFab?.getCoordinates()?.let { coordinates ->
+                    if (!binding.revealView.showRevealEffect(
+                            coordinates.x,
+                            coordinates.y,
+                            revealAnimationListener
+                        )
+                    ) {
+                        openChapter(item.chapter)
                     }
-                } else {
-                    view?.context?.toast(R.string.no_next_chapter)
                 }
+            } else {
+                view?.context?.toast(R.string.no_next_chapter)
             }
-            .launchIn(scope)
+        }
     }
 
     override fun cleanupFab(fab: ExtendedFloatingActionButton) {
+        fab.setOnClickListener(null)
         actionFabScrollListener?.let { binding.recycler.removeOnScrollListener(it) }
         actionFab = null
     }
 
     override fun onDestroyView(view: View) {
         destroyActionModeIfNeeded()
+        (activity!! as MainActivity).clearFixViewToBottom(binding.actionToolbar)
         binding.actionToolbar.destroy()
         mangaInfoAdapter = null
         chaptersHeaderAdapter = null

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackController.kt

@@ -77,7 +77,7 @@ class TrackController :
         binding.swipeRefresh.isEnabled = false
         binding.swipeRefresh.refreshes()
             .onEach { presenter.refresh() }
-            .launchIn(scope)
+            .launchIn(viewScope)
     }
 
     override fun onDestroyView(view: View) {

+ 2 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchDialog.kt

@@ -77,7 +77,7 @@ class TrackSearchDialog : DialogController {
             .onEach { position ->
                 selectedItem = adapter.getItem(position)
             }
-            .launchIn(trackController.scope)
+            .launchIn(trackController.viewScope)
 
         // Do an initial search based on the manga's title
         if (savedState == null) {
@@ -99,7 +99,7 @@ class TrackSearchDialog : DialogController {
             .debounce(TimeUnit.SECONDS.toMillis(1))
             .filter { it.isNotBlank() }
             .onEach { search(it.toString()) }
-            .launchIn(trackController.scope)
+            .launchIn(trackController.viewScope)
     }
 
     private fun search(query: String) {

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryController.kt

@@ -189,7 +189,7 @@ class HistoryController :
                 query = it.toString()
                 presenter.updateList(query)
             }
-            .launchIn(scope)
+            .launchIn(viewScope)
 
         // Fixes problem with the overflow icon showing up in lieu of search
         searchItem.fixExpand(

+ 3 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesController.kt

@@ -97,7 +97,7 @@ class UpdatesController :
                 val firstPos = layoutManager.findFirstCompletelyVisibleItemPosition()
                 binding.swipeRefresh.isEnabled = firstPos <= 0
             }
-            .launchIn(scope)
+            .launchIn(viewScope)
 
         binding.swipeRefresh.setDistanceToTriggerSync((2 * 64 * view.resources.displayMetrics.density).toInt())
         binding.swipeRefresh.refreshes()
@@ -107,13 +107,14 @@ class UpdatesController :
                 // It can be a very long operation, so we disable swipe refresh and show a toast.
                 binding.swipeRefresh.isRefreshing = false
             }
-            .launchIn(scope)
+            .launchIn(viewScope)
 
         (activity!! as MainActivity).fixViewToBottom(binding.actionToolbar)
     }
 
     override fun onDestroyView(view: View) {
         destroyActionModeIfNeeded()
+        (activity!! as MainActivity).clearFixViewToBottom(binding.actionToolbar)
         binding.actionToolbar.destroy()
         adapter = null
         super.onDestroyView(view)