Ошибка OutOfMemoryError при воспроизведении GIF-файла в Android

#android

#Android

Вопрос:

Я хочу воспроизвести GIF-файл в Android на imageview. Я использовал эту ссылку http://droid-blog.net/2011/10/14/tutorial-how-to-use-animated-gifs-in-android-part-1 / . Это работает нормально, но когда я использую две кнопки и постоянно меняю местами GIF-изображения, приложение через некоторое время вылетело..

Это мой код

 public class GifDecoder {
        public static final int STATUS_OK = 0;
        public static final int STATUS_FORMAT_ERROR = 1;
        /**
         * File read status: Unable to open source.
         */
        public static final int STATUS_OPEN_ERROR = 2;
        /** max decoder pixel stack size */
        protected static final int MAX_STACK_SIZE = 4096;
        protected InputStream in;
        protected int status;
        protected int width; // full image width
        protected int height; // full image height
        protected boolean gctFlag; // global color table used
        protected int gctSize; // size of global color table
        protected int loopCount = 1; // iterations; 0 = repeat forever
        protected int[] gct; // global color table
        protected int[] lct; // local color table
        protected int[] act; // active color table
        protected int bgIndex; // background color index
        protected int bgColor; // background color
        protected int lastBgColor; // previous bg color
        protected int pixelAspect; // pixel aspect ratio
        protected boolean lctFlag; // local color table flag
        protected boolean interlace; // interlace flag
        protected int lctSize; // local color table size
        protected int ix, iy, iw, ih; // current image rectangle
        protected int lrx, lry, lrw, lrh;
        protected Bitmap image; // current frame
        protected Bitmap lastBitmap; // previous frame
        protected byte[] block = new byte[256]; // current data block
        protected int blockSize = 0; // block size last graphic control extension info
        protected int dispose = 0; // 0=no action; 1=leave in place; 2=restore to bg; 3=restore to prev
        protected int lastDispose = 0;
        protected boolean transparency = false; // use transparent color
        protected int delay = 0; // delay in milliseconds
        protected int transIndex; // transparent color index
        // LZW decoder working arrays
        protected short[] prefix;
        protected byte[] suffix;
        protected byte[] pixelStack;
        protected byte[] pixels;
        protected Vector<GifFrame> frames; // frames read from current file
        protected int frameCount;

        private static class GifFrame {
                public GifFrame(Bitmap im, int del) {
                        image = im;
                        delay = del;
                }

                public Bitmap image;
                public int delay;
        }

        /**
         * Gets display duration for specified frame.
         * 
         * @param n
         *          int index of frame
         * @return delay in milliseconds
         */
        public int getDelay(int n) {
                delay = -1;
                if ((n >= 0) amp;amp; (n < frameCount)) {
                        delay = frames.elementAt(n).delay;
                }
                return delay;
        }

        /**
         * Gets the number of frames read from file.
         * 
         * @return frame count
         */
        public int getFrameCount() {
                return frameCount;
        }

        public Bitmap getBitmap() {
                return getFrame(0);
        }

        public int getLoopCount() {
                return loopCount;
        }

        protected void setPixels() {
                // expose destination image's pixels as int array
                int[] dest = new int[width * height];
                // fill in starting image contents based on last image's dispose code
                if (lastDispose > 0) {
                        if (lastDispose == 3) {
                                // use image before last
                                int n = frameCount - 2;
                                if (n > 0) {
                                        lastBitmap = getFrame(n - 1);
                                } else {
                                        lastBitmap = null;
                                }
                        }
                        if (lastBitmap != null) {
                                lastBitmap.getPixels(dest, 0, width, 0, 0, width, height);
                                // copy pixels
                                if (lastDispose == 2) {
                                        // fill last image rect area with background color
                                        int c = 0;
                                        if (!transparency) {
                                                c = lastBgColor;
                                        }
                                        for (int i = 0; i < lrh; i  ) {
                                                int n1 = (lry   i) * width   lrx;
                                                int n2 = n1   lrw;
                                                for (int k = n1; k < n2; k  ) {
                                                        dest[k] = c;
                                                }
                                        }
                                }
                        }
                }
                // copy each source line to the appropriate place in the destination
                int pass = 1;
                int inc = 8;
                int iline = 0;
                for (int i = 0; i < ih; i  ) {
                        int line = i;
                        if (interlace) {
                                if (iline >= ih) {
                                        pass  ;
                                        switch (pass) {
                                        case 2:
                                                iline = 4;
                                                break;
                                        case 3:
                                                iline = 2;
                                                inc = 4;
                                                break;
                                        case 4:
                                                iline = 1;
                                                inc = 2;
                                                break;
                                        default:
                                                break;
                                        }
                                }
                                line = iline;
                                iline  = inc;
                        }
                        line  = iy;
                        if (line < height) {
                                int k = line * width;
                                int dx = k   ix; // start of line in dest
                                int dlim = dx   iw; // end of dest line
                                if ((k   width) < dlim) {
                                        dlim = k   width; // past dest edge
                                }
                                int sx = i * iw; // start of line in source
                                while (dx < dlim) {
                                        // map color and insert in destination
                                        int index = ((int) pixels[sx  ]) amp; 0xff;
                                        int c = act[index];
                                        if (c != 0) {
                                                dest[dx] = c;
                                        }
                                        dx  ;
                                }
                        }
                }
                image = Bitmap.createBitmap(dest, width, height, Config.ARGB_4444);
        }

        public Bitmap getFrame(int n) {
                if (frameCount <= 0)
                        return null;
                n = n % frameCount;
                return ((GifFrame) frames.elementAt(n)).image;
        }

        public int read(InputStream is) {
                init();
                if (is != null) {
                        in = is;
                        readHeader();
                        if (!err()) {
                                readContents();
                                if (frameCount < 0) {
                                        status = STATUS_FORMAT_ERROR;
                                }
                        }
                } else {
                        status = STATUS_OPEN_ERROR;
                }
                try {
                        is.close();
                } catch (Exception e) {
                }
                return status;
        }

        protected void decodeBitmapData() {
                int nullCode = -1;
                int npix = iw * ih;
                int available, clear, code_mask, code_size, end_of_information, in_code, old_code, bits, code, count, i, datum, data_size, first, top, bi, pi;
                if ((pixels == null) || (pixels.length < npix)) {
                        pixels = new byte[npix]; // allocate new pixel array
                }
                if (prefix == null) {
                        prefix = new short[MAX_STACK_SIZE];
                }
                if (suffix == null) {
                        suffix = new byte[MAX_STACK_SIZE];
                }
                if (pixelStack == null) {
                        pixelStack = new byte[MAX_STACK_SIZE   1];
                }
                // Initialize GIF data stream decoder.
                data_size = read();
                clear = 1 << data_size;
                end_of_information = clear   1;
                available = clear   2;
                old_code = nullCode;
                code_size = data_size   1;
                code_mask = (1 << code_size) - 1;
                for (code = 0; code < clear; code  ) {
                        prefix[code] = 0; // XXX ArrayIndexOutOfBoundsException
                        suffix[code] = (byte) code;
                }
                // Decode GIF pixel stream.
                datum = bits = count = first = top = pi = bi = 0;
                for (i = 0; i < npix;) {
                        if (top == 0) {
                                if (bits < code_size) {
                                        // Load bytes until there are enough bits for a code.
                                        if (count == 0) {
                                                // Read a new data block.
                                                count = readBlock();
                                                if (count <= 0) {
                                                        break;
                                                }
                                                bi = 0;
                                        }
                                        datum  = (((int) block[bi]) amp; 0xff) << bits;
                                        bits  = 8;
                                        bi  ;
                                        count--;
                                        continue;
                                }
                                // Get the next code.
                                code = datum amp; code_mask;
                                datum >>= code_size;
                                bits -= code_size;
                                // Interpret the code
                                if ((code > available) || (code == end_of_information)) {
                                        break;
                                }
                                if (code == clear) {
                                        // Reset decoder.
                                        code_size = data_size   1;
                                        code_mask = (1 << code_size) - 1;
                                        available = clear   2;
                                        old_code = nullCode;
                                        continue;
                                }
                                if (old_code == nullCode) {
                                        pixelStack[top  ] = suffix[code];
                                        old_code = code;
                                        first = code;
                                        continue;
                                }
                                in_code = code;
                                if (code == available) {
                                        pixelStack[top  ] = (byte) first;
                                        code = old_code;
                                }
                                while (code > clear) {
                                        pixelStack[top  ] = suffix[code];
                                        code = prefix[code];
                                }
                                first = ((int) suffix[code]) amp; 0xff;
                                // Add a new string to the string table,
                                if (available >= MAX_STACK_SIZE) {
                                        break;
                                }
                                pixelStack[top  ] = (byte) first;
                                prefix[available] = (short) old_code;
                                suffix[available] = (byte) first;
                                available  ;
                                if (((available amp; code_mask) == 0) amp;amp; (available < MAX_STACK_SIZE)) {
                                        code_size  ;
                                        code_mask  = available;
                                }
                                old_code = in_code;
                        }
                        // Pop a pixel off the pixel stack.
                        top--;
                        pixels[pi  ] = pixelStack[top];
                        i  ;
                }
                for (i = pi; i < npix; i  ) {
                        pixels[i] = 0; // clear missing pixels
                }
        }
        protected boolean err() {
                return status != STATUS_OK;
        }
        protected void init() {
                status = STATUS_OK;
                frameCount = 0;
                frames = new Vector<GifFrame>();
                gct = null;
                lct = null;
        }

        protected int read() {
                int curByte = 0;
                try {
                        curByte = in.read();
                } catch (Exception e) {
                        status = STATUS_FORMAT_ERROR;
                }
                return curByte;
        }

        protected int readBlock() {
                blockSize = read();
                int n = 0;
                if (blockSize > 0) {
                        try {
                                int count = 0;
                                while (n < blockSize) {
                                        count = in.read(block, n, blockSize - n);
                                        if (count == -1) {
                                                break;
                                        }
                                        n  = count;
                                }
                        } catch (Exception e) {
                                e.printStackTrace();
                        }
                        if (n < blockSize) {
                                status = STATUS_FORMAT_ERROR;
                        }
                }
                return n;
        }

        protected int[] readColorTable(int ncolors) {
                int nbytes = 3 * ncolors;
                int[] tab = null;
                byte[] c = new byte[nbytes];
                int n = 0;
                try {
                        n = in.read(c);
                } catch (Exception e) {
                        e.printStackTrace();
                }
                if (n < nbytes) {
                        status = STATUS_FORMAT_ERROR;
                } else {
                        tab = new int[256]; // max size to avoid bounds checks
                        int i = 0;
                        int j = 0;
                        while (i < ncolors) {
                                int r = ((int) c[j  ]) amp; 0xff;
                                int g = ((int) c[j  ]) amp; 0xff;
                                int b = ((int) c[j  ]) amp; 0xff;
                                tab[i  ] = 0xff000000 | (r << 16) | (g << 8) | b;
                        }
                }
                return tab;
        }

        /**
         * Main file parser. Reads GIF content blocks.
         */
        protected void readContents() {
                // read GIF file content blocks
                boolean done = false;
                while (!(done || err())) {
                        int code = read();
                        switch (code) {
                        case 0x2C: // image separator
                                readBitmap();
                                break;
                        case 0x21: // extension
                                code = read();
                                switch (code) {
                                case 0xf9: // graphics control extension
                                        readGraphicControlExt();
                                        break;
                                case 0xff: // application extension
                                        readBlock();
                                        String app = "";
                                        for (int i = 0; i < 11; i  ) {
                                                app  = (char) block[i];
                                        }
                                        if (app.equals("NETSCAPE2.0")) {
                                                readNetscapeExt();
                                        } else {
                                                skip(); // don't care
                                        }
                                        break;
                                case 0xfe:// comment extension
                                        skip();
                                        break;
                                case 0x01:// plain text extension
                                        skip();
                                        break;
                                default: // uninteresting extension
                                        skip();
                                }
                                break;
                        case 0x3b: // terminator
                                done = true;
                                break;
                        case 0x00: // bad byte, but keep going and see what happens break;
                        default:
                                status = STATUS_FORMAT_ERROR;
                        }
                }
        }


        protected void readGraphicControlExt() {
                read(); // block size
                int packed = read(); // packed fields
                dispose = (packed amp; 0x1c) >> 2; // disposal method
                if (dispose == 0) {
                        dispose = 1; // elect to keep old image if discretionary
                }
                transparency = (packed amp; 1) != 0;
                delay = readShort() * 10; // delay in milliseconds
                transIndex = read(); // transparent color index
                read(); // block terminator
        }

        protected void readHeader() {
                String id = "";
                for (int i = 0; i < 6; i  ) {
                        id  = (char) read();
                }
                if (!id.startsWith("GIF")) {
                        status = STATUS_FORMAT_ERROR;
                        return;
                }
                readLSD();
                if (gctFlag amp;amp; !err()) {
                        gct = readColorTable(gctSize);
                        bgColor = gct[bgIndex];
                }
        }
        protected void readBitmap() {
                ix = readShort(); // (sub)image position amp; size
                iy = readShort();
                iw = readShort();
                ih = readShort();
                int packed = read();
                lctFlag = (packed amp; 0x80) != 0; // 1 - local color table flag interlace
                lctSize = (int) Math.pow(2, (packed amp; 0x07)   1);
                interlace = (packed amp; 0x40) != 0;
                if (lctFlag) {
                        lct = readColorTable(lctSize); // read table
                        act = lct; // make local table active
                } else {
                        act = gct; // make global table active
                        if (bgIndex == transIndex) {
                                bgColor = 0;
                        }
                }
                int save = 0;
                if (transparency) {
                        save = act[transIndex];
                        act[transIndex] = 0; // set transparent color if specified
                }
                if (act == null) {
                        status = STATUS_FORMAT_ERROR; // no color table defined
                }
                if (err()) {
                        return;
                }
                decodeBitmapData(); // decode pixel data
                skip();
                if (err()) {
                        return;
                }
                frameCount  ;
                image = Bitmap.createBitmap(width, height, Config.ARGB_4444);
                setPixels(); // transfer pixel data to image
                frames.addElement(new GifFrame(image, delay)); // add image to frame
                if (transparency) {
                        act[transIndex] = save;
                }
                resetFrame();
        }

        protected void readLSD() {
                // logical screen size
                width = readShort();
                height = readShort();
                // packed fields
                int packed = read();
                gctFlag = (packed amp; 0x80) != 0; // 1 : global color table flag
                // 2-4 : color resolution
                // 5 : gct sort flag
                gctSize = 2 << (packed amp; 7); // 6-8 : gct size
                bgIndex = read(); // background color index
                pixelAspect = read(); // pixel aspect ratio
        }

        protected void readNetscapeExt() {
                do {
                        readBlock();
                        if (block[0] == 1) {
                                int b1 = ((int) block[1]) amp; 0xff;
                                int b2 = ((int) block[2]) amp; 0xff;
                                loopCount = (b2 << 8) | b1;
                        }
                } while ((blockSize > 0) amp;amp; !err());
        }
        protected int readShort() {
                // read 16-bit value, LSB first
                return read() | (read() << 8);
        }
        protected void resetFrame() {
                lastDispose = dispose;
                lrx = ix;
                lry = iy;
                lrw = iw;
                lrh = ih;
                lastBitmap = image;
                lastBgColor = bgColor;
                dispose = 0;
                transparency = false;
                delay = 0;
                lct = null;
        }
        protected void skip() {
                do {
                        readBlock();
                } while ((blockSize > 0) amp;amp; !err());
        }
}





