Loại bỏ vấn đề bộ nhớ trong khi tải hình ảnh vào đối tượng Bitmap


1289

Tôi có một 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 bấm 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ì vấn đề với bố cục máy ảnh. Các hoạt động được đưa ra cho kết quả là một bản đồ. Nếu tôi nhấp vào nút của mình để khởi chạy chế độ xem trước hình ảnh (tải hình ảnh từ thẻ SD), ứng dụng sẽ quay trở lại từ hoạt động trở lại listviewhoạt động để xử lý kết quả để khởi chạy lại hoạt động mới của tôi, không gì khác hơn là tiện ích hình ảnh.

Việc 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 làm thế nào tôi có thể đặt một hình ảnh đã thay đổi kích thước (Kích thước bit nhỏ hơn không phải pixel như srcnút hình ảnh khi đang di chuyển. Vì vậy, tôi chỉ thay đổi kích thước hình ảnh từ camera điện thoại.

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

  • Có cách nào để tôi có thể dễ dàng xây dựng bộ điều hợp danh sách theo từng hàng, nơi tôi có thể thay đổi kích thước một cách nhanh chóng ( bit khôn ngoan ) không?

Điều này sẽ tốt hơn vì tôi cũng cần thực hiện một số thay đổi đối với các thuộc tính của các widget / 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 vì vấn đề lấy nét. ( Tôi có thể sử dụng bóng lăn. )

  • Tôi biết tôi có thể thay đổi kích thước băng tần 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ẽ rất tuyệt.

Ngay sau khi tôi tắt hình ảnh trên danh sách, nó hoạt động tốt trở lại.

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.imagefilenamea 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 gặp 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

8
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

1
Tôi cũng phải đối mặt với vấn đề tương tự vài tuần trước và tôi đã giải quyết nó bằng cách thu nhỏ hình ảnh lên đến điểm tối ưu. Tôi đã viết cách tiếp cận hoàn toàn trong blog của tôi codingjunkiesforum.wordpress.com/2014/06/12/... và tải lên dự án mẫu hoàn chỉnh với oom đang nghiêng vs tạihttps đang oom Proof: //github.com/shailendra123/BitmapHandlingDemo
Shailendra Singh Rajawat

5
Câu trả lời được chấp nhận cho câu hỏi này đang được thảo luận trên meta
rene

4
Đ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

2
Điều này xảy ra vì kiến ​​trúc Android xấu. Nó nên thay đổi kích thước hình ảnh giống như ios và UWP thực hiện điều này. Tôi không phải tự làm việc này. Các nhà phát triển Android đã quen với địa ngục đó và nghĩ rằng nó hoạt động theo cách nó nên.
Truy cập bị từ chối

Câu trả lời:


651

Lớp đào tạo Android , " 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 budgetkhi tải Bitmap.


Đọc Kích thước và Loại Bitmap

Các BitmapFactorylớp học cung cấp một số phương pháp giải mã ( decodeByteArray(), decodeFile(), decodeResource(), vv) để tạo một Bitmaptừ nhiều nguồn khác nhau. Chọn phương pháp giải mã phù 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 phân bổ bộ nhớ cho bitmap được xây dựng và do đó có thể dễ dàng dẫn đến một OutOfMemoryngoạ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.Optionslớp. Thiết lập các inJustDecodeBoundstài sản để truekhi giải mã tránh phân bổ bộ nhớ, trở về nullcho đối tượng bitmap nhưng thiết lập outWidth, outHeightoutMimeType. Kỹ thuật này cho phép bạn đọc kích thước và loại dữ liệu hình ảnh trước khi xây dựng (và phân bổ 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 các java.lang.OutOfMemorytrường hợp ngoại lệ, hãy 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 vào 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 với bộ nhớ khả dụng.


Tải phiên bản thu nhỏ vào Bộ nhớ

Bây giờ 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 đủ sẽ được tải vào bộ nhớ hay nếu một phiên bản mẫu phụ nên được tải thay thế. Dưới đây là một số yếu tố cần xem xét:

  • Sử dụng bộ nhớ ước tính của việc tải hình ảnh đầy đủ trong bộ nhớ.
  • Dung lượng bộ nhớ bạn sẵn sàng cam kết tải hình ảnh này với 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 ImageView hoặc UI đích 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 đáng để tải hình ảnh 1024x768 pixel vào bộ nhớ nếu cuối cùng nó sẽ được hiển thị trong hình thu nhỏ 128x96 pixel trong một ImageView.

Để nói các bộ giải mã để subsample hình ảnh, tải một phiên bản nhỏ hơn vào bộ nhớ, bộ inSampleSizeđể trueở của bạn BitmapFactory.Optionsđối tượng. Ví dụ: một hình ảnh có độ phân giải 2048x1536 được giải mã với inSampleSize4 là tạo ra một bitmap khoảng 512x384. Tải phần này vào bộ nhớ sử dụng 0,75 MB thay vì 12 MB cho hình ảnh đầy đủ (giả sử cấu hình bitmap là ARGB_8888). Đây là một phương pháp để tính toán giá trị kích thước mẫu có sức mạnh bằng 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;
}

Lưu ý : Công suất của hai giá trị được tính 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 inSampleSizetài liệu.

Để sử dụng phương thức này, trước tiên hãy giải mã với inJustDecodeBoundsset to true, chuyển các tùy chọn qua và sau đó giải mã lại bằng inSampleSizegiá trị mới 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 một bitmap có kích thước lớn tùy ý vào một ImageViewmàn hình 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*phương pháp thích hợp khi cần thiết.


21
Câu trả lời này đang được thảo luận trên meta
rene

9
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 như 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

7
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à Community Wiki, 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 điều không cần sự can thiệp của người điều hành.
Martijn Pieters

liên kết hiện tại đến nội dung và hỗ trợ của Kotlin có thể được tìm thấy tại: developer.android.com/topic/performance/graphics/load-bitmap
Panos Gr

891

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

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

inSampleSizeTùy chọn này làm 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 giải mã chính nội dung. Sau đó, nó tìm thấy inSampleSizegiá trị tốt nhất , nó phải là lũy thừa của 2 và cuối cùng 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;
}

31
Lưu ý rằng 10 có thể không phải là giá trị tốt nhất cho inSampleSize, tài liệu cho thấy sử dụng quyền hạn của 2.
Mirko N.

70
Tôi đang đối mặt với cùng một vấn đề như Chrispix, nhưng tôi không nghĩ giải pháp ở đây thực sự giải quyết được vấn đề, mà là vượt qua nó. Thay đổi kích thước mẫu sẽ giảm lượng bộ nhớ được sử dụng (với chi phí chất lượng hình ảnh, có thể ổn cho xem trước hình ảnh), nhưng sẽ không ngăn được ngoại lệ nếu một luồng hình ảnh đủ lớn được giải mã, nếu nhiều luồng hình ảnh được 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ó) tôi sẽ đăng câu trả lời ở đây.
Flynn81

4
Bạn chỉ cần một kích thước phù hợp để phù hợp với màn hình theo mật độ pixel, để phóng to và như vậy bạn có thể lấy một mẫu hình ảnh ở mật độ cao hơn.
tàng hình

4
REQUIRED_SIZE là kích thước mới mà bạn muốn chia tỷ lệ.
Fedor

8
Giải pháp này đã giúp tôi nhưng chất lượng hình ảnh rất tệ. Tôi đang sử dụng một viewfilpper để hiển thị hình ảnh bất kỳ đề xuất?
1106888

