在Android中引起帧中的帧animationOutOfMemoryError

我有很多图像作为我的资源/可绘制文件夹中的帧(比方说约200)。 而使用这个图像,我想运行一个animation。 最长的animation是80帧。 我成功地能够点击一些点击button来运行animation,但是对于一些animation,它给了我OutOfMemoryError,说VM不能提供这样的内存。 它超出了VM预算。 我计算了所有图像的大小约10MB。 每个图像的大小是320×480像素。

我尝试使用Google,发现我需要使用System.gc()方法显式调用垃圾收集器。 我已经做到了,但仍然有一些内存错误。 任何人都可以请帮助我在这个。

一些代码: –

ImageView img = (ImageView)findViewById(R.id.xxx); img.setBackgroundResource(R.anim.angry_tail_animation); AnimationDrawable mailAnimation = (AnimationDrawable) img.getBackground(); MediaPlayer player = MediaPlayer.create(this.getApplicationContext(), R.raw.angry); if(mailAnimation.isRunning()) { mailAnimation.stop(); mailAnimation.start(); if (player.isPlaying()) { player.stop(); player.start(); } else { player.start(); } } else { mailAnimation.start(); if (player.isPlaying()) { player.stop(); player.start(); } else { player.start(); } } 

这是我点击一个button写的代码…..

res / drawable / anim中的资源文件

 <?xml version="1.0" encoding="utf-8"?> <animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="true" > <item android:drawable="@drawable/cat_angry0000" android:duration="50"/> <item android:drawable="@drawable/cat_angry0001" android:duration="50"/> <item android:drawable="@drawable/cat_angry0002" android:duration="50"/> <item android:drawable="@drawable/cat_angry0003" android:duration="50"/> <item android:drawable="@drawable/cat_angry0004" android:duration="50"/> <item android:drawable="@drawable/cat_angry0005" android:duration="50"/> <item android:drawable="@drawable/cat_angry0006" android:duration="50"/> <item android:drawable="@drawable/cat_angry0007" android:duration="50"/> <item android:drawable="@drawable/cat_angry0008" android:duration="50"/> <item android:drawable="@drawable/cat_angry0009" android:duration="50"/> <item android:drawable="@drawable/cat_angry0010" android:duration="50"/> <item android:drawable="@drawable/cat_angry0011" android:duration="50"/> <item android:drawable="@drawable/cat_angry0012" android:duration="50"/> <item android:drawable="@drawable/cat_angry0013" android:duration="50"/> <item android:drawable="@drawable/cat_angry0014" android:duration="50"/> <item android:drawable="@drawable/cat_angry0015" android:duration="50"/> <item android:drawable="@drawable/cat_angry0016" android:duration="50"/> <item android:drawable="@drawable/cat_angry0017" android:duration="50"/> <item android:drawable="@drawable/cat_angry0018" android:duration="50"/> <item android:drawable="@drawable/cat_angry0019" android:duration="50"/> <item android:drawable="@drawable/cat_angry0020" android:duration="50"/> <item android:drawable="@drawable/cat_angry0021" android:duration="50"/> <item android:drawable="@drawable/cat_angry0022" android:duration="50"/> <item android:drawable="@drawable/cat_angry0023" android:duration="50"/> <item android:drawable="@drawable/cat_angry0024" android:duration="50"/> <item android:drawable="@drawable/cat_angry0025" android:duration="50"/> </animation-list> 

**以上是在setBackgroundResource中使用的资源文件,同样的方式我有另外10个文件用于其他不同的animation。 **

