Powodowanie OutOfMemoryError w animacji klatka po klatce w Androidzie

Mam wiele obrazów jako ramek w moim folderze resources/drawable (powiedzmy około 200). I za pomocą tych obrazów chcę uruchomić animację. Najdłuższa animacja to 80Frames. Jestem w stanie uruchomić animację po kliknięciu przycisków dla niektórych, ale dla niektórych animacji daje mi OutOfMemoryError mówiąc, że VM nie może dostarczyć takiej pamięci. Jest poza budżetem VM. Liczę rozmiar wszystkich obrazów jego około 10MB. Rozmiar każdego obrazu to 320x480 w pikselach.

Próbowałem googlować i okazało się, że muszę jawnie wywołać Garbage Collector za pomocą systemu.metoda gc (). Zrobiłem to, ale wciąż dostaję jakiś błąd pamięci. Czy ktoś może mi w tym pomóc?

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

To jest kod, który napisałem po kliknięciu przycisku.....

Plik zasobów wewnątrz 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>

** powyższy plik jest plikiem zasobów używanym w setBackgroundResource, tak samo sposób mam 10 więcej plików dla innych różnych animacji. **

Dziennik Błędów

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)

W ten sam sposób mam różne przyciski do różnych animacji... Dzięki

Author: Scorpion, 2012-01-01

10 answers

Zakładam, że obrazy klatek animacji są skompresowane (PNG lub JPG). Skompresowany rozmiar nie jest przydatny do obliczania ilości pamięci potrzebnej do ich wyświetlenia. W tym celu musisz pomyśleć o nieskompresowanym rozmiarze. Będzie to liczba pikseli (320x480) pomnożona przez liczbę bajtów na piksel, która zazwyczaj wynosi 4 (32 bity). W przypadku obrazów każdy z nich będzie wynosił 614 400 bajtów. Dla przykładu animacji 26 klatek, który podałeś, będzie wymagał łącznie 15 974 400 bajtów przechowywanie surowych danych bitmapowych dla wszystkich ramek, nie licząc nadmiarowych obiektów.

Patrząc na kod źródłowy AnimationDrawable, wydaje się, że ładuje wszystkie klatki do pamięci na raz, co w zasadzie musiałoby zrobić dla dobrej wydajności.

To, czy możesz przydzielić tyle pamięci, czy nie, zależy od systemu. Przynajmniej polecam wypróbowanie tego na prawdziwym urządzeniu zamiast emulatora. Możesz również spróbować dostosować Dostępny rozmiar pamięci RAM emulatora, ale jest to tylko zgaduję.

Istnieją sposoby użycia BitmapFactory.inPreferredConfig do ładowania bitmap w bardziej wydajnym formacie, takim jak RGB 565 (zamiast ARGB 8888). Zaoszczędziłoby to trochę miejsca, ale nadal może to nie wystarczyć.

Jeśli nie możesz przydzielić tyle pamięci na raz, musisz rozważyć inne opcje. Większość wysokowydajnych aplikacji graficznych (np. gier) czerpie swoją grafikę z kombinacji mniejszych grafik (sprite) lub prymitywów 2D lub 3D (prostokąty, trójkąty). Rysowanie bitmapy pełnoekranowej dla każdej klatki jest skutecznie taki sam jak renderowanie wideo; niekoniecznie najbardziej wydajny.

Czy cała zawartość animacji zmienia się z każdą klatką? Inną optymalizacją może być animacja tylko części, która faktycznie się zmienia, i posiekanie bitmap, aby to uwzględnić.

Podsumowując, musisz znaleźć sposób na narysowanie animacji przy użyciu mniejszej ilości pamięci. Opcji jest wiele, ale zależy to w dużej mierze od tego, jak powinna wyglądać Twoja animacja.

 25
Author: lyricsboy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-17 04:57:21

Miałem ten sam problem. Android ładuje wszystkie rysunki na raz, więc animacja z wieloma klatkami powoduje ten błąd.

W końcu stworzyłem własną prostą animację Sekwencji:

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

Użycie:

FramesSequenceAnimation anim = AnimationsContainer.getInstance().createSplashAnim(mSplashImageView);
anim.start();
    Nie zapomnij go zatrzymać...
 52
Author: Asaf Pinhassi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-14 13:54:35

Spędziłem nad tym dużo czasu i mam dwa różne rozwiązania, oba dobre..

Po pierwsze, problem: 1) Android ładuje wszystkie obrazy DO PAMIĘCI RAM, w nieskompresowanym formacie bitmapy. 2) Android wykorzystuje skalowanie zasobów, więc w telefonie z wyświetlaczem xxxhdpi (takim jak LG G3) każda ramka zajmuje mnóstwo miejsca, więc szybko kończy się pamięć RAM.