373

Tôi đã thực hiện một cải tiến nhỏ cho mã của Fedor. Về cơ bản nó cũng làm như vậy, nhưng không có vòng lặp xấu (theo ý kiến ​​của tôi) và nó luôn dẫn đến sức mạnh của hai. Kudos nói với Fedor vì đã đưa ra giải pháp ban đầu, tôi đã bị mắc kẹt cho đến khi tôi tìm thấy giải pháp của mình, và sau đó tôi đã có thể thực hiện giải pháp 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;
}

40
Có bạn đúng trong khi không đẹp lắm. Tôi chỉ cố gắng để làm cho nó rõ ràng với mọi người. Cảm ơn mã của bạn.
Fedor

10
@Thomas Vervest - Có một vấn đề lớn với mã đó. ^ không tăng 2 lên một sức mạnh, kết quả là 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

6
Ồ, đó là một trong những rất tốt! Xấu của tôi, tôi sẽ sửa nó ngay lập tức, cảm ơn vì đã trả lời!
Thomas Vervest

8
Bạn đang tạo hai FileInputStreams mới, một cho mỗi cuộc gọi đến BitmapFactory.decodeStream(). Bạn không phải lưu một tài liệu tham khảo cho mỗi người trong số họ để có thể đóng chúng trong một finallykhối?
matsev

1
@Babibu Tài liệu không nói rằng luồng được đóng cho bạn, do đó tôi cho rằng nó vẫn nên được đóng. Một cuộc thảo luận thú vị và có liên quan có thể được tìm thấy ở đây . Lưu ý nhận xét của Adrian Smith, liên quan trực tiếp đến cuộc tranh luận của chúng tôi.
Thomas Vervest

233

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

1) Mỗi ​​khi bạn làm BitmapFactory.decodeXYZ(), hãy đảm bảo chuyển qua một BitmapFactory.Optionsvới inPurgeablethiết lập true(và tốt nhất inInputShareablelà 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 chưa bao giờ có điều đó không tăng lỗi bộ nhớ sau vài lần vượt qua. Không có số lượng recycle(), System.gc(), bất cứ điều gì giúp. Nó luôn đưa ra 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 hình vẽ của bạn (hoặc một Bitmap khác mà bạn đã giải mã bằng cách sử dụng bước 1 ở trên), giải quyết nó thành bất cứ điều gì bạn muốn, sau đó thao tác với Bitmap kết quả (chẳng hạn như chuyển nó vào Canvas cho 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ì bất kỳ lý do gì bạn PHẢI sử dụng phương pháp tạo lực lượng vũ phu, thì ít nhất hãy vượt qua Config.ARGB_4444.

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


22
BitmapFactory.Options options = new BitmapFactory.Options(); options.inPurgeable = true;Bitmap.createScaledBitmap(srcBitmap, width, height, false);giải quyết vấn đề của tôi, tôi đã có ngoại lệ bộ nhớ trên Android 4.0.0. Cảm ơn bạn đời!
Jan-Terje SøDRen

5
Trong cuộc gọi Bitmap.createScaledBitmap () có lẽ bạn nên sử dụng true làm tham số cờ. Nếu không, chất lượng của hình ảnh sẽ không được mượt mà khi tăng tỷ lệ. Kiểm tra chủ đề này stackoverflow.com/questions/2895065/ Cách
rOrlig

11
Đó 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 để đưa Google vào nhiệm vụ cho lỗi dink đáng kinh ngạc này. Ý tôi là ... nếu đó 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 trọng nói rằng "ĐÂY LÀ CÁCH BẠN QUÁ TRÌNH ẢNH", vì tôi đã phải vật lộn với điều này trong 2 năm và mới tìm thấy bài đăng này. Tuyệt vời tìm thấy.
Yevgeny Simkin

Thu nhỏ hình ảnh của bạn xuống chắc chắn có ích, nhưng đây là một bước quan trọng và điều cuối cùng đã giải quyết vấn đề này cho tôi. Vấn đề với việc chỉ thu nhỏ 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 rất lớn thì bạn vẫn có thể gặp phải vấn đề tương tự. +1 cho bạn Ephraim.
Dave

10
Kể từ Lollipop, BitmapFactory.Options.inPurgeablenhà phát triểnBitmapFactory.Options.inInputShareable không dùng
nữa.android.com/reference/android/graphics/ Khăn

93

Đó là một lỗi đã biết , không phải vì các tệp lớn. Vì 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 khác cho nó, bằng cách bỏ qua hệ thống bộ đệm mặc định của Android.

Giải pháp : Di chuyển hình ảnh vào thư mục "tài sản" và sử dụng chức năng 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);
}

79

Tôi đã có vấn đề tương tự 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 riêng khác với decodeStream / decodeFile.

Dù sao, những gì làm việc là 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 gì là rất quan trọng là các cuộc gọi đến. BitmapFactory.decodeFileDescriptor thay vì decodeStream hoặc 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 hàm gốc được sử dụng trong decodeStream / decodeFile. Tôi đã xác nhận rằng một phương thức riêng khác được gọi khi sử dụng decodeFileDescriptor. Ngoài ra, điều tôi đã đọc là "Hình ảnh (Bitmap) không được phân bổ theo cách Java tiêu chuẩn mà thông qua các cuộc gọi riêng; việc phân bổ được thực hiện bên ngoài đống ảo, nhưng được tính theo nó! "


1
cùng một kết quả ngoài memeory, thực sự không quan trọng bạn đang sử dụng phương thức nào tùy thuộc vào số byte bạn đang giữ để đọc dữ liệu thoát ra khỏi bộ nhớ.
PiyushMishra

72

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

