From f65e29af395d369c4faae5c56d044e580519de62 Mon Sep 17 00:00:00 2001 From: Hugh Daschbach Date: Sun, 20 Aug 2023 22:49:26 -0700 Subject: [PATCH] Do not create unnecessary Picasso objects. Address "java.lang.IllegalStateException: Too many receivers" exceptions. (See Issue #145). Each new Picasso object registers its own NetworkBroadcastReceiver. Worse, we create a new Picasso object each time we transform an AlbumCover image. So do not create unnecessary Picasso objects. Rather than depend on receiving a Context object when called to load an cover art, fetch the Application context as returned from FFA.get() at singleton construction time. The Application context is long lived. This has an additional advantage. Not generation new Picasso objects for each CoverArt image avoids holding a reference to an object that cannot, later, be garbage collected. --- .../audio/funkwhale/ffa/utils/CoverArt.kt | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/audio/funkwhale/ffa/utils/CoverArt.kt b/app/src/main/java/audio/funkwhale/ffa/utils/CoverArt.kt index 85b137f..bfc972e 100644 --- a/app/src/main/java/audio/funkwhale/ffa/utils/CoverArt.kt +++ b/app/src/main/java/audio/funkwhale/ffa/utils/CoverArt.kt @@ -6,6 +6,7 @@ import android.transition.CircularPropagation import android.util.Log import androidx.swiperefreshlayout.widget.CircularProgressDrawable import audio.funkwhale.ffa.BuildConfig +import audio.funkwhale.ffa.FFA import audio.funkwhale.ffa.R import com.squareup.picasso.Downloader import com.squareup.picasso.NetworkPolicy @@ -73,6 +74,19 @@ open class CoverArt private constructor() { // Cache with some useful concurrency semantics. See its docs for details. val fileCache = Bottleneck() + private val picasso = with (FFA.get()) { + Picasso.Builder(this) + .addRequestHandler(CoverNetworkRequestHandler(this)) + // Be careful with this. There's at least one place in Picasso where it + // doesn't null-check when logging, so it'll throw errors in places you + // wouldn't get them with logging turned off. /sigh + .loggingEnabled(false) // (BuildConfig.DEBUG) + // Occasionally, we may get transient HTTP issues, or bogus files. + // Listen for Picasso errors and invalidate those files + .listener(invalidateIn(this)) + .build() + } + /** * We don't need to hang onto the Context, just the Path it gets us. */ @@ -204,17 +218,6 @@ open class CoverArt private constructor() { /** * Low-level Picasso wiring. */ - private fun buildPicasso(context: Context) = Picasso.Builder(context) - // The bulk of the work happens here - .addRequestHandler(CoverNetworkRequestHandler(context)) - // Be careful with this. There's at least one place in Picasso where it - // doesn't null-check when logging, so it'll throw errors in places you - // wouldn't get them with logging turned off. /sigh - .loggingEnabled(false) // (BuildConfig.DEBUG) - // Occasionally, we may get transient HTTP issues, or bogus files. - // Listen for Picasso errors and invalidate those files - .listener(invalidateIn(context)) - .build() /** * We don't want to cache the HTTP part of the flow, because: @@ -254,9 +257,9 @@ open class CoverArt private constructor() { * The primary entrypoint for the codebase. */ fun withContext(context: Context, url: String?): RequestCreator { - val request = buildPicasso(context).load(url) + val request = picasso.load(url) if(url == null) request.placeholder(R.drawable.cover) - else request.placeholder(CircularProgressDrawable(context)) + else request.placeholder(CircularProgressDrawable(FFA.get())) return request.error(R.drawable.cover) } }