Rozwiązanie # 1

[[2]}1) omija skalowanie zasobów Androida. 2) zapisuje w pamięci wszystkie pliki (są to małe, zwłaszcza dla Jpegów). 3) generuje bitmapy klatka po klatce, więc prawie niemożliwe jest wyczerpanie pamięci RAM.

Wady: spamuje Twoje dzienniki, ponieważ Android alokuje pamięć na nowe bitmapy i recykling starych. Działa również kiepsko na starszych urządzeniach (Galaxy S1), ale działa ładnie na obecnych telefonach budżetowych (Czytaj: Alcatel C1 za 10 dolarów odebrałem w BestBuy). Drugie rozwiązanie poniżej działa lepiej na starszych urządzeniach, ale w niektórych okolicznościach nadal może zabraknąć pamięci RAM.

public class MyAnimationDrawable {
public static class MyFrame {
    byte[] bytes;
    int duration;
    Drawable drawable;
    boolean isReady = false;
}


public interface OnDrawableLoadedListener {
    public void onDrawableLoaded(List<MyFrame> myFrames);
}

public static void loadRaw(final int resourceId, final Context context, final OnDrawableLoadedListener onDrawableLoadedListener) {
    loadFromXml(resourceId, context, onDrawableLoadedListener);
}

private static void loadFromXml(final int resourceId, final Context context, final OnDrawableLoadedListener onDrawableLoadedListener) {
    new Thread(new Runnable() {
        @Override
        public void run() {
            final ArrayList<MyFrame> myFrames = new ArrayList<>();

            XmlResourceParser parser = context.getResources().getXml(resourceId);

            try {
                int eventType = parser.getEventType();
                while (eventType != XmlPullParser.END_DOCUMENT) {
                    if (eventType == XmlPullParser.START_DOCUMENT) {

                    } else if (eventType == XmlPullParser.START_TAG) {

                        if (parser.getName().equals("item")) {
                            byte[] bytes = null;
                            int duration = 1000;

                            for (int i=0; i<parser.getAttributeCount(); i++) {
                                if (parser.getAttributeName(i).equals("drawable")) {
                                    int resId = Integer.parseInt(parser.getAttributeValue(i).substring(1));
                                    bytes = IOUtils.toByteArray(context.getResources().openRawResource(resId));
                                }
                                else if (parser.getAttributeName(i).equals("duration")) {
                                    duration = parser.getAttributeIntValue(i, 1000);
                                }
                            }

                            MyFrame myFrame = new MyFrame();
                            myFrame.bytes = bytes;
                            myFrame.duration = duration;
                            myFrames.add(myFrame);
                        }

                    } else if (eventType == XmlPullParser.END_TAG) {

                    } else if (eventType == XmlPullParser.TEXT) {

                    }

                    eventType = parser.next();
                }
            }
            catch (IOException | XmlPullParserException e) {
                e.printStackTrace();
            }

            // Run on UI Thread
            new Handler(context.getMainLooper()).post(new Runnable() {
                @Override
                public void run() {
                    if (onDrawableLoadedListener != null) {
                        onDrawableLoadedListener.onDrawableLoaded(myFrames);
                    }
                }
            });
        }
    }).run();
}

public static void animateRawManually(int resourceId, final ImageView imageView, final Runnable onStart, final Runnable onComplete) {
    loadRaw(resourceId, imageView.getContext(), new OnDrawableLoadedListener() {
        @Override
        public void onDrawableLoaded(List<MyFrame> myFrames) {
            if (onStart != null) {
                onStart.run();
            }

            animateRawManually(myFrames, imageView, onComplete);
        }
    });
}

public static void animateRawManually(List<MyFrame> myFrames, ImageView imageView, Runnable onComplete) {
    animateRawManually(myFrames, imageView, onComplete, 0);
}

private static void animateRawManually(final List<MyFrame> myFrames, final ImageView imageView, final Runnable onComplete, final int frameNumber) {
    final MyFrame thisFrame = myFrames.get(frameNumber);

    if (frameNumber == 0) {
        thisFrame.drawable = new BitmapDrawable(imageView.getContext().getResources(), BitmapFactory.decodeByteArray(thisFrame.bytes, 0, thisFrame.bytes.length));
    }
    else {
        MyFrame previousFrame = myFrames.get(frameNumber - 1);
        ((BitmapDrawable) previousFrame.drawable).getBitmap().recycle();
        previousFrame.drawable = null;
        previousFrame.isReady = false;
    }

    imageView.setImageDrawable(thisFrame.drawable);
    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
            // Make sure ImageView hasn't been changed to a different Image in this time
            if (imageView.getDrawable() == thisFrame.drawable) {
                if (frameNumber + 1 < myFrames.size()) {
                    MyFrame nextFrame = myFrames.get(frameNumber+1);

                    if (nextFrame.isReady) {
                        // Animate next frame
                        animateRawManually(myFrames, imageView, onComplete, frameNumber + 1);
                    }
                    else {
                        nextFrame.isReady = true;
                    }
                }
                else {
                    if (onComplete != null) {
                        onComplete.run();
                    }
                }
            }
        }
    }, thisFrame.duration);

    // Load next frame
    if (frameNumber + 1 < myFrames.size()) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                MyFrame nextFrame = myFrames.get(frameNumber+1);
                nextFrame.drawable = new BitmapDrawable(imageView.getContext().getResources(), BitmapFactory.decodeByteArray(nextFrame.bytes, 0, nextFrame.bytes.length));
                if (nextFrame.isReady) {
                    // Animate next frame
                    animateRawManually(myFrames, imageView, onComplete, frameNumber + 1);
                }
                else {
                    nextFrame.isReady = true;
                }

            }
        }).run();
    }
}
}

