Câu hỏi Ngắt ra khỏi vấn đề bộ nhớ trong khi tải một hình ảnh vào một đối tượng Bitmap


Tôi có chế độ xem danh sách với một vài nút hình ảnh trên mỗi hàng. Khi bạn nhấp vào hàng danh sách, nó sẽ khởi chạy một hoạt động mới. Tôi đã phải xây dựng các tab của riêng mình vì có vấn đề với bố cục máy ảnh. Hoạt động được khởi chạy cho kết quả là bản đồ. Nếu tôi nhấp vào nút của tôi để khởi chạy xem trước hình ảnh (tải hình ảnh ra khỏi thẻ SD), ứng dụng sẽ trả về từ hoạt động trở lại listview hoạt động với trình xử lý kết quả để khởi chạy lại hoạt động mới của tôi mà không có gì hơn là một tiện ích hình ảnh.

Xem trước hình ảnh trên chế độ xem danh sách đang được thực hiện với con trỏ và ListAdapter. Điều này làm cho nó khá đơn giản, nhưng tôi không chắc chắn làm thế nào tôi có thể đặt một hình ảnh thay đổi kích cỡ (I.e. Kích thước bit nhỏ hơn không pixel như src cho nút hình ảnh khi đang di chuyển. Vì vậy, tôi chỉ cần thay đổi kích thước hình ảnh mà ra khỏi máy ảnh điện thoại.

Vấn đề là tôi nhận được một lỗi bộ nhớ khi nó cố gắng quay trở lại và khởi động lại hoạt động thứ 2.

  • Có cách nào tôi có thể xây dựng bộ điều hợp danh sách dễ dàng từng hàng, nơi tôi có thể thay đổi kích cỡ khi đang di chuyển (hơi khôn ngoan)?

Điều này sẽ thích hợp hơn vì tôi cũng cần phải thực hiện một số thay đổi đối với các thuộc tính của các tiện ích / thành phần trong mỗi hàng vì tôi không thể chọn một hàng có màn hình cảm ứng do vấn đề tiêu điểm. (Tôi có thể sử dụng bi lăn.)

  • Tôi biết tôi có thể thực hiện việc thay đổi kích thước và lưu hình ảnh của mình, nhưng đó không thực sự là điều tôi muốn làm, nhưng một số mã mẫu cho điều đó sẽ tốt đẹp.

Ngay sau khi tôi vô hiệu hóa hình ảnh trên danh sách xem nó làm việc tốt một lần nữa.

FYI: Đây là cách tôi đã làm nó:

String[] from = new String[] { DBHelper.KEY_BUSINESSNAME,DBHelper.KEY_ADDRESS,DBHelper.KEY_CITY,DBHelper.KEY_GPSLONG,DBHelper.KEY_GPSLAT,DBHelper.KEY_IMAGEFILENAME  + ""};
int[] to = new int[] {R.id.businessname,R.id.address,R.id.city,R.id.gpslong,R.id.gpslat,R.id.imagefilename };
notes = new SimpleCursorAdapter(this, R.layout.notes_row, c, from, to);
setListAdapter(notes);

Ở đâu R.id.imagefilename là một ButtonImage.

Đây là LogCat của tôi:

01-25 05:05:49.877: ERROR/dalvikvm-heap(3896): 6291456-byte external allocation too large for this process.
01-25 05:05:49.877: ERROR/(3896): VM wont let us allocate 6291456 bytes
01-25 05:05:49.877: ERROR/AndroidRuntime(3896): Uncaught handler: thread main exiting due to uncaught exception
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): java.lang.OutOfMemoryError: bitmap size exceeds VM budget
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:304)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:149)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:174)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.drawable.Drawable.createFromPath(Drawable.java:729)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ImageView.resolveUri(ImageView.java:484)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ImageView.setImageURI(ImageView.java:281)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.SimpleCursorAdapter.setViewImage(SimpleCursorAdapter.java:183)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.SimpleCursorAdapter.bindView(SimpleCursorAdapter.java:129)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.CursorAdapter.getView(CursorAdapter.java:150)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.AbsListView.obtainView(AbsListView.java:1057)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ListView.makeAndAddView(ListView.java:1616)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ListView.fillSpecific(ListView.java:1177)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ListView.layoutChildren(ListView.java:1454)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.AbsListView.onLayout(AbsListView.java:937)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.layoutHorizontal(LinearLayout.java:1108)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.onLayout(LinearLayout.java:922)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.FrameLayout.onLayout(FrameLayout.java:294)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.layoutVertical(LinearLayout.java:999)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.onLayout(LinearLayout.java:920)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.FrameLayout.onLayout(FrameLayout.java:294)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.ViewRoot.performTraversals(ViewRoot.java:771)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.ViewRoot.handleMessage(ViewRoot.java:1103)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.os.Handler.dispatchMessage(Handler.java:88)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.os.Looper.loop(Looper.java:123)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.app.ActivityThread.main(ActivityThread.java:3742)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at java.lang.reflect.Method.invokeNative(Native Method)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at java.lang.reflect.Method.invoke(Method.java:515)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:739)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:497)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at dalvik.system.NativeStart.main(Native Method)
01-25 05:10:01.127: ERROR/AndroidRuntime(3943): ERROR: thread attach failed 