public class GifDecoderView extends ImageView {
    private boolean mIsPlayingGif = false;
    private GifDecoder mGifDecoder;
    private Bitmap mTmpBitmap;
    final Handler mHandler = new Handler();
    final Runnable mUpdateResults = new Runnable() {
        public void run() {
            if (mTmpBitmap != null amp;amp; !mTmpBitmap.isRecycled()) {
                GifDecoderView.this.setImageBitmap(mTmpBitmap);
            }
        }
    };
    public GifDecoderView(Context context, InputStream stream) {
        super(context);
        playGif(stream);
    }
    public  void playGif(InputStream stream) {
        mGifDecoder = new GifDecoder();
        mGifDecoder.read(stream);
        mIsPlayingGif = true;
        new Thread(new Runnable() {
            public void run() {
                final int n = mGifDecoder.getFrameCount();
                final int ntimes = mGifDecoder.getLoopCount();
                int repetitionCounter = 0;
                do {
                    for (int i = 0; i < n; i  ) {
                        mTmpBitmap = mGifDecoder.getFrame(i);
                        int t = mGifDecoder.getDelay(i);
                        mHandler.post(mUpdateResults);
                        try {
                            Thread.sleep(t);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }

                    if(ntimes != 0) {
                        repetitionCounter   ;
                    }
                } while (mIsPlayingGif amp;amp; (repetitionCounter <= ntimes));
            }
        }).start();
    }
    public void stopRendering() {
        mIsPlayingGif = true;
    }
}
 

Это моя деятельность