Tôi đã tạo một ứng dụng để cố tình gây ra OutOfMemoryErrorvà theo dõi việ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ó kết luận như 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 riêng, nhưng nó sẽ tự động thu gom rác, gọi tái chế () là không cần thiết.

  2. Nếu {VM heap size} + {bộ nhớ heap gốc được phân bổ}> = {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.

    THÔNG BÁO: KÍCH THƯỚC VM HEAP được tính thay vì NHỚ NHỚ VM.

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

  4. Vì vậy, bạn phải giữ bộ nhớ VM cao nhất có thể để giữ cho Kích thước Heap của VM không tăng quá lớn để tiết kiệm bộ nhớ khả dụng cho Bitmap.

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

  6. Kích thước Heap bản địa cũng sẽ không bao giờ co lại, nhưng nó không được tính cho OOM, vì vậy không cần phải lo lắng về điều đó.

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

  1. Bitmap được lưu trữ trong 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: {Kích thước heap VM}> = {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ớ khả dụng hơ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 hơn.

Dưới đây là một số quan sát của tôi về Bộ sưu tập rác và Rò rỉ bộ nhớ.

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

Điều này là do AsyncTask là một thể hiện của một lớp bên trong ẩn danh, nó chứa một tham chiếu của Activity.

Gọi AsyncTask.celon (true) sẽ không dừng thực thi nếu tác vụ bị chặn trong thao tác IO trong luồng nền.

Các cuộc gọi lại cũng là các lớp bên trong ẩn danh, 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 bị trì hoãn, ví dụ như Timer và bạn không gọi hủy () và purge () trong onPause (), bộ nhớ sẽ bị rò rỉ.


AsyncTask không nhất thiết phải là "một thể hiện của lớp bên trong ẩn danh" và điều tương tự cũng xảy ra với 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ỳ tài liệu tham khảo nào cho Hoạt động (trừ khi bạn cung cấp cho họ một khóa học)
Simon Forsberg

65

Tôi đã 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 thực sự hay về vấn đề này, nhưng một số có xu hướng thất bại trong việc thực hiện nó theo cách phù hợp.

Vì điều này tôi đã viết một ứng dụng ví dụ minh họa bộ nhớ đệm trong môi trường Android. Việc thực hiện này vẫn chưa nhận được OOM.

Nhìn vào phần cuối của câu trả lời này để biết liên kết đến mã nguồn.

Yêu cầu:

  • API Android 2.1 trở lên (Đơn giản là tôi không thể quản lý để có bộ nhớ khả dụng 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

Ảnh chụp màn hình

Đặc trưng:

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

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

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

Mã mẫu:

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ẽ thu nhỏ nó xuống nếu 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.

LruCachemộ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 LruCachelớp bộ đệm khác mà tôi đã tạo để ứng dụng khả thi hơn.

Nội dung quan trọng của Cache.java ( loadBitmap()phương thức 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 phải chỉnh sửa bất cứ điều gì trong tệp Cache.java trừ khi bạn muốn triển khai bộ đệm ẩn.

Nội dung 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. Thông thường không nên tải xuống hình ảnh ở đó nếu chúng tôi chưa thực hiện kiểm tra để đảm bảo rằng chúng tôi sẽ không bắt đầu số lượng luồng vô hạn trên mỗi hàng. Cache.java kiểm tra xem cái rowObject.mBitmapUrlđã có trong một tác vụ chưa và nếu có, nó sẽ không bắt đầu cái khác. Do đó, rất có thể chúng tôi không vượt quá giới hạn hàng đợi công việc từAsyncTask .

Tải xuống:

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 điều này trong vài tuần nay, tôi chưa nhận được một ngoại lệ OOM nào. Tôi đã thử nghiệm điều này trên trình giả lập, trên Nexus One và trên Nexus S. Tôi đã kiểm tra các url hình ảnh có chứa hình ảnh có chất lượng HD. Nút thắt 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 khả dĩ 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ộ đệm, sẽ đồng thời chiếm nhiều bộ nhớ hơn và gây ra OOM. Nhưng dù sao đó cũng không phải là một tình huống lý tưởng và rất có thể sẽ không thể giải quyết theo cách khả thi hơn.

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


43

Tôi đã làm như sau để chụp ảnh và thay đổi kích thước nó một cách nhanh chóng. 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);    

26
Cách tiếp cận này chia tỷ lệ bitmap. Nhưng nó không giải quyết được vấn đề OutOfMemory vì dù sao toàn bộ bitmap đang được giải mã.
Fedor

5
Tôi sẽ xem liệu tôi có thể xem mã cũ của mình không, nhưng tôi nghĩ nó đã giải quyết được vấn đề bộ nhớ của tôi. Sẽ kiểm tra lại mã cũ của tôi.
Chrispix

2
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 làm giảm chất lượng của hình ảnh.
Pamela Sillah

35

Có vẻ như đây là một vấn đề rất dài, với rất nhiều lời giải thích khác nhau. Tôi đã lấy lời khuyên của hai câu trả lời được trình bày phổ biến nhất ở đây, nhưng không ai trong số chúng giải quyết được các vấn đề của tôi về VM khi cho rằng nó không đủ khả năng để thực hiện phần giải mã của quá trình. Sau khi đào bới, tôi biết rằng vấn đề thực sự ở đây là quá trình giải mã lấy đi từ đống NATIVE .

Xem ở đây: BitmapFactory OOM thúc đẩy tôi

Điều đó dẫn tôi đến một chủ đề thảo luận khác, nơi tôi tìm thấy một vài giải pháp cho vấn đề này. Một là gọi System.gc();thủ công sau khi hình ảnh của bạn được hiển thị. Nhưng điều đó thực sự làm cho ứng dụng của bạn sử dụng bộ nhớ THÊM, trong nỗ lực giảm heap gốc. Giải pháp tốt hơn kể từ khi phát hành 2.0 (Donut) là sử dụng tùy chọn BitmapFactory "inPurgizable". Vì vậy, tôi chỉ cần thêm vào o2.inPurgeable=true;ngay sauo2.inSampleSize=scale; .

Thêm về chủ đề đó ở đây: Có phải giới hạn của bộ nhớ heap chỉ là 6M?

Bây giờ, khi đã nói tất cả những điều này, tôi cũng là một người hoàn hảo với Java và Android. Vì vậy, nếu bạn nghĩ rằng đây là một cách khủng khiếp để giải quyết vấn đề này, có lẽ bạn đã đúng. ;-) Nhưng điều này đã làm việc kỳ diệu đối với tôi và tôi thấy rằng không thể chạy VM ra khỏi bộ đệm heap ngay bây giờ. Hạn chế duy nhất tôi có thể tìm thấy là bạn đang bỏ rác hình ảnh được lưu trong bộ nhớ cache của bạn. Điều đó có nghĩa là nếu bạn quay trở lại hình ảnh đó, bạn sẽ vẽ lại nó mỗi lần. Trong trường hợp ứng dụng của tôi hoạt động như thế nào, đó không thực sự là vấn đề. Số dặm của bạn có thể thay đổi.


OOM cố định không thể sửa chữa cho tôi.
Artem Russakovskii

35

không may nếu không có ở trên hoạt động, sau đó Add này để bạn Manifest file. Thẻ ứng dụng bên trong

 <application
         android:largeHeap="true"

1
Bạn có thể giải thích điều này thực sự làm gì? Đơn giản chỉ cần nói với mọi người để thêm điều này không giúp đỡ.
Rabbi tàng hình

1
Đây là một giải pháp rất xấu. Về cơ bản, bạn không cố gắng khắc phục vấn đề. Thay vào đó, yêu cầu hệ thống Android phân bổ nhiều không gian heap cho ứng dụng của bạn. Điều này sẽ có tác động rất xấu đến ứng dụng của bạn như ứng dụng của bạn tiêu tốn nhiều pin vì GC phải chạy qua không gian lớn để dọn bộ nhớ và hiệu suất ứng dụng của bạn sẽ chậm hơn.
Prakash

2
Vậy thì tại sao Android lại cho phép chúng ta thêm android này: LargeHeap = "true" trong tệp kê khai của chúng tôi? Bây giờ bạn đang thách thức Android.
Himanshu Mori

32

Sử dụng bitmap.recycle();Điều này giúp mà không có bất kỳ vấn đề chất lượng hình ảnh.


9
Theo API, việc gọi tái chế () là không cần thiết.
Artem Russakovskii

28

Tôi có một giải pháp hiệu quả hơn nhiều mà không cần phải mở rộng quy mô nào. Đơn giản chỉ cần giải mã bitmap của bạn một lần và sau đó lưu nó vào bản đồ theo tên của nó. Sau đó, chỉ cần lấy bitmap theo tên và đặt nó trong ImageView. Không có gì hơn cần phải được thực hiện.

Điều này sẽ hoạt động vì dữ liệu nhị phân thực tế của bitmap được giải mã không được lưu trữ trong heap VM dalvik. Nó được lưu trữ bên ngoài. Vì vậy, mỗi khi bạn giải mã một bitmap, nó sẽ cấp phát bộ nhớ bên ngoài heap VM không bao giờ được thu hồi bởi GC

Để giúp bạn đánh giá cao hơn điều này, hãy tưởng tượng bạn đã giữ hình ảnh của bạn trong thư mục drawable. Bạn chỉ cần lấy hình ảnh bằng cách thực hiện getResource (). GetDrwable (R.drawable.). Điều này sẽ KHÔNG giải mã hình ảnh của bạn mọi lúc mà sử dụng lại một thể hiện đã được giải mã mỗi khi bạn gọi nó. Vì vậy, về bản chất nó được lưu trữ.

Bây giờ vì hình ảnh của bạn nằm trong một tệp ở đâu đó (hoặc thậm chí có thể đến từ máy chủ bên ngoài), bạn có trách nhiệm lưu trữ bản sao bitmap đã giải mã để được sử dụng lại bất kỳ nơi nào cần thiết.

Hi vọng điêu nay co ich.


4
"và sau đó lưu trữ nó trong bản đồ với tên của nó." Làm thế nào chính xác để bạn lưu trữ hình ảnh của bạn?
Vincent

3
Bạn đã thực sự thử điều này? Mặc dù dữ liệu pixel không thực sự được lưu trữ trong heap Dalvik, kích thước của nó trong bộ nhớ riêng được báo cáo cho VM và được tính vào bộ nhớ khả dụng của nó.
ErikR

3
@Vincent Tôi nghĩ không khó để lưu trữ chúng trong Bản đồ. Tôi sẽ đề xuất một cái gì đó như bản đồ HashMap <KEY, Bitmap>, trong đó Khóa có thể là Chuỗi nguồn hoặc bất cứ thứ gì có ý nghĩa đối với bạn. Giả sử bạn lấy một đường dẫn là KEY, bạn lưu nó dưới dạng map.put (Đường dẫn, Bitmap) và nhận nó thông qua map.get (Đường dẫn)
Rafael T

3
bạn có thể muốn sử dụng HashMap <String, SoftReference <Bitmap >> nếu bạn đang triển khai Cache hình ảnh nếu không bạn có thể hết bộ nhớ - dù sao tôi cũng không nghĩ rằng "nó phân bổ bộ nhớ bên ngoài heap VM không bao giờ được thu hồi bởi GC "là sự thật, bộ nhớ được lấy lại vì tôi hiểu có thể là một sự chậm trễ, đó là những gì bitmap.recycle () dành cho, như một gợi ý để lấy lại mem sớm ...
Dori

28

Tôi đã giải quyết vấn đề tương tự theo cách sau.

Bitmap b = null;
Drawable d;
ImageView i = new ImageView(mContext);
try {
    b = Bitmap.createBitmap(320,424,Bitmap.Config.RGB_565);
    b.eraseColor(0xFFFFFFFF);
    Rect r = new Rect(0, 0,320 , 424);
    Canvas c = new Canvas(b);
    Paint p = new Paint();
    p.setColor(0xFFC0C0C0);
    c.drawRect(r, p);
    d = mContext.getResources().getDrawable(mImageIds[position]);
    d.setBounds(r);
    d.draw(c);

    /*   
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inTempStorage = new byte[128*1024];
        b = BitmapFactory.decodeStream(mContext.getResources().openRawResource(mImageIds[position]), null, o2);
        o2.inSampleSize=16;
        o2.inPurgeable = true;
    */
} catch (Exception e) {

}
i.setImageBitmap(b);

Không sao đâu nhưng tôi đang sử dụng nhiều bitmap để vẽ vòng tròn trong OnCreate và hoạt động gọi 4-5 lần để làm thế nào để xóa bitmap và cách xóa bitmap và tạo lại lần thứ hai khi hoạt động 0nCreate ..
ckpatel

27

Có hai vấn đề ở đây....

  • Bộ nhớ bitmap không nằm trong heap VM mà là trong heap riêng - hãy xem BitmapFactory OOM điều khiển tôi
  • Bộ sưu tập rác cho vùng heap nguyên gốc lười hơn so với đống VM - vì vậy bạn cần khá tích cực khi thực hiện bitmap.recycle và bitmap = null mỗi khi bạn đi qua một hoạt động của OnPause hoặc onDestroy

Nó nằm trong đống VM kể từ Android 2.3+
FindOut_Quran

27

Điều này làm việc cho tôi!

public Bitmap readAssetsBitmap(String filename) throws IOException {
    try {
        BitmapFactory.Options options = new BitmapFactory.Options(); 
        options.inPurgeable = true;
        Bitmap bitmap = BitmapFactory.decodeStream(assets.open(filename), null, options);
        if(bitmap == null) {
            throw new IOException("File cannot be opened: It's value is null");
        } else {
            return bitmap;
        }
    } catch (IOException e) {
        throw new IOException("File cannot be opened: " + e.getMessage());
    }
}

20

Không có câu trả lời nào ở trên có tác dụng với tôi, nhưng tôi đã đưa ra một cách giải quyết xấu xí khủng khiếp để giải quyết vấn đề. Tôi đã thêm một hình ảnh pixel 1x1 rất nhỏ vào dự án của mình dưới dạng tài nguyên và tải nó vào ImageView trước khi gọi vào bộ sưu tập rác. Tôi nghĩ rằng có thể ImageView không phát hành Bitmap, do đó, GC không bao giờ chọn nó. Nó xấu, nhưng dường như nó đang hoạt động.

if (bitmap != null)
{
  bitmap.recycle();
  bitmap = null;
}
if (imageView != null)
{
  imageView.setImageResource(R.drawable.tiny); // This is my 1x1 png.
}
System.gc();

imageView.setImageBitmap(...); // Do whatever you need to do to load the image you want.

có vẻ như imageView thực sự không tái chế bitmap. Giúp tôi với, cảm ơn
Dmitry Zaytsev

@Mike bạn có thể thêm mã hoàn chỉnh của trình tải hình ảnh hay không, nếu không bạn có thể cho tôi liên kết tải hình ảnh bitmap. Nếu tôi sử dụng tái chế trên bitmap, tất cả listview của tôi sẽ được hiển thị nhưng tất cả các mục được hiển thị trống
TNR

@Mike bạn có thể cho biết nếu tôi làm imageView = null trước khi gọi vào bộ sưu tập rác thì có tốt hay không?
Youddh

@TNR Tôi nghĩ điều bạn thiếu ở đây là bitmaptrong đoạn mã trên là hình ảnh đã được hiển thị trước đó, bạn cần tái chế nó, xóa mọi tham chiếu đến nó, làm cho imageViewnó quên đi bằng cách đặt một thay thế nhỏ , gc(); và sau tất cả điều này: tải hình ảnh MỚI của bạn vào bitmapvà hiển thị nó, ...trong đoạn mã trên.
TWiStErRob

Cái này sai. Bạn phải luôn luôn xóa nội dung imageView TRƯỚC KHI bitmap tái chế (thay vì trong khi nó thực sự được hiển thị và đang được sử dụng).
FindOut_Quran

20

Câu trả lời tuyệt vời ở đây, nhưng tôi muốn một lớp hoàn toàn có thể sử dụng để giải quyết vấn đề này .. vì vậy tôi đã làm một.

Đây là lớp BitmapHelper của tôi là bằng chứng OutOfMemoryError :-)

import java.io.File;
import java.io.FileInputStream;

import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;

public class BitmapHelper
{

    //decodes image and scales it to reduce memory consumption
    public static Bitmap decodeFile(File bitmapFile, int requiredWidth, int requiredHeight, boolean quickAndDirty)
    {
        try
        {
            //Decode image size
            BitmapFactory.Options bitmapSizeOptions = new BitmapFactory.Options();
            bitmapSizeOptions.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(new FileInputStream(bitmapFile), null, bitmapSizeOptions);

            // load image using inSampleSize adapted to required image size
            BitmapFactory.Options bitmapDecodeOptions = new BitmapFactory.Options();
            bitmapDecodeOptions.inTempStorage = new byte[16 * 1024];
            bitmapDecodeOptions.inSampleSize = computeInSampleSize(bitmapSizeOptions, requiredWidth, requiredHeight, false);
            bitmapDecodeOptions.inPurgeable = true;
            bitmapDecodeOptions.inDither = !quickAndDirty;
            bitmapDecodeOptions.inPreferredConfig = quickAndDirty ? Bitmap.Config.RGB_565 : Bitmap.Config.ARGB_8888;

            Bitmap decodedBitmap = BitmapFactory.decodeStream(new FileInputStream(bitmapFile), null, bitmapDecodeOptions);

            // scale bitmap to mathc required size (and keep aspect ratio)

            float srcWidth = (float) bitmapDecodeOptions.outWidth;
            float srcHeight = (float) bitmapDecodeOptions.outHeight;

            float dstWidth = (float) requiredWidth;
            float dstHeight = (float) requiredHeight;

            float srcAspectRatio = srcWidth / srcHeight;
            float dstAspectRatio = dstWidth / dstHeight;

            // recycleDecodedBitmap is used to know if we must recycle intermediary 'decodedBitmap'
            // (DO NOT recycle it right away: wait for end of bitmap manipulation process to avoid
            // java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@416ee7d8
            // I do not excatly understand why, but this way it's OK

            boolean recycleDecodedBitmap = false;

            Bitmap scaledBitmap = decodedBitmap;
            if (srcAspectRatio < dstAspectRatio)
            {
                scaledBitmap = getScaledBitmap(decodedBitmap, (int) dstWidth, (int) (srcHeight * (dstWidth / srcWidth)));
                // will recycle recycleDecodedBitmap
                recycleDecodedBitmap = true;
            }
            else if (srcAspectRatio > dstAspectRatio)
            {
                scaledBitmap = getScaledBitmap(decodedBitmap, (int) (srcWidth * (dstHeight / srcHeight)), (int) dstHeight);
                recycleDecodedBitmap = true;
            }

            // crop image to match required image size

            int scaledBitmapWidth = scaledBitmap.getWidth();
            int scaledBitmapHeight = scaledBitmap.getHeight();

            Bitmap croppedBitmap = scaledBitmap;

            if (scaledBitmapWidth > requiredWidth)
            {
                int xOffset = (scaledBitmapWidth - requiredWidth) / 2;
                croppedBitmap = Bitmap.createBitmap(scaledBitmap, xOffset, 0, requiredWidth, requiredHeight);
                scaledBitmap.recycle();
            }
            else if (scaledBitmapHeight > requiredHeight)
            {
                int yOffset = (scaledBitmapHeight - requiredHeight) / 2;
                croppedBitmap = Bitmap.createBitmap(scaledBitmap, 0, yOffset, requiredWidth, requiredHeight);
                scaledBitmap.recycle();
            }

            if (recycleDecodedBitmap)
            {
                decodedBitmap.recycle();
            }
            decodedBitmap = null;

            scaledBitmap = null;
            return croppedBitmap;
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
        }
        return null;
    }

    /**
     * compute powerOf2 or exact scale to be used as {@link BitmapFactory.Options#inSampleSize} value (for subSampling)
     * 
     * @param requiredWidth
     * @param requiredHeight
     * @param powerOf2
     *            weither we want a power of 2 sclae or not
     * @return
     */
    public static int computeInSampleSize(BitmapFactory.Options options, int dstWidth, int dstHeight, boolean powerOf2)
    {
        int inSampleSize = 1;

        // Raw height and width of image
        final int srcHeight = options.outHeight;
        final int srcWidth = options.outWidth;

        if (powerOf2)
        {
            //Find the correct scale value. It should be the power of 2.

            int tmpWidth = srcWidth, tmpHeight = srcHeight;
            while (true)
            {
                if (tmpWidth / 2 < dstWidth || tmpHeight / 2 < dstHeight)
                    break;
                tmpWidth /= 2;
                tmpHeight /= 2;
                inSampleSize *= 2;
            }
        }
        else
        {
            // Calculate ratios of height and width to requested height and width
            final int heightRatio = Math.round((float) srcHeight / (float) dstHeight);
            final int widthRatio = Math.round((float) srcWidth / (float) dstWidth);

            // Choose the smallest ratio as inSampleSize value, this will guarantee
            // a final image with both dimensions larger than or equal to the
            // requested height and width.
            inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
        }

        return inSampleSize;
    }

    public static Bitmap drawableToBitmap(Drawable drawable)
    {
        if (drawable instanceof BitmapDrawable)
        {
            return ((BitmapDrawable) drawable).getBitmap();
        }

        Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        drawable.draw(canvas);

        return bitmap;
    }

    public static Bitmap getScaledBitmap(Bitmap bitmap, int newWidth, int newHeight)
    {
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        float scaleWidth = ((float) newWidth) / width;
        float scaleHeight = ((float) newHeight) / height;

        // CREATE A MATRIX FOR THE MANIPULATION
        Matrix matrix = new Matrix();
        // RESIZE THE BIT MAP
        matrix.postScale(scaleWidth, scaleHeight);

        // RECREATE THE NEW BITMAP
        Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, false);
        return resizedBitmap;
    }

}