错误日志

 01-16 22:23:41.594: E/AndroidRuntime(399): FATAL EXCEPTION: main 01-16 22:23:41.594: E/AndroidRuntime(399): java.lang.IllegalStateException: Could not execute method of the activity 01-16 22:23:41.594: E/AndroidRuntime(399): at android.view.View$1.onClick(View.java:2144) 01-16 22:23:41.594: E/AndroidRuntime(399): at android.view.View.performClick(View.java:2485) 01-16 22:23:41.594: E/AndroidRuntime(399): at android.view.View$PerformClick.run(View.java:9080) 01-16 22:23:41.594: E/AndroidRuntime(399): at android.os.Handler.handleCallback(Handler.java:587) 01-16 22:23:41.594: E/AndroidRuntime(399): at android.os.Handler.dispatchMessage(Handler.java:92) 01-16 22:23:41.594: E/AndroidRuntime(399): at android.os.Looper.loop(Looper.java:123) 01-16 22:23:41.594: E/AndroidRuntime(399): at android.app.ActivityThread.main(ActivityThread.java:3683) 01-16 22:23:41.594: E/AndroidRuntime(399): at java.lang.reflect.Method.invokeNative(Native Method) 01-16 22:23:41.594: E/AndroidRuntime(399): at java.lang.reflect.Method.invoke(Method.java:507) 01-16 22:23:41.594: E/AndroidRuntime(399): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839) 01-16 22:23:41.594: E/AndroidRuntime(399): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597) 01-16 22:23:41.594: E/AndroidRuntime(399): at dalvik.system.NativeStart.main(Native Method) 01-16 22:23:41.594: E/AndroidRuntime(399): Caused by: java.lang.reflect.InvocationTargetException 01-16 22:23:41.594: E/AndroidRuntime(399): at java.lang.reflect.Method.invokeNative(Native Method) 01-16 22:23:41.594: E/AndroidRuntime(399): at java.lang.reflect.Method.invoke(Method.java:507) 01-16 22:23:41.594: E/AndroidRuntime(399): at android.view.View$1.onClick(View.java:2139) 01-16 22:23:41.594: E/AndroidRuntime(399): ... 11 more 01-16 22:23:41.594: E/AndroidRuntime(399): Caused by: java.lang.OutOfMemoryError: bitmap size exceeds VM budget 01-16 22:23:41.594: E/AndroidRuntime(399): at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method) 01-16 22:23:41.594: E/AndroidRuntime(399): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:460) 01-16 22:23:41.594: E/AndroidRuntime(399): at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:336) 01-16 22:23:41.594: E/AndroidRuntime(399): at android.graphics.drawable.Drawable.createFromResourceStream(Drawable.java:697) 01-16 22:23:41.594: E/AndroidRuntime(399): at android.content.res.Resources.loadDrawable(Resources.java:1709) 01-16 22:23:41.594: E/AndroidRuntime(399): at android.content.res.Resources.getDrawable(Resources.java:581) 01-16 22:23:41.594: E/AndroidRuntime(399): at android.graphics.drawable.AnimationDrawable.inflate(AnimationDrawable.java:267) 01-16 22:23:41.594: E/AndroidRuntime(399): at android.graphics.drawable.Drawable.createFromXmlInner(Drawable.java:787) 01-16 22:23:41.594: E/AndroidRuntime(399): at android.graphics.drawable.Drawable.createFromXml(Drawable.java:728) 01-16 22:23:41.594: E/AndroidRuntime(399): at android.content.res.Resources.loadDrawable(Resources.java:1694) 01-16 22:23:41.594: E/AndroidRuntime(399): at android.content.res.Resources.getDrawable(Resources.java:581) 01-16 22:23:41.594: E/AndroidRuntime(399): at android.view.View.setBackgroundResource(View.java:7533) 01-16 22:23:41.594: E/AndroidRuntime(399): at talking.cat.CatActivity.middleButtonClicked(CatActivity.java:83) 

同样的方式,我有不同的button不同的animation…谢谢

Solutions Collecting From Web of "在Android中引起帧中的帧animationOutOfMemoryError"

我假设你的animation帧图像被压缩(PNG或JPG)。 压缩的大小对于计算需要多less内存才是有用的。 为此,您需要考虑未压缩的大小。 这将是像素数(320×480)乘以每个像素的字节数,通常是4(32位)。 那么对于你的图片,每一个将是614400字节。 对于您提供的26帧animation示例,将需要总共15,974,400字节来保存所有帧的原始位图数据,而不包括对象开销。

查看AnimationDrawable的源代码,它似乎一次加载所有的帧到内存中,这基本上要做好性能。

你是否可以分配这么多的内存是非常依赖系统的。 我至less会build议在真实的设备上而不是模拟器上尝试。 您也可以尝试调整模拟器的可用RAM大小,但这只是猜测。

有很多方法可以使用BitmapFactory.inPreferredConfig以更高效的格式(如RGB 565(而不是ARGB 8888))加载位图。 这样可以节省一些空间,但仍然可能不够。

如果你不能一次分配那么多的内存,你必须考虑其他的select。 大多数高性能graphics应用程序(例如游戏)从小graphics(精灵)或2D或3D基元(矩形,三angular形)的组合中绘制graphics。 为每个帧绘制全屏位图与渲染video效果相同; 不一定是最有效的。

你的animation的全部内容是否随着每一帧而改变? 另一个优化可能是animation只有实际改变的部分,并砍掉你的位图来说明这一点。

总而言之,您需要find一种使用较less内存来绘制animation的方法。 有很多select,但是这取决于你的animation需要看的很多。

我有同样的问题。 Android会一次加载所有可绘制对象,因此具有多个框架的animation会导致此错误。

