下载Bitmap并写入现有的Bitmap

时间:2014-02-24 13:30:27

标签: android memory-management bitmap bitmapimage

我正在使用以下代码从URL下载位图。如果我这样做是循环的(比如从相机中流式传输图像),那么位图将一次又一次地重新分配。所以我想知道是否有办法将新下载的字节数组写入已在内存中分配的现有位图。

public static Bitmap downloadBitmap(String url) {
    try {
        URL newUrl = new URL(url);
        return BitmapFactory.decodeStream(newUrl.openConnection()
                .getInputStream());
    } catch (MalformedURLException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }

    return null;
}

2 个答案:

答案 0 :(得分:0)

bitmap memory management部分的此段中,该部分名为“在Android 3.0及更高版本上管理内存”。他们开始谈论如何操作位图,以便您可以重用位图空间,以便不需要重新分配位图本身的位置。如果您确实在使用相机中的流,那么这将覆盖Honeycomb,因为它们的尺寸相同。否则,它可能只能帮助过去4.4 Kitkat。

但是,您可以在downloadBitmap类中存储本地WeakReference(如果您希望在内存出现问题时收集它),然后重新分配到该空间并返回该空间而不是每次在单个位置创建位图线。

答案 1 :(得分:0)

应用程序速度变慢,因为它在每个周期中分配和取消分配内存。有三种方法可以避免这种情况。

第一个版本在没有OpenCV的情况下工作,但仍然在每个周期中分配一些内存。但是数量要小得多,因此速度至少要快两倍。怎么样?通过重用现有的和已经分配的缓冲区(byte [])。我正在使用预先分配的长度为1.000.000的SteamInfo缓冲区(大约是我预期的两倍)。

顺便说一句 - 以块的形式读取输入流并使用BitmapFactory.decodeByteArray比将URL的输入流直接放入BitmapFactory.decodeStream要快得多。

public static class StreamInfo {
    public byte[] buffer;
    public int length;

    public StreamInfo(int length) {
        buffer = new byte[length];
    }
}

public static StreamInfo imageByte(StreamInfo buffer, String url) {
    try {

        URL newUrl = new URL(url);
        InputStream is = (InputStream) newUrl.getContent();
        byte[] tempBuffer = new byte[8192];
        int bytesRead;
        int position = 0;

        if (buffer != null) {
            // re-using existing buffer

            while ((bytesRead = is.read(tempBuffer)) != -1) {
                System.arraycopy(tempBuffer, 0, buffer.buffer, position,
                        bytesRead);
                position += bytesRead;
            }

            buffer.length = position;
            return buffer;
        } else {
            // allocating new buffer

            ByteArrayOutputStream output = new ByteArrayOutputStream();
            while ((bytesRead = is.read(tempBuffer)) != -1) {
                output.write(tempBuffer, 0, bytesRead);
                position += bytesRead;
            }

            byte[] result = output.toByteArray();
            buffer = new StreamInfo(result.length * 2, false);
            buffer.length = position;

            System.arraycopy(result, 0, buffer.buffer, 0, result.length);
            return buffer;
        }
    } catch (MalformedURLException e) {
        e.printStackTrace();
        return null;
    } catch (IOException e) {
        e.printStackTrace();
        return null;
    }
}

第二个版本使用OpenCV Mat和预先分配的Bitmap。接收流与第一版一样。所以它不再需要进一步的内存分配(详细信息请查看this link)。这个版本工作正常,但它有点慢,因为它包含OpenCV Mat和Bitmap之间的转换。

private NetworkCameraFrame frame;
private HttpUtils.StreamInfo buffer = new HttpUtils.StreamInfo(1000000);
private MatOfByte matForConversion;

    private NetworkCameraFrame receive() {

        buffer = HttpUtils.imageByte(buffer, uri);

        if (buffer == null || buffer.length == 0)
            return null;

        Log.d(TAG, "Received image with byte-array of length: "
                + buffer.length / 1024 + "kb");

        if (frame == null) {
            final BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;

            Bitmap bmp = BitmapFactory.decodeByteArray(buffer.buffer, 0,
                    buffer.length);

            frame = new NetworkCameraFrame(bmp.getWidth(), bmp.getHeight());
            Log.d(TAG, "NetworkCameraFrame created");

            bmp.recycle();
        }

        if (matForConversion == null)
            matForConversion = new MatOfByte(buffer.buffer);
        else
            matForConversion.fromArray(buffer.buffer);

        Mat newImage = Highgui.imdecode(matForConversion,
                Highgui.IMREAD_UNCHANGED);
        frame.put(newImage);
        return frame;
    }

private class NetworkCameraFrame implements CameraFrame {
    Mat mat;
    private int mWidth;
    private int mHeight;
    private Bitmap mCachedBitmap;
    private boolean mBitmapConverted;

    public NetworkCameraFrame(int width, int height) {

        this.mWidth = width;
        this.mHeight = height;
        this.mat = new Mat(new Size(width, height), CvType.CV_8U);

        this.mCachedBitmap = Bitmap.createBitmap(width, height,
                Bitmap.Config.ARGB_8888);
    }

    @Override
    public Mat gray() {
        return mat.submat(0, mHeight, 0, mWidth);
    }

    @Override
    public Mat rgba() {
        return mat;
    }

    // @Override
    // public Mat yuv() {
    // return mYuvFrameData;
    // }

    @Override
    public synchronized Bitmap toBitmap() {
        if (mBitmapConverted)
            return mCachedBitmap;

        Mat rgba = this.rgba();
        Utils.matToBitmap(rgba, mCachedBitmap);

        mBitmapConverted = true;
        return mCachedBitmap;
    }

    public synchronized void put(Mat frame) {
        mat = frame;
        invalidate();
    }

    public void release() {
        mat.release();
        mCachedBitmap.recycle();
    }

    public void invalidate() {
        mBitmapConverted = false;
    }
};

第三个版本在BitmapFactory.Options上使用the instructions“BitmapFactory的使用”和一个可变的Bitmap,然后在解码时重复使用。它甚至可以在Android JellyBean上为我工作。确保在创建第一个Bitmap时使用正确的BitmapFactory.Options。

        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inBitmap = bmp;  // the old Bitmap that should be reused
        options.inMutable = true;
        options.inSampleSize = 1;

        Bitmap bmp = BitmapFactory.decodeByteArray(buffer, 0, buffer.length, options);
        options.inBitmap = bmp;

这实际上是当时最快的流媒体。