Tôi cũng có một lỗi mới khi hiển thị hình ảnh:

01-25 22:13:18.594: DEBUG/skia(4204): xxxxxxxxxxx jpeg error 20 Improper call to JPEG library in state %d
01-25 22:13:18.604: INFO/System.out(4204): resolveUri failed on bad bitmap uri: 
01-25 22:13:18.694: ERROR/dalvikvm-heap(4204): 6291456-byte external allocation too large for this process.
01-25 22:13:18.694: ERROR/(4204): VM won't let us allocate 6291456 bytes
01-25 22:13:18.694: DEBUG/skia(4204): xxxxxxxxxxxxxxxxxxxx allocPixelRef failed

1108


gốc


Tôi đặt nhật ký vào một khối mã để cung cấp cho nó thanh cuộn - UnkwnTech
Tôi đã giải quyết điều này bằng cách tránh Bitmap.decodeStream hoặc decodeFile và sử dụng phương thức BitmapFactory.decodeFileDescriptor. - Fraggle
Tôi cũng phải đối mặt với vấn đề tương tự vài tuần trở lại và tôi giải quyết nó bằng cách thu nhỏ hình ảnh tối đa điểm tối ưu. Tôi đã viết cách tiếp cận hoàn chỉnh trong blog của mình codingjunkiesforum.wordpress.com/2014/06/12/… và tải lên dự án mẫu hoàn chỉnh với mã dễ bị OOM so với mã Proof OOM athttps: //github.com/shailendra123/BitmapHandlingDemo - Shailendra Singh Rajawat
Câu trả lời được chấp nhận về câu hỏi này đang được thảo luận meta - rene
Điều này xảy ra khi bạn không đọc hướng dẫn dành cho nhà phát triển Android - Pedro Varela


Các câu trả lời:


Các Đào tạo Android lớp học, "Hiển thị bitmap hiệu quả", cung cấp một số thông tin tuyệt vời để hiểu và xử lý ngoại lệ java.lang.OutOfMemoryError: bitmap size exceeds VM budget khi tải Bitmap.


Đọc kích thước và loại bitmap

Các BitmapFactory lớp cung cấp một số phương thức giải mã (decodeByteArray(), decodeFile(), decodeResource(), v.v.) để tạo Bitmap từ nhiều nguồn khác nhau. Chọn phương thức giải mã thích hợp nhất dựa trên nguồn dữ liệu hình ảnh của bạn. Các phương thức này cố gắng cấp phát bộ nhớ cho bitmap đã được xây dựng và do đó có thể dễ dàng dẫn đến một OutOfMemory ngoại lệ. Mỗi loại phương thức giải mã có chữ ký bổ sung cho phép bạn chỉ định các tùy chọn giải mã thông qua BitmapFactory.Options lớp học. Đặt inJustDecodeBounds tài sản để true trong khi giải mã tránh phân bổ bộ nhớ, trả lại null cho đối tượng bitmap nhưng thiết lập outWidth, outHeight và outMimeType. Kỹ thuật này cho phép bạn đọc các kích thước và loại dữ liệu hình ảnh trước khi xây dựng (và cấp phát bộ nhớ) của bitmap.

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;

Tránh java.lang.OutOfMemory ngoại lệ, kiểm tra kích thước của bitmap trước khi giải mã nó, trừ khi bạn hoàn toàn tin tưởng nguồn để cung cấp cho bạn dữ liệu hình ảnh có kích thước dự đoán phù hợp thoải mái trong bộ nhớ có sẵn.


Tải phiên bản thu nhỏ xuống bộ nhớ

Giờ đây, kích thước hình ảnh đã được biết, chúng có thể được sử dụng để quyết định xem hình ảnh đầy đủ có nên được tải vào bộ nhớ hay không hoặc nếu phiên bản được lấy mẫu phụ sẽ được tải thay thế. Dưới đây là một số yếu tố cần xem xét:

  • Mức sử dụng bộ nhớ ước tính khi tải toàn bộ hình ảnh trong bộ nhớ.
  • Số lượng bộ nhớ bạn sẵn sàng cam kết tải hình ảnh này cho bất kỳ yêu cầu bộ nhớ nào khác của ứng dụng của bạn.
  • Kích thước của thành phần mục tiêu ImageView hoặc giao diện người dùng mà hình ảnh sẽ được tải vào.
  • Kích thước màn hình và mật độ của thiết bị hiện tại.

