diff --git a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/Downsampler.java b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/Downsampler.java index 065e9796a9..7db211e775 100644 --- a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/Downsampler.java +++ b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/Downsampler.java @@ -400,17 +400,19 @@ private static Bitmap decodeStream(InputStream is, BitmapFactory.Options options int sourceHeight = options.outHeight; String outMimeType = options.outMimeType; final Bitmap result; + TransformationUtils.getBitmapDrawableLock().lock(); try { result = BitmapFactory.decodeStream(is, null, options); } catch (IllegalArgumentException e) { throw newIoExceptionForInBitmapAssertion(e, sourceWidth, sourceHeight, outMimeType, options); + } finally { + TransformationUtils.getBitmapDrawableLock().unlock(); } if (options.inJustDecodeBounds) { is.reset(); } - return result; } diff --git a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/TransformationUtils.java b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/TransformationUtils.java index 5cebbe1789..9ddd47535e 100644 --- a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/TransformationUtils.java +++ b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/TransformationUtils.java @@ -20,6 +20,11 @@ import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; import com.bumptech.glide.util.Preconditions; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + /** * A class with methods to efficiently resize Bitmaps. */ @@ -30,6 +35,17 @@ public final class TransformationUtils { private static final int CIRCLE_CROP_PAINT_FLAGS = PAINT_FLAGS | Paint.ANTI_ALIAS_FLAG; private static final Paint CIRCLE_CROP_SHAPE_PAINT = new Paint(CIRCLE_CROP_PAINT_FLAGS); private static final Paint CIRCLE_CROP_BITMAP_PAINT; + /** + * https://github.com/bumptech/glide/issues/738 On some devices (Moto X with android 5.1) bitmap + * drawing is not thread safe. + * This lock only locks for these specific devices. For other types of devices the lock is always + * available and therefore does not impact performance + */ + private static final Lock BITMAP_DRAWABLE_LOCK = "XT1097".equals(Build.MODEL) + && Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP_MR1 + ? new ReentrantLock() + : new NoLock(); + static { CIRCLE_CROP_BITMAP_PAINT = new Paint(CIRCLE_CROP_PAINT_FLAGS); CIRCLE_CROP_BITMAP_PAINT.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); @@ -39,6 +55,11 @@ private TransformationUtils() { // Utility class. } + + public static Lock getBitmapDrawableLock() { + return BITMAP_DRAWABLE_LOCK; + } + /** * A potentially expensive operation to crop the given Bitmap so that it fills the given * dimensions. This operation is significantly less expensive in terms of memory if a mutable @@ -74,9 +95,7 @@ public static Bitmap centerCrop(@NonNull BitmapPool pool, @NonNull Bitmap inBitm // We don't add or remove alpha, so keep the alpha setting of the Bitmap we were given. TransformationUtils.setAlpha(inBitmap, result); - Canvas canvas = new Canvas(result); - canvas.drawBitmap(inBitmap, m, DEFAULT_PAINT); - clear(canvas); + applyMatrix(inBitmap, result, m); return result; } @@ -129,11 +148,9 @@ public static Bitmap fitCenter(@NonNull BitmapPool pool, @NonNull Bitmap inBitma Log.v(TAG, "minPct: " + minPercentage); } - Canvas canvas = new Canvas(toReuse); Matrix matrix = new Matrix(); matrix.setScale(minPercentage, minPercentage); - canvas.drawBitmap(inBitmap, matrix, DEFAULT_PAINT); - clear(canvas); + applyMatrix(inBitmap, toReuse, matrix); return toReuse; } @@ -266,10 +283,7 @@ public static Bitmap rotateImageExif(@NonNull BitmapPool pool, @NonNull Bitmap i matrix.postTranslate(-newRect.left, -newRect.top); - final Canvas canvas = new Canvas(result); - canvas.drawBitmap(inBitmap, matrix, DEFAULT_PAINT); - clear(canvas); - + applyMatrix(inBitmap, result, matrix); return result; } @@ -301,15 +315,20 @@ public static Bitmap circleCrop(@NonNull BitmapPool pool, @NonNull Bitmap inBitm Bitmap result = pool.get(destWidth, destHeight, getSafeConfig(toTransform)); setAlphaIfAvailable(result, true /*hasAlpha*/); - Canvas canvas = new Canvas(result); - // Draw a circle - canvas.drawCircle(destRect.left + radius, destRect.top + radius, radius, - CIRCLE_CROP_SHAPE_PAINT); - // Draw the bitmap in the circle - canvas.drawBitmap(toTransform, srcRect, destRect, CIRCLE_CROP_BITMAP_PAINT); - clear(canvas); + BITMAP_DRAWABLE_LOCK.lock(); + try { + Canvas canvas = new Canvas(result); + // Draw a circle + canvas.drawCircle(destRect.left + radius, destRect.top + radius, radius, + CIRCLE_CROP_SHAPE_PAINT); + // Draw the bitmap in the circle + canvas.drawBitmap(toTransform, srcRect, destRect, CIRCLE_CROP_BITMAP_PAINT); + clear(canvas); + } finally { + BITMAP_DRAWABLE_LOCK.unlock(); + } if (!toTransform.equals(inBitmap)) { pool.put(toTransform); @@ -361,10 +380,15 @@ public static Bitmap roundedCorners(@NonNull BitmapPool pool, @NonNull Bitmap in paint.setAntiAlias(true); paint.setShader(shader); RectF rect = new RectF(0, 0, result.getWidth(), result.getHeight()); - Canvas canvas = new Canvas(result); - canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); - canvas.drawRoundRect(rect, roundingRadius, roundingRadius, paint); - clear(canvas); + BITMAP_DRAWABLE_LOCK.lock(); + try { + Canvas canvas = new Canvas(result); + canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); + canvas.drawRoundRect(rect, roundingRadius, roundingRadius, paint); + clear(canvas); + } finally { + BITMAP_DRAWABLE_LOCK.unlock(); + } if (!toTransform.equals(inBitmap)) { pool.put(toTransform); @@ -382,6 +406,18 @@ private static Bitmap.Config getSafeConfig(Bitmap bitmap) { return bitmap.getConfig() != null ? bitmap.getConfig() : Bitmap.Config.ARGB_8888; } + private static void applyMatrix(@NonNull Bitmap inBitmap, @NonNull Bitmap targetBitmap, + Matrix matrix) { + BITMAP_DRAWABLE_LOCK.lock(); + try { + Canvas canvas = new Canvas(targetBitmap); + canvas.drawBitmap(inBitmap, matrix, DEFAULT_PAINT); + clear(canvas); + } finally { + BITMAP_DRAWABLE_LOCK.unlock(); + } + } + // Visible for testing. static void initializeMatrixForRotation(int exifOrientation, Matrix matrix) { switch (exifOrientation) { @@ -413,4 +449,37 @@ static void initializeMatrixForRotation(int exifOrientation, Matrix matrix) { // Do nothing. } } + + private static final class NoLock implements Lock { + @Override + public void lock() { + // do nothing + } + + @Override + public void lockInterruptibly() throws InterruptedException { + // do nothing + } + + @Override + public boolean tryLock() { + return true; + } + + @Override + public boolean tryLock(long time, @NonNull TimeUnit unit) throws InterruptedException { + return true; + } + + @Override + public void unlock() { + // do nothing + } + + @NonNull + @Override + public Condition newCondition() { + throw new UnsupportedOperationException("Should not be called"); + } + } }