 public class MainActivity extends FragmentActivity implements OnClickListener {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_gif_main);
            playGif("animation2.gif");
        findViewById(R.id.btn1).setOnClickListener(MainActivity.this);
        findViewById(R.id.btn2).setOnClickListener(MainActivity.this);
    }
    public void playGif(String name)
    {
        try
        {
            InputStream is = getApplicationContext().getAssets().open(name);
            GifDecoderView gif= new GifDecoderView(getApplicationContext(), is);

            LinearLayout ll=(LinearLayout)findViewById(R.id.ll);
            ll.removeAllViews();
            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(100,100);
            ll.addView(gif, is), params);
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
    }
    @Override
    public void onClick(View v) {
        switch(v.getId())
        {
        case R.id.btn1:
            playGif("animation2.gif");
            break;
        case R.id.btn2:
            playGif("animation1.gif");
            break;
        }
    }
}
 

Пожалуйста, проверьте этот код. Заранее спасибо. Приложение разбивается, когда я постоянно меняю изображение.

Комментарии:

1. пожалуйста, покажите нам вывод logcat

2. Поскольку этот GIF-файл встроен в ваше приложение, вы можете просто преобразовать его в AnimationDrawable : commonsware.com/blog/2013/10/01 /…

Ответ №1:

Проблема вызвана тем, что GarbageCollector не выпустил старые экземпляры GIF, поскольку вы воссоздаете их все время, снова и снова!

