Recently in project I faced challenge of resizing VirtualDisplay "on flight". So the use case is :
Start stream
In undetermined period of stream there may come specific data which indicates that my streaming capabilities have changed
Update VirtualDisplay's parameters without recreation, so that state loss is avoided
I've found in documentation for VirtualDisplay resize method, though it seems to have no effect on new parameters incoming. For implementation I am using
virtualDisplay = mDisplayManager.createVirtualDisplay("DispName",
getResolution().getResolutionWidth(), getResolution().getResolutionHeight(),
getDisplayDensity(), inputSurface, DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION);
where inputSurface is created by mediaEncoder.createInputSurface() and cofigured properly by this moment. So, the question is, how can I resize VirtualDisplay? I also didn't find any examples how to do it in official sources, would appreciate any help!
UPDATE
Just forgot to mention, I've put Listener for VirtualDisplays and onChange method is triggered, though check if actual metrics were changed shows negative results
Answering to my own question
The resize method of VirtualDisplay works pretty fine, though it was from my side misunderstanding of how to achieve very specific behaviour, when only underlying layout changes it's size, though elements are keeping their properties on smaller window
So, in case if you want to get some kind of "scalar" resize (like everything comes bigger or smaller) you should call resize
But, whenewer your project demands some kind of resizing and making your controls
bigger, though screen comes smaller you should check your extention of your concrete Presentation class linked with VirtualDisplay and just update your layout manually without having VirtualDisplay resized
public void resizeView(final int newWidth, final int newHeight) {
uiHandler.post(new Runnable() {
#Override
public void run() {
try {
Constructor<? extends ViewGroup.LayoutParams> ctor =
mainView.getLayoutParams().getClass().getDeclaredConstructor(int.class, int.class);
mainView.setLayoutParams(ctor.newInstance(newWidth, newHeight));
mainView.requestLayout();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
where uiHandler could be simply obtained even in background with
Handler uiHandler = new Handler(Looper.getMainLooper());
I hope this would be useful for somebody!
Related
Google suggests that the following is not that uncommon a question: having loaded something into a Flash stage using a Loader, I want to resize it. However, if you do this before the content is loaded, resizing the image causes it to disappear.
The proposed solution is usually to use an Event listener for Event.COMPLETE. Here's my code:
public function FlixelTest()
{
super();
// support autoOrients
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
myLoader = new Loader();
myLoader.x = (stage.fullScreenWidth-640)/2;
myLoader.y = (stage.fullScreenHeight-480)/2;
addChild(myLoader);
var url:URLRequest = new URLRequest("stuff.swf");
myLoader.load(url);
myLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, loadProdComplete);
}
function loadProdComplete(e:Event):void{
myLoader.height = 480;
myLoader.width = 640;
}
According to every posting I can find so far online, this solution should work. When the event fires, the Loader is done, and can be resized. However, it doesn't. Commenting out the lines that modify .height and .width cause the SWF to appear, uncommenting them and running again and the SWF never loads.
Could anything else be interfering here? This is using FlashBuilder to construct an Actionscript3 -> Android project.
EDIT - The solution here doesn't appear to work either: Problem resizing loader after loading swf
UPDATE - I have a working, and horrific, solution that is as followed:
function loadProdComplete(e:Event):void{
if(myLoader.width != 0){
myLoader.width = 640;
myLoader.content.width = 640;
}
else{
timer = new Timer(500);
timer.addEventListener(TimerEvent.TIMER, timertick); // there should be a comma here but yahoo replaces it with ...
timer.start();
}
}
function timertick(e:TimerEvent){
timer.stop();
stage.dispatchEvent(new Event(Event.COMPLETE));
}
It basically uses the content's width to see if it's finished loading. If it hasn't, it waits a half second and refires the COMPLETE event. This actually helps display it (although the width hasn't been adjusted, I assume that's a separate issue) - I can't believe this is the only way to get it working...
While not strictly a solution, the way I've circumvented this is by not resizing it at all - I am instead cropping an area over the window so all that's seen is the section I wanted to display.
function loadProdComplete(e:Event):void{
var gameMask : Shape = new Shape;
gameMask.graphics.beginFill(0xffcc00);
gameMask.graphics.drawRect(myLoader.x,myLoader.y,640,480);
gameMask.graphics.endFill();
myLoader.content.mask = gameMask;
}
Since the mask would've been necessary for me anyway to hide off-stage clutter, this solved two problems at once.
I won't accept this as the answer in case anyone has any alternative insights, but if anyone comes across this question, this is a posisble solution.
I'm just trying to figure out the best approach for running a scolling background on an android device. The method I have so far.... its pretty laggy. I use threads, which I believe is not the best bet for android platforms
#Override
public void run() {
// Game Loop
while(runningThread){
//Scroll background down
bgY += 1;
try {
this.postInvalidate();
t.sleep(10);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
where postinvalidate in the onDraw function simply pushings the background image down
canvas.drawBitmap(backgroundImage, bgX, bgY, null);
Thanks in advance
UPDATE
I've identified the problem. And it is the fact that my player updates the same rate as the background scrolls (making it look choppy). from top to bottom. This is because both get drawn in the same function. I'm not really sure how to tackle this and would be grateful for any help. i.e so that player movement is handled separately from the map scrolling
Also how can I control the speed at which onDraw(canvas) get called?
Thanks in advance.
However, I have patched together a different run loop for anyone having the same problem. This is partially from the jetboy example on google.
Below is my inner class in my surfaceview
class MapThread extends Thread{
private Map map;
private SurfaceHolder holder;
private boolean run = false;
public MapThread(Map map, SurfaceHolder holder){
this.holder = holder;
this.map = map;
setRunning(true);
}
public void setRunning(boolean run){
this.run = run;
}
#Override
public void run(){
while(run){
try{
Canvas c = null;
try {
c = holder.lockCanvas(null);
synchronized (holder) {
map.onDraw(c);
}
} finally {
if (c != null) {
holder.unlockCanvasAndPost(c);
}
}
}
}
}
SOLUTION
https://gamedev.stackexchange.com/questions/8127/android-game-scrolling-background
Use the SurfaceView implementation draw on the screen. It allows you more control of what to draw and when.
The SurfaceView is a special subclass of View that offers a dedicated drawing surface within the View hierarchy. The aim is to offer this drawing surface to an application's secondary thread, so that the application isn't required to wait until the system's View hierarchy is ready to draw.
The basic design is to have a surfaceview that draws continuously in a while loop. Then add an if-statement whose condition is to be true if a timer thread tells you its time to draw. Say, every 30ms, draw the bitmap. This will give you about 33 fps.
Now you may also have another timer thread that tells you when to update the the bgX or bgY values. Say at every 60ms, it will set a boolean updateFlag = true; Then in your main thread, you have an if-statement check this flag, set it to false, and update your bgX and bgY values. By accurately controlling the timer and the bgX/bgY increments, you should be able to produce smooth animations.
It would be a good idea to look at the LunarLander source code provided by Google.
One thing to keep in mind is that sleep is very inaccurate. To work around this, you can keep track of exactly how much time passed during the sleep and update how much you move things accordingly.
Its not clear from you code, but you need to make sure that all of your UI updates happen in the UI thread.
You do need to do your timing outside of the UI thread, because otherwise the UI will never update. There are other methods of timing, like using a Handler that can be a little bit cleaner, but I think the overhead on them might be a bit much for what you are trying to do. I think a simple thread has the least amount of overhead.
I am using this method on the second level of my SpaceQuestAlpha game. This makes a seemless scroll.
I used the 2 lines below to set original position.
moony=0;
moon2y=-(heighty);
Then these lines increment both versions of the background image. One starts at 0 and one starts at negative screen height. Every time one of the images goes below the bottom of the screen it is moved up twice the height to move it back into position. I am using surface view with no latency issues.
moony+=5;
moon2y+=5;
if(moon2y>=heighty) {moon2y=moon2y-(heighty*2);}
canvas.drawBitmap(lavabackground, 0, moon2y, null);
if(moony>=heighty){moony=moony-(heighty*2);}
canvas.drawBitmap(lavabackground, 0, moony, null);
I've created an application that show around 250 images in ImageView. Images are loaded one after another, 15-30 images per second. Basically the whole thing gives an illusion of a rotating 3D object, at least it should.
The problem is next, app hangs when loading certain images(i.e. I see a few seconds of fluid animation and then animation hangs, jump 10-15 frames(images) ahead and continues. It always happens at the same places in animation cycle.
I though that Android might not have enough resources to handle something like this, so I've resized images to half their size, but it did't help. I've tried buffering images but that did't help either(actually, maybe a little, I think that animation looks a little bit smoother).
And now the weirdest thing. I use the touch screen to allow users to "rotate" the 3D object on those images, and while rotating I again experience those hangs at exactly the same places as with the animation.
All images are in .png format and their size vary from 15kB to 40kB.
I use the following code for the animation:
new Thread(new Runnable() {
#Override
public void run() {
while (!stopStartupAnimation && li < images_360.length) {
final int fli = li;
handler.post(new Runnable() {
#Override
public void run() {
//Bitmap b = BitmapFactory.decodeResource(getResources(), R.drawable.icon);
//imageCanvas.setImageResource(images_360[fli]);
imageCanvas.setImageBitmap(imageStackNext.pop());
System.out.println("rawX = " + fli);
}
});
int ti = fli +25;
if(ti > images_360.length-1){
ti = ti - images_360.length;
}
imageStackNext.push(BitmapFactory.decodeResource(getResources(), images_360[ti]));
synchronized (this) {
try {
wait(1000 / 25);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
li++;
li++;
if (li >= images_360.length) {
li = 0;
}
}
}
}).start();
First, 15-40KB is their compressed form. Uncompressed, as Bitmaps, they are probably substantially larger. 250 of them may be using many MB of RAM, which is not a good idea.
Second, given a choice between using OpenGL for 3D (which is its purpose), or the 2D drawing primitives on the Canvas, or using ImageView, you chose the worst-performing option.
Third, postRunnable() does not take effect immediately, but rather puts things on a message queue for the main application thread to process when it gets a chance. If it gets tied up -- say, handling touch events -- it may well skip over some seemingly redundant ImageView redraws, or have them go by so fast they appear to not happen. All your 40ms wait() does is ensure that you are only raising events every 40ms, not that they will paint every 40ms. Besides, you could have more easily just used postDelayed() for your 40ms timing.
Bitmaps should be loaded efficiently.
Refer example on official page: https://developer.android.com/training/displaying-bitmaps/index.html
I developed an application that uses lots of images on Android.
The app runs once, fills the information on the screen (Layouts, Listviews, Textviews, ImageViews, etc) and user reads the information.
There is no animation, no special effects or anything that can fill the memory.
Sometimes the drawables can change. Some are android resources and some are files saved in a folder in the SDCARD.
Then the user quits (the onDestroy method is executed and app stays in memory by the VM ) and then at some point the user enters again.
Each time the user enters to the app, I can see the memory growing more and more until user gets the java.lang.OutOfMemoryError.
So what is the best/correct way to handle many images?
Should I put them in static methods so they are not loaded all the time?
Do I have to clean the layout or the images used in the layout in a special way?
One of the most common errors that I found developing Android Apps is the “java.lang.OutOfMemoryError: Bitmap Size Exceeds VM Budget” error. I found this error frequently on activities using lots of bitmaps after changing orientation: the Activity is destroyed, created again and the layouts are “inflated” from the XML consuming the VM memory available for bitmaps.
Bitmaps on the previous activity layout are not properly de-allocated by the garbage collector because they have crossed references to their activity. After many experiments I found a quite good solution for this problem.
First, set the “id” attribute on the parent view of your XML layout:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="#+id/RootView"
>
...
Then, on the onDestroy() method of your Activity, call the unbindDrawables() method passing a reference to the parent View and then do a System.gc().
#Override
protected void onDestroy() {
super.onDestroy();
unbindDrawables(findViewById(R.id.RootView));
System.gc();
}
private void unbindDrawables(View view) {
if (view.getBackground() != null) {
view.getBackground().setCallback(null);
}
if (view instanceof ViewGroup) {
for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
unbindDrawables(((ViewGroup) view).getChildAt(i));
}
((ViewGroup) view).removeAllViews();
}
}
This unbindDrawables() method explores the view tree recursively and:
Removes callbacks on all the background drawables
Removes children on every viewgroup
It sounds like you have a memory leak. The problem isn't handling many images, it's that your images aren't getting deallocated when your activity is destroyed.
It's difficult to say why this is without looking at your code. However, this article has some tips that might help:
http://android-developers.blogspot.de/2009/01/avoiding-memory-leaks.html
In particular, using static variables is likely to make things worse, not better. You might need to add code that removes callbacks when your application redraws -- but again, there's not enough information here to say for sure.
To avoid this problem you can use native method Bitmap.recycle() before null-ing Bitmap object (or setting another value). Example:
public final void setMyBitmap(Bitmap bitmap) {
if (this.myBitmap != null) {
this.myBitmap.recycle();
}
this.myBitmap = bitmap;
}
And next you can change myBitmap w/o calling System.gc() like:
setMyBitmap(null);
setMyBitmap(anotherBitmap);
I've ran into this exact problem. The heap is pretty small so these images can get out of control rather quickly in regards to memory. One way is to give the garbage collector a hint to collect memory on a bitmap by calling its recycle method.
Also, the onDestroy method is not guaranteed to get called. You may want to move this logic/clean up into the onPause activity. Check out the Activity Lifecycle diagram/table on this page for more info.
This explanation might help:
http://code.google.com/p/android/issues/detail?id=8488#c80
"Fast Tips:
1) NEVER call System.gc() yourself. This has been propagated as a fix here, and it doesn't work. Do not do it. If you noticed in my explanation, before getting an OutOfMemoryError, the JVM already runs a garbage collection so there is no reason to do one again (its slowing your program down). Doing one at the end of your activity is just covering up the problem. It may causes the bitmap to be put on the finalizer queue faster, but there is no reason you couldn't have simply called recycle on each bitmap instead.
2) Always call recycle() on bitmaps you don't need anymore. At the very least, in the onDestroy of your activity go through and recycle all the bitmaps you were using. Also, if you want the bitmap instances to be collected from the dalvik heap faster, it doesn't hurt to clear any references to the bitmap.
3) Calling recycle() and then System.gc() still might not remove the bitmap from the Dalvik heap. DO NOT BE CONCERNED about this. recycle() did its job and freed the native memory, it will just take some time to go through the steps I outlined earlier to actually remove the bitmap from the Dalvik heap. This is NOT a big deal because the large chunk of native memory is already free!
4) Always assume there is a bug in the framework last. Dalvik is doing exactly what its supposed to do. It may not be what you expect or what you want, but its how it works. "
I had the exact same problem. After a few testing I found that this error is appearing for large image scaling. I reduced the image scaling and the problem disappeared.
P.S. At first I tried to reduce the image size without scaling the image down. That did not stop the error.
Following points really helped me a lot. There might be other points too, but these are very crucial:
Use application context(instead of activity.this) where ever possible.
Stop and release your threads in onPause() method of activity
Release your views / callbacks in onDestroy() method of activity
I suggest a convenient way to solve this problem.
Just assign the attribute "android:configChanges" value as followed in the Mainfest.xml for your errored activity.
like this:
<activity android:name=".main.MainActivity"
android:label="mainActivity"
android:configChanges="orientation|keyboardHidden|navigation">
</activity>
the first solution I gave out had really reduced the frequency of OOM error to a low level. But, it did not solve the problem totally. And then I will give out the 2nd solution:
As the OOM detailed, I have used too much runtime memory. So, I reduce the picture size in ~/res/drawable of my project. Such as an overqualified picture which has a resolution of 128X128, could be resized to 64x64 which would also be suitable for my application. And after I did so with a pile of pictures, the OOM error doesn't occur again.
I too am frustrated by the outofmemory bug. And yes, I too found that this error pops up a lot when scaling images. At first I tried creating image sizes for all densities, but I found this substantially increased the size of my app. So I'm now just using one image for all densities and scaling my images.
My application would throw an outofmemory error whenever the user went from one activity to another. Setting my drawables to null and calling System.gc() didn't work, neither did recycling my bitmapDrawables with getBitMap().recycle(). Android would continue to throw the outofmemory error with the first approach, and it would throw a canvas error message whenever it tried using a recycled bitmap with the second approach.
I took an even third approach. I set all views to null and the background to black. I do this cleanup in my onStop() method. This is the method that gets called as soon as the activity is no longer visible. If you do it in the onPause() method, users will see a black background. Not ideal. As for doing it in the onDestroy() method, there is no guarantee that it will get called.
To prevent a black screen from occurring if the user presses the back button on the device, I reload the activity in the onRestart() method by calling the startActivity(getIntent()) and then finish() methods.
Note: it's not really necessary to change the background to black.
The BitmapFactory.decode* methods, discussed in the Load Large Bitmaps Efficiently lesson, should not be executed on the main UI thread if the source data is read from disk or a network location (or really any source other than memory). The time this data takes to load is unpredictable and depends on a variety of factors (speed of reading from disk or network, size of image, power of CPU, etc.). If one of these tasks blocks the UI thread, the system flags your application as non-responsive and the user has the option of closing it (see Designing for Responsiveness for more information).
Well I've tried everything I found on the internet and none of them worked. Calling System.gc() only drags down the speed of app. Recycling bitmaps in onDestroy didn't work for me too.
The only thing that works now is to have a static list of all the bitmap so that the bitmaps survive after a restart. And just use the saved bitmaps instead of creating new ones every time the activity if restarted.
In my case the code looks like this:
private static BitmapDrawable currentBGDrawable;
if (new File(uriString).exists()) {
if (!uriString.equals(currentBGUri)) {
freeBackground();
bg = BitmapFactory.decodeFile(uriString);
currentBGUri = uriString;
bgDrawable = new BitmapDrawable(bg);
currentBGDrawable = bgDrawable;
} else {
bgDrawable = currentBGDrawable;
}
}
I had the same problem just with switching the background images with reasonable sizes. I got better results with setting the ImageView to null before putting in a new picture.
ImageView ivBg = (ImageView) findViewById(R.id.main_backgroundImage);
ivBg.setImageDrawable(null);
ivBg.setImageDrawable(getResources().getDrawable(R.drawable.new_picture));
FWIW, here's a lightweight bitmap-cache I coded and have used for a few months. It's not all-the-bells-and-whistles, so read the code before you use it.
/**
* Lightweight cache for Bitmap objects.
*
* There is no thread-safety built into this class.
*
* Note: you may wish to create bitmaps using the application-context, rather than the activity-context.
* I believe the activity-context has a reference to the Activity object.
* So for as long as the bitmap exists, it will have an indirect link to the activity,
* and prevent the garbaage collector from disposing the activity object, leading to memory leaks.
*/
public class BitmapCache {
private Hashtable<String,ArrayList<Bitmap>> hashtable = new Hashtable<String, ArrayList<Bitmap>>();
private StringBuilder sb = new StringBuilder();
public BitmapCache() {
}
/**
* A Bitmap with the given width and height will be returned.
* It is removed from the cache.
*
* An attempt is made to return the correct config, but for unusual configs (as at 30may13) this might not happen.
*
* Note that thread-safety is the caller's responsibility.
*/
public Bitmap get(int width, int height, Bitmap.Config config) {
String key = getKey(width, height, config);
ArrayList<Bitmap> list = getList(key);
int listSize = list.size();
if (listSize>0) {
return list.remove(listSize-1);
} else {
try {
return Bitmap.createBitmap(width, height, config);
} catch (RuntimeException e) {
// TODO: Test appendHockeyApp() works.
App.appendHockeyApp("BitmapCache has "+hashtable.size()+":"+listSize+" request "+width+"x"+height);
throw e ;
}
}
}
/**
* Puts a Bitmap object into the cache.
*
* Note that thread-safety is the caller's responsibility.
*/
public void put(Bitmap bitmap) {
if (bitmap==null) return ;
String key = getKey(bitmap);
ArrayList<Bitmap> list = getList(key);
list.add(bitmap);
}
private ArrayList<Bitmap> getList(String key) {
ArrayList<Bitmap> list = hashtable.get(key);
if (list==null) {
list = new ArrayList<Bitmap>();
hashtable.put(key, list);
}
return list;
}
private String getKey(Bitmap bitmap) {
int width = bitmap.getWidth();
int height = bitmap.getHeight();
Config config = bitmap.getConfig();
return getKey(width, height, config);
}
private String getKey(int width, int height, Config config) {
sb.setLength(0);
sb.append(width);
sb.append("x");
sb.append(height);
sb.append(" ");
switch (config) {
case ALPHA_8:
sb.append("ALPHA_8");
break;
case ARGB_4444:
sb.append("ARGB_4444");
break;
case ARGB_8888:
sb.append("ARGB_8888");
break;
case RGB_565:
sb.append("RGB_565");
break;
default:
sb.append("unknown");
break;
}
return sb.toString();
}
}
Suppose I have the URL for large animated gif and I wanted to make a youtube like activity that displays the animation in a streaming way. How do I
stream in the image?
get it do display with actual animation?
I know ImageView is not the answer as it only shows the first frame.
A bonus would be having access to its buffering status so I can synchronize streaming sound as well -- this is part of a YTMND viewer application. While I could create a service that transcodes the public gif files into a nicer format, I'd like the app to function without additional dependencies.
The general sketch of the solution is to use employ custom View which draws asks a Movie to draw itself to the Canvas periodically.
The first step is building the Movie instance. There is factory called decodeStream that can make a movie given an InputStream but it isn't enough to use the stream from a UrlConnection. If you try this you will get an IOException when the movie loader tries to call reset on the stream. The hack, unfortunate as it is, is to use a separated BufferedInputStream with a manually-set mark to tell it to save enough data that reset won't fail. Luckily, the URLConnection can tell us how much data to expect. I say this hack is unfortunate because it effectively requires the entire image to be buffered in memory (which is no problem for desktop apps, but it is a serious issue on a memory-constrained mobile device).
Here is a snip of the Movie setup code:
URL url = new URL(gifSource);
URLConnection conn = url.openConnection();
InputStream is = conn.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
bis.mark(conn.getContentLength());
Movie movie = Movie.decodeStream(bis);
bis.close();
Next, you need to create a view that will display this Movie. A subclass of View with a custom onDraw will do the trick (assuming it has access to the Movie you created with the previous code).
#Override protected void onDraw(Canvas canvas) {
if(movie != null) {
long now = android.os.SystemClock.uptimeMillis();
int dur = Math.max(movie.duration(), 1); // is it really animated?
int pos = (int)(now % dur);
movie.setTime(pos);
movie.draw(canvas, x, y);
}
}
The view won't trigger itself to be redrawn without help, and blindly calling invalidate() at the end of onDraw is just an energy waste. In another thread (probably the one you used to download the image data), you can post messages to the main thread, asking for the view to be invalidated at a steady (but not insane) pace.
Handler handler = new Handler();
new Thread() {
#Override public void run() {
// ... setup the movie (using the code from above)
// ... create and display the custom view, passing the movie
while(!Thread.currentThread().isInterrupted()) {
handler.post(new Runnable() {
public void run(){
view.invalidate();
}
});
try {
Thread.sleep(50); // yields 20 fps
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}.start();
A really nice solution would have all sorts of sweet progress bars and error checking, but the core is here.
Did you try BitmapDecode?
There's an example in the API Demos here.
Glide prove most easy and efficient way to achieve this with just one line of code :-
Glide.with(getApplicationContext())
.load(Uri.parse("https://media1.giphy.com/media/5ziaphcUWGKPu/200.gif"))
.asGif().placeholder(R.drawable.ic_launcher).crossFade()
.into(imageView);
Result is here :-
Gif taken from here http://giphy.com/search/big-gif/3
Add jar from here :- https://github.com/bumptech/glide/releases
Maybe AnimationDrawable could work for you? EDIT: Not if you want to load from a URL like this post is about. Sorry
http://developer.android.com/reference/android/graphics/drawable/AnimationDrawable.html
Depending on the complexity of the GIF i.e. if it is a simple loading/progress indicator you could break the GIF apart, save each image separately and use the Android Framework's AnimationDrawable.
For simple progress bars this may be less error prone, maybe even more performant.