Đối với bất cứ ai sử dụng điều này: Tôi vừa sửa một lỗi: "int scaledBitmapHeight = scaledBitmap.getWidth ();" là sai (rõ ràng. Tôi đã thay thế nó bằng "int scaledBitmapHeight = scaledBitmap.getHeight ();"
Pascal

19

Điều này làm việc cho tôi.

Bitmap myBitmap;

BitmapFactory.Options options = new BitmapFactory.Options(); 
options.InPurgeable = true;
options.OutHeight = 50;
options.OutWidth = 50;
options.InSampleSize = 4;

File imgFile = new File(filepath);
myBitmap = BitmapFactory.DecodeFile(imgFile.AbsolutePath, options);

và đây là trên C # monodroid. bạn có thể dễ dàng thay đổi đường dẫn của hình ảnh. Điều quan trọng ở đây là các tùy chọn được thiết lập.


16

Đây có vẻ như là nơi thích hợp để chia sẻ lớp tiện ích của tôi để tải và xử lý hình ảnh với cộng đồng, bạn có thể sử dụng nó và sửa đổi nó một cách tự do.

package com.emil;

import java.io.IOException;
import java.io.InputStream;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

/**
 * A class to load and process images of various sizes from input streams and file paths.
 * 
 * @author Emil http://stackoverflow.com/users/220710/emil
 *
 */
public class ImageProcessing {

    public static Bitmap getBitmap(InputStream stream, int sampleSize, Bitmap.Config bitmapConfig) throws IOException{
        BitmapFactory.Options options=ImageProcessing.getOptionsForSampling(sampleSize, bitmapConfig);
        Bitmap bm = BitmapFactory.decodeStream(stream,null,options);
        if(ImageProcessing.checkDecode(options)){
            return bm;
        }else{
            throw new IOException("Image decoding failed, using stream.");
        }
    }

    public static Bitmap getBitmap(String imgPath, int sampleSize, Bitmap.Config bitmapConfig) throws IOException{
        BitmapFactory.Options options=ImageProcessing.getOptionsForSampling(sampleSize, bitmapConfig);
        Bitmap bm = BitmapFactory.decodeFile(imgPath,options);
        if(ImageProcessing.checkDecode(options)){
            return bm;
        }else{
            throw new IOException("Image decoding failed, using file path.");
        }
    }

    public static Dimensions getDimensions(InputStream stream) throws IOException{
        BitmapFactory.Options options=ImageProcessing.getOptionsForDimensions();
        BitmapFactory.decodeStream(stream,null,options);
        if(ImageProcessing.checkDecode(options)){
            return new ImageProcessing.Dimensions(options.outWidth,options.outHeight);
        }else{
            throw new IOException("Image decoding failed, using stream.");
        }
    }

    public static Dimensions getDimensions(String imgPath) throws IOException{
        BitmapFactory.Options options=ImageProcessing.getOptionsForDimensions();
        BitmapFactory.decodeFile(imgPath,options);
        if(ImageProcessing.checkDecode(options)){
            return new ImageProcessing.Dimensions(options.outWidth,options.outHeight);
        }else{
            throw new IOException("Image decoding failed, using file path.");
        }
    }

    private static boolean checkDecode(BitmapFactory.Options options){
        // Did decode work?
        if( options.outWidth<0 || options.outHeight<0 ){
            return false;
        }else{
            return true;
        }
    }

    /**
     * Creates a Bitmap that is of the minimum dimensions necessary
     * @param bm
     * @param min
     * @return
     */
    public static Bitmap createMinimalBitmap(Bitmap bm, ImageProcessing.Minimize min){
        int newWidth, newHeight;
        switch(min.type){
        case WIDTH:
            if(bm.getWidth()>min.minWidth){
                newWidth=min.minWidth;
                newHeight=ImageProcessing.getScaledHeight(newWidth, bm);
            }else{
                // No resize
                newWidth=bm.getWidth();
                newHeight=bm.getHeight();
            }
            break;
        case HEIGHT:
            if(bm.getHeight()>min.minHeight){
                newHeight=min.minHeight;
                newWidth=ImageProcessing.getScaledWidth(newHeight, bm);
            }else{
                // No resize
                newWidth=bm.getWidth();
                newHeight=bm.getHeight();
            }
            break;
        case BOTH: // minimize to the maximum dimension
        case MAX:
            if(bm.getHeight()>bm.getWidth()){
                // Height needs to minimized
                min.minDim=min.minDim!=null ? min.minDim : min.minHeight;
                if(bm.getHeight()>min.minDim){
                    newHeight=min.minDim;
                    newWidth=ImageProcessing.getScaledWidth(newHeight, bm);
                }else{
                    // No resize
                    newWidth=bm.getWidth();
                    newHeight=bm.getHeight();
                }
            }else{
                // Width needs to be minimized
                min.minDim=min.minDim!=null ? min.minDim : min.minWidth;
                if(bm.getWidth()>min.minDim){
                    newWidth=min.minDim;
                    newHeight=ImageProcessing.getScaledHeight(newWidth, bm);
                }else{
                    // No resize
                    newWidth=bm.getWidth();
                    newHeight=bm.getHeight();
                }
            }
            break;
        default:
            // No resize
            newWidth=bm.getWidth();
            newHeight=bm.getHeight();
        }
        return Bitmap.createScaledBitmap(bm, newWidth, newHeight, true);
    }

    public static int getScaledWidth(int height, Bitmap bm){
        return (int)(((double)bm.getWidth()/bm.getHeight())*height);
    }

    public static int getScaledHeight(int width, Bitmap bm){
        return (int)(((double)bm.getHeight()/bm.getWidth())*width);
    }

    /**
     * Get the proper sample size to meet minimization restraints
     * @param dim
     * @param min
     * @param multipleOf2 for fastest processing it is recommended that the sample size be a multiple of 2
     * @return
     */
    public static int getSampleSize(ImageProcessing.Dimensions dim, ImageProcessing.Minimize min, boolean multipleOf2){
        switch(min.type){
        case WIDTH:
            return ImageProcessing.getMaxSampleSize(dim.width, min.minWidth, multipleOf2);
        case HEIGHT:
            return ImageProcessing.getMaxSampleSize(dim.height, min.minHeight, multipleOf2);
        case BOTH:
            int widthMaxSampleSize=ImageProcessing.getMaxSampleSize(dim.width, min.minWidth, multipleOf2);
            int heightMaxSampleSize=ImageProcessing.getMaxSampleSize(dim.height, min.minHeight, multipleOf2);
            // Return the smaller of the two
            if(widthMaxSampleSize<heightMaxSampleSize){
                return widthMaxSampleSize;
            }else{
                return heightMaxSampleSize;
            }
        case MAX:
            // Find the larger dimension and go bases on that
            if(dim.width>dim.height){
                return ImageProcessing.getMaxSampleSize(dim.width, min.minDim, multipleOf2);
            }else{
                return ImageProcessing.getMaxSampleSize(dim.height, min.minDim, multipleOf2);
            }
        }
        return 1;
    }

    public static int getMaxSampleSize(int dim, int min, boolean multipleOf2){
        int add=multipleOf2 ? 2 : 1;
        int size=0;
        while(min<(dim/(size+add))){
            size+=add;
        }
        size = size==0 ? 1 : size;
        return size;        
    }

    public static class Dimensions {
        int width;
        int height;

        public Dimensions(int width, int height) {
            super();
            this.width = width;
            this.height = height;
        }

        @Override
        public String toString() {
            return width+" x "+height;
        }
    }

    public static class Minimize {
        public enum Type {
            WIDTH,HEIGHT,BOTH,MAX
        }
        Integer minWidth;
        Integer minHeight;
        Integer minDim;
        Type type;

        public Minimize(int min, Type type) {
            super();
            this.type = type;
            switch(type){
            case WIDTH:
                this.minWidth=min;
                break;
            case HEIGHT:
                this.minHeight=min;
                break;
            case BOTH:
                this.minWidth=min;
                this.minHeight=min;
                break;
            case MAX:
                this.minDim=min;
                break;
            }
        }

        public Minimize(int minWidth, int minHeight) {
            super();
            this.type=Type.BOTH;
            this.minWidth = minWidth;
            this.minHeight = minHeight;
        }

    }

    /**
     * Estimates size of Bitmap in bytes depending on dimensions and Bitmap.Config
     * @param width
     * @param height
     * @param config
     * @return
     */
    public static long estimateBitmapBytes(int width, int height, Bitmap.Config config){
        long pixels=width*height;
        switch(config){
        case ALPHA_8: // 1 byte per pixel
            return pixels;
        case ARGB_4444: // 2 bytes per pixel, but depreciated
            return pixels*2;
        case ARGB_8888: // 4 bytes per pixel
            return pixels*4;
        case RGB_565: // 2 bytes per pixel
            return pixels*2;
        default:
            return pixels;
        }
    }

    private static BitmapFactory.Options getOptionsForDimensions(){
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds=true;
        return options;
    }

    private static BitmapFactory.Options getOptionsForSampling(int sampleSize, Bitmap.Config bitmapConfig){
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = false;
        options.inDither = false;
        options.inSampleSize = sampleSize;
        options.inScaled = false;
        options.inPreferredConfig = bitmapConfig;
        return options;
    }
}

16

Trong một trong những ứng dụng của tôi, tôi cần phải chụp ảnh từ Camera/Gallery. Nếu người dùng nhấp vào hình ảnh từ Camera (có thể là 2MP, 5MP hoặc 8MP), kích thước hình ảnh sẽ thay đổi từ kBs sang MBs. Nếu kích thước hình ảnh nhỏ hơn (hoặc tối đa 1-2 MB) ở trên mã hoạt động tốt nhưng nếu tôi có hình ảnh có kích thước trên 4MB hoặc 5MB thì OOMđi vào khung :(

sau đó tôi đã làm việc để giải quyết vấn đề này và cuối cùng tôi đã thực hiện cải tiến dưới đây cho mã của Fedor (Tất cả tín dụng cho Fedor để tạo ra một giải pháp tốt như vậy) :)

private Bitmap decodeFile(String fPath) {
    // Decode image size
    BitmapFactory.Options opts = new BitmapFactory.Options();
    /*
     * If set to true, the decoder will return null (no bitmap), but the
     * out... fields will still be set, allowing the caller to query the
     * bitmap without having to allocate the memory for its pixels.
     */
    opts.inJustDecodeBounds = true;
    opts.inDither = false; // Disable Dithering mode
    opts.inPurgeable = true; // Tell to gc that whether it needs free
                                // memory, the Bitmap can be cleared
    opts.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

    BitmapFactory.decodeFile(fPath, opts);

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

    // Find the correct scale value. 
    int scale = 1;

    if (opts.outHeight > REQUIRED_SIZE || opts.outWidth > REQUIRED_SIZE) {

        // Calculate ratios of height and width to requested height and width
        final int heightRatio = Math.round((float) opts.outHeight
                / (float) REQUIRED_SIZE);
        final int widthRatio = Math.round((float) opts.outWidth
                / (float) REQUIRED_SIZE);

        // Choose the smallest ratio as inSampleSize value, this will guarantee
        // a final image with both dimensions larger than or equal to the
        // requested height and width.
        scale = heightRatio < widthRatio ? heightRatio : widthRatio;//
    }

    // Decode bitmap with inSampleSize set
    opts.inJustDecodeBounds = false;

    opts.inSampleSize = scale;

    Bitmap bm = BitmapFactory.decodeFile(fPath, opts).copy(
            Bitmap.Config.RGB_565, false);

    return bm;

}

Tôi hy vọng điều này sẽ giúp những người bạn phải đối mặt với cùng một vấn đề!

để biết thêm xin vui lòng tham khảo điều này


14

Tôi mới gặp vấn đề này vài phút trước. Tôi đã giải quyết nó bằng cách thực hiện một công việc tốt hơn trong việc quản lý bộ điều hợp listview của tôi. Tôi nghĩ đó là một vấn đề với hàng trăm hình ảnh 50x50px mà tôi đang sử dụng, hóa ra tôi đang cố gắng làm tăng chế độ xem tùy chỉnh của mình mỗi khi hàng được hiển thị. Đơn giản bằng cách kiểm tra để xem hàng đã bị thổi phồng tôi đã loại bỏ lỗi này và tôi đang sử dụng hàng trăm bitmap. Điều này thực sự dành cho Spinner, nhưng bộ điều hợp cơ sở hoạt động hoàn toàn giống với ListView. Khắc phục đơn giản này cũng cải thiện đáng kể hiệu suất của bộ chuyển đổi.

@Override
public View getView(final int position, View convertView, final ViewGroup parent) {

    if(convertView == null){
        LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        convertView = inflater.inflate(R.layout.spinner_row, null);
    }
...

3
Tôi không thể cảm ơn bạn đủ cho điều này! Tôi đã theo đuổi vấn đề sai trước khi nhìn thấy điều này. Câu hỏi cho bạn: Vì mỗi hàng trong danh sách của tôi có một tên và ảnh duy nhất, tôi đã phải sử dụng một mảng convertView để giữ lại từng giá trị của các hàng. Tôi không thể thấy cách sử dụng một biến duy nhất sẽ cho phép bạn làm như vậy. Tui bỏ lỡ điều gì vậy?
PeteH

13

Tôi đã dành cả ngày để thử nghiệm các giải pháp này và điều duy nhất hiệu quả với tôi là các cách tiếp cận ở trên để lấy hình ảnh và gọi thủ công cho GC, điều mà tôi biết là không cần thiết, nhưng đó là điều duy nhất hiệu quả khi tôi đặt ứng dụng của mình dưới chế độ kiểm tra tải nặng chuyển đổi giữa các hoạt động. Ứng dụng của tôi có một danh sách các hình ảnh thu nhỏ trong một listview trong (giả sử hoạt động A) và khi bạn nhấp vào một trong những hình ảnh đó, nó sẽ đưa bạn đến một hoạt động khác (giả sử hoạt động B) hiển thị hình ảnh chính cho mục đó. Khi tôi chuyển đổi qua lại giữa hai hoạt động, cuối cùng tôi sẽ gặp lỗi OOM và ứng dụng sẽ buộc đóng.

Khi tôi xuống được nửa đường thì nó sẽ bị sập.

Bây giờ khi tôi thực hiện các thao tác sau trong hoạt động B, tôi có thể đi qua toàn bộ danh sách nghe mà không gặp vấn đề gì và tiếp tục đi và đi ... và rất nhanh.

@Override
public void onDestroy()
{   
    Cleanup();
    super.onDestroy();
}

private void Cleanup()
{    
    bitmap.recycle();
    System.gc();
    Runtime.getRuntime().gc();  
}

Yêu giải pháp của bạn! Tôi cũng đã dành hàng giờ để giải quyết lỗi này, thật khó chịu! Chỉnh sửa: Đáng buồn thay, sự cố vẫn tồn tại khi tôi thay đổi hướng màn hình của mình ở chế độ ngang ...
Xarialon

Điều này cuối cùng đã giúp tôi cùng với: - Tùy chọn BitmapFactory.Options = new BitmapFactory.Options (); tùy chọn.InPurgizable = true; tùy chọn.InSampleSize = 2;
dùng3833732

13

Vấn đề này chỉ xảy ra trong trình giả lập Android. Tôi cũng gặp phải vấn đề này trong một trình giả lập nhưng khi tôi kiểm tra trong một thiết bị thì nó hoạt động tốt.

Vì vậy, vui lòng kiểm tra trong một thiết bị. Nó có thể được chạy trong thiết bị.


12

2 xu của tôi: tôi đã giải quyết các lỗi OOM của mình bằng bitmap bằng cách:

a) nhân rộng hình ảnh của tôi theo hệ số 2

b) sử dụng thư viện Picasso trong Bộ điều hợp tùy chỉnh của tôi cho ListView, với một cuộc gọi trong getView như thế này:Picasso.with(context).load(R.id.myImage).into(R.id.myImageView);