Попробуйте кэшировать оба экземпляра GifDecoderView, т.Е. Создайте их один раз и сохраните во время выполнения, вместо того, чтобы воссоздавать их, т.Е. в вашем oncreate сделайте это:

 InputStream is = getApplicationContext().getAssets().open(gif0Name);
this.mGif0 = new GifDecoderView(getApplicationContext(), is);
is = getApplicationContext().getAssets().open(gif1Name);
this.mGif1 = new GifDecoderView(getApplicationContext(), is);
 

Добавьте два поля в класс соответственно и не забудьте уничтожить их, как только ваша активность будет уничтожена.

Кроме того: подумайте не о том, чтобы воссоздавать все представления, а о том, чтобы остановить воспроизведение скрытого GIF-файла и скрыть окружающий вид! Сделает ваш код намного меньше, и вам не придется все время воссоздавать материал!

РЕДАКТИРОВАТЬ: Кстати, что делает ваш DecoderView, если вы добавите этот GIFMovieView?

Комментарии:

1. еще один вопрос, есть ли у меня 20 или 30 GIF-файлов. Должен ли я загружать их все в oncreate ()..

2. Скорее всего, вы будете снова и снова сталкиваться с ошибками OutOfMemory, используя это огромное количество GIF-файлов, я думаю. На некоторых устройствах может помочь даже отсутствие кэширования, которое потребляет много памяти! Однако основным принципом оптимизации этого является создание локального кэша, который отображает GIF-объекты по строке (Filename-> Object) и создает и кэширует их по требованию. Это уменьшит потребление памяти до минимума и сохранит в памяти только те данные, которые требуются.