提高Android的图像/位图显示速度

时间:2012-07-05 13:16:44

标签: android image bitmap garbage-collection bytearray

我编写了一个程序,它以字节[]的形式从套接字中读取图像,然后尝试在屏幕上显示它们。我已经按照我发现的一些链接说最好的方法就是使用SurfaceView。我正在尝试分配一个字节数组,它将占用我需要的最大图像,然后不断地为我收到的新图像使用相同的存储空间。这样,内存就没有机会收集垃圾。

我想我已经将问题缩小到“BitmapFactory.decodeByteArray(f,0,fileSize);”的函数调用。我认为这个调用每次创建一个新的位图,一旦方法返回它就是GC。我尝试使用inMutable和inBitmap字段但没有运气,因为图像的大小每次都在变化。我可能做错了。如果有人有使用它的经验并且它为他们工作,请告诉我。每次byte []大小改变甚至只有10个元素。

我正在尝试尽可能快地显示我正在阅读的图像,并希望重用相同的内存空间/位图,这样我就不必继续承担垃圾收集的30ms成本。有谁知道我怎么能避免垃圾收集,或至少最小化它?图像为640x480和1280x720。下面是一段代码片段,它读入图像并与LogCat输出一起显示。任何帮助,将不胜感激。感谢。

static byte[] f = new byte[250000]; // allocate enough memory space for biggest image
private TutorialThread _thread;

class Panel extends SurfaceView implements SurfaceHolder.Callback {

    /* On start up connect socket */
    public Panel(Context context) {
        super(context);
        getHolder().addCallback(this);
        _thread = new TutorialThread(getHolder(), this);
        if (connectSockets) {
            s2 = connect(ip, s2, port);
        }
    }
    /*
     * OnDraw - Take the byte[] and use Bitmap.decodeByteArray to decode
     * Image and then draw it on canvas
     * 
     * (Slow, Causing 30ms delay due to Garbage Collection)
     */
    @Override
    public void onDraw(Canvas canvas) {
        Bitmap i = BitmapFactory.decodeByteArray(f, 0, fileSize);
        canvas.drawBitmap(i, 10, 10, null);
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int width,
            int height) {
        // TODO Auto-generated method stub

    }

    public void surfaceCreated(SurfaceHolder holder) {
        // TODO Auto-generated method stub
        _thread.setRunning(true);
        _thread.start();
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        // TODO Auto-generated method stub
        // simply copied from sample application LunarLander:
        // we have to tell thread to shut down & wait for it to finish, or
        // else
        // it might touch the Surface after we return and explode
        boolean retry = true;
        _thread.setRunning(false);
        while (retry) {
            try {
                _thread.join();
                retry = false;
            } catch (InterruptedException e) {
                // we will try it again and again...
            }
        }
    }
}

class TutorialThread extends Thread {
    private SurfaceHolder _surfaceHolder;
    private Panel _panel;
    private boolean _run = false;

    public TutorialThread(SurfaceHolder surfaceHolder, Panel panel) {
        _surfaceHolder = surfaceHolder;
        _panel = panel;
    }

    public void setRunning(boolean run) {
        _run = run;
    }

