web-dev-qa-db-ja.com

Android

Resources/drawableフォルダーにフレームとして多くの画像があります(約200としましょう)。そして、この画像を使用してアニメーションを実行します。最長のアニメーションは80フレームです。一部のボタンをクリックするとアニメーションを正常に実行できますが、一部のアニメーションでは、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();
    }
}

これは、ボタンをクリックしたときに記述したコードです。...

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個のファイルがあります。 **

エラーログ

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)

同じように、アニメーションごとに異なるボタンがあります...ありがとう

38
Scorpion

アニメーションフレームイメージは圧縮されている(PNGまたはJPG)と仮定します。圧縮サイズは、それらを表示するために必要なメモリの量を計算するのに役立ちません。そのためには、非圧縮サイズについて考える必要があります。これは、ピクセル数(320x480)にピクセルあたりのバイト数を掛けたもので、通常は4(32ビット)です。画像の場合、それぞれは614,400バイトになります。提供した26フレームのアニメーションの例では、オブジェクトのオーバーヘッドをカウントせずに、すべてのフレームの生のビットマップデータを保持するために合計15,974,400バイトが必要です。

AnimationDrawableのソースコードを見ると、すべてのフレームが一度にメモリに読み込まれているように見えます。

これだけのメモリを割り当てることができるかどうかは、システムに大きく依存します。少なくともエミュレータではなく実際のデバイスでこれを試すことをお勧めします。エミュレータの利用可能なRAMサイズを調整することもできますが、これは推測に過ぎません。

BitmapFactory.inPreferredConfigを使用して、ビットマップを(ARGB 8888ではなく)RGB 565などのメモリ効率の高い形式でロードする方法があります。これによりスペースを節約できますが、それでも十分ではない場合があります。

一度にそれほど多くのメモリを割り当てることができない場合は、他のオプションを検討する必要があります。ほとんどの高性能グラフィックアプリケーション(ゲームなど)は、小さなグラフィック(スプライト)または2Dまたは3Dプリミティブ(長方形、三角形)の組み合わせからグラフィックを描画します。フレームごとにフルスクリーンビットマップを描画することは、ビデオをレンダリングすることと実質的に同じです。必ずしも最も効率的ではありません。

アニメーションのコンテンツ全体がフレームごとに変わりますか?別の最適化は、実際に変化する部分のみをアニメーション化し、それを考慮してビットマップを切り刻むことです。

要約すると、より少ないメモリを使用してアニメーションを描画する方法を見つける必要があります。多くのオプションがありますが、アニメーションの外観に大きく依存します。

25
lyricsboy

同じ問題がありました。 Androidはすべてのドロウアブルを一度にロードするため、多くのフレームを含むアニメーションはこのエラーを引き起こします。

私は自分の単純なシーケンスアニメーションを作成することになりました。

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();
  • それを止めることを忘れないでください...
53
Asaf Pinhassi

私はこれに多くの時間を費やし、2つの異なる解決策を持っています。

まず、問題:1)Androidすべての画像を非圧縮ビットマップ形式でRAMにロードします。2)Androidはリソースのスケーリングを使用するため、 xxxhdpiディスプレイ(LG G3)など)では、各フレームが1トンのスペースを占有するため、すぐにRAMが不足します。

ソリューション#1

1)Androidのリソーススケーリングをバイパスします。 2)すべてのファイルのバイト配列をメモリに保存します(特にJPEGの場合、これらは小さいです)。 3)ビットマップをフレームごとに生成するため、RAMを使い果たすことはほとんど不可能です。

短所:Androidは新しいビットマップにメモリを割り当て、古いビットマップをリサイクルしています。古いデバイス(Galaxy S1)でもパフォーマンスが低下しますが、現在の予算の電話ではうまく動作します。 10ドルのAlcatel C1 BestBuyで拾いました。以下の2番目のソリューションは、古いデバイスでより良いパフォーマンスを発揮しますが、状況によっては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();
    }
}
}

**ソリューション#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));
}

それでもメモリの問題がある場合は、より小さい画像を使用するか、リソース名+期間を保存して、各フレームでバイト配列+ Drawableを生成します。それはほとんど確実にフレーム間で過度のチョッピングを引き起こしますが、ほとんどゼロのRAMを使用します。

10
Steven L

渡された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);
3
Yar

私はこの問題を抱えていて、次の2つのことを実行して解決しました。

  1. アニメーション画像の解像度を、非圧縮バイトのサイズの半分... 1/4にカットします。
  2. 画像をdrawable-nodpiフォルダーに入れて、Androidで拡大されないようにします。

私のアニメーションは、ステップ1を実行した後も一部の電話機でロードできませんでした。ステップ2では、それらの電話機で動作するようになりました。

これが誰かの時間を節約することを願っています。

編集:AnimationDrawableを再生するアクティビティに行った後、まだクラッシュが発生していましたが、今は動作しています。私が行った追加事項は次のとおりです。

  1. Xmlでアニメーションリストを使用しないでください。代わりに、使用する必要があるたびにAnimationDrawableを作成します。そうしないと、リソースから描画可能なアニメーションを次回ロードするときに、最終的にリサイクルされるビットマップを使用しようとします。
  2. 使用が終了したら、AnimationDrawableのビットマップをリサイクルします。これは、メモリを解放する魔法です。
  3. Android Device Monitorを使用して、ヒープに割り当てられたバイトを監視します。

AnimationDrawableの作成に使用しているコードは次のとおりです。

    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は、上記で作成したAnimationDrawableです。この場合、SetCallback()が何をするのか知りたいです。 SOのどこかからコピーしただけです。

        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;
        }

テッド

2
tmrog

Xamarin Androidにソリューションを移植し、いくつかの改善を行いました。

向きの変更、特に幅と高さ約300の画像でうまく機能します(画像が大きいほど、画像の読み込みに時間がかかるほど、ちらつきが大きくなります)。

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);
            }
        }
    }
}

これは私のスプラッシュスクリーンクラスです(このクラスは、「splash_0001、splash_0002 ...」という名前のドローアブルフォルダーから画像を読み取ります。したがって、アレイ上の画像リソースに名前を付ける必要はありません。1秒あたりのフレーム数(FPS )アニメーションを高速化します)。

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());
        }
    }
}

他の回答と同様に、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;
    }
}
1
Nitsan Avni

SDKには大きな問題がありますが、画像全体を同時に読み込むのではなく、スレッドを使用してビットマップ画像を同時に読み込むことで解決できます。

0
Jaber Shabeek

フレームレートを残酷に削減し、gimpで画像を縮小することで、outOfMemoryErrorの問題を解決しました。あなたが何をしているのかにもよりますが、おそらく予想よりもはるかに少ないfpsで逃げることができます。

0
gnyrfta