Provoquer OutOfMemoryError dans L'Animation Image par image dans Android

J'ai beaucoup d'images comme cadres dans mon dossier resources/drawable (disons environ 200). Et en utilisant ces images, je veux lancer une animation. La plus longue animation est de 80Frames. Je suis capable avec succès d'exécuter l'animation en cliquant sur les boutons pour certains, mais pour certains de l'animation, il me donne OutOfMemoryError en disant que VM ne peut pas fournir une telle mémoire. Il est hors du Budget VM. Je compte la taille de toutes les images sur 10MB. La taille de chaque image est de 320x480 en pixels.

J'essaie de googler et j'ai trouvé que je devais appeler explicitement le garbage Collector en utilisant le système.gc() la méthode. Je l'ai fait mais je reçois toujours une erreur de mémoire. Quelqu'un peut merci de bien vouloir m'aider dans cette.

Un Peu De Code:-

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

C'est le code que j'ai écrit en cliquant sur un bouton.....

Fichier de ressources dans 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>

* * ce qui précède est le fichier de ressources utilisé dans setBackgroundResource, même façon dont j'ai 10 plus de fichiers pour d'autres animations différentes. **

Journal Des Erreurs

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)

De la même manière que j'ai différents boutons pour différentes animations... Merci

36
demandé sur Scorpion 2012-01-01 12:00:26

10 réponses

Je suppose que vos images de cadre d'animation sont compressées (PNG ou JPG). La taille compressée n'est pas utile pour calculer la quantité de mémoire nécessaire pour les afficher. Pour cela, vous devez penser à la taille non compressée. Ce sera le nombre de pixels (320x480) multiplié par le nombre d'octets par pixel, qui est généralement 4 (32 bits). Pour vos images, alors, chacune sera de 614 400 octets. Pour l'exemple d'animation de 26 images que vous avez fourni, cela nécessitera un total de 15 974 400 octets pour contenir les données bitmap brutes pour toutes les images, sans compter la surcharge de l'objet.

En regardant le code source de AnimationDrawable, Il semble charger toutes les images en mémoire à la fois, ce qu'il devrait essentiellement faire pour de bonnes performances.

Si vous pouvez allouer autant de mémoire ou non est très dépendant du système. Je recommanderais au moins d'essayer cela sur un appareil réel au lieu de l'émulateur. Vous pouvez également essayer de peaufiner la taille de RAM disponible de l'émulateur, mais c'est juste deviner.

Il existe des façons d'utiliser BitmapFactory.inPreferredConfig pour charger des bitmaps dans un format plus efficace en mémoire comme RGB 565 (plutôt que ARGB 8888). Cela permettrait d'économiser de l'espace, mais cela pourrait encore ne pas être suffisant.

Si vous ne pouvez pas allouer autant de mémoire à la fois, vous devez envisager d'autres options. La plupart des applications graphiques haute performance (par exemple les jeux) tirent leurs graphiques à partir de combinaisons de graphiques plus petits (sprites) ou de primitives 2D ou 3D (rectangles, triangles). Dessin d'un bitmap plein écran pour chaque image est effectivement le même que le rendu vidéo; pas nécessairement le plus efficace.

Le contenu complet de votre animation change-t-il à chaque image? Une autre optimisation pourrait être d'animer uniquement la partie qui change réellement, et de couper vos bitmaps pour en tenir compte.

Pour résumer, vous devez trouver un moyen de dessiner votre animation en utilisant moins de mémoire. Il y a beaucoup d'options, mais cela dépend beaucoup de la façon dont votre animation doit regarder.

25
répondu lyricsboy 2012-01-17 04:57:21

J'ai eu le même problème. Android charge tous les drawables à la fois, donc l'animation avec de nombreuses images provoque cette erreur.

J'ai fini par créer ma propre animation de séquence simple:

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

Utilisation:

FramesSequenceAnimation anim = AnimationsContainer.getInstance().createSplashAnim(mSplashImageView);
anim.start();
  • n'oublie pas de l'arrêter...
52
répondu Asaf Pinhassi 2013-04-14 13:54:35

J'ai passé beaucoup de temps là-dessus et j'ai deux solutions différentes, toutes deux bonnes..

Tout d'Abord, le problème: 1) android charge toutes les images dans la RAM, au format bitmap non compressé. 2) Android utilise la mise à l'échelle des ressources, donc sur un téléphone avec un écran xxxhdpi (tel que LG G3), chaque image prend une tonne d'espace, de sorte que vous manquez rapidement de RAM.

Solution # 1

1) contourne la mise à l'échelle des ressources D'Android. 2) stocke les bytearrays de tous les fichiers en mémoire (ceux-ci sont petits, en particulier pour les Jpeg). 3) génère bitmaps image par image, il est donc presque impossible de manquer de RAM.

Inconvénients: il spams vos journaux comme Android alloue de la mémoire pour les nouvelles Bitmaps et le recyclage des anciens. Il effectue également moche sur les appareils plus anciens (Galaxy S1), mais fonctionne bien sur les téléphones budgétaires actuels (Lire: 10 $Alcatel C1 Je ramassé à BestBuy). La deuxième solution ci-dessous fonctionne mieux sur les appareils plus anciens, mais pourrait encore manquer de RAM dans certaines circonstances.

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