Ví dụ: không nên tải hình ảnh pixel 1024x768 vào bộ nhớ nếu hình ảnh cuối cùng sẽ được hiển thị trong hình thu nhỏ pixel 128x96 trong ImageView.

Để báo cho bộ giải mã ghi lại hình ảnh, tải phiên bản nhỏ hơn vào bộ nhớ, hãy đặt inSampleSize đến true trong của bạn BitmapFactory.Options vật. Ví dụ: hình ảnh có độ phân giải 2048x1536 được giải mã bằng inSampleSize trong số 4 tạo bitmap khoảng 512x384. Tải bộ nhớ này vào bộ nhớ sử dụng 0.75MB thay vì 12MB cho hình ảnh đầy đủ (giả sử cấu hình bitmap của ARGB_8888). Dưới đây là một phương pháp để tính giá trị kích thước mẫu là sức mạnh của hai dựa trên chiều rộng và chiều cao mục tiêu:

public static int calculateInSampleSize(
        BitmapFactory.Options options, int reqWidth, int reqHeight) {
    // Raw height and width of image
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;

    if (height > reqHeight || width > reqWidth) {

        final int halfHeight = height / 2;
        final int halfWidth = width / 2;

        // Calculate the largest inSampleSize value that is a power of 2 and keeps both
        // height and width larger than the requested height and width.
        while ((halfHeight / inSampleSize) > reqHeight
                && (halfWidth / inSampleSize) > reqWidth) {
            inSampleSize *= 2;
        }
    }

    return inSampleSize;
}

chú thích: Giá trị của hai giá trị được tính bởi vì bộ giải mã sử dụng   giá trị cuối cùng bằng cách làm tròn xuống công suất gần nhất của hai, theo    inSampleSize tài liệu.

Để sử dụng phương pháp này, trước tiên hãy giải mã bằng inJustDecodeBounds đặt thành true, chuyển các tùy chọn qua và sau đó giải mã lại bằng cách sử dụng inSampleSize giá trị và inJustDecodeBounds đặt thành false:

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
    int reqWidth, int reqHeight) {

    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);

    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
}

Phương pháp này giúp dễ dàng tải bitmap có kích thước lớn tùy ý vào ImageView hiển thị hình thu nhỏ 100x100 pixel, như được hiển thị trong mã ví dụ sau:

mImageView.setImageBitmap(
    decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));

Bạn có thể làm theo một quy trình tương tự để giải mã bitmap từ các nguồn khác, bằng cách thay thế BitmapFactory.decode* khi cần.


561



Câu trả lời này đang được thảo luận meta - rene
Câu trả lời này (ngoại trừ thông tin đạt được thông qua liên kết) không cung cấp nhiều giải pháp cho câu trả lời. Các phần quan trọng của liên kết nên được hợp nhất vào câu hỏi. - FallenAngel
Câu trả lời này, giống như câu hỏi và các câu trả lời khác là Wiki Cộng đồng, vì vậy đây là điều mà cộng đồng có thể khắc phục bằng cách chỉnh sửa, một thứ không yêu cầu sự can thiệp của người kiểm duyệt. - Martijn Pieters♦


Để khắc phục lỗi OutOfMemory, bạn nên làm như sau:

BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 8;
Bitmap preview_bitmap = BitmapFactory.decodeStream(is, null, options);

Điều này inSampleSize tùy chọn giảm mức tiêu thụ bộ nhớ.

Đây là một phương pháp hoàn chỉnh. Đầu tiên nó đọc kích thước hình ảnh mà không cần giải mã nội dung. Sau đó, nó tìm thấy tốt nhất inSampleSize giá trị, nó phải là một sức mạnh của 2, và cuối cùng là hình ảnh được giải mã.

// Decodes image and scales it to reduce memory consumption
private Bitmap decodeFile(File f) {
    try {
        // Decode image size
        BitmapFactory.Options o = new BitmapFactory.Options();
        o.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(new FileInputStream(f), null, o);

        // The new size we want to scale to
        final int REQUIRED_SIZE=70;

        // Find the correct scale value. It should be the power of 2.
        int scale = 1;
        while(o.outWidth / scale / 2 >= REQUIRED_SIZE && 
              o.outHeight / scale / 2 >= REQUIRED_SIZE) {
            scale *= 2;
        }

        // Decode with inSampleSize
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inSampleSize = scale;
        return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
    } catch (FileNotFoundException e) {}
    return null;
}

858