** Rozwiązanie #2 **

Ładuje zasób XML, przetwarza go i ładuje surowce - w ten sposób omijając skalowanie zasobów Androida (które jest odpowiedzialne za większość OutOfMemoryExceptions) i tworzy AnimationDrawable.

Zalety: działa lepiej na starszych urządzeniach (np. Galaxy S1)

Wady: nadal może zabraknąć pamięci RAM, ponieważ trzyma wszystkie nieskompresowane bitmapy w pamięci (ale są mniejsze, ponieważ nie są skalowane w sposób, w jaki Android normalnie skaluje 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));
}

Jeśli nadal masz problemy z pamięcią, użyj mniejszych obrazów... lub zapisać nazwę zasobu + czas trwania i wygenerować tablicę bajtów + Drawable na każdej ramce. Prawie na pewno spowodowałoby to zbyt duże przecięcia między klatkami, ale używa prawie zerowej pamięci RAM.

 10
Author: Steven L,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-16 01:56:14

Stworzyłem klasę animacji, która wyświetla klatki na podstawie przekazywanych zasobów i czasu trwania klatek w programie drawables.

 protected class SceneAnimation{
    private ImageView mImageView;
    private int[] mFrameRess;
    private int[] mDurations;
    private int mDuration;

    private int mLastFrameNo;
    private long mBreakDelay;

 public SceneAnimation(ImageView pImageView, int[] pFrameRess, int[] pDurations){
        mImageView = pImageView;
        mFrameRess = pFrameRess;
        mDurations = pDurations;
        mLastFrameNo = pFrameRess.length - 1;

        mImageView.setImageResource(mFrameRess[0]);
        play(1);
    }

    public SceneAnimation(ImageView pImageView, int[] pFrameRess, int pDuration){
        mImageView = pImageView;
        mFrameRess = pFrameRess;
        mDuration = pDuration;
        mLastFrameNo = pFrameRess.length - 1;

        mImageView.setImageResource(mFrameRess[0]);
        playConstant(1);
    }

    public SceneAnimation(ImageView pImageView, int[] pFrameRess, int pDuration, long pBreakDelay){            
        mImageView = pImageView;
        mFrameRess = pFrameRess;
        mDuration = pDuration;
        mLastFrameNo = pFrameRess.length - 1;
        mBreakDelay = pBreakDelay;

        mImageView.setImageResource(mFrameRess[0]);
        playConstant(1);
    }


    private void play(final int pFrameNo){
        mImageView.postDelayed(new Runnable(){
            public void run() {
                mImageView.setImageResource(mFrameRess[pFrameNo]);
                if(pFrameNo == mLastFrameNo)
                    play(0);
                else
                    play(pFrameNo + 1);
            }
        }, mDurations[pFrameNo]);
    }


    private void playConstant(final int pFrameNo){
        mImageView.postDelayed(new Runnable(){
            public void run() {                    
                mImageView.setImageResource(mFrameRess[pFrameNo]);

                if(pFrameNo == mLastFrameNo)
                    playConstant(0);
                else
                    playConstant(pFrameNo + 1);
            }
        }, pFrameNo==mLastFrameNo && mBreakDelay>0 ? mBreakDelay : mDuration);
    }        
};

Używa się go tak:

 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;