Tôi rất vui vì bạn đã đề cập đến Picasso, vì nó làm cho việc tải hình ảnh dễ dàng hơn nhiều. Đặc biệt là những người được lưu trữ từ xa.
Chrispix

12

sử dụng các mã này cho mọi hình ảnh được chọn từ SdCard hoặc có thể vẽ để chuyển đổi đối tượng bitmap.

Resources res = getResources();
WindowManager window = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
Display display = window.getDefaultDisplay();
@SuppressWarnings("deprecation")
int width = display.getWidth();
@SuppressWarnings("deprecation")
int height = display.getHeight();
try {
    if (bitmap != null) {
        bitmap.recycle();
        bitmap = null;
        System.gc();
    }
    bitmap = Bitmap.createScaledBitmap(BitmapFactory
        .decodeFile(ImageData_Path.get(img_pos).getPath()),
        width, height, true);
} catch (OutOfMemoryError e) {
    if (bitmap != null) {
        bitmap.recycle();
        bitmap = null;
        System.gc();
    }
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inPreferredConfig = Config.RGB_565;
    options.inSampleSize = 1;
    options.inPurgeable = true;
    bitmapBitmap.createScaledBitmap(BitmapFactory.decodeFile(ImageData_Path.get(img_pos)
        .getPath().toString(), options), width, height,true);
}
return bitmap;