我结束了创build我自己的简单的序列animation:

 public class AnimationsContainer { public int FPS = 30; // animation FPS // single instance procedures private static AnimationsContainer mInstance; private AnimationsContainer() { }; public static AnimationsContainer getInstance() { if (mInstance == null) mInstance = new AnimationsContainer(); return mInstance; } // animation progress dialog frames private int[] mProgressAnimFrames = { R.drawable.logo_30001, R.drawable.logo_30002, R.drawable.logo_30003 }; // animation splash screen frames private int[] mSplashAnimFrames = { R.drawable.logo_ding200480001, R.drawable.logo_ding200480002 }; /** * @param imageView * @return progress dialog animation */ public FramesSequenceAnimation createProgressDialogAnim(ImageView imageView) { return new FramesSequenceAnimation(imageView, mProgressAnimFrames); } /** * @param imageView * @return splash screen animation */ public FramesSequenceAnimation createSplashAnim(ImageView imageView) { return new FramesSequenceAnimation(imageView, mSplashAnimFrames); } /** * AnimationPlayer. Plays animation frames sequence in loop */ public class FramesSequenceAnimation { private int[] mFrames; // animation frames private int mIndex; // current frame private boolean mShouldRun; // true if the animation should continue running. Used to stop the animation private boolean mIsRunning; // true if the animation currently running. prevents starting the animation twice private SoftReference<ImageView> mSoftReferenceImageView; // Used to prevent holding ImageView when it should be dead. private Handler mHandler; private int mDelayMillis; private OnAnimationStoppedListener mOnAnimationStoppedListener; private Bitmap mBitmap = null; private BitmapFactory.Options mBitmapOptions; public FramesSequenceAnimation(ImageView imageView, int[] frames, int fps) { mHandler = new Handler(); mFrames = frames; mIndex = -1; mSoftReferenceImageView = new SoftReference<ImageView>(imageView); mShouldRun = false; mIsRunning = false; mDelayMillis = 1000 / fps; imageView.setImageResource(mFrames[0]); // use in place bitmap to save GC work (when animation images are the same size & type) if (Build.VERSION.SDK_INT >= 11) { Bitmap bmp = ((BitmapDrawable) imageView.getDrawable()).getBitmap(); int width = bmp.getWidth(); int height = bmp.getHeight(); Bitmap.Config config = bmp.getConfig(); mBitmap = Bitmap.createBitmap(width, height, config); mBitmapOptions = new BitmapFactory.Options(); // setup bitmap reuse options. mBitmapOptions.inBitmap = mBitmap; mBitmapOptions.inMutable = true; mBitmapOptions.inSampleSize = 1; } } private int getNext() { mIndex++; if (mIndex >= mFrames.length) mIndex = 0; return mFrames[mIndex]; } /** * Starts the animation */ public synchronized void start() { mShouldRun = true; if (mIsRunning) return; Runnable runnable = new Runnable() { @Override public void run() { ImageView imageView = mSoftReferenceImageView.get(); if (!mShouldRun || imageView == null) { mIsRunning = false; if (mOnAnimationStoppedListener != null) { mOnAnimationStoppedListener.AnimationStopped(); } return; } mIsRunning = true; mHandler.postDelayed(this, mDelayMillis); if (imageView.isShown()) { int imageRes = getNext(); if (mBitmap != null) { // so Build.VERSION.SDK_INT >= 11 Bitmap bitmap = null; try { bitmap = BitmapFactory.decodeResource(imageView.getResources(), imageRes, mBitmapOptions); } catch (Exception e) { e.printStackTrace(); } if (bitmap != null) { imageView.setImageBitmap(bitmap); } else { imageView.setImageResource(imageRes); mBitmap.recycle(); mBitmap = null; } } else { imageView.setImageResource(imageRes); } } } }; mHandler.post(runnable); } /** * Stops the animation */ public synchronized void stop() { mShouldRun = false; } } } 

用法:

 FramesSequenceAnimation anim = AnimationsContainer.getInstance().createSplashAnim(mSplashImageView); anim.start(); 
  • 不要忘记阻止它…

我花了很多时间在这个,有两个不同的解决scheme,都好..

首先,问题是:1)Android以未压缩的位图格式将所有图像加载到RAM中。 2)Android使用资源缩放,所以在具有xxxhdpi显示的电话(如LG G3)上,每个帧占用一个TON空间,所以很快就会耗尽RAM。

解决scheme#1

1)旁观Android的资源扩展。 2)存储所有文件在内存中的字节数(这些很小,尤其是JPEG)。 3)逐帧生成位图,所以几乎不可能用完RAM。