I in onCreate:

 mTapScreenTextAnimImgView = (ImageView) findViewById(R.id.scene1AnimBottom);
    new SceneAnimation(mTapScreenTextAnimImgView, mTapScreenTextAnimRes, mTapScreenTextAnimDuration, mTapScreenTextAnimBreak);
 3
Author: Yar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-24 08:10:28

Miałem ten problem i rozwiązałem go, robiąc dwie następujące rzeczy:

  1. Zmniejsz rozdzielczość obrazów animacji o połowę...1/4 rozmiar w nieskompresowanych bajtach.
  2. Umieść obrazy w folderze drawable-nodpi, aby nie były skalowane przez Androida dla Ciebie.

Moja animacja nadal nie ładowała się na niektórych telefonach po wykonaniu kroku 1. Krok 2 działa na tych telefonach.

Mam nadzieję, że to oszczędzi komuś czasu.

EDIT: I was still doświadczanie awarii po przejściu do aktywności, która odtwarza AnimationDrawable, ale mam ją teraz działającą. Oto dodatkowe rzeczy, które zrobiłem:

  1. nie używaj listy animacji w xml. Zamiast tego utwórz AnimationDrawable za każdym razem, gdy musisz go użyć. W przeciwnym razie, następnym razem, gdy załadujesz animację z zasobu, nadal będziesz próbował użyć bitmap, które skończysz recyklingu.
  2. Po zakończeniu korzystania z programu AnimationDrawable należy poddać recyklingowi bitmapy. To to magia, która uwalnia pamięć.
  3. Użyj monitora Urządzenia z Androidem, aby monitorować przydzielone bajty w stercie.

Oto kod, którego używam do tworzenia 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;
    }

I kod do uwolnienia bitmap po ich użyciu. Możesz to zrobić w OnPause lub OnDestroy. _loadingAnimation to moje AnimationDrawable stworzone powyżej. Chciałbym wiedzieć, co SetCallback() robi dla Ciebie w tym przypadku. Skopiowałem to z innego miejsca na Więc.

        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
Author: tmrog,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-15 22:34:17

Przeportowałem rozwiązanie do Xamarin Android i zrobiłem kilka ulepszeń.

Działa dobrze ze zmianami orientacji, a szczególnie z obrazami o szerokości i wysokości około 300 (im większy obraz, tym dłużej trwa ładowanie obrazu, tym większe migotanie).

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

To jest moja klasa ekranu powitalnego: (ta klasa odczytuje obrazy z folderu, który można narysować o nazwie " splash_0001, splash_0002 ...". Więc nie ma potrzeby nazywania zasobów obrazu na tablicy. Zwiększ liczbę klatek na sekundę (FPS), aby przyspieszyć animację).

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
Author: Gustavo Baiocchi Costa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-23 17:09:07

Jest to duży problem z sdk, ale można go rozwiązać za pomocą wątków do jednoczesnego ładowania obrazów bitmapowych zamiast ładowania całego obrazu w tym samym czasie.

 0
Author: Jaber Shabeek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-17 04:39:25

Rozwiązałem problem outOfMemoryError poprzez brutalne ograniczenie liczby klatek na sekundę i skalowanie obrazów w gimp. W zależności od tego, co robisz, prawdopodobnie ujdzie ci to na sucho z dużo mniejszą liczbą klatek na sekundę, niż byś się spodziewał.

 0
Author: gnyrfta,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-01 23:59:34

Rozwiązałem ten problem, umieszczając wszystkie obrazy w tablicy i używając opóźnienia po pokazaniu każdego z nich. Tablica źródeł obrazów w 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> Ogłaszam o spinner imageView private static ImageView imagespinner;

W mojej klasie nazywam to tutaj:

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

A potem na runimage robię pętlę z opóźnieniem Tak:

        /* handle the spinner frame by frame */

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

  int size = array.length();

    if(index<size) {// show in sequence the images

    final int localindex= index;

        handler.postDelayed(new Runnable() {
            public void run() {
                imagespinner.setImageResource(array.getResourceId(localindex, -1));// find the picture to show
                runimage(array,(localindex+1));// because use final arg need to do the increase inside
            }
        }, 55);

    }
    else // after show all images go ahead
    {
        textview2.setVisibility(View.VISIBLE);
        handler.postDelayed(myRunnablewait, 2000); // make some time to see text before go to ather fragment
    }

}

Więc uruchamiam Wszystkie zdjęcia z opóźnieniem 55milsec na imagespinner. Po zakończeniu wykonaj zadanie nex.

 0
Author: Bar Strauss,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-16 15:40:44

Podobne do innych odpowiedzi, używając 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
Author: Nitsan Avni,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-12 09:37:54