sử dụng đường dẫn hình ảnh của bạn theo ImageData_Path.get (img_pose) .getPath () .


12

Nói chung, kích thước heap của thiết bị Android chỉ là 16MB (thay đổi từ thiết bị / HĐH xem bài Heap Size ), nếu bạn đang tải hình ảnh và nó vượt qua kích thước 16 MB, nó sẽ loại bỏ ngoại lệ bộ nhớ, thay vì sử dụng Bitmap cho, tải hình ảnh từ thẻ SD hoặc từ tài nguyên hoặc thậm chí từ mạng cố gắng sử dụng getImageUri , tải bitmap cần thêm bộ nhớ hoặc bạn có thể đặt bitmap thành null nếu công việc của bạn được thực hiện với bitmap đó.


1
Và nếu setImageURI vẫn nhận được ngoại lệ thì hãy tham khảo stackoverflow.com/questions/15377186/
Mahesh

11

Tất cả các giải pháp ở đây đều yêu cầu đặt IMAGE_MAX_SIZE. Điều này giới hạn các thiết bị có phần cứng mạnh hơn và nếu kích thước hình ảnh quá thấp, nó trông xấu xí trên màn hình HD.

Tôi đã đưa ra một giải pháp hoạt động với Samsung Galaxy S3 của tôi và một số thiết bị khác bao gồm những thiết bị kém mạnh hơn, với chất lượng hình ảnh tốt hơn khi sử dụng một thiết bị mạnh hơn.

