Animated GIFs in Android is a difficult topic. It is an issue that has been discussed heavily and still, it is very difficult for developers to bring GIFs to life. There are three ways to animate GIFs on Android, each of them has its pros and cons. Each part of this series will cover one of these approaches.
Getting started
For this example, we will use an image I found on gifs.net, it’s this one:
I will store it in our project’s asset folder and name it ‘piggy.gif’. We will also use an Activity to set the views we define as content views. If you want to know everything about playing GIFs, please start at part one.
Approach 2: Extracting the Bitmaps
For this approach, we will use the GifDecoder class published here on googlecode. It’s Apache licensed, so don’t worry. What this class esentially does is, it extracts the different bitmaps from the given stream so you can use it the way you want.
To get started, download this class first. Place it somewhere in your project, maybe in a util package.
Now, we create a new class which inherits from ImageView:
public class GifDecoderView extends ImageView
We create a constructor with a Context and an InputStream, just like in the first part of this series. This time, we call a method playGif(InputStream) and pass it our InputStream:
public GifDecoderView(Context context, InputStream stream) { super(context); playGif(stream);
We give our class five member variables: A boolean which will state whether the thread we will use to play our animation is runningor not, an instance of the GifDecoder-class you just downloaded, a Bitmap which stores the different frames of the animation, a Handler to post our updates to the UI-thread and a Runnable that will arrange that the Bitmap we just defined will be drawn using the Handler:
private boolean mIsPlayingGif = false; private GifDecoder mGifDecoder; private Bitmap mTmpBitmap; final Handler mHandler = new Handler(); final Runnable mUpdateResults = new Runnable() { public void run() { if (mTmpBitmap != null && !mTmpBitmap.isRecycled()) { GifDecoderView.this.setImageBitmap(mTmpBitmap); } } };
Let’s take a look at playGif(InputStream). First, we need to initialise mGifDecoder. After that, we let it read our stream and set mIsPlayingGif to true, so that our thread can run:
private void playGif(InputStream stream) { mGifDecoder = new GifDecoder(); mGifDecoder.read(stream); mIsPlayingGif = true;
Now we need to define our thread. We retreive the frame count of our GIF’s frames and the number of repetitions. When GifDecoder.getLoopCount() returns 0, this means the GIF should be played infinitely. Now for every frame, we receive the according Bitmap by calling getFrame() on the GifDecoder. We post our new Bitmap using the Handler and Runnable members we defined and send our thread to sleep until the next Bitmap needs to be drawn.
new Thread(new Runnable() { public void run() { final int n = mGifDecoder.getFrameCount(); final int ntimes = mGifDecoder.getLoopCount(); int repetitionCounter = 0; do { for (int i = 0; i < n; i++) { mTmpBitmap = mGifDecoder.getFrame(i); final int t = mGifDecoder.getDelay(i); mHandler.post(mUpdateResults); try { Thread.sleep(t); } catch (InterruptedException e) { e.printStackTrace(); } } if(ntimes != 0) { repetitionCounter ++; } } while (mIsPlayingGif && (repetitionCounter <= ntimes)) } }).start();
That’s it. All we have to do now is use our GifDecoderView in an Activity, just like we did in the last part of this tutorial:
// ... InputStream stream = null; try { stream = getAssets().open("piggy.gif"); } catch (IOException e) { e.printStackTrace(); } // GifMovieView view = new GifMovieView(this, stream); GifDecoderView view = new GifDecoderView(this, stream); setContentView(view); // ...
Now, what’ the bad thing about this? It’s the memory footprint. Bitmaps on pre-Honeycomb-devices are stored in an native heap, not in the heap the Dalvik VM uses. Still, this heap counts to the maximum of memory your app can use. So when you have a lot of Bitmaps, the garbage collector might not notice that you are running out of memory because it only controls the heap of the Dalvik VM. To avoid this, make sure you call recycle() on every Bitmap you don’t need anymore. If you want to see how much space your native heap allocated, call Debug.getNativeHeapAllocatedSize().
If you want to know another, maybe better way of playing animated GIFs, stay tuned: The next part of this series will come soon.
You can checkout the code of the three parts of this series at http://code.google.com/p/animated-gifs-in-android/.
As always, please feel free to leave your thoughts in the comments.
2011/10/26 at 17:47
For this approach, if you have the GIF already, you can actually use a GIF splitter to split the GIF into multiple bitmap resources. Then you just use AnimationDrawable to play the images.
Of course, the webview approach that you have in the next part is probably better, but this work decently pre 2.2
2012/07/21 at 10:58
Thanks a lot for the article Johannes, that was of great help!
When I am displaying the above image it is occupying the entire widht and height of the device, is there any way to display it in same size?
2012/07/23 at 08:33
Hi avi_yach,
when embedding the View into a ViewGroup and setting layout_width and layout_height, it should work.
Best regards
Johannes
2012/07/23 at 22:39
Thanks so much for this. The methods were great, particularly the second method. I ended up refactoring it to make an on the fly decoder since I wanted to guarantee playback whilst reducing memory footprint. For my purposes the processor usage was not as much of an issue. Let me know if you want to see the code.
2012/07/26 at 13:37
Hi Joseph,
sure, feel free to share your code here.
Best regards
Johannes
2013/03/15 at 02:42
Johannes could you post the code that would help me a lot i’ve been trying to figure out how to do just that. thanks.
2013/04/21 at 17:27
Sure, you can find it here: https://code.google.com/p/animated-gifs-in-android/
2012/08/13 at 20:37
Thanks for your sample
But it is occupying the whole screen, in which part of code I should make change to display the same size. please reply soon.
2012/11/21 at 00:33
Thanks a lot Johannes… It’s a very useful post, it was what I’m looking for… Congratulations… Your blog is on my favorites now…
2012/12/06 at 19:36
Thanks, good to hear!
2013/04/13 at 18:46
Thank you!
This helped.
2013/06/21 at 20:44
i have 2 problems with Gif Decoder
1- i use it inside Grid view , multiple gifs run simultaneously , error occured when i scroll down then
up again some gifs change positions or some gifs overlap other and other not appeared in screen O_O
2 – the VM size is out of memory and i don’t know how i can replace this bitmaps with grid view
where i can put size line of code , and theres many bitmaps in Gifdecoder and Gif Decoder view
2013/06/26 at 09:03
Thanks for you tutorial. Where we will recycle the bitmaps in your code to avoid the OME. I did like following but it’s not working.
final Runnable mUpdateResults = new Runnable() {
public void run() {
if (mTmpBitmap != null && !mTmpBitmap.isRecycled()) {
GifDecoderView.this.setImageBitmap(mTmpBitmap);
mTmpBitmap.recycle()
}
}
};
2013/08/14 at 17:53
Hello, I can’t find your codes in the site above. Whyyyyy?
2013/08/21 at 04:33
Hey Johannes, I made some changes and improvements to this solution and posted a summary in my blog and a gist if you want to take a look, here is the link: http://felipecsl.com/blog/2013/08/20/animated-gifs-with-android/
Thanks for the awesome work!
2013/08/26 at 22:26
Hi Felipe,
great work, thank you for sharing!
Best regards,
Johannes
2013/09/13 at 17:09
Yey, i was just about to refactor this class as well. saved me a bunch of time…thanks :D
2015/01/27 at 03:39
Hello Johannes,
From your tutorial, the GIF needs to be initially put in /assets folder. Besides, it displays one GIF at one time. A similar use case to Moaz’s I came across is we have to download bunch of files with MIMETYPE of ‘image/JPEG/PNG/GIF’, from a remote server and populate them in a ListView. So a custom ImageView in a row of the ListView would be set as a GIF, or JPEG, or PNG. In this regard, I tried your GifDecoder and GifDecoderView but caught an ArrayIndexOutOfBoundException from gifDecoder.getFrame(index) when playGif() got called in the ListView. Similarly, when I modified a bit to avoid this RuntimeException, the OME issue blocked the main thread seriously and the system log complained “Skipped N frames blabla…”
Any suggestions? Appreciated.
Best regards,
Edward
2015/01/27 at 20:55
Hello Edward,
thank you for your comment. Did you try using a WebView instead?
Best regards,
Johannes
2015/02/02 at 17:17
Hi Johannes,
Thanks for your quick reply.
Actually I never tried a WebView in a ListView. For a specific GIF or other image files, probably it works fine. In my project, however, I have to use an ImageView in a ListView and its relevant adapter to render data from remote. I have to decode the media resources and get a thumbnail respectively. Meanwhile, the MIMETYPE of data could be any type and be clickable to show in full size or play back if it’s video/mp4 or audio/3gpp.
So what I concern is the memory consumption in the ListView. In terms of WebView, if it replaces ImageView in the list, the WebView seems to load data from local storage each time if there is.
Do you have any idea in this scenario? I do need to display GIF properly.
Thanks,
Edward
2015/02/16 at 18:21
Hi Edward,
I see. Some time has passed since I worked with WebView the last time but I think there are options to change its caching policy and also to write into the cache. From the measurements I did back then, it worked much less memory intensive than by decoding the GIF myself.
Best regards,
Johannes
2015/02/10 at 18:30
Hey there!
Great tutorial! This is exactly what I needed!
..However, when I implemented this in my own app, for some reason my GIF image looks like it was sped up to about 10 times the original speed!! I tried to alter the delay values in various parts of the code, but I cannot figure out why my gif has been sped up so much!
Do you have any suggestions as to what could be causing this?
Thank you!
~Alissa
2015/02/16 at 18:27
Hey Alissa,
thank you for your message. Unfortunately I don’t. Have you tried reproducing this with different GIFs?
Best regards,
Johannes
2015/12/04 at 21:38
Hi Johannes … I came across your tutorial just recently, and because I want to use this example in WallpaperService, I have NO layout xml as such. I run into a problem with setContentView … cannot resolve method setContentView(GifDecoderView). Do you have any ideas for a workaround?
2015/12/08 at 15:58
Hi Michael,
thank you for your question.
I think you can just use the bitmap you’re getting in mGifDecoder.getFrame() and draw it to your canvas using canvas.drawBitmap().
Best regards,
Johannes