Hi Clementine Team,
I’m a PhD student researching Android thread-related issues. My research group recently ran a static-analysis scan for thread-related bugs in real-world F-Droid apps, and our prototype flagged a potential issue in the Clementine app.
Checked target
- Source-level caller:
de.qspool.clementineremote.ui.fragments.playerpages.PlayerPageFragment.updateTrackMetadata()
- Detected API / pattern:
Bitmap.sameAs(...) used during cover-art refresh
- Observed context: main/UI thread, through fragment lifecycle and UI metadata update paths
- Expected context: avoid pixel-by-pixel bitmap comparison on the UI thread; run it on a worker thread or compare lightweight artwork identifiers instead
What I found
PlayerPageFragment.updateTrackMetadata() is a UI refresh method. It updates track title/artist/album labels, seek-bar state, and cover-art ImageView state. In the same method, it compares the previous and current cover-art bitmaps using Bitmap.sameAs(...):
Bitmap newArt = currentSong.getArt();
Bitmap oldArt = mCurrentSong.getArt();
if (newArt == null) {
mImgArt.setImageResource(R.drawable.ic_notif_large);
} else if (oldArt == null || !oldArt.sameAs(newArt)) {
if (mFirstCall) {
mImgArt.setImageBitmap(newArt);
} else {
mImgArt.startAnimation(mAlphaDown);
}
}
The method is reachable from UI-facing paths, including fragment lifecycle updates and metadata updates delivered to the player page.
Verified bug trace
Clementine backend connection receives CURRENT_METAINFO
-> sendUiMessage(clementineMessage)
-> main/UI Handler dispatches the message
-> PlayerFragment.MessageFromClementine(...)
-> PlayerPageFragment.MessageFromClementine(...)
-> PlayerPageFragment.updateTrackMetadata()
-> oldArt.sameAs(newArt)
This means a cover-art equality check can be performed synchronously on the main/UI thread.
Why this matters
Android documents Bitmap.sameAs(Bitmap) as a potentially expensive operation because it compares bitmap pixel data. The documentation says it may take several seconds and should only be called from a worker thread.
In this case, the compared bitmaps are cover-art images. If the artwork is large, if metadata updates are frequent, or if this occurs during UI transitions, the app can suffer from jank, delayed input handling, or short UI freezes.
This is a performance/threading issue rather than a guaranteed crash. It may be hard to reproduce with small artwork, but the pattern is fragile because the main thread performs a pixel-by-pixel bitmap comparison.
Possible fix
Avoid using Bitmap.sameAs(...) directly on the UI thread.
A cleaner design is to compare a lightweight artwork identity instead of pixel data, for example:
- album-art URL/path/id from the Clementine metadata,
- a stable artwork version or cache key,
- a hash computed when the artwork is decoded or received on a worker thread.
If pixel equality is truly required, run the comparison on a worker thread and post only the UI update back to the main thread.
Example sketch:
private final ExecutorService artworkExecutor = Executors.newSingleThreadExecutor();
private void updateCoverArtAsync(final Bitmap oldArt, final Bitmap newArt) {
artworkExecutor.execute(() -> {
final boolean changed = oldArt == null || !oldArt.sameAs(newArt);
mImgArt.post(() -> {
if (!isAdded()) {
return;
}
if (newArt == null) {
mImgArt.setImageResource(R.drawable.ic_notif_large);
} else if (changed) {
if (mFirstCall) {
mImgArt.setImageBitmap(newArt);
} else {
mImgArt.startAnimation(mAlphaDown);
}
}
});
});
}
An even safer patch would avoid pixel comparison entirely and use metadata/cache identity to decide whether the cover art changed.
Duplicate check
I searched the current GitHub issues for related terms such as sameAs, Bitmap.sameAs, and cover art, and did not find an existing matching report.
References
- Android
Bitmap.sameAs(Bitmap) documentation: the method compares pixel data and should only be called from a worker thread.
- Source-level caller:
PlayerPageFragment.updateTrackMetadata().
- Message-delivery path: Clementine backend connection sends received protocol messages to the UI handler before fragment update.
Hi Clementine Team,
I’m a PhD student researching Android thread-related issues. My research group recently ran a static-analysis scan for thread-related bugs in real-world F-Droid apps, and our prototype flagged a potential issue in the Clementine app.
Checked target
de.qspool.clementineremote.ui.fragments.playerpages.PlayerPageFragment.updateTrackMetadata()Bitmap.sameAs(...)used during cover-art refreshWhat I found
PlayerPageFragment.updateTrackMetadata()is a UI refresh method. It updates track title/artist/album labels, seek-bar state, and cover-artImageViewstate. In the same method, it compares the previous and current cover-art bitmaps usingBitmap.sameAs(...):The method is reachable from UI-facing paths, including fragment lifecycle updates and metadata updates delivered to the player page.
Verified bug trace
This means a cover-art equality check can be performed synchronously on the main/UI thread.
Why this matters
Android documents
Bitmap.sameAs(Bitmap)as a potentially expensive operation because it compares bitmap pixel data. The documentation says it may take several seconds and should only be called from a worker thread.In this case, the compared bitmaps are cover-art images. If the artwork is large, if metadata updates are frequent, or if this occurs during UI transitions, the app can suffer from jank, delayed input handling, or short UI freezes.
This is a performance/threading issue rather than a guaranteed crash. It may be hard to reproduce with small artwork, but the pattern is fragile because the main thread performs a pixel-by-pixel bitmap comparison.
Possible fix
Avoid using
Bitmap.sameAs(...)directly on the UI thread.A cleaner design is to compare a lightweight artwork identity instead of pixel data, for example:
If pixel equality is truly required, run the comparison on a worker thread and post only the UI update back to the main thread.
Example sketch:
An even safer patch would avoid pixel comparison entirely and use metadata/cache identity to decide whether the cover art changed.
Duplicate check
I searched the current GitHub issues for related terms such as
sameAs,Bitmap.sameAs, andcover art, and did not find an existing matching report.References
Bitmap.sameAs(Bitmap)documentation: the method compares pixel data and should only be called from a worker thread.PlayerPageFragment.updateTrackMetadata().