Ý chính của nó là tính toán bộ nhớ tối đa được phân bổ cho ứng dụng trên một thiết bị cụ thể, sau đó đặt tỷ lệ ở mức thấp nhất có thể mà không vượt quá bộ nhớ này. Đây là mã:

public static Bitmap decodeFile(File f)
{
    Bitmap b = null;
    try
    {
        // Decode image size
        BitmapFactory.Options o = new BitmapFactory.Options();
        o.inJustDecodeBounds = true;

        FileInputStream fis = new FileInputStream(f);
        try
        {
            BitmapFactory.decodeStream(fis, null, o);
        }
        finally
        {
            fis.close();
        }

        // In Samsung Galaxy S3, typically max memory is 64mb
        // Camera max resolution is 3264 x 2448, times 4 to get Bitmap memory of 30.5mb for one bitmap
        // If we use scale of 2, resolution will be halved, 1632 x 1224 and x 4 to get Bitmap memory of 7.62mb
        // We try use 25% memory which equals to 16mb maximum for one bitmap
        long maxMemory = Runtime.getRuntime().maxMemory();
        int maxMemoryForImage = (int) (maxMemory / 100 * 25);

        // Refer to
        // http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
        // A full screen GridView filled with images on a device with
        // 800x480 resolution would use around 1.5MB (800*480*4 bytes)
        // When bitmap option's inSampleSize doubled, pixel height and
        // weight both reduce in half
        int scale = 1;
        while ((o.outWidth / scale) * (o.outHeight / scale) * 4 > maxMemoryForImage)
        scale *= 2;

        // Decode with inSampleSize
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inSampleSize = scale;
        fis = new FileInputStream(f);
        try
        {
            b = BitmapFactory.decodeStream(fis, null, o2);
        }
        finally
        {
            fis.close();
        }
    }
    catch (IOException e)
    {
    }
    return b;
}