Lưu ý rằng 10 có thể không phải là giá trị tốt nhất cho inSampleSize, tài liệu đề xuất sử dụng quyền hạn của 2. - Mirko N.
Tôi đang đối mặt với vấn đề tương tự như Chrispix, nhưng tôi không nghĩ giải pháp ở đây thực sự giải quyết được vấn đề, nhưng thay vào đó là giải quyết vấn đề. Thay đổi kích thước mẫu làm giảm dung lượng bộ nhớ được sử dụng (với chi phí chất lượng hình ảnh, có thể là không phù hợp cho xem trước hình ảnh), nhưng nó sẽ không ngăn ngoại lệ nếu một luồng hình ảnh đủ lớn được giải mã, nếu có nhiều luồng hình ảnh giải mã. Nếu tôi tìm thấy một giải pháp tốt hơn (và có thể không có một giải pháp) Tôi sẽ đăng câu trả lời ở đây. - Flynn81
Bạn chỉ cần kích thước phù hợp để phù hợp với màn hình ở mật độ điểm ảnh, để phóng to và bạn có thể lấy mẫu của hình ảnh ở mật độ cao hơn. - stealthcopter
REQUIRED_SIZE là kích thước mới bạn muốn mở rộng. - Fedor
giải pháp này đã giúp tôi nhưng chất lượng hình ảnh là khủng khiếp. Tôi đang sử dụng một viewfilpper để hiển thị các hình ảnh bất kỳ đề xuất? - user1106888


Tôi đã thực hiện một cải tiến nhỏ cho mã của Fedor. Về cơ bản nó giống nhau, nhưng không có (theo ý kiến ​​của tôi) xấu xí trong khi vòng lặp và nó luôn luôn dẫn đến một sức mạnh của hai. Kudos cho Fedor để làm giải pháp ban đầu, tôi đã bị mắc kẹt cho đến khi tôi tìm thấy của mình, và sau đó tôi đã có thể làm điều này :)

 private Bitmap decodeFile(File f){
    Bitmap b = null;

        //Decode image size
    BitmapFactory.Options o = new BitmapFactory.Options();
    o.inJustDecodeBounds = true;

    FileInputStream fis = new FileInputStream(f);
    BitmapFactory.decodeStream(fis, null, o);
    fis.close();

    int scale = 1;
    if (o.outHeight > IMAGE_MAX_SIZE || o.outWidth > IMAGE_MAX_SIZE) {
        scale = (int)Math.pow(2, (int) Math.ceil(Math.log(IMAGE_MAX_SIZE / 
           (double) Math.max(o.outHeight, o.outWidth)) / Math.log(0.5)));
    }

    //Decode with inSampleSize
    BitmapFactory.Options o2 = new BitmapFactory.Options();
    o2.inSampleSize = scale;
    fis = new FileInputStream(f);
    b = BitmapFactory.decodeStream(fis, null, o2);
    fis.close();

    return b;
}

354



Có, bạn đúng trong khi không phải là quá đẹp. Tôi chỉ cố gắng làm rõ mọi người. Cảm ơn mã của bạn. - Fedor
@Thomas Vervest - Có một vấn đề lớn với mã đó. ^ không tăng 2 đến một sức mạnh, nó xors 2 với kết quả. Bạn muốn Math.pow (2.0, ...). Nếu không, điều này có vẻ tốt. - DougW
Ooh, đó là một điều rất hay! Xấu của tôi, tôi sẽ sửa nó ngay lập tức, cảm ơn cho trả lời! - Thomas Vervest
whats, IMAGE_SIZE trong này? - nicky
Bạn đang tạo hai FileInputStream mới, một cho mỗi cuộc gọi đến BitmapFactory.decodeStream(). Bạn không phải lưu một tham chiếu cho từng người trong số họ để họ có thể bị đóng trong một finally khối? - matsev


Tôi đến từ trải nghiệm iOS và tôi đã thất vọng khi phát hiện sự cố với thứ gì đó cơ bản như tải và hiển thị hình ảnh. Sau khi tất cả, tất cả mọi người có vấn đề này đang cố gắng hiển thị hình ảnh có kích thước hợp lý. Dù sao, đây là hai thay đổi cố định vấn đề của tôi (và làm cho ứng dụng của tôi rất nhạy).

1) Mỗi ​​lần bạn làm BitmapFactory.decodeXYZ(), hãy chắc chắn để vượt qua trong một BitmapFactory.Options với inPurgeable đặt thành true (và tốt hơn là với inInputShareable cũng được đặt thành true).

2) KHÔNG BAO GIỜ sử dụng Bitmap.createBitmap(width, height, Config.ARGB_8888). Ý tôi là KHÔNG BAO GIỜ! Tôi đã không bao giờ có điều đó không nâng cao lỗi bộ nhớ sau vài lần. Không có số lượng recycle(), System.gc(), bất cứ điều gì đã giúp. Nó luôn luôn ngoại lệ. Một cách khác thực sự hoạt động là có một hình ảnh giả trong các drawables của bạn (hoặc một Bitmap khác mà bạn đã giải mã bằng bước 1 ở trên), rescale thành bất cứ thứ gì bạn muốn, sau đó thao tác Bitmap (chẳng hạn như chuyển nó vào Canvas để vui hơn). Vì vậy, những gì bạn nên sử dụng thay thế là: Bitmap.createScaledBitmap(srcBitmap, width, height, false). Nếu vì lý do gì bạn PHẢI sử dụng phương pháp tạo vũ phu, thì ít nhất là vượt qua Config.ARGB_4444.