* * Solution #2 **

Il charge la ressource XML, l'analyse et charge les ressources brutes-contournant ainsi la mise à l'échelle des ressources D'Android (qui est responsable de la plupart des OutOfMemoryExceptions), et crée un AnimationDrawable.

Avantages: fonctionne mieux sur les appareils plus anciens (par exemple. Galaxy S1)

Inconvénients: peut encore manquer de RAM car il contient toutes les Bitmaps non compressées en mémoire (mais elles sont plus petites car elles ne sont pas mises à l'échelle comme Android évolue normalement les images)

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

Si vous rencontrez toujours des problèmes de mémoire, utilisez des images plus petites... ou stockez le nom de la ressource + la durée , et générez le tableau d'octets + Drawable sur chaque image. Cela causerait presque certainement trop de hachage entre les images, mais utilise presque zéro RAM.

10
répondu Steven L 2015-07-16 01:56:14

J'ai créé une classe d'animation qui affiche les images en fonction des ressources drawables et des durées des images transmises.

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

, Il est utilisé comme ceci:

 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;

Et dans onCreate:

 mTapScreenTextAnimImgView = (ImageView) findViewById(R.id.scene1AnimBottom);
    new SceneAnimation(mTapScreenTextAnimImgView, mTapScreenTextAnimRes, mTapScreenTextAnimDuration, mTapScreenTextAnimBreak);
3
répondu Yar 2012-09-24 08:10:28

J'ai eu ce problème et l'ai résolu en faisant les deux choses suivantes:

  1. Couper la résolution des images d'animation en deux...1/4 la taille en octets non compressés.
  2. Mettez les images dans le dossier drawable-nodpi afin qu'elles ne soient pas mises à l'échelle par Android pour vous.

Mon animation ne parvenait toujours pas à se charger sur certains téléphones après avoir fait l'étape 1. Étape 2 a obtenu de travailler sur ces téléphones.

J'espère que cela fera gagner du temps à quelqu'un d'autre.

EDIT: j'étais toujours éprouver des plantages après être allé à l'activité qui joue L'AnimationDrawable mais je l'ai en train de travailler maintenant. Voici les choses supplémentaires que j'ai faites:

  1. n'utilisez pas de liste d'animation en xml. Au lieu de cela, créez AnimationDrawable chaque fois que vous devez l'utiliser. Sinon, la prochaine fois que vous chargerez l'animation dessinée à partir de la ressource, il essayera toujours d'utiliser les bitmaps que vous finirez par recycler.
  2. Recyclez les bitmaps dans AnimationDrawable lorsque vous avez terminé de l'utiliser. Ce est la magie qui libère la mémoire.
  3. Utilisez le moniteur de périphérique Android pour surveiller les octets alloués dans votre tas.

Voici le code que j'utilise pour créer L'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;
    }

Et le code pour libérer les images lorsque vous avez terminé de les utiliser. Vous pouvez le faire en OnPause ou OnDestroy. _loadingAnimation est mon AnimationDrawable créé ci-dessus. J'aimerais savoir ce que SetCallback() fait pour vous dans ce cas. J'ai juste copié qu'à partir de quelque part sur SI.

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

Ted

2
répondu tmrog 2015-10-15 22:34:17

J'ai porté une solution à Xamarin Android et j'ai fait quelques améliorations.

Cela fonctionne bien avec les changements d'orientation et spécialement avec les images autour de 300 largeur et hauteur (plus l'image est grande, plus il faut de temps pour charger l'image, plus le scintillement est grand).

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

C'est ma classe d'écran de démarrage: (cette classe lit les images du dossier drawable qui sont nommées "splash_0001, splash_0002 ...". Donc, pas besoin de nommer vos ressources d'image sur un tableau. Augmenter le nombre de images par seconde (FPS) pour accélérer l'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());
        }
    }
}
1
répondu Gustavo Baiocchi Costa 2017-11-23 17:09:07

C'est un gros problème avec le sdk mais il peut être résolu en utilisant des threads pour charger simultanément les images bitmap au lieu de charger l'image entière en même temps.

0
répondu Jaber Shabeek 2012-01-17 04:39:25

J'ai résolu mon problème outOfMemoryError en réduisant brutalement le framerate et en réduisant les images dans gimp. Selon ce que vous faites, vous pouvez probablement vous en sortir avec beaucoup moins de fps que prévu.

0
répondu gnyrfta 2015-12-01 23:59:34

J'ai résolu ce problème en mettant toutes les images dans le tableau et en utilisant delay after show each of them. Le tableau d'images source dans 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> Je déclare à propos du spinner imageView ImageView statique privé imagespinner;

, Puis dans ma classe, je l'appelle ici:

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

Et puis sur runimage je fais la boucle avec retard comme ceci:

        /* handle the spinner frame by frame */

Public void runimage(tableau final TypedArray, index int) {

  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
    }

}

Donc, je lance toutes les images avec un retard de 55milsec sur le imagespinner. Après avoir terminé faire le travail nex.

0
répondu Bar Strauss 2015-12-16 15:40:44

Similaire aux autres réponses, en utilisant 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;
    }
}
0
répondu Nitsan Avni 2016-08-12 09:37:54