Sco*_*ion 38 animation android memory-leaks out-of-memory
我在我的资源/可绘制文件夹中有很多图像作为帧(比如大约200).使用这个图像我想运行动画.最长的动画是80Frames.我成功地能够通过单击某些按钮来运行动画,但对于某些动画,它给了我OutOfMemoryError,说VM无法提供这样的内存.它超出了VM预算.我计算所有图像的大小约10MB.每个图像的大小为320x480像素.
我尝试谷歌搜索,发现我需要使用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();
}
}
Run Code Online (Sandbox Code Playgroud)
这是我在点击按钮时编写的代码.....
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>
Run Code Online (Sandbox Code Playgroud)
**以上是setBackgroundResource中使用的资源文件,同样我还有10个文件用于其他不同的动画.**
错误日志
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)
Run Code Online (Sandbox Code Playgroud)
同样的方式我有不同的动画不同的按钮...谢谢
Asa*_*ssi 53
我有同样的问题.Android一次加载所有drawable,因此具有多个帧的动画会导致此错误.
我最终创建了自己的简单序列动画:
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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
用法:
FramesSequenceAnimation anim = AnimationsContainer.getInstance().createSplashAnim(mSplashImageView);
anim.start();
Run Code Online (Sandbox Code Playgroud)
lyr*_*boy 25
我假设你的动画帧图像是压缩的(PNG或JPG).压缩大小对于计算显示它们需要多少内存没有用.为此,您需要考虑未压缩的大小.这将是像素数(320x480)乘以每像素的字节数,通常为4(32位).对于您的图像,每个将是614,400字节.对于您提供的26帧动画示例,将需要总共15,974,400个字节来保存所有帧的原始位图数据,而不计算对象开销.
看一下源代码AnimationDrawable
,它似乎会立即将所有帧加载到内存中,这基本上是为了获得良好的性能.
是否可以分配这么多内存是非常依赖于系统的.我至少建议在真实设备而不是模拟器上尝试这个.您也可以尝试调整模拟器的可用RAM大小,但这只是猜测.
有一些方法可用于BitmapFactory.inPreferredConfig
以更高内存效率的格式(如RGB 565(而不是ARGB 8888))加载位图.这样可以节省一些空间,但仍然可能还不够.
如果您不能一次分配那么多内存,则必须考虑其他选项.大多数高性能图形应用程序(例如游戏)从较小图形(精灵)或2D或3D图元(矩形,三角形)的组合中绘制图形.为每个帧绘制一个全屏位图实际上与渲染视频相同; 不一定是效率最高的.
动画的整个内容是否随每帧而变化?另一个优化可能是仅动画实际更改的部分,并切断您的位图以解决这个问题.
总而言之,您需要找到一种使用更少内存绘制动画的方法.有很多选项,但它很大程度上取决于动画的外观.
Ste*_*n L 11
我花了很多时间在这上面,有两种不同的解决方案,都很好..
首先,问题是:1)Android以未压缩的Bitmap格式将所有图像加载到RAM中.2)Android使用资源缩放,因此在具有xxxhdpi显示的手机(例如LG G3)上,每个帧占用一个TON空间,因此您很快就会耗尽RAM.
解决方案#1
1)绕过Android的资源扩展.2)将所有文件的字节数存储在内存中(这些文件很小,特别是对于JPEG).3)逐帧生成位图,因此几乎不可能用完RAM.
缺点:当Android为新的Bitmaps分配内存并回收旧的Bitmaps时,它会阻止您的日志.它在旧设备上也表现糟糕(Galaxy S1),但在目前的预算手机上表现不错(阅读:10美元Alcatel C1我在BestBuy购买).下面的第二个解决方案在旧设备上表现更好,但在某些情况下仍可能用完RAM.
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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
**解决方案#2**
它加载XML资源,解析它并加载原始资源 - 从而绕过Android的资源扩展(负责大多数OutOfMemoryExceptions),并创建一个AnimationDrawable.
优点:在旧设备上表现更好(例如Galaxy S1)
缺点:仍然可以用完RAM,因为它将所有未压缩的位图保存在内存中(但它们较小,因为它们不像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));
}
Run Code Online (Sandbox Code Playgroud)
如果您仍然遇到内存问题,请使用较小的图像...或存储资源名称+持续时间,并在每个帧上生成字节数组+ Drawable.这几乎肯定会导致帧之间过多的斩波,但使用几乎为零的RAM.
归档时间: |
|
查看次数: |
30961 次 |
最近记录: |