web-dev-qa-db-fra.com

Causer OutOfMemoryError dans l'animation image par image dans Android

J'ai beaucoup d'images comme cadres dans mes ressources/dossier dessinable (disons environ 200). Et en utilisant ces images, je veux exécuter une animation. L'animation la plus longue est de 80 images. Je peux exécuter l'animation avec succès en cliquant sur les boutons pour certains, mais pour une partie de l'animation, cela me donne OutOfMemoryError en disant que VM ne peut pas fournir une telle mémoire. Il est hors de VM Budget. Je compte la taille de toutes les images, c'est environ 10 Mo. La taille de chaque image est de 320x480 en pixels.

J'ai essayé de googler et j'ai trouvé que j'avais besoin d'appeler explicitement le garbage collector en utilisant la méthode System.gc (). Je l'ai fait mais je reçois toujours une erreur de temps de mémoire. Quelqu'un peut-il m'aider avec bonté?.

Certains codes: -

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, de la même manière que j'ai 10 fichiers de plus pour une autre animation différente. **

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, j'ai différents boutons pour différentes animations ... Merci

38
Scorpion

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 de 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.

Que vous puissiez allouer autant de mémoire ou pas dépend du système. Je recommanderais au moins d'essayer cela sur un vrai appareil au lieu de l'émulateur. Vous pouvez également essayer de modifier la taille RAM RAM disponible de l'émulateur), mais ce n'est qu'une supposition.

Il existe des moyens 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 économiserait de l'espace, mais cela pourrait ne pas être suffisant.

Si vous ne pouvez pas allouer autant de mémoire à la fois, vous devez considérer d'autres options. La plupart des applications graphiques hautes performances (par exemple les jeux) tirent leurs graphiques de combinaisons de graphiques plus petits (sprites) ou de primitives 2D ou 3D (rectangles, triangles). Dessiner une image bitmap plein écran pour chaque image est en fait identique au rendu vidéo; pas nécessairement le plus efficace.

Le contenu entier de votre animation change-t-il à chaque image? Une autre optimisation pourrait être d'animer uniquement la partie qui change réellement et de dé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 existe de nombreuses options, mais cela dépend beaucoup de l'apparence de votre animation.

25
lyricsboy

J'ai eu le même problème. Android charge tous les drawables en même temps, 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;
        }
    }
}

Usage:

FramesSequenceAnimation anim = AnimationsContainer.getInstance().createSplashAnim(mSplashImageView);
anim.start();
  • n'oubliez pas de l'arrêter ...
53
Asaf Pinhassi

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 affichage xxxhdpi (tel que LG G3), chaque image prend une tonne d'espace, donc vous manquez rapidement de RAM.

Solution n ° 1

1) Contourne la mise à l'échelle des ressources d'Android. 2) Stocke les sous-tableaux de tous les fichiers en mémoire (ils sont petits, surtout 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 nouveaux bitmaps et recycle les anciens. Il fonctionne également pourri sur les appareils plus anciens (Galaxy S1), mais fonctionne bien sur les téléphones à budget actuel (lire: 10 $ Alcatel C1 que j'ai acheté chez BestBuy.) La deuxième solution ci-dessous fonctionne mieux sur les appareils plus anciens, mais pourrait toujours 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 n ° 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 toujours manquer de RAM car il contient tous les bitmaps non compressés en mémoire (mais ils sont plus petits car ils ne sont pas mis à l'échelle de la manière Android normalement redimensionne 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 trame. Cela entraînerait presque certainement trop de découpage entre les images, mais utilise presque zéro RAM.

10
Steven L

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

 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
Yar

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

  1. Réduisez la résolution des images d'animation de moitié ... 1/4 de la taille en octets non compressés.
  2. Placez les images dans le dossier drawable-nodpi afin qu'elles ne soient pas agrandies par Android pour vous.

Mon animation n'arrivait toujours pas à se charger sur certains téléphones après avoir effectué l'étape 1. L'étape 2 l'a fait fonctionner sur ces téléphones.

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

EDIT: Je rencontrais toujours des plantages après être allé à l'activité qui joue l'AnimationDrawable mais je le fais fonctionner maintenant. Voici les choses supplémentaires que j'ai faites:

  1. N'utilisez pas de liste d'animation en xml. Créez plutôt AnimationDrawable à chaque fois que vous en avez besoin. Sinon, la prochaine fois que vous chargerez l'animation tirable de la ressource, elle essaiera toujours d'utiliser les bitmaps que vous finirez par recycler.
  2. Recyclez les bitmaps dans AnimationDrawable lorsque vous avez fini de l'utiliser. C'est la magie qui libère la mémoire.
  3. Utilisez le Android Device Monitor pour surveiller les octets alloués dans votre tas.

Voici le code que j'utilise pour créer le 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 du code pour libérer les bitmaps lorsque vous avez fini de les utiliser. Vous pouvez le faire dans OnPause ou OnDestroy. _loadingAnimation est mon AnimationDrawable créé ci-dessus. J'aimerais savoir ce que SetCallback () fait pour vous dans ce cas. Je viens de le copier ailleurs sur 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;
        }

Ted

2
tmrog

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

Il fonctionne bien avec les changements d'orientation et spécialement avec les images d'environ 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);
            }
        }
    }
}

Voici ma classe d'écran de démarrage: (cette classe lit les images du dossier dessinable qui sont nommées "splash_0001, splash_0002 ...". Donc, pas besoin de nommer vos ressources d'image sur un tableau. Augmentez le nombre d'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

Semblable à d'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;
    }
}
1
Nitsan Avni

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
Jaber Shabeek

J'ai résolu ce problème en mettant toutes les images dans le tableau et en utilisant le délai après avoir montré chacune d'elles. Le tableau des sources d'images 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 privé statique ImageView imagespinner;

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

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

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

        /* handle the spinner frame by frame */

public void runimage (tableau TypedArray final, 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é, faites le prochain travail.

0
Bar Strauss

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

0
gnyrfta