提交 4185efc3 编写于 作者: S Sam Judd

Decode first gif frame before returning resource.

Fixes #159.
上级 e08fca67
......@@ -49,6 +49,7 @@ import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.shadows.ShadowBitmap;
import java.io.ByteArrayInputStream;
import java.io.File;
......@@ -79,7 +80,7 @@ import static org.mockito.Mockito.when;
*/
@RunWith(RobolectricTestRunner.class)
@Config(shadows = { GlideTest.ShadowFileDescriptorContentResolver.class, GlideTest.ShadowMediaMetadataRetriever.class,
GlideShadowLooper.class })
GlideShadowLooper.class, GlideTest.MutableShadowBitmap.class })
public class GlideTest {
private Target target = null;
private ImageView imageView;
......@@ -808,6 +809,17 @@ public class GlideTest {
}
}
@Implements(Bitmap.class)
public static class MutableShadowBitmap extends ShadowBitmap {
@Implementation
public static Bitmap createBitmap(int width, int height, Bitmap.Config config) {
Bitmap bitmap = ShadowBitmap.createBitmap(width, height, config);
Robolectric.shadowOf(bitmap).setMutable(true);
return bitmap;
}
}
@Implements(MediaMetadataRetriever.class)
public static class ShadowMediaMetadataRetriever {
......
package com.bumptech.glide.load.resource.gif;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import com.bumptech.glide.util.Util;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
......@@ -41,11 +42,13 @@ public class GifDrawableResourceTest {
}
@Test
public void testReturnsDrawableSize() {
public void testReturnsDrawableSizePlusFirstFrameSize() {
final int size = 2134;
Bitmap firstFrame = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
when(drawable.getFirstFrame()).thenReturn(firstFrame);
when(drawable.getData()).thenReturn(new byte[size]);
assertEquals(size, resource.getSize());
assertEquals(size + Util.getBitmapByteSize(firstFrame), resource.getSize());
}
@Test
......
......@@ -9,6 +9,7 @@ import android.graphics.drawable.Drawable;
import com.bumptech.glide.gifdecoder.GifDecoder;
import com.bumptech.glide.gifdecoder.GifHeader;
import com.bumptech.glide.load.Transformation;
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
import com.bumptech.glide.load.resource.drawable.GlideDrawable;
import com.bumptech.glide.tests.GlideShadowLooper;
......@@ -42,24 +43,47 @@ public class GifDrawableTest {
private Drawable.Callback cb = mock(Drawable.Callback.class);
private int frameHeight;
private int frameWidth;
private Bitmap firstFrame;
private BitmapPool bitmapPool;
@Before
public void setUp() {
frameWidth = 120;
frameHeight = 450;
gifDecoder = mock(GifDecoder.class);
drawable = new GifDrawable(gifDecoder, frameManager, frameWidth, frameHeight);
firstFrame = Bitmap.createBitmap(frameWidth, frameHeight, Bitmap.Config.RGB_565);
bitmapPool = mock(BitmapPool.class);
drawable = new GifDrawable(gifDecoder, frameManager, firstFrame, bitmapPool);
drawable.setCallback(cb);
}
@Test
public void testShouldNotDrawNullBitmap() {
public void testShouldDrawFirstFrameBeforeAnyFrameRead() {
Canvas canvas = mock(Canvas.class);
drawable.draw(canvas);
verify(canvas).drawBitmap(eq(firstFrame), anyInt(), anyInt(), any(Paint.class));
}
@Test
public void testShouldNotDrawNullBitmapFrame() {
Canvas canvas = mock(Canvas.class);
drawable = new GifDrawable(gifDecoder, frameManager, firstFrame, bitmapPool);
drawable.onFrameRead(null, 0);
drawable.draw(canvas);
verify(canvas).drawBitmap(eq(firstFrame), anyInt(), anyInt(), any(Paint.class));
verify(canvas, never()).drawBitmap((Bitmap) isNull(), anyInt(), anyInt(), any(Paint.class));
}
@Test
public void testDoesNotDrawNullFirstFrame() {
drawable = new GifDrawable(gifDecoder, frameManager, null, bitmapPool);
Canvas canvas = mock(Canvas.class);
verify(canvas, never()).drawBitmap(any(Bitmap.class), anyInt(), anyInt(), any(Paint.class));
}
@Test
public void testRequestsNextFrameOnStart() {
drawable.setVisible(true, true);
......@@ -80,7 +104,7 @@ public class GifDrawableTest {
drawable.setVisible(false, false);
drawable.start();
verify(frameManager, never()).getNextFrame(eq(drawable));
verify(frameManager, never()).getNextFrame(any(GifFrameManager.FrameCallback.class));
}
@Test
......@@ -226,6 +250,13 @@ public class GifDrawableTest {
verify(frameManager).clear();
}
@Test
public void testRecycleReturnsFirstFrameToPool() {
drawable.recycle();
verify(bitmapPool).put(eq(firstFrame));
}
@Test
public void testIsNotRecycledIfNotRecycled() {
assertFalse(drawable.isRecycled());
......@@ -248,8 +279,9 @@ public class GifDrawableTest {
GifHeader gifHeader = new GifHeader();
Transformation<Bitmap> transformation = mock(Transformation.class);
GifDecoder.BitmapProvider provider = mock(GifDecoder.BitmapProvider.class);
drawable = new GifDrawable(Robolectric.application, provider, transformation, 100, 100, "fakeId", gifHeader,
new byte[0], 100, 100);
Bitmap firstFrame = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
drawable = new GifDrawable(Robolectric.application, provider, bitmapPool, transformation, 100, 100, "fakeId",
gifHeader, new byte[0], firstFrame);
assertNotNull(drawable.getConstantState().newDrawable());
assertNotNull(drawable.getConstantState().newDrawable(Robolectric.application.getResources()));
......
......@@ -51,6 +51,9 @@ public class GifDrawableTransformationTest {
when(gifDrawable.getIntrinsicHeight()).thenReturn(500);
when(resource.get()).thenReturn(gifDrawable);
Bitmap firstFrame = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
when(gifDrawable.getFirstFrame()).thenReturn(firstFrame);
final int width = 123;
final int height = 456;
Bitmap expectedBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
......@@ -60,6 +63,6 @@ public class GifDrawableTransformationTest {
transformation.transform(resource, width, height);
verify(gifDrawable).setFrameTransformation(any(Transformation.class), eq(width), eq(height));
verify(gifDrawable).setFrameTransformation(any(Transformation.class), eq(expectedBitmap));
}
}
package com.bumptech.glide.load.resource.gif;
import android.graphics.Bitmap;
import com.bumptech.glide.gifdecoder.GifDecoder;
import com.bumptech.glide.gifdecoder.GifHeader;
import com.bumptech.glide.gifdecoder.GifHeaderParser;
......@@ -9,6 +10,8 @@ import com.bumptech.glide.tests.GlideShadowLooper;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
import org.mockito.Mockito;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
......@@ -17,10 +20,12 @@ import java.io.ByteArrayInputStream;
import java.io.IOException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
......@@ -31,48 +36,50 @@ public class GifResourceDecoderTest {
private GifResourceDecoder decoder;
private GifHeaderParser parser;
private GifResourceDecoder.GifHeaderParserPool parserPool;
private GifResourceDecoder.GifDecoderPool decoderPool;
private GifDecoder gifDecoder;
private GifHeader gifHeader;
@Before
public void setUp() {
BitmapPool bitmapPool = mock(BitmapPool.class);
gifHeader = Mockito.spy(new GifHeader());
parser = mock(GifHeaderParser.class);
when(parser.parseHeader()).thenReturn(gifHeader);
parserPool = mock(GifResourceDecoder.GifHeaderParserPool.class);
when(parserPool.obtain(any(byte[].class))).thenReturn(parser);
decoder = new GifResourceDecoder(Robolectric.application, bitmapPool, parserPool);
gifDecoder = mock(GifDecoder.class);
decoderPool = mock(GifResourceDecoder.GifDecoderPool.class);
when(decoderPool.obtain(any(GifDecoder.BitmapProvider.class))).thenReturn(gifDecoder);
decoder = new GifResourceDecoder(Robolectric.application, bitmapPool, parserPool, decoderPool);
}
@Test
public void testReturnsNullIfParsedHeaderHasZeroFrames() throws IOException {
GifHeader header = mock(GifHeader.class);
when(parser.parseHeader()).thenReturn(header);
when(header.getNumFrames()).thenReturn(0);
when(gifHeader.getNumFrames()).thenReturn(0);
assertNull(decoder.decode(new ByteArrayInputStream(new byte[0]), 100, 100));
}
@Test
public void testReturnsNullIfParsedHeaderHasFormatError() {
GifHeader header = mock(GifHeader.class);
when(parser.parseHeader()).thenReturn(header);
when(header.getStatus()).thenReturn(GifDecoder.STATUS_FORMAT_ERROR);
when(gifHeader.getStatus()).thenReturn(GifDecoder.STATUS_FORMAT_ERROR);
assertNull(decoder.decode(new ByteArrayInputStream(new byte[0]), 100, 100));
}
@Test
public void testReturnsNullIfParsedHeaderHasOpenError() {
GifHeader header = mock(GifHeader.class);
when(parser.parseHeader()).thenReturn(header);
when(header.getStatus()).thenReturn(GifDecoder.STATUS_OPEN_ERROR);
when(gifHeader.getStatus()).thenReturn(GifDecoder.STATUS_OPEN_ERROR);
assertNull(decoder.decode(new ByteArrayInputStream(new byte[0]), 100, 100));
}
@Test
public void testReturnsParserToPool() throws IOException {
when(parserPool.obtain(any(byte[].class))).thenReturn(parser);
when(parser.parseHeader()).thenReturn(mock(GifHeader.class));
decoder.decode(new ByteArrayInputStream(new byte[0]), 100, 100);
verify(parserPool).release(eq(parser));
}
......@@ -82,7 +89,7 @@ public class GifResourceDecoderTest {
when(parser.parseHeader()).thenThrow(new RuntimeException("Test"));
try {
decoder.decode(new ByteArrayInputStream(new byte[0]), 100, 100);
fail("Expected exception is not thrown.");
fail("Failed to receive expected exception");
} catch (RuntimeException e) {
// Expected.
}
......@@ -90,6 +97,60 @@ public class GifResourceDecoderTest {
verify(parserPool).release(eq(parser));
}
@Test
public void testDecodesFirstFrameAndReturnsGifDecoderToPool() {
when(gifHeader.getNumFrames()).thenReturn(1);
when(gifHeader.getStatus()).thenReturn(GifDecoder.STATUS_OK);
when(gifDecoder.getNextFrame()).thenReturn(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888));
byte[] data = new byte[100];
decoder.decode(new ByteArrayInputStream(data), 100, 100);
InOrder order = inOrder(decoderPool, gifDecoder);
order.verify(decoderPool).obtain(any(GifDecoder.BitmapProvider.class));
order.verify(gifDecoder).setData(any(String.class), eq(gifHeader), eq(data));
order.verify(gifDecoder).advance();
order.verify(gifDecoder).getNextFrame();
order.verify(decoderPool).release(eq(gifDecoder));
}
@Test
public void testReturnsGifDecoderToPoolWhenDecoderThrows() {
when(gifHeader.getNumFrames()).thenReturn(1);
when(gifHeader.getStatus()).thenReturn(GifDecoder.STATUS_OK);
when(gifDecoder.getNextFrame()).thenThrow(new RuntimeException("test"));
try {
decoder.decode(new ByteArrayInputStream(new byte[0]), 100, 100);
fail("Failed to receive expected exception");
} catch (RuntimeException e) {
// Expected.
}
verify(decoderPool).release(eq(gifDecoder));
}
@Test
public void testCanObtainNonNullDecoderFromPool() {
GifDecoder.BitmapProvider provider = mock(GifDecoder.BitmapProvider.class);
GifResourceDecoder.GifDecoderPool pool = new GifResourceDecoder.GifDecoderPool();
assertNotNull(pool.obtain(provider));
}
@Test
public void testCanPutAndObtainDecoderFromPool() {
GifResourceDecoder.GifDecoderPool pool = new GifResourceDecoder.GifDecoderPool();
pool.release(gifDecoder);
GifDecoder fromPool = pool.obtain(mock(GifDecoder.BitmapProvider.class));
assertEquals(gifDecoder, fromPool);
}
@Test
public void testDecoderPoolClearsDecoders() {
GifResourceDecoder.GifDecoderPool pool = new GifResourceDecoder.GifDecoderPool();
pool.release(gifDecoder);
verify(gifDecoder).clear();
}
@Test
public void testHasValidId() {
assertEquals("", decoder.getId());
......
......@@ -14,6 +14,7 @@ import android.os.Build;
import com.bumptech.glide.gifdecoder.GifDecoder;
import com.bumptech.glide.gifdecoder.GifHeader;
import com.bumptech.glide.load.Transformation;
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
import com.bumptech.glide.load.resource.drawable.GlideDrawable;
/**
......@@ -47,11 +48,13 @@ public class GifDrawable extends GlideDrawable implements GifFrameManager.FrameC
/**
* Constructor for GifDrawable.
*
* @see #setFrameTransformation(com.bumptech.glide.load.Transformation, int, int)
* @see #setFrameTransformation(com.bumptech.glide.load.Transformation, android.graphics.Bitmap)
*
* @param context A context.
* @param bitmapProvider An {@link com.bumptech.glide.gifdecoder.GifDecoder.BitmapProvider} that can be used to
* retrieve re-usable {@link android.graphics.Bitmap}s.
* @param bitmapPool A {@link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool} that can be used to return
* the first frame when this drawable is recycled.
* @param frameTransformation An {@link com.bumptech.glide.load.Transformation} that can be applied to each frame.
* @param targetFrameWidth The desired width of the frames displayed by this drawable (the width of the view or
* {@link com.bumptech.glide.request.target.Target} this drawable is being loaded into).
......@@ -60,15 +63,13 @@ public class GifDrawable extends GlideDrawable implements GifFrameManager.FrameC
* @param id An id that uniquely identifies this particular gif.
* @param gifHeader The header data for this gif.
* @param data The full bytes of the gif.
* @param finalFrameWidth The final width of the frames displayed by this drawable after they have been transformed.
* @param finalFrameHeight The final height of the frames displayed by this drwaable after they have been
* transformed.
* @param firstFrame The decoded and transformed first frame of this gif.
*/
public GifDrawable(Context context, GifDecoder.BitmapProvider bitmapProvider,
public GifDrawable(Context context, GifDecoder.BitmapProvider bitmapProvider, BitmapPool bitmapPool,
Transformation<Bitmap> frameTransformation, int targetFrameWidth, int targetFrameHeight, String id,
GifHeader gifHeader, byte[] data, int finalFrameWidth, int finalFrameHeight) {
GifHeader gifHeader, byte[] data, Bitmap firstFrame) {
this(new GifState(id, gifHeader, data, context, frameTransformation, targetFrameWidth, targetFrameHeight,
bitmapProvider, finalFrameWidth, finalFrameHeight));
bitmapProvider, bitmapPool, firstFrame));
}
private GifDrawable(GifState state) {
......@@ -76,23 +77,25 @@ public class GifDrawable extends GlideDrawable implements GifFrameManager.FrameC
this.decoder = new GifDecoder(state.bitmapProvider);
decoder.setData(state.id, state.gifHeader, state.data);
frameManager = new GifFrameManager(state.context, decoder, state.frameTransformation, state.targetWidth,
state.targetHeight, state.finalFrameWidth, state.finalFrameHeight);
state.targetHeight, state.firstFrame.getWidth(), state.firstFrame.getHeight());
}
// For testing.
GifDrawable(GifDecoder decoder, GifFrameManager frameManager, int finalFrameWidth, int finalFrameHeight) {
// Visible for testing.
GifDrawable(GifDecoder decoder, GifFrameManager frameManager, Bitmap firstFrame, BitmapPool bitmapPool) {
this.decoder = decoder;
this.frameManager = frameManager;
this.state = new GifState(null);
state.finalFrameWidth = finalFrameWidth;
state.finalFrameHeight = finalFrameHeight;
state.bitmapPool = bitmapPool;
state.firstFrame = firstFrame;
}
public Bitmap getFirstFrame() {
return state.firstFrame;
}
public void setFrameTransformation(Transformation<Bitmap> frameTransformation, int finalFrameWidth,
int finalFrameHeight) {
public void setFrameTransformation(Transformation<Bitmap> frameTransformation, Bitmap firstFrame) {
state.frameTransformation = frameTransformation;
state.finalFrameWidth = finalFrameWidth;
state.finalFrameHeight = finalFrameHeight;
state.firstFrame = firstFrame;
}
public Transformation<Bitmap> getFrameTransformation() {
......@@ -147,12 +150,12 @@ public class GifDrawable extends GlideDrawable implements GifFrameManager.FrameC
@Override
public int getIntrinsicWidth() {
return state.finalFrameWidth;
return state.firstFrame.getWidth();
}
@Override
public int getIntrinsicHeight() {
return state.finalFrameHeight;
return state.firstFrame.getHeight();
}
@Override
......@@ -167,9 +170,8 @@ public class GifDrawable extends GlideDrawable implements GifFrameManager.FrameC
@Override
public void draw(Canvas canvas) {
if (currentFrame != null) {
canvas.drawBitmap(currentFrame, 0, 0, paint);
}
Bitmap toDraw = currentFrame != null ? currentFrame : state.firstFrame;
canvas.drawBitmap(toDraw, 0, 0, paint);
}
@Override
......@@ -224,6 +226,7 @@ public class GifDrawable extends GlideDrawable implements GifFrameManager.FrameC
*/
public void recycle() {
isRecycled = true;
state.bitmapPool.put(state.firstFrame);
frameManager.clear();
}
......@@ -255,22 +258,22 @@ public class GifDrawable extends GlideDrawable implements GifFrameManager.FrameC
String id;
GifHeader gifHeader;
byte[] data;
int finalFrameWidth;
int finalFrameHeight;
Context context;
Transformation<Bitmap> frameTransformation;
int targetWidth;
int targetHeight;
GifDecoder.BitmapProvider bitmapProvider;
BitmapPool bitmapPool;
Bitmap firstFrame;
public GifState(String id, GifHeader header, byte[] data, Context context,
Transformation<Bitmap> frameTransformation, int targetWidth, int targetHeight,
GifDecoder.BitmapProvider provider, int finalFrameWidth, int finalFrameHeight) {
GifDecoder.BitmapProvider provider, BitmapPool bitmapPool, Bitmap firstFrame) {
this.id = id;
gifHeader = header;
this.data = data;
this.finalFrameWidth = finalFrameWidth;
this.finalFrameHeight = finalFrameHeight;
this.bitmapPool = bitmapPool;
this.firstFrame = firstFrame;
this.context = context.getApplicationContext();
this.frameTransformation = frameTransformation;
this.targetWidth = targetWidth;
......@@ -288,8 +291,8 @@ public class GifDrawable extends GlideDrawable implements GifFrameManager.FrameC
targetWidth = original.targetWidth;
targetHeight = original.targetHeight;
bitmapProvider = original.bitmapProvider;
finalFrameWidth = original.finalFrameWidth;
finalFrameHeight = original.finalFrameHeight;
bitmapPool = original.bitmapPool;
firstFrame = original.firstFrame;
}
}
......
package com.bumptech.glide.load.resource.gif;
import com.bumptech.glide.load.resource.drawable.DrawableResource;
import com.bumptech.glide.util.Util;
/**
* A resource wrapping an {@link com.bumptech.glide.load.resource.gif.GifDrawable}.
......@@ -12,7 +13,7 @@ public class GifDrawableResource extends DrawableResource<GifDrawable> {
@Override
public int getSize() {
return drawable.getData().length;
return drawable.getData().length + Util.getBitmapByteSize(drawable.getFirstFrame());
}
@Override
......
package com.bumptech.glide.load.resource.gif;
import android.graphics.Bitmap;
import com.bumptech.glide.load.MultiTransformation;
import com.bumptech.glide.load.Transformation;
import com.bumptech.glide.load.engine.Resource;
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
......@@ -24,32 +22,20 @@ public class GifDrawableTransformation implements Transformation<GifDrawable> {
@Override
public Resource<GifDrawable> transform(Resource<GifDrawable> resource, int outWidth, int outHeight) {
GifDrawable drawable = resource.get();
@SuppressWarnings("unchecked")
Transformation<Bitmap> newTransformation =
new MultiTransformation<Bitmap>(drawable.getFrameTransformation(), wrapped);
// The drawable needs to be initialized with the correct width and height in order for a view displaying it
// to end up with the right dimensions. Since our transformations may arbitrarily modify the dimensions of
// our gif, here we create a stand in for a frame and pass it to the transformation to see what the final
// transformed dimensions will be so that our drawable can report the correct intrinsict width and height.
Bitmap toTest = bitmapPool.get(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(),
Bitmap.Config.RGB_565);
if (toTest == null) {
toTest = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(),
Bitmap.Config.RGB_565);
}
Resource<Bitmap> bitmapResource = new BitmapResource(toTest, bitmapPool);
Resource<Bitmap> transformed = newTransformation.transform(bitmapResource, outWidth, outHeight);
// transformed dimensions will be so that our drawable can report the correct intrinsic width and height.
Bitmap firstFrame = resource.get().getFirstFrame();
Resource<Bitmap> bitmapResource = new BitmapResource(firstFrame, bitmapPool);
Resource<Bitmap> transformed = wrapped.transform(bitmapResource, outWidth, outHeight);
if (bitmapResource != transformed) {
bitmapResource.recycle();
}
Bitmap bitmap = transformed.get();
final int transformedWidth = bitmap.getWidth();
final int transformedHeight = bitmap.getHeight();
transformed.recycle();
Bitmap transformedFrame = transformed.get();
drawable.setFrameTransformation(newTransformation, transformedWidth, transformedHeight);
drawable.setFrameTransformation(wrapped, transformedFrame);
return resource;
}
......
......@@ -3,7 +3,6 @@ package com.bumptech.glide.load.resource.gif;
import android.content.Context;
import android.graphics.Bitmap;
import android.util.Log;
import com.bumptech.glide.Glide;
import com.bumptech.glide.gifdecoder.GifDecoder;
import com.bumptech.glide.gifdecoder.GifHeader;
......@@ -28,22 +27,30 @@ import java.util.UUID;
*/
public class GifResourceDecoder implements ResourceDecoder<InputStream, GifDrawable> {
private static final String TAG = "GifResourceDecoder";
private static final GifHeaderParserPool PARSER_POOL = new DefaultGifHeaderParserPool();
private static final GifHeaderParserPool PARSER_POOL = new GifHeaderParserPool();
private static final GifDecoderPool DECODER_POOL = new GifDecoderPool();
private final Context context;
private final BitmapPool bitmapPool;
private final GifHeaderParserPool parserPool;
private BitmapPool bitmapPool;
private final GifDecoderPool decoderPool;
private final GifBitmapProvider provider;
public GifResourceDecoder(Context context) {
this(context, Glide.get(context).getBitmapPool());
}
public GifResourceDecoder(Context context, BitmapPool bitmapPool) {
this(context, bitmapPool, PARSER_POOL);
this(context, bitmapPool, PARSER_POOL, DECODER_POOL);
}
GifResourceDecoder(Context context, BitmapPool bitmapPool, GifHeaderParserPool parserPool) {
// Visible for testing.
GifResourceDecoder(Context context, BitmapPool bitmapPool, GifHeaderParserPool parserPool,
GifDecoderPool decoderPool) {
this.context = context;
this.bitmapPool = bitmapPool;
this.decoderPool = decoderPool;
this.provider = new GifBitmapProvider(bitmapPool);
this.parserPool = parserPool;
}
......@@ -51,14 +58,16 @@ public class GifResourceDecoder implements ResourceDecoder<InputStream, GifDrawa
public GifDrawableResource decode(InputStream source, int width, int height) {
byte[] data = inputStreamToBytes(source);
final GifHeaderParser parser = parserPool.obtain(data);
final GifDecoder decoder = decoderPool.obtain(provider);
try {
return decode(data, width, height, parser);
return decode(data, width, height, parser, decoder);
} finally {
parserPool.release(parser);
decoderPool.release(decoder);
}
}
private GifDrawableResource decode(byte[] data, int width, int height, GifHeaderParser parser) {
private GifDrawableResource decode(byte[] data, int width, int height, GifHeaderParser parser, GifDecoder decoder) {
final GifHeader header = parser.parseHeader();
if (header.getNumFrames() <= 0 || header.getStatus() != GifDecoder.STATUS_OK) {
// If we couldn't decode the GIF, we will end up with a frame count of 0.
......@@ -66,14 +75,21 @@ public class GifResourceDecoder implements ResourceDecoder<InputStream, GifDrawa
}
String id = getGifId(data);
Bitmap firstFrame = decodeFirstFrame(decoder, id, header, data);
Transformation<Bitmap> unitTransformation = UnitTransformation.get();
Transformation<Bitmap> transformation = UnitTransformation.get();
GifDrawable gifDrawable = new GifDrawable(context, new GifBitmapProvider(bitmapPool), transformation, width,
height, id, header, data, header.getWidth(), header.getHeight());
GifDrawable gifDrawable = new GifDrawable(context, provider, bitmapPool, unitTransformation, width, height, id,
header, data, firstFrame);
return new GifDrawableResource(gifDrawable);
}
private Bitmap decodeFirstFrame(GifDecoder decoder, String id, GifHeader header, byte[] data) {
decoder.setData(id, header, data);
decoder.advance();
return decoder.getNextFrame();
}
@Override
public String getId() {
return "";
......@@ -94,12 +110,12 @@ public class GifResourceDecoder implements ResourceDecoder<InputStream, GifDrawa
}
private static byte[] inputStreamToBytes(InputStream is) {
final int bufferSize = 16384, initialCapacity = bufferSize;
ByteArrayOutputStream buffer = new ByteArrayOutputStream(initialCapacity);
final int bufferSize = 16384;
ByteArrayOutputStream buffer = new ByteArrayOutputStream(bufferSize);
try {
int nRead;
byte[] data = new byte[bufferSize];
while ((nRead = is.read(data, 0, data.length)) != -1) {
while ((nRead = is.read(data)) != -1) {
buffer.write(data, 0, nRead);
}
buffer.flush();
......@@ -110,32 +126,38 @@ public class GifResourceDecoder implements ResourceDecoder<InputStream, GifDrawa
return buffer.toByteArray();
}
interface GifHeaderParserPool {
public GifHeaderParser obtain(byte[] data);
public void release(GifHeaderParser parser);
// Visible for testing.
static class GifDecoderPool {
private final Queue<GifDecoder> pool = Util.createQueue(0);
public synchronized GifDecoder obtain(GifDecoder.BitmapProvider bitmapProvider) {
GifDecoder result = pool.poll();
if (result == null) {
result = new GifDecoder(bitmapProvider);
}
return result;
}
public synchronized void release(GifDecoder decoder) {
decoder.clear();
pool.offer(decoder);
}
}
private static class DefaultGifHeaderParserPool implements GifHeaderParserPool {
private static final Queue<GifHeaderParser> POOL = Util.createQueue(0);
// Visible for testing.
static class GifHeaderParserPool {
private final Queue<GifHeaderParser> pool = Util.createQueue(0);
@Override
public GifHeaderParser obtain(byte[] data) {
GifHeaderParser result;
synchronized (POOL) {
result = POOL.poll();
}
public synchronized GifHeaderParser obtain(byte[] data) {
GifHeaderParser result = pool.poll();
if (result == null) {
result = new GifHeaderParser();
}
return result.setData(data);
}
@Override
public void release(GifHeaderParser parser) {
synchronized (POOL) {
POOL.offer(parser);
}
public synchronized void release(GifHeaderParser parser) {
pool.offer(parser);
}
}
......
......@@ -8,10 +8,13 @@ import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import com.bumptech.glide.GenericRequestBuilder;
import com.bumptech.glide.Glide;
import com.bumptech.glide.ListPreloader;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
/**
......@@ -37,8 +40,11 @@ public class MainActivity extends Activity implements Api.Monitor {
.into(giphyLogoView);
ListView gifList = (ListView) findViewById(R.id.gif_list);
adapter = new GifAdapter(this);
GiphyPreloader preloader = new GiphyPreloader(2);
adapter = new GifAdapter(this, preloader);
gifList.setAdapter(adapter);
gifList.setOnScrollListener(preloader);
}
@Override
......@@ -58,14 +64,47 @@ public class MainActivity extends Activity implements Api.Monitor {
adapter.setResults(result.data);
}
private class GiphyPreloader extends ListPreloader<Api.GifResult> {
private int[] dimensions;
public GiphyPreloader(int maxPreload) {
super(maxPreload);
}
@Override
protected int[] getDimensions(Api.GifResult item) {
return dimensions;
}
@Override
protected List<Api.GifResult> getItems(int start, int end) {
List<Api.GifResult> items = new ArrayList<Api.GifResult>(end - start);
for (int i = start; i < end; i++) {
items.add(adapter.getItem(i));
}
return items;
}
@Override
protected GenericRequestBuilder getRequestBuilder(Api.GifResult item) {
return Glide.with(MainActivity.this)
.load(item)
.fitCenter();
}
}
private static class GifAdapter extends BaseAdapter {
private static final Api.GifResult[] EMPTY_RESULTS = new Api.GifResult[0];
private final Activity activity;
private final GiphyPreloader preloader;
private Api.GifResult[] results = EMPTY_RESULTS;
private Activity activity;
public GifAdapter(Activity activity) {
public GifAdapter(Activity activity, GiphyPreloader preloader) {
this.activity = activity;
this.preloader = preloader;
}
public void setResults(Api.GifResult[] results) {
......@@ -83,8 +122,8 @@ public class MainActivity extends Activity implements Api.Monitor {
}
@Override
public Object getItem(int i) {
return null;
public Api.GifResult getItem(int i) {
return results[i];
}
@Override
......@@ -102,13 +141,26 @@ public class MainActivity extends Activity implements Api.Monitor {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "load result: " + result);
}
ImageView gifView = (ImageView) convertView.findViewById(R.id.gif_view);
final ImageView gifView = (ImageView) convertView.findViewById(R.id.gif_view);
Glide.with(activity)
.load(result)
.fitCenter()
.into(gifView);
if (preloader.dimensions == null) {
gifView.post(new Runnable() {
@Override
public void run() {
if (gifView.getWidth() > 0 && gifView.getHeight() > 0) {
preloader.dimensions = new int[2];
preloader.dimensions[0] = gifView.getWidth();
preloader.dimensions[1] = gifView.getHeight();
}
}
});
}
return convertView;
}
}
......
......@@ -297,6 +297,14 @@ public class GifDecoder {
return header.status;
}
public void clear() {
id = null;
header = null;
data = null;
mainPixels = null;
mainScratch = null;
}
public void setData(String id, GifHeader header, byte[] data) {
this.id = id;
this.header = header;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册