Merge pull request #3525 from k9mail/optimize_contact_picture_loading

Optimize contact picture loading
This commit is contained in:
cketti 2018-07-25 02:02:15 +02:00 committed by GitHub
commit efac64da93
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 150 additions and 104 deletions

View file

@ -83,7 +83,7 @@ public class K9ChooserTargetService extends ChooserTargetService {
@Nullable
private Icon loadRecipientIcon(Recipient recipient) {
Bitmap bitmap = contactPictureLoader.loadContactPictureIcon(recipient);
Bitmap bitmap = contactPictureLoader.getContactPicture(recipient);
if (bitmap == null) {
return null;
}

View file

@ -153,7 +153,7 @@ public class RecipientAdapter extends BaseAdapter implements Filterable {
}
public static void setContactPhotoOrPlaceholder(Context context, ImageView imageView, Recipient recipient) {
ContactPicture.getContactPictureLoader().loadContactPicture(recipient, imageView);
ContactPicture.getContactPictureLoader().setContactPicture(imageView, recipient);
}
@Override

View file

@ -11,7 +11,7 @@ import com.fsck.k9.mail.Address
*/
class ContactLetterBitmapCreator(
private val letterExtractor: ContactLetterExtractor,
private val config: ContactLetterBitmapConfig
val config: ContactLetterBitmapConfig
) {
fun drawBitmap(bitmap: Bitmap, pictureSizeInPx: Int, address: Address): Bitmap {
val canvas = Canvas(bitmap)

View file

@ -11,9 +11,11 @@ import com.bumptech.glide.Glide
import com.bumptech.glide.Priority
import com.bumptech.glide.load.ResourceDecoder
import com.bumptech.glide.load.data.DataFetcher
import com.bumptech.glide.load.data.StreamLocalUriFetcher
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.engine.Resource
import com.bumptech.glide.load.model.ModelLoader
import com.bumptech.glide.load.model.stream.StreamModelLoader
import com.bumptech.glide.load.resource.bitmap.BitmapEncoder
import com.bumptech.glide.load.resource.bitmap.BitmapResource
import com.bumptech.glide.load.resource.bitmap.StreamBitmapDecoder
@ -26,7 +28,7 @@ import com.bumptech.glide.request.target.Target
import com.fsck.k9.helper.Contacts
import com.fsck.k9.mail.Address
import com.fsck.k9.view.RecipientSelectView.Recipient
import java.util.Locale
import java.io.InputStream
class ContactPictureLoader(
@ -35,112 +37,107 @@ class ContactPictureLoader(
) {
private val contactsHelper: Contacts = Contacts.getInstance(context)
private val pictureSizeInPx: Int = PICTURE_SIZE.toDip(context)
fun loadContactPicture(address: Address, imageView: ImageView) {
val photoUri = contactsHelper.getPhotoUri(address.address)
loadContactPicture(photoUri, address, imageView)
private val backgroundCacheId: String = with(contactLetterBitmapCreator.config) {
if (hasDefaultBackgroundColor) defaultBackgroundColor.toString() else "*"
}
fun loadContactPicture(recipient: Recipient, imageView: ImageView) {
loadContactPicture(recipient.photoThumbnailUri, recipient.address, imageView)
}
private fun loadFallbackPicture(address: Address, imageView: ImageView) {
val context = imageView.context
Glide.with(context)
.using(FallbackGlideModelLoader(), FallbackGlideParams::class.java)
.from(FallbackGlideParams::class.java)
.`as`(Bitmap::class.java)
.transcode(BitmapToGlideDrawableTranscoder(context), GlideDrawable::class.java)
.decoder(FallbackGlideBitmapDecoder())
.encoder(BitmapEncoder(Bitmap.CompressFormat.PNG, 0))
.cacheDecoder(FileToStreamDecoder(StreamBitmapDecoder(context)))
fun setContactPicture(imageView: ImageView, address: Address) {
Glide.with(imageView.context)
.using(ContactPictureModelLoader())
.from(Address::class.java)
.load(address)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.load(FallbackGlideParams(address))
.listener(FallbackImageRequestListener(address, imageView))
// for some reason, following 2 lines fix loading issues.
.dontAnimate()
.override(pictureSizeInPx, pictureSizeInPx)
.into(imageView)
}
private fun loadContactPicture(photoUri: Uri?, address: Address, imageView: ImageView) {
if (photoUri != null) {
val noPhotoListener = object : RequestListener<Uri, GlideDrawable> {
override fun onException(
e: Exception,
model: Uri,
target: Target<GlideDrawable>,
isFirstResource: Boolean
): Boolean {
loadFallbackPicture(address, imageView)
return true
}
override fun onResourceReady(
resource: GlideDrawable,
model: Uri,
target: Target<GlideDrawable>,
isFromMemoryCache: Boolean,
isFirstResource: Boolean
): Boolean {
return false
}
}
Glide.with(imageView.context)
.load(photoUri)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.listener(noPhotoListener)
// for some reason, following 2 lines fix loading issues.
.dontAnimate()
.override(pictureSizeInPx, pictureSizeInPx)
.into(imageView)
fun setContactPicture(imageView: ImageView, recipient: Recipient) {
val contactPictureUri = recipient.photoThumbnailUri
if (contactPictureUri != null) {
setContactPicture(imageView, contactPictureUri, recipient.address)
} else {
loadFallbackPicture(address, imageView)
setFallbackPicture(imageView, recipient.address)
}
}
fun loadContactPictureIcon(recipient: Recipient): Bitmap? {
return loadContactPicture(recipient.photoThumbnailUri, recipient.address)
private fun setContactPicture(imageView: ImageView, contactPictureUri: Uri, address: Address) {
Glide.with(imageView.context)
.load(contactPictureUri)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.listener(FallbackImageRequestListener(address, imageView))
// for some reason, following 2 lines fix loading issues.
.dontAnimate()
.override(pictureSizeInPx, pictureSizeInPx)
.into(imageView)
}
private fun setFallbackPicture(imageView: ImageView, address: Address) {
val context = imageView.context
Glide.with(context)
.using(AddressModelLoader(backgroundCacheId), Address::class.java)
.from(Address::class.java)
.`as`(Bitmap::class.java)
.transcode(BitmapToGlideDrawableTranscoder(context), GlideDrawable::class.java)
.decoder(ContactLetterBitmapDecoder())
.encoder(BitmapEncoder(Bitmap.CompressFormat.PNG, 0))
.cacheDecoder(FileToStreamDecoder(StreamBitmapDecoder(context)))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.load(address)
// for some reason, following 2 lines fix loading issues.
.dontAnimate()
.override(pictureSizeInPx, pictureSizeInPx)
.into(imageView)
}
@WorkerThread
private fun loadContactPicture(photoUri: Uri?, address: Address): Bitmap? {
val bitmapTarget: FutureTarget<Bitmap>
if (photoUri != null) {
bitmapTarget = Glide.with(context)
.load(photoUri)
.asBitmap()
.diskCacheStrategy(DiskCacheStrategy.NONE)
.dontAnimate()
.into(pictureSizeInPx, pictureSizeInPx)
} else {
bitmapTarget = Glide.with(context)
.using(FallbackGlideModelLoader(), FallbackGlideParams::class.java)
.from(FallbackGlideParams::class.java)
.`as`(Bitmap::class.java)
.decoder(FallbackGlideBitmapDecoder())
.encoder(BitmapEncoder(CompressFormat.PNG, 0))
.cacheDecoder(FileToStreamDecoder(StreamBitmapDecoder(context)))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.load(FallbackGlideParams(address))
.dontAnimate()
.into(pictureSizeInPx, pictureSizeInPx)
}
fun getContactPicture(recipient: Recipient): Bitmap? {
val contactPictureUri = recipient.photoThumbnailUri
val address = recipient.address
return loadIgnoringErrors(bitmapTarget)
return if (contactPictureUri != null) {
getContactPicture(contactPictureUri)
} else {
getFallbackPicture(address)
}
}
private inner class FallbackGlideBitmapDecoder : ResourceDecoder<FallbackGlideParams, Bitmap> {
override fun decode(source: FallbackGlideParams, width: Int, height: Int): Resource<Bitmap> {
private fun getContactPicture(contactPictureUri: Uri): Bitmap? {
return Glide.with(context)
.load(contactPictureUri)
.asBitmap()
.diskCacheStrategy(DiskCacheStrategy.NONE)
.dontAnimate()
.into(pictureSizeInPx, pictureSizeInPx)
.getOrNull()
}
private fun getFallbackPicture(address: Address): Bitmap? {
return Glide.with(context)
.using(AddressModelLoader(backgroundCacheId), Address::class.java)
.from(Address::class.java)
.`as`(Bitmap::class.java)
.decoder(ContactLetterBitmapDecoder())
.encoder(BitmapEncoder(CompressFormat.PNG, 0))
.cacheDecoder(FileToStreamDecoder(StreamBitmapDecoder(context)))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.load(address)
.dontAnimate()
.into(pictureSizeInPx, pictureSizeInPx)
.getOrNull()
}
private inner class ContactLetterBitmapDecoder : ResourceDecoder<Address, Bitmap> {
override fun decode(address: Address, width: Int, height: Int): Resource<Bitmap> {
val pool = Glide.get(context).bitmapPool
val bitmap: Bitmap =
pool.getDirty(pictureSizeInPx, pictureSizeInPx, Bitmap.Config.ARGB_8888) ?:
Bitmap.createBitmap(pictureSizeInPx, pictureSizeInPx, Bitmap.Config.ARGB_8888)
contactLetterBitmapCreator.drawBitmap(bitmap, pictureSizeInPx, source.address)
contactLetterBitmapCreator.drawBitmap(bitmap, pictureSizeInPx, address)
return BitmapResource.obtain(bitmap, pool)
}
@ -150,28 +147,77 @@ class ContactPictureLoader(
}
}
private inner class FallbackGlideParams(val address: Address) {
val id: String
get() = String.format(Locale.ROOT, "%s-%s", address.address, address.personal)
}
private class AddressModelLoader(val backgroundCacheId: String) : ModelLoader<Address, Address> {
override fun getResourceFetcher(address: Address, width: Int, height: Int): DataFetcher<Address> {
return object : DataFetcher<Address> {
override fun getId() = "${address.address}-${address.personal}-$backgroundCacheId"
private inner class FallbackGlideModelLoader : ModelLoader<FallbackGlideParams, FallbackGlideParams> {
override fun getResourceFetcher(
model: FallbackGlideParams,
width: Int,
height: Int
): DataFetcher<FallbackGlideParams> = object : DataFetcher<FallbackGlideParams> {
override fun loadData(priority: Priority): FallbackGlideParams = model
override fun getId(): String = model.id
override fun cleanup() = Unit
override fun cancel() = Unit
override fun loadData(priority: Priority?): Address = address
override fun cleanup() = Unit
override fun cancel() = Unit
}
}
}
@WorkerThread
private fun <T> loadIgnoringErrors(target: FutureTarget<T>): T? {
private inner class ContactPictureModelLoader : StreamModelLoader<Address> {
override fun getResourceFetcher(address: Address, width: Int, height: Int): DataFetcher<InputStream>? {
return ContactPictureDataFetcher(address)
}
}
private inner class ContactPictureDataFetcher(val address: Address) : DataFetcher<InputStream> {
var streamLocalUriFetcher: StreamLocalUriFetcher? = null
override fun loadData(priority: Priority?): InputStream? {
val photoUri = contactsHelper.getPhotoUri(address.address)
return photoUri?.let {
StreamLocalUriFetcher(context, photoUri).also {
streamLocalUriFetcher = it
}.loadData(priority)
}
}
override fun cancel() = Unit
override fun cleanup() {
streamLocalUriFetcher?.cleanup()
}
override fun getId() = "contact:${address.address}"
}
private inner class FallbackImageRequestListener<T>(
val address: Address,
val imageView: ImageView
) : RequestListener<T, GlideDrawable> {
override fun onException(
e: Exception?,
model: T,
target: Target<GlideDrawable>,
isFirstResource: Boolean
): Boolean {
setFallbackPicture(imageView, address)
return true
}
override fun onResourceReady(
resource: GlideDrawable,
model: T,
target: Target<GlideDrawable>,
isFromMemoryCache: Boolean,
isFirstResource: Boolean
): Boolean {
return false
}
}
private fun <T> FutureTarget<T>.getOrNull(): T? {
return try {
target.get()
get()
} catch (e: Exception) {
null
}

View file

@ -279,7 +279,7 @@ public class MessageListAdapter extends CursorAdapter {
* doesn't reset the padding, so we do it ourselves.
*/
holder.contactBadge.setPadding(0, 0, 0, 0);
fragment.contactsPictureLoader.loadContactPicture(counterpartyAddress, holder.contactBadge);
fragment.contactsPictureLoader.setContactPicture(holder.contactBadge, counterpartyAddress);
} else {
holder.contactBadge.assignContactUri(null);
holder.contactBadge.setImageResource(R.drawable.ic_contact_picture);

View file

@ -316,7 +316,7 @@ public class MessageHeader extends LinearLayout implements OnClickListener, OnLo
if (K9.showContactPicture()) {
if (counterpartyAddress != null) {
mContactBadge.setContact(counterpartyAddress);
mContactsPictureLoader.loadContactPicture(counterpartyAddress, mContactBadge);
mContactsPictureLoader.setContactPicture(mContactBadge, counterpartyAddress);
} else {
mContactBadge.setImageResource(R.drawable.ic_contact_picture);
}