Tôi đặt bộ nhớ tối đa được sử dụng bởi bitmap này là 25% bộ nhớ được phân bổ tối đa, bạn có thể cần điều chỉnh điều này theo nhu cầu của mình và đảm bảo bitmap này được dọn sạch và không lưu lại trong bộ nhớ khi bạn sử dụng xong. Thông thường tôi sử dụng mã này để thực hiện xoay ảnh (bitmap nguồn và đích) vì vậy ứng dụng của tôi cần tải 2 bitmap trong bộ nhớ cùng lúc và 25% cho tôi một bộ đệm tốt mà không bị hết bộ nhớ khi thực hiện xoay ảnh.

Hy vọng điều này sẽ giúp ai đó ngoài kia ..


11

Như vậy OutofMemoryExceptionkhông thể được giải quyết hoàn toàn bằng cách gọi System.gc()và như vậy.

Bằng cách tham khảo Vòng đời hoạt động

Các trạng thái Hoạt động được xác định bởi chính HĐH tùy thuộc vào mức sử dụng bộ nhớ cho từng quy trình và mức độ ưu tiên của từng quy trình.

Bạn có thể xem xét kích thước và độ phân giải cho từng hình ảnh bitmap được sử dụng. Tôi khuyên bạn nên giảm kích thước, lấy mẫu lại thành độ phân giải thấp hơn, tham khảo thiết kế phòng trưng bày (một hình ảnh nhỏ PNG và một hình ảnh gốc.)


11

Mã này sẽ giúp tải bitmap lớn từ drawable

public class BitmapUtilsTask extends AsyncTask<Object, Void, Bitmap> {

    Context context;

    public BitmapUtilsTask(Context context) {
        this.context = context;
    }

    /**
     * Loads a bitmap from the specified url.
     * 
     * @param url The location of the bitmap asset
     * @return The bitmap, or null if it could not be loaded
     * @throws IOException
     * @throws MalformedURLException
     */
    public Bitmap getBitmap() throws MalformedURLException, IOException {       

        // Get the source image's dimensions
        int desiredWidth = 1000;
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;

        BitmapFactory.decodeResource(context.getResources(), R.drawable.green_background , options);

        int srcWidth = options.outWidth;
        int srcHeight = options.outHeight;

        // Only scale if the source is big enough. This code is just trying
        // to fit a image into a certain width.
        if (desiredWidth > srcWidth)
            desiredWidth = srcWidth;

        // Calculate the correct inSampleSize/scale value. This helps reduce
        // memory use. It should be a power of 2
        int inSampleSize = 1;
        while (srcWidth / 2 > desiredWidth) {
            srcWidth /= 2;
            srcHeight /= 2;
            inSampleSize *= 2;
        }
        // Decode with inSampleSize
        options.inJustDecodeBounds = false;
        options.inDither = false;
        options.inSampleSize = inSampleSize;
        options.inScaled = false;
        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
        options.inPurgeable = true;
        Bitmap sampledSrcBitmap;

        sampledSrcBitmap =  BitmapFactory.decodeResource(context.getResources(), R.drawable.green_background , options);

        return sampledSrcBitmap;
    }

    /**
     * The system calls this to perform work in a worker thread and delivers
     * it the parameters given to AsyncTask.execute()
     */
    @Override
    protected Bitmap doInBackground(Object... item) {
        try { 
          return getBitmap();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

Thật tuyệt, nghĩ rằng sẽ tốt hơn nếu sử dụng Trình tải thay vì Tác vụ Async?
Chrispix

Thế còn Bitmap.Config.ARGB_565? Nếu chất lượng cao không quan trọng.
Hamzeh Soboh
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.