缺点:当Android为新的Bitmaps分配内存并回收旧的日志时,它会监视日志。 它也在旧设备(Galaxy S1)上performance糟糕,但是在当前的预算手机上performance很好(阅读:我在Best Buy购买的价格为10美元的Alcatel C1)。 以下第二种解决scheme在较旧的设备上执行得更好,但在某些情况下可能仍会耗尽内存。

 public class MyAnimationDrawable { public static class MyFrame { byte[] bytes; int duration; Drawable drawable; boolean isReady = false; } public interface OnDrawableLoadedListener { public void onDrawableLoaded(List<MyFrame> myFrames); } public static void loadRaw(final int resourceId, final Context context, final OnDrawableLoadedListener onDrawableLoadedListener) { loadFromXml(resourceId, context, onDrawableLoadedListener); } private static void loadFromXml(final int resourceId, final Context context, final OnDrawableLoadedListener onDrawableLoadedListener) { new Thread(new Runnable() { @Override public void run() { final ArrayList<MyFrame> myFrames = new ArrayList<>(); XmlResourceParser parser = context.getResources().getXml(resourceId); try { int eventType = parser.getEventType(); while (eventType != XmlPullParser.END_DOCUMENT) { if (eventType == XmlPullParser.START_DOCUMENT) { } else if (eventType == XmlPullParser.START_TAG) { if (parser.getName().equals("item")) { byte[] bytes = null; int duration = 1000; for (int i=0; i<parser.getAttributeCount(); i++) { if (parser.getAttributeName(i).equals("drawable")) { int resId = Integer.parseInt(parser.getAttributeValue(i).substring(1)); bytes = IOUtils.toByteArray(context.getResources().openRawResource(resId)); } else if (parser.getAttributeName(i).equals("duration")) { duration = parser.getAttributeIntValue(i, 1000); } } MyFrame myFrame = new MyFrame(); myFrame.bytes = bytes; myFrame.duration = duration; myFrames.add(myFrame); } } else if (eventType == XmlPullParser.END_TAG) { } else if (eventType == XmlPullParser.TEXT) { } eventType = parser.next(); } } catch (IOException | XmlPullParserException e) { e.printStackTrace(); } // Run on UI Thread new Handler(context.getMainLooper()).post(new Runnable() { @Override public void run() { if (onDrawableLoadedListener != null) { onDrawableLoadedListener.onDrawableLoaded(myFrames); } } }); } }).run(); } public static void animateRawManually(int resourceId, final ImageView imageView, final Runnable onStart, final Runnable onComplete) { loadRaw(resourceId, imageView.getContext(), new OnDrawableLoadedListener() { @Override public void onDrawableLoaded(List<MyFrame> myFrames) { if (onStart != null) { onStart.run(); } animateRawManually(myFrames, imageView, onComplete); } }); } public static void animateRawManually(List<MyFrame> myFrames, ImageView imageView, Runnable onComplete) { animateRawManually(myFrames, imageView, onComplete, 0); } private static void animateRawManually(final List<MyFrame> myFrames, final ImageView imageView, final Runnable onComplete, final int frameNumber) { final MyFrame thisFrame = myFrames.get(frameNumber); if (frameNumber == 0) { thisFrame.drawable = new BitmapDrawable(imageView.getContext().getResources(), BitmapFactory.decodeByteArray(thisFrame.bytes, 0, thisFrame.bytes.length)); } else { MyFrame previousFrame = myFrames.get(frameNumber - 1); ((BitmapDrawable) previousFrame.drawable).getBitmap().recycle(); previousFrame.drawable = null; previousFrame.isReady = false; } imageView.setImageDrawable(thisFrame.drawable); new Handler().postDelayed(new Runnable() { @Override public void run() { // Make sure ImageView hasn't been changed to a different Image in this time if (imageView.getDrawable() == thisFrame.drawable) { if (frameNumber + 1 < myFrames.size()) { MyFrame nextFrame = myFrames.get(frameNumber+1); if (nextFrame.isReady) { // Animate next frame animateRawManually(myFrames, imageView, onComplete, frameNumber + 1); } else { nextFrame.isReady = true; } } else { if (onComplete != null) { onComplete.run(); } } } } }, thisFrame.duration); // Load next frame if (frameNumber + 1 < myFrames.size()) { new Thread(new Runnable() { @Override public void run() { MyFrame nextFrame = myFrames.get(frameNumber+1); nextFrame.drawable = new BitmapDrawable(imageView.getContext().getResources(), BitmapFactory.decodeByteArray(nextFrame.bytes, 0, nextFrame.bytes.length)); if (nextFrame.isReady) { // Animate next frame animateRawManually(myFrames, imageView, onComplete, frameNumber + 1); } else { nextFrame.isReady = true; } } }).run(); } } } 

**解决scheme#2 **

它加载XML资源,parsing并加载原始资源 – 从而绕过Android的资源扩展(这是大多数OutOfMemoryExceptions的负责),并创build一个AnimationDrawable。

优点:在旧设备(如Galaxy S1)

缺点:仍然可以耗尽内存,因为它将内存中的所有未压缩位图保存起来(但是它们更小,因为它们不像Android通常缩放图像那样缩放)

 public static void animateManuallyFromRawResource(int animationDrawableResourceId, ImageView imageView, Runnable onStart, Runnable onComplete) { AnimationDrawable animationDrawable = new AnimationDrawable(); XmlResourceParser parser = imageView.getContext().getResources().getXml(animationDrawableResourceId); try { int eventType = parser.getEventType(); while (eventType != XmlPullParser.END_DOCUMENT) { if (eventType == XmlPullParser.START_DOCUMENT) { } else if (eventType == XmlPullParser.START_TAG) { if (parser.getName().equals("item")) { Drawable drawable = null; int duration = 1000; for (int i=0; i<parser.getAttributeCount(); i++) { if (parser.getAttributeName(i).equals("drawable")) { int resId = Integer.parseInt(parser.getAttributeValue(i).substring(1)); byte[] bytes = IoUtils.readBytes(imageView.getContext().getResources().openRawResource(resId)); drawable = new BitmapDrawable(imageView.getContext().getResources(), BitmapFactory.decodeByteArray(bytes, 0, bytes.length)); } else if (parser.getAttributeName(i).equals("duration")) { duration = parser.getAttributeIntValue(i, 66); } } animationDrawable.addFrame(drawable, duration); } } else if (eventType == XmlPullParser.END_TAG) { } else if (eventType == XmlPullParser.TEXT) { } eventType = parser.next(); } } catch (IOException | XmlPullParserException e) { e.printStackTrace(); } if (onStart != null) { onStart.run(); } animateDrawableManually(animationDrawable, imageView, onComplete, 0); } private static void animateDrawableManually(final AnimationDrawable animationDrawable, final ImageView imageView, final Runnable onComplete, final int frameNumber) { final Drawable frame = animationDrawable.getFrame(frameNumber); imageView.setImageDrawable(frame); new Handler().postDelayed(new Runnable() { @Override public void run() { // Make sure ImageView hasn't been changed to a different Image in this time if (imageView.getDrawable() == frame) { if (frameNumber + 1 < animationDrawable.getNumberOfFrames()) { // Animate next frame animateDrawableManually(animationDrawable, imageView, onComplete, frameNumber + 1); } else { // Animation complete if (onComplete != null) { onComplete.run(); } } } } }, animationDrawable.getDuration(frameNumber)); } 

如果仍然有内存问题,请使用较小的图像…或者存储资源名称+持续时间,并在每个帧上生成字节数组+ Drawable。 这几乎肯定会导致帧之间的切换太多,但使用几乎为零的RAM。

我创build了一个animation类,根据传入的drawables资源和帧持续时间来显示帧。

  protected class SceneAnimation{ private ImageView mImageView; private int[] mFrameRess; private int[] mDurations; private int mDuration; private int mLastFrameNo; private long mBreakDelay; public SceneAnimation(ImageView pImageView, int[] pFrameRess, int[] pDurations){ mImageView = pImageView; mFrameRess = pFrameRess; mDurations = pDurations; mLastFrameNo = pFrameRess.length - 1; mImageView.setImageResource(mFrameRess[0]); play(1); } public SceneAnimation(ImageView pImageView, int[] pFrameRess, int pDuration){ mImageView = pImageView; mFrameRess = pFrameRess; mDuration = pDuration; mLastFrameNo = pFrameRess.length - 1; mImageView.setImageResource(mFrameRess[0]); playConstant(1); } public SceneAnimation(ImageView pImageView, int[] pFrameRess, int pDuration, long pBreakDelay){ mImageView = pImageView; mFrameRess = pFrameRess; mDuration = pDuration; mLastFrameNo = pFrameRess.length - 1; mBreakDelay = pBreakDelay; mImageView.setImageResource(mFrameRess[0]); playConstant(1); } private void play(final int pFrameNo){ mImageView.postDelayed(new Runnable(){ public void run() { mImageView.setImageResource(mFrameRess[pFrameNo]); if(pFrameNo == mLastFrameNo) play(0); else play(pFrameNo + 1); } }, mDurations[pFrameNo]); } private void playConstant(final int pFrameNo){ mImageView.postDelayed(new Runnable(){ public void run() { mImageView.setImageResource(mFrameRess[pFrameNo]); if(pFrameNo == mLastFrameNo) playConstant(0); else playConstant(pFrameNo + 1); } }, pFrameNo==mLastFrameNo && mBreakDelay>0 ? mBreakDelay : mDuration); } }; 

它是这样使用的:

  private ImageView mTapScreenTextAnimImgView; private final int[] mTapScreenTextAnimRes = {R.drawable.tap0001_b, R.drawable.tap0002_b, R.drawable.tap0003_b, R.drawable.tap0004_b, R.drawable.tap0005_b, R.drawable.tap0006_b, R.drawable.tap0005_b, R.drawable.tap0004_b, R.drawable.tap0003_b, R.drawable.tap0002_b, R.drawable.tap0001_b}; private final int mTapScreenTextAnimDuration = 100; private final int mTapScreenTextAnimBreak = 500; 

并在onCreate:

  mTapScreenTextAnimImgView = (ImageView) findViewById(R.id.scene1AnimBottom); new SceneAnimation(mTapScreenTextAnimImgView, mTapScreenTextAnimRes, mTapScreenTextAnimDuration, mTapScreenTextAnimBreak); 

我遇到了这个问题,通过做以下两件事来解决它:

  1. 将animation图像的分辨率降低一半,为未压缩字节的1/4。
  2. 把图像放在drawable-nodpi文件夹中,这样它们就不会被Android放大。

在执行步骤1之后,我的animation仍然无法加载到某些电话上。步骤2在这些电话上工作。

希望这能节省一些人的时间。

编辑:我仍然遇到崩溃之后去播放AnimationDrawable的活动,但我现在工作。 以下是我所做的其他事情:

  1. 不要在xml中使用animation列表。 相反,每次需要使用时都要创buildAnimationDrawable。 否则,下次您从资源加载可绘制的animation时,它仍然会尝试使用您最终回收的位图。
  2. 当您完成使用时,请回收AnimationDrawable中的位图。 这是释放记忆的魔力。
  3. 使用Android设备监视器来监视堆中分配的字节。

这里是我用来创buildAnimationDrawable的代码:

  protected AnimationDrawable CreateLoadingAnimationDrawable() { AnimationDrawable animation = new AnimationDrawable (); animation.OneShot = false; for (int i = 0; i < kNumberOfFrames; ++i) { int index = (i * 2) + 1; string stringIndex = index.ToString ("00"); string bitmapStringId = kBaseAnimationName + stringIndex; int resID = this.Resources.GetIdentifier (bitmapStringId, "drawable", this.PackageName); Bitmap bitmap = BitmapFactory.DecodeResource (this.Resources, resID); BitmapDrawable frame = new BitmapDrawable (bitmap); //Drawable frame = Resources.GetDrawable (resID); animation.AddFrame (frame, 111); } return animation; } 

和代码释放位图,当你完成使用它们。 你可以在OnPause或OnDestroy中做到这一点。 _loadingAnimation是我上面创build的AnimationDrawable。 我很想知道在这种情况下,SetCallback()为你做了什么。 我只是从其他地方复制它。

  if (_loadingAnimation != null) { _loadingAnimation.Stop (); _loadingImageView.SetBackgroundResource (Resource.Drawable.loading_anim_full7001); for (int i = 0; i < _loadingAnimation.NumberOfFrames; ++i) { BitmapDrawable frame = _loadingAnimation.GetFrame (i) as BitmapDrawable; if (frame != null) { Android.Graphics.Bitmap bitmap = frame.Bitmap; bitmap.Recycle (); frame.SetCallback(null); } } _loadingAnimation.SetCallback(null); _loadingAnimation = null; } 

摊晒

这是sdk的大问题,但可以通过使用线程来并行加载位图图像来解决,而不是同时加载整个图像。

我解决了我的outOfMemoryError问题,通过严格的削减帧率,并缩小在gimp的图像。 根据你在做什么,你可能会比你想象的less得多的fps。

我已经解决了这个问题,把所有的图像放在数组中,并在显示每个图像后使用延迟。 图像数组源文件res / string <!-- Array table for the pictures to show on the spinner--> <array name="spinner_list"> <item>@drawable/arrows_loop__00000_org</item> <item>@drawable/arrows_loop__00005_org</item> <item >@drawable/arrows_loop__00010_org</item> <item>@drawable/arrows_loop__00015_org</item> <item >@drawable/arrows_loop__00020_org</item> <item >@drawable/arrows_loop__00025_org</item> . . . </array> <!-- Array table for the pictures to show on the spinner--> <array name="spinner_list"> <item>@drawable/arrows_loop__00000_org</item> <item>@drawable/arrows_loop__00005_org</item> <item >@drawable/arrows_loop__00010_org</item> <item>@drawable/arrows_loop__00015_org</item> <item >@drawable/arrows_loop__00020_org</item> <item >@drawable/arrows_loop__00025_org</item> . . . </array> <!-- Array table for the pictures to show on the spinner--> <array name="spinner_list"> <item>@drawable/arrows_loop__00000_org</item> <item>@drawable/arrows_loop__00005_org</item> <item >@drawable/arrows_loop__00010_org</item> <item>@drawable/arrows_loop__00015_org</item> <item >@drawable/arrows_loop__00020_org</item> <item >@drawable/arrows_loop__00025_org</item> . . . </array>我声明了微调器imageView private static ImageView imagespinner;

然后在我的课堂上,我在这里称之为:

  final TypedArray imgs = getResources().obtainTypedArray(R.array.spinner_list); runimage(imgs, imgs.length()); 

然后在runimage我做这样的延迟循环:

  /* handle the spinner frame by frame */ 

public void runimage(final TypedArray array,int index){

  int size = array.length(); if(index<size) {// show in sequence the images final int localindex= index; handler.postDelayed(new Runnable() { public void run() { imagespinner.setImageResource(array.getResourceId(localindex, -1));// find the picture to show runimage(array,(localindex+1));// because use final arg need to do the increase inside } }, 55); } else // after show all images go ahead { textview2.setVisibility(View.VISIBLE); handler.postDelayed(myRunnablewait, 2000); // make some time to see text before go to ather fragment } } 

所以我在imagespinner上以55milsec的延迟运行所有图像。 完成后做好工作。

类似于其他的答案,使用rxjava:

 public final class RxSequenceAnimation { private static final int[] PNG_RESOURCES = new int[]{ R.drawable.sequence_frame_00, R.drawable.sequence_frame_01, R.drawable.sequence_frame_02 }; private static final String TAG = "rx-seq-anim"; private final Resources mResource; private final ImageView mImageView; private final byte[][] RAW_PNG_DATA = new byte[PNG_RESOURCES.length][]; private final byte[] buff = new byte[1024]; private Subscription sub; public RxSequenceAnimation(Resources resources, ImageView imageView) { mResource = resources; mImageView = imageView; } public void start() { sub = Observable .interval(16, TimeUnit.MILLISECONDS) .map(new Func1<Long, Bitmap>() { @Override public Bitmap call(Long l) { int i = (int) (l % PNG_RESOURCES.length); if (RAW_PNG_DATA[i] == null) { // read raw png data (compressed) if not read already into RAM try { RAW_PNG_DATA[i] = read(PNG_RESOURCES[i]); } catch (IOException e) { Log.e(TAG, "IOException " + String.valueOf(e)); } Log.d(TAG, "decoded " + i + " size " + RAW_PNG_DATA[i].length); } // decode directly from RAM - only one full blown bitmap is in RAM at a time return BitmapFactory.decodeByteArray(RAW_PNG_DATA[i], 0, RAW_PNG_DATA[i].length); } }) .subscribeOn(Schedulers.newThread()) .onBackpressureDrop() .observeOn(AndroidSchedulers.mainThread()) .doOnNext(new Action1<Bitmap>() { @Override public void call(Bitmap b) { mImageView.setImageBitmap(b); } }) .subscribe(LogErrorSubscriber.newInstance(TAG)); } public void stop() { if (sub != null) { sub.unsubscribe(); } } private byte[] read(int resId) throws IOException { return streamToByteArray(inputStream(resId)); } private InputStream inputStream(int id) { return mResource.openRawResource(id); } private byte[] streamToByteArray(InputStream is) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); int i; while ((i = is.read(buff, 0, buff.length)) > 0) { baos.write(buff, 0, i); } byte[] bytes = baos.toByteArray(); is.close(); return bytes; } } 

I ported a solution to Xamarin Android and did some improvements.

It works well with orientation changes and specially with images around 300 width and height (the larger the image the longer it takes to load the image, the bigger the flickering).

 using Android.Content; using Android.Graphics; using Android.OS; using Android.Widget; using System; namespace ...Droid.Util { public class FramesSequenceAnimation { private int[] animationFrames; private int currentFrame; private bool shouldRun; // true if the animation should continue running. Used to stop the animation private bool isRunning; // true if the animation currently running. prevents starting the animation twice private ImageView imageview; private Handler handler; private int delayMillis; private bool oneShot = false; private FramesSequenceAnimationListener onAnimationStoppedListener; private Bitmap bitmap = null; private BitmapFactory.Options bitmapOptions; private Action action; private static object Lock = new object(); public interface FramesSequenceAnimationListener { void AnimationStopped(); } public void SetFramesSequenceAnimationListener(FramesSequenceAnimationListener onAnimationStoppedListener) { this.onAnimationStoppedListener = onAnimationStoppedListener; } public int GetCurrentFrame() { return currentFrame; } public void SetCurrentFrame(int currentFrame) { this.currentFrame = currentFrame; } public FramesSequenceAnimation(FramesSequenceAnimationListener onAnimationStoppedListener, ImageView imageview, int[] animationFrames, int fps) { this.onAnimationStoppedListener = onAnimationStoppedListener; this.imageview = imageview; this.animationFrames = animationFrames; delayMillis = 1000 / fps; currentFrame = -1; shouldRun = false; isRunning = false; handler = new Handler(); imageview.SetImageResource(this.animationFrames[0]); //// use in place bitmap to save GC work (when animation images are the same size & type) //if (Build.VERSION.SdkInt >= BuildVersionCodes.Honeycomb) //{ // Bitmap bmp = ((BitmapDrawable)imageview.Drawable).Bitmap; // int width = bmp.Width; // int height = bmp.Height; // Bitmap.Config config = bmp.GetConfig(); // bitmap = Bitmap.CreateBitmap(width, height, config); // bitmapOptions = new BitmapFactory.Options(); // setup bitmap reuse options // bitmapOptions.InBitmap = bitmap; // reuse this bitmap when loading content // bitmapOptions.InMutable = true; // bitmapOptions.InSampleSize = 1; //} bitmapOptions = newOptions(); bitmap = decode(bitmapOptions, getNext()); bitmapOptions.InBitmap = bitmap; } private BitmapFactory.Options newOptions() { BitmapFactory.Options options = new BitmapFactory.Options(); options.InSampleSize = 1; options.InMutable = true; options.InJustDecodeBounds = true; options.InPurgeable = true; options.InInputShareable = true; options.InPreferredConfig = Bitmap.Config.Rgb565; return options; } private Bitmap decode(BitmapFactory.Options options, int imageRes) { return BitmapFactory.DecodeResource(imageview.Resources, imageRes, bitmapOptions); } public void SetOneShot(bool oneShot) { this.oneShot = oneShot; } private int getNext() { currentFrame++; if (currentFrame >= animationFrames.Length) { if (oneShot) { shouldRun = false; currentFrame = animationFrames.Length - 1; } else { currentFrame = 0; } } return animationFrames[currentFrame]; } public void stop() { lock (Lock) { shouldRun = false; } } public void start() { lock (Lock) { shouldRun = true; if (isRunning) { return; } Action tempAction = new Action(delegate { if (!shouldRun || imageview == null) { isRunning = false; if (onAnimationStoppedListener != null) { onAnimationStoppedListener.AnimationStopped(); onAnimationStoppedListener = null; handler.RemoveCallbacks(action); } return; } isRunning = true; handler.PostDelayed(action, delayMillis); if (imageview.IsShown) { int imageRes = getNext(); if (bitmap != null) { if (Build.VERSION.SdkInt >= BuildVersionCodes.Honeycomb) { if (bitmap != null && !bitmap.IsRecycled) { bitmap.Recycle(); bitmap = null; } } try { bitmap = BitmapFactory.DecodeResource(imageview.Resources, imageRes, bitmapOptions); } catch (Exception e) { bitmap.Recycle(); bitmap = null; Console.WriteLine("Exception: " + e.StackTrace); } if (bitmap != null) { imageview.SetImageBitmap(bitmap); } else { imageview.SetImageResource(imageRes); bitmap.Recycle(); bitmap = null; } } else { imageview.SetImageResource(imageRes); } } }); action = tempAction; handler.Post(action); } } } } 

This is my splash screen class: (this class reads the images from the drawable folder that are named "splash_0001, splash_0002 …". So no need to name your image resources on an array. Increase the number of frames per second (FPS) to speed up the animation).

 using Android.App; using Android.Content; using Android.OS; using Android.Widget; using ...Droid.Base; using ...Droid.Util; using System; using System.Collections.Generic; using static ...Util.FramesSequenceAnimation; namespace ...Droid.Activities { [Activity(MainLauncher = true)] public class SplashActivity : BaseActivity, FramesSequenceAnimationListener { private FramesSequenceAnimation framesSequenceAnimation; private const string IMAGE_NAME_PREFIX = "splash_", KEY_CURRENT_FRAME = "key_current_frame"; private int FPS = 50; private int numberOfImages; protected override OrientationEnum GetOrientation() { return OrientationEnum.ORIENTATION_CHECK_DEVICE_SIZE; } protected override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); SetContentView(Resource.Layout.activity_splash); RelativeLayout background = FindViewById<RelativeLayout>(Resource.Id.splash_background); background.Click += Click; ImageView imageView = FindViewById<ImageView>(Resource.Id.splash_imageview); imageView.Click += Click; numberOfImages = GetSplashImagesCount(); framesSequenceAnimation = new FramesSequenceAnimation(this, imageView, GetImageResourcesIDs(), FPS); framesSequenceAnimation.SetOneShot(true); if (savedInstanceState != null) { int currentFrame = savedInstanceState.GetInt(KEY_CURRENT_FRAME) + 1; if (currentFrame < numberOfImages) { framesSequenceAnimation.SetCurrentFrame(currentFrame); } } framesSequenceAnimation.start(); } private int[] GetImageResourcesIDs() { List<int> list = new List<int>(); for (int i = 1; i <= numberOfImages; i++) { var image_name = IMAGE_NAME_PREFIX + i.ToString().PadLeft(4, '0'); int resID = Resources.GetIdentifier(image_name, "drawable", PackageName); list.Add(resID); } return list.ToArray(); } private int GetSplashImagesCount() { // Count number of images in drawable folder int count = 0; var fields = typeof(Resource.Drawable).GetFields(); foreach (var field in fields) { if (field.Name.StartsWith(IMAGE_NAME_PREFIX)) { count++; } } return count; } private void Click(object sender, EventArgs e) { framesSequenceAnimation.SetFramesSequenceAnimationListener(null); GoToLoginScreen(); } private void GoToLoginScreen() { Finish(); StartActivity(new Intent(this, typeof(LoginActivity))); OverridePendingTransition(0, Resource.Animation.abc_fade_out); } void FramesSequenceAnimationListener.AnimationStopped() { GoToLoginScreen(); } protected override void OnSaveInstanceState(Bundle outState) { base.OnSaveInstanceState(outState); outState.PutInt(KEY_CURRENT_FRAME, framesSequenceAnimation.GetCurrentFrame()); } } }