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.
This commit is contained in:
Hugh Daschbach 2023-08-20 22:49:26 -07:00 committed by Georg Krause
parent 4dba9e29dd
commit f65e29af39
1 changed files with 16 additions and 13 deletions

View File

@ -6,6 +6,7 @@ import android.transition.CircularPropagation
import android.util.Log import android.util.Log
import androidx.swiperefreshlayout.widget.CircularProgressDrawable import androidx.swiperefreshlayout.widget.CircularProgressDrawable
import audio.funkwhale.ffa.BuildConfig import audio.funkwhale.ffa.BuildConfig
import audio.funkwhale.ffa.FFA
import audio.funkwhale.ffa.R import audio.funkwhale.ffa.R
import com.squareup.picasso.Downloader import com.squareup.picasso.Downloader
import com.squareup.picasso.NetworkPolicy 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. // Cache with some useful concurrency semantics. See its docs for details.
val fileCache = Bottleneck<File>() val fileCache = Bottleneck<File>()
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. * 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. * 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: * 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. * The primary entrypoint for the codebase.
*/ */
fun withContext(context: Context, url: String?): RequestCreator { 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) 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) return request.error(R.drawable.cover)
} }
} }