Điều này gần như được đảm bảo để tiết kiệm cho bạn giờ nếu không phải ngày. Tất cả những gì nói về việc chia tỷ lệ hình ảnh, v.v. không thực sự hoạt động (trừ khi bạn xem xét việc nhận được kích thước sai hoặc hình ảnh bị suy giảm một giải pháp).


221



BitmapFactory.Options options = new BitmapFactory.Options(); options.inPurgeable = true; và Bitmap.createScaledBitmap(srcBitmap, width, height, false); giải quyết vấn đề của tôi tôi đã có với bộ nhớ ngoài exception trên android 4.0.0. Cảm ơn bạn! - Jan-Terje Sørensen
Trong Bitmap.createScaledBitmap () gọi bạn có lẽ nên sử dụng true như tham số flag. Nếu không, chất lượng của hình ảnh sẽ không được mịn khi mở rộng quy mô. Kiểm tra chủ đề này stackoverflow.com/questions/2895065/… - rOrlig
Đó thực sự là lời khuyên tuyệt vời. Ước gì tôi có thể cung cấp cho bạn thêm +1 cho việc Google thực hiện nhiệm vụ cho lỗi dink đáng kinh ngạc này. Ý tôi là ... nếu nó không phải là một lỗi thì tài liệu thực sự cần phải có một số dấu hiệu neon nhấp nháy nghiêm túc nói rằng "Đây là cách bạn xử lý ảnh", vì tôi đã đấu tranh với điều này trong 2 năm và bây giờ tìm thấy bài đăng này. Tuyệt vời tìm thấy. - Genia S.
Chia tỷ lệ hình ảnh của bạn xuống chắc chắn sẽ giúp, nhưng đây là một bước quan trọng và cuối cùng giải quyết vấn đề này cho tôi. Vấn đề với việc chia tỷ lệ hình ảnh của bạn là nếu bạn có nhiều hình ảnh, hoặc nếu hình ảnh nguồn quá lớn thì bạn vẫn có thể gặp phải vấn đề tương tự. 1 cho bạn Ephraim. - Dave
Kể từ Lollipop, BitmapFactory.Options.inPurgeable và BitmapFactory.Options.inInputShareable không được chấp nhận developer.android.com/reference/android/graphics/… - Denis Kniazhev


nó là một lỗi đã biết, không phải vì các tệp lớn. Kể từ khi Android lưu trữ các Drawables, nó sẽ hết bộ nhớ sau khi sử dụng một vài hình ảnh. Nhưng tôi đã tìm thấy một cách thay thế cho nó, bằng cách bỏ qua hệ thống bộ nhớ cache mặc định của Android.

Dung dịch: Di chuyển hình ảnh vào thư mục "asset" và sử dụng hàm sau để nhận BitmapDrawable:

public static Drawable getAssetImage(Context context, String filename) throws IOException {
    AssetManager assets = context.getResources().getAssets();
    InputStream buffer = new BufferedInputStream((assets.open("drawable/" + filename + ".png")));
    Bitmap bitmap = BitmapFactory.decodeStream(buffer);
    return new BitmapDrawable(context.getResources(), bitmap);
}

85





Tôi đã có cùng một vấn đề này và giải quyết nó bằng cách tránh các hàm BitmapFactory.decodeStream hoặc decodeFile và thay vào đó được sử dụng BitmapFactory.decodeFileDescriptor

decodeFileDescriptor có vẻ như nó gọi các phương thức gốc khác với decodeStream / decodeFile.

Dù sao, những gì làm việc được điều này (lưu ý rằng tôi đã thêm một số tùy chọn như một số đã có ở trên, nhưng đó không phải là những gì làm cho sự khác biệt. BitmapFactory.decodeFileDescriptor thay vì decodeStream hoặc là decodeFile):

