2014-02-24 55 views
1

我正在使用以下代碼從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; 
} 
+0

請考慮使用http://square.github.io/picasso/ –

+0

我確實相信在操作系統改進的某些時候,以前位圖空間的內部存儲空間會一遍又一遍地被重複使用,而不是被重新分配。但我不記得細節,如果我能找到它們,我會指出。然而,這可能與此不同。 –

回答

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墊和預分配的位圖。接收流是按照版本1完成的。所以它不再需要進一步的內存分配(詳情請查看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; 
    } 
}; 

第三版本使用the instructions上BitmapFactory.Options「BitmapFactory的用法」和一個可變的位圖,其然後在解碼重新使用。它甚至可以在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; 

這實際上是最快的流媒體。

0

在這一細分市場中bitmap memory management節題爲「關於Android 3.0的管理內存和更高」他們開始說的如何​​處理位圖,這樣就可以重複使用位圖的空間,以便爲位圖本身的位置不需要重新分配。如果你確實在使用攝像頭的流,那麼這將會覆蓋到Honeycomb,因爲它們的尺寸相同。否則,它可能只會幫助過去4.4 Kitkat。

但是,您可以在downloadBitmap類中存儲本地WeakReference(如果您希望在內存問題下收集它),然後重新分配給該空間並返回,而不是每次創建一個位圖單線。