    @Override
    public void run() {
        Canvas c = null;
        byte[] header = new byte[16];
        while (_run) {
            Log.d("Brian", "Running");
            if (s2 != null) { // if socket isCreated
                int bytesRead = 0;
                int totalBytesRead = 0;
                fileSize = 0;

                /*
                 * read the header that contains the filesize, height, and
                 * width
                 * 
                 * don't break until we read all header values
                 */
                while (totalBytesRead != (header.length)) {
                    try {
                        bytesRead = s2.getInputStream().read(header,
                                totalBytesRead,
                                (header.length - totalBytesRead));
                    } catch (IOException ex) {
                    }
                    if (bytesRead == -1) {
                    } else {
                        totalBytesRead += bytesRead;
                    }
                }

                // convert the filesize from bytes to int
                fileSize = (fileSize << 8) + (header[0] & 0xff);
                fileSize = (fileSize << 8) + (header[1] & 0xff);
                fileSize = (fileSize << 8) + (header[2] & 0xff);
                fileSize = (fileSize << 8) + (header[3] & 0xff);

                bytesRead = 0;

                // read the entire file. don't break until we read
                // everything
                do {
                    bytesRead = readChunk(s2, bytesRead, fileSize, f);
                } while (bytesRead != fileSize);
            } else {
                Log.d("Brian", "Socket Null");
            }
            c = null;
            try {
                c = _surfaceHolder.lockCanvas(null);
                synchronized (_surfaceHolder) {
                    _panel.onDraw(c);
                }
            } finally {
                // do this in a finally so that if an exception is thrown
                // during the above, we don't leave the Surface in an
                // inconsistent state
                if (c != null) {
                    _surfaceHolder.unlockCanvasAndPost(c);
                }
            }
        }
    }

07-05 09:13:21.980: D/dalvikvm(25075): GC_FOR_ALLOC freed 0K, 6% free 7040K/7431K, paused 18ms
07-05 09:13:22.010: D/dalvikvm(25075): GC_CONCURRENT freed <1K, 6% free 7040K/7431K, paused 4ms+2ms
07-05 09:13:22.040: D/dalvikvm(25075): GC_EXPLICIT freed 600K, 14% free 6440K/7431K, paused 1ms+2ms
07-05 09:13:22.060: D/dalvikvm(25075): GC_FOR_ALLOC freed <1K, 14% free 6440K/7431K, paused 19ms
07-05 09:13:22.060: I/dalvikvm-heap(25075): Grow heap (frag case) to 6.994MB for 614416-byte allocation
07-05 09:13:22.080: D/dalvikvm(25075): GC_FOR_ALLOC freed 0K, 6% free 7040K/7431K, paused 21ms
07-05 09:13:22.120: D/dalvikvm(25075): GC_CONCURRENT freed <1K, 6% free 7040K/7431K, paused 3ms+2ms
07-05 09:13:22.150: D/dalvikvm(25075): GC_EXPLICIT freed 600K, 14% free 6440K/7431K, paused 2ms+2ms
07-05 09:13:22.170: D/dalvikvm(25075): GC_FOR_ALLOC freed <1K, 14% free 6440K/7431K, paused 19ms
07-05 09:13:22.170: I/dalvikvm-heap(25075): Grow heap (frag case) to 6.994MB for 614416-byte allocation
07-05 09:13:22.190: D/dalvikvm(25075): GC_FOR_ALLOC freed 0K, 6% free 7040K/7431K, paused 19ms
07-05 09:13:22.220: D/dalvikvm(25075): GC_CONCURRENT freed <1K, 6% free 7040K/7431K, paused 2ms+2ms
07-05 09:13:22.250: D/dalvikvm(25075): GC_EXPLICIT freed 600K, 14% free 6440K/7431K, paused 2ms+1ms
07-05 09:13:22.270: D/dalvikvm(25075): GC_FOR_ALLOC freed <1K, 14% free 6440K/7431K, paused 18ms
07-05 09:13:22.270: I/dalvikvm-heap(25075): Grow heap (frag case) to 6.994MB for 614416-byte allocation

2 个答案:

答案 0 :(得分:2)

我遇到了同样的问题,你已经提到了解决方案:

opts.inBitmap

正如您所说,新的Bitmap必须与前一个相同。 此外,你必须设置

opts.inSampleSize = 1;

示例:

BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inSampleSize = 1;

while (...) {
    // first call will create a new bitmap
    Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, opts);
    // subsequent calls will re-use the first bitmap
    opt.inBitmap = bitmap;
    // check (dump bitmaps memory address, should always output the same string)
    Log.d("bitmapID", bitmap.toString());
}

就我而言,这完全删除了Bitmap所需的GC。

答案 1 :(得分:0)

  

我认为这次调用每次都会创建一个新的位图   当方法返回时,它正在被GC编辑。

这正是发生的事情。它每次调用都会从缓冲区创建一个新的位图。不管它是否是相同的位图,旧的都会丢弃。不要在&#34; onDraw&#34;中创建对象。视图的方法,尤其是位图。在视图存在的情况下,此方法可能会被调用数百次,并且创建对象的成本很高。

最好的选择是等待缓冲区填充新的位图,然后调用BitmapFactory.decodeByteArray(f, 0, fileSize);保持对类范围内位图的引用。还记得在创建新位图之前在旧位图上调用recycle()