private void showImage(String path)   {
    Log.i("showImage","loading:"+path);
    BitmapFactory.Options bfOptions=new BitmapFactory.Options();
    bfOptions.inDither=false;                     //Disable Dithering mode
    bfOptions.inPurgeable=true;                   //Tell to gc that whether it needs free memory, the Bitmap can be cleared
    bfOptions.inInputShareable=true;              //Which kind of reference will be used to recover the Bitmap data after being clear, when it will be used in the future
    bfOptions.inTempStorage=new byte[32 * 1024]; 


    File file=new File(path);
    FileInputStream fs=null;
    try {
        fs = new FileInputStream(file);
    } catch (FileNotFoundException e) {
        //TODO do something intelligent
        e.printStackTrace();
    }

    try {
        if(fs!=null) bm=BitmapFactory.decodeFileDescriptor(fs.getFD(), null, bfOptions);
    } catch (IOException e) {
        //TODO do something intelligent
        e.printStackTrace();
    } finally{ 
        if(fs!=null) {
            try {
                fs.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    //bm=BitmapFactory.decodeFile(path, bfOptions); This one causes error: java.lang.OutOfMemoryError: bitmap size exceeds VM budget

    im.setImageBitmap(bm);
    //bm.recycle();
    bm=null;



}

Tôi nghĩ rằng có một vấn đề với các chức năng bản địa được sử dụng trong decodeStream / decodeFile. Tôi đã xác nhận rằng một phương thức gốc khác được gọi khi sử dụng decodeFileDescriptor. Ngoài ra những gì tôi đã đọc là "rằng hình ảnh (bitmap) không được phân bổ theo một cách Java tiêu chuẩn nhưng thông qua các cuộc gọi bản địa, phân bổ được thực hiện bên ngoài của heap ảo, nhưng tính chống lại nó!"


70



cùng một kết quả ra khỏi memeory, thực sự nó wont vấn đề mà phương pháp bạn đang sử dụng nó phụ thuộc vào số lượng byte bạn đang nắm giữ trên để đọc dữ liệu cho ra khỏi bộ nhớ. - PiyushMishra
Điều gì xảy ra nếu đường dẫn là url? - Jesse


Tôi nghĩ cách tốt nhất để tránh OutOfMemoryError là đối mặt với nó và hiểu nó.

Tôi đã thực hiện một ứng dụng cố ý gây ra OutOfMemoryErrorvà theo dõi mức sử dụng bộ nhớ.

Sau khi tôi đã thực hiện rất nhiều thử nghiệm với Ứng dụng này, tôi đã có các kết luận sau:

Tôi sẽ nói về các phiên bản SDK trước Honey Comb trước.

  1. Bitmap được lưu trữ trong heap bản địa, nhưng nó sẽ nhận được rác thu thập tự động, gọi recycle () là không cần thiết.

  2. Nếu {VM heap size} + {được cấp phát bộ nhớ heap gốc}> = {giới hạn kích thước heap VM cho thiết bị}, và bạn đang cố gắng tạo bitmap, OOM sẽ bị ném.

    LƯU Ý: VM HEAP SIZE được tính thay vì VM ALLOCATED MEMORY.

  3. Kích thước VM Heap sẽ không bao giờ thu nhỏ sau khi được phát triển, ngay cả khi bộ nhớ VM được phân bổ bị co lại.

  4. Vì vậy, bạn phải giữ cho bộ nhớ VM cao nhất càng thấp càng tốt để giữ cho VM Heap Size phát triển quá lớn để tiết kiệm bộ nhớ có sẵn cho Bitmap.

  5. Gọi System.gc () theo cách thủ công là vô nghĩa, hệ thống sẽ gọi nó trước tiên trước khi cố gắng tăng kích thước heap.

  6. Native Heap Size sẽ không bao giờ thu nhỏ quá, nhưng nó không được tính cho OOM, vì vậy không cần phải lo lắng về nó.

Sau đó, chúng ta hãy nói về SDK Bắt đầu từ Honey Comb.

  1. Bitmap được lưu trữ trong vùng heap VM, bộ nhớ riêng không được tính cho OOM.

  2. Điều kiện cho OOM đơn giản hơn nhiều: {VM heap size}> = {Giới hạn kích thước heap VM cho thiết bị}.

  3. Vì vậy, bạn có nhiều bộ nhớ có sẵn để tạo bitmap với cùng giới hạn kích thước heap, OOM ít có khả năng bị ném.

Đây là một số quan sát của tôi về Garbage Collection và Memory Leak.

Bạn có thể nhìn thấy nó trong ứng dụng. Nếu một Activity thực thi AsyncTask vẫn đang chạy sau khi Activity bị hủy, Activity sẽ không thu gom rác cho đến khi AsyncTask kết thúc.

Điều này là do AsyncTask là một cá thể của một lớp bên trong vô danh, nó giữ một tham chiếu của Activity.

Gọi AsyncTask.cancel (true) sẽ không dừng việc thực hiện nếu tác vụ bị chặn trong một hoạt động IO trong chuỗi nền.

Gọi lại là các lớp bên trong vô danh quá, vì vậy nếu một cá thể tĩnh trong dự án của bạn giữ chúng và không giải phóng chúng, bộ nhớ sẽ bị rò rỉ.

Nếu bạn lên lịch một tác vụ lặp lại hoặc trì hoãn, ví dụ như Bộ hẹn giờ và bạn không gọi hủy () và thanh lọc () trong onPause (), bộ nhớ sẽ bị rò rỉ.


64



AsyncTask không nhất thiết phải là "một thể hiện của một lớp bên trong vô danh", và tương tự như vậy đối với các Callbackks. Bạn có thể tạo một lớp công khai mới trong tệp riêng của nó mở rộng AsyncTask hoặc thậm chí là private static classtrong cùng một lớp. Họ sẽ không giữ bất kỳ tham chiếu nào đến Hoạt động (trừ khi bạn cung cấp cho họ một khóa học) - Simon Forsberg


Tôi đã nhìn thấy rất nhiều câu hỏi về ngoại lệ OOM và bộ nhớ đệm gần đây. Hướng dẫn dành cho nhà phát triển có một bài viết rất hay về điều này, nhưng một số có xu hướng không thực hiện nó một cách phù hợp.

Bởi vì điều này tôi đã viết một ứng dụng ví dụ thể hiện bộ nhớ đệm trong một môi trường Android. Việc triển khai này chưa nhận được OOM.

Nhìn vào cuối câu trả lời này cho một liên kết đến mã nguồn.

Yêu cầu:

  • Android API 2.1 hoặc cao hơn (tôi chỉ đơn giản là không thể quản lý để có được bộ nhớ có sẵn cho một ứng dụng trong API 1.6 - đó là đoạn mã duy nhất không hoạt động trong API 1.6)
  • Gói hỗ trợ Android

Screenshot

Tính năng, đặc điểm:

  • Giữ lại bộ nhớ cache nếu có thay đổi hướng, sử dụng một singleton
  • Sử dụng một phần tám bộ nhớ ứng dụng được gán cho bộ nhớ cache (sửa đổi nếu bạn muốn)
  • Ảnh bitmap lớn được thu nhỏ (bạn có thể xác định các pixel tối đa mà bạn muốn cho phép)
  • Kiểm soát có kết nối internet trước khi tải xuống bitmap
  • Đảm bảo rằng bạn chỉ đang khởi tạo một nhiệm vụ mỗi hàng
  • Nếu bạn đang ném các ListView đi, nó chỉ đơn giản là sẽ không tải về các bitmap giữa

Điều này không bao gồm:

  • Bộ nhớ đệm trên đĩa. Điều này sẽ dễ dàng thực hiện anyway - chỉ cần trỏ đến một nhiệm vụ khác mà lấy các bitmap từ đĩa

Mã mẫu:

Các hình ảnh đang được tải xuống là hình ảnh (75x75) từ Flickr. Tuy nhiên, đặt bất kỳ url hình ảnh nào bạn muốn được xử lý và ứng dụng sẽ giảm kích thước nếu nó vượt quá mức tối đa. Trong ứng dụng này, các url chỉ đơn giản là trong một Stringmảng.

Các LruCache có một cách tốt để đối phó với bitmap. Tuy nhiên, trong ứng dụng này tôi đặt một thể hiện của một LruCache bên trong một lớp bộ nhớ cache khác mà tôi đã tạo để có được ứng dụng khả thi hơn.

Công cụ quan trọng của Cache.java ( loadBitmap() phương pháp là quan trọng nhất):

public Cache(int size, int maxWidth, int maxHeight) {
    // Into the constructor you add the maximum pixels
    // that you want to allow in order to not scale images.
    mMaxWidth = maxWidth;
    mMaxHeight = maxHeight;

    mBitmapCache = new LruCache<String, Bitmap>(size) {
        protected int sizeOf(String key, Bitmap b) {
            // Assuming that one pixel contains four bytes.
            return b.getHeight() * b.getWidth() * 4;
        }
    };

    mCurrentTasks = new ArrayList<String>();    
}

/**
 * Gets a bitmap from cache. 
 * If it is not in cache, this method will:
 * 
 * 1: check if the bitmap url is currently being processed in the
 * BitmapLoaderTask and cancel if it is already in a task (a control to see
 * if it's inside the currentTasks list).
 * 
 * 2: check if an internet connection is available and continue if so.
 * 
 * 3: download the bitmap, scale the bitmap if necessary and put it into
 * the memory cache.
 * 
 * 4: Remove the bitmap url from the currentTasks list.
 * 
 * 5: Notify the ListAdapter.
 * 
 * @param mainActivity - Reference to activity object, in order to
 * call notifyDataSetChanged() on the ListAdapter.
 * @param imageKey - The bitmap url (will be the key).
 * @param imageView - The ImageView that should get an
 * available bitmap or a placeholder image.
 * @param isScrolling - If set to true, we skip executing more tasks since
 * the user probably has flinged away the view.
 */
public void loadBitmap(MainActivity mainActivity, 
        String imageKey, ImageView imageView,
        boolean isScrolling) {
    final Bitmap bitmap = getBitmapFromCache(imageKey); 

    if (bitmap != null) {
        imageView.setImageBitmap(bitmap);
    } else {
        imageView.setImageResource(R.drawable.ic_launcher);
        if (!isScrolling && !mCurrentTasks.contains(imageKey) && 
                mainActivity.internetIsAvailable()) {
            BitmapLoaderTask task = new BitmapLoaderTask(imageKey,
                    mainActivity.getAdapter());
            task.execute();
        }
    } 
}

Bạn không cần chỉnh sửa bất kỳ thứ gì trong tệp Cache.java trừ khi bạn muốn triển khai bộ nhớ đệm trên đĩa.

Công cụ quan trọng của MainActivity.java:

public void onScrollStateChanged(AbsListView view, int scrollState) {
    if (view.getId() == android.R.id.list) {
        // Set scrolling to true only if the user has flinged the       
        // ListView away, hence we skip downloading a series
        // of unnecessary bitmaps that the user probably
        // just want to skip anyways. If we scroll slowly it
        // will still download bitmaps - that means
        // that the application won't wait for the user
        // to lift its finger off the screen in order to
        // download.
        if (scrollState == SCROLL_STATE_FLING) {
            mIsScrolling = true;
        } else {
            mIsScrolling = false;
            mListAdapter.notifyDataSetChanged();
        }
    } 
}

// Inside ListAdapter...
@Override
public View getView(final int position, View convertView, ViewGroup parent) {           
    View row = convertView;
    final ViewHolder holder;

    if (row == null) {
        LayoutInflater inflater = getLayoutInflater();
        row = inflater.inflate(R.layout.main_listview_row, parent, false);  
        holder = new ViewHolder(row);
        row.setTag(holder);
    } else {
        holder = (ViewHolder) row.getTag();
    }   

    final Row rowObject = getItem(position);

    // Look at the loadBitmap() method description...
    holder.mTextView.setText(rowObject.mText);      
    mCache.loadBitmap(MainActivity.this,
            rowObject.mBitmapUrl, holder.mImageView,
            mIsScrolling);  

    return row;
}

getView() được gọi rất thường xuyên. Nó thường không phải là một ý tưởng tốt để tải hình ảnh ở đó nếu chúng tôi đã không thực hiện một kiểm tra đảm bảo với chúng tôi rằng chúng tôi sẽ không bắt đầu một số lượng vô hạn của chủ đề cho mỗi hàng. Cache.java kiểm tra xem liệu rowObject.mBitmapUrl đã có trong một nhiệm vụ và nếu nó là, nó sẽ không bắt đầu một công việc khác. Do đó, chúng tôi rất có thể không vượt quá giới hạn hàng đợi công việc từ AsyncTask hồ bơi.

Tải về:

Bạn có thể tải xuống mã nguồn từ https://www.dropbox.com/s/pvr9zyl811tfeem/ListViewImageCache.zip.


Những từ cuối:

Tôi đã thử nghiệm này cho một vài tuần nay, tôi đã không nhận được một ngoại lệ OOM duy nhất được nêu ra. Tôi đã thử nghiệm điều này trên trình mô phỏng, trên Nexus One và trên Nexus S. Tôi đã thử nghiệm các url hình ảnh chứa các hình ảnh có chất lượng HD. Nút cổ chai duy nhất là phải mất nhiều thời gian hơn để tải xuống.

Chỉ có một kịch bản mà tôi có thể tưởng tượng rằng OOM sẽ xuất hiện, và đó là nếu chúng ta tải xuống nhiều hình ảnh thực sự lớn và trước khi chúng được thu nhỏ và đưa vào bộ nhớ cache, đồng thời sẽ chiếm nhiều bộ nhớ hơn và gây ra OOM. Nhưng đó thậm chí không phải là một tình huống lý tưởng và nó có khả năng sẽ không thể giải quyết một cách khả thi hơn.

Báo cáo lỗi trong các ý kiến! :-)


59





Tôi đã làm như sau để chụp ảnh và thay đổi kích cỡ nó trên bay. Hi vọng điêu nay co ich

Bitmap bm;
bm = Bitmap.createScaledBitmap(BitmapFactory.decodeFile(filepath), 100, 100, true);
mPicture = new ImageView(context);
mPicture.setImageBitmap(bm);    

37



Cách tiếp cận này quy mô bitmap. Nhưng nó không giải quyết được vấn đề OutOfMemory vì bitmap đầy đủ vẫn đang được giải mã. - Fedor
Tôi sẽ thấy nếu tôi có thể nhìn vào mã cũ của tôi, nhưng tôi nghĩ rằng nó đã giải quyết vấn đề bộ nhớ của tôi. Sẽ kiểm tra lại mã cũ của tôi. - Chrispix
Trong ví dụ này ít nhất, có vẻ như bạn không giữ tham chiếu đến bitmap đầy đủ, do đó tiết kiệm bộ nhớ. - NoBugs
Đối với tôi nó đã giải quyết vấn đề bộ nhớ, nhưng giảm chất lượng của hình ảnh. - Pamela Sillah