비트 맵 객체에 이미지를로드하는 동안 메모리 부족 문제


1288

각 행에 몇 개의 이미지 버튼이있는 목록보기가 있습니다. 목록 행을 클릭하면 새 활동이 시작됩니다. 카메라 레이아웃에 문제가있어 탭을 직접 만들어야했습니다. 결과에 대해 시작된 활동은 맵입니다. 내 버튼을 클릭하여 이미지 미리보기를 시작하면 (이미지를 SD 카드에로드) 애플리케이션이 액티비티에서 액티비티로 다시 listview결과 핸들러로 돌아와 이미지 위젯에 지나지 않는 새로운 액티비티를 다시 시작합니다.

커서로 및을 사용하여 목록보기의 이미지 미리보기를 수행합니다 ListAdapter. 이것은 매우 간단하지만 크기가 조정 된 이미지를 넣을 수있는 방법을 잘 모르겠습니다 (즉 src, 이미지 버튼 의 경우 픽셀이 아닌 작은 비트 크기) 즉 , 휴대 전화 카메라에서 나온 이미지의 크기를 조정했습니다.

문제는 돌아가서 두 번째 활동을 다시 시작하려고 할 때 메모리 부족 오류가 발생한다는 것입니다.

  • 내가 쉽게 목록 어댑터를 구축 나는 비행 (에 크기를 조정할 수 행에 의해 행 할 수있는 방법이 있습니까 비트 현명한 )?

포커스 문제로 인해 터치 스크린으로 행을 선택할 수 없으므로 각 행의 위젯 / 요소 속성을 일부 변경해야하기 때문에 바람직합니다. ( 롤러 볼을 사용할 수 있습니다. )

  • 대역 외 크기를 조정하고 이미지를 저장할 수 있다는 것을 알고 있지만 실제로 원하는 것은 아니지만 샘플 코드가 좋을 것입니다.

목록보기에서 이미지를 비활성화하자마자 다시 정상적으로 작동했습니다.

참고 : 이것이 내가 한 일입니다 :

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);

어디 R.id.imagefilename입니다 ButtonImage.

내 LogCat은 다음과 같습니다.

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 

이미지를 표시 할 때 새로운 오류가 있습니다.

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
Bitmap.decodeStream 또는 decodeFile을 피하고 BitmapFactory.decodeFileDescriptor 메서드를 사용하여이 문제를 해결했습니다.
Fraggle

1
나는 몇 주 전에 비슷한 문제에 직면했으며 이미지를 최적의 지점까지 축소하여 문제를 해결했습니다. 내 블로그 코딩 에 완전한 접근 방식을 작성 했으며 https://github.com/shailendra123/BitmapHandlingDemo
Shailendra Singh Rajawat

5
이 질문에 대한 대답은 meta
rene

4
이것은 안드로이드 개발자 가이드를 읽지 않을 때 발생합니다
Pedro Varela

2
이것은 나쁜 안드로이드 아키텍처로 인해 발생합니다. ios와 같이 이미지 자체의 크기를 조정해야하며 UWP 가이를 수행합니다. 이 일을 직접 할 필요는 없습니다. 안드로이드 개발자들은 그 지옥에 익숙해지고 그것이 제대로 작동한다고 생각합니다.
액세스 거부

답변:


650

비트 맵을 효율적으로 표시 하는 Android Training 클래스 는 비트 맵을로드 할 때 예외를 이해하고 처리하는 데 유용한 정보를 제공합니다 .java.lang.OutOfMemoryError: bitmap size exceeds VM budget


비트 맵 치수 및 유형 읽기

BitmapFactory클래스 (여러 복호화 방법을 제공한다 decodeByteArray(), decodeFile(), decodeResource()의 작성 등) Bitmap다양한 소스. 이미지 데이터 소스에 따라 가장 적합한 디코딩 방법을 선택하십시오. 이러한 메소드는 구성된 비트 맵에 메모리를 할당하려고 시도하므로 쉽게 OutOfMemory예외가 발생할 수 있습니다 . 각 유형의 디코딩 방법에는 BitmapFactory.Options클래스 를 통해 디코딩 옵션을 지정할 수있는 추가 서명이 있습니다 . 설정 inJustDecodeBounds에 속성 true복귀 피할 메모리 할당을 디코딩 할 때 null비트 맵 오브젝트에 대해 설정되지만 outWidth, outHeightoutMimeType. 이 기술을 사용하면 비트 맵을 구성 (및 메모리 할당)하기 전에 이미지 데이터의 크기와 유형을 읽을 수 있습니다.

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;

java.lang.OutOfMemory예외 를 피하려면 사용 가능한 메모리에 편안하게 맞는 예측 가능한 크기의 이미지 데이터를 제공하도록 소스를 절대 신뢰하지 않는 한 비트 맵을 디코딩하기 전에 비트 맵의 ​​크기를 확인하십시오.


축소 된 버전을 메모리에로드

이제 이미지 크기를 알았으므로 전체 이미지를 메모리에로드해야하는지 또는 서브 샘플링 된 버전을로드해야하는지 결정하는 데 사용할 수 있습니다. 고려해야 할 몇 가지 요소는 다음과 같습니다.

  • 메모리에 전체 이미지를로드하는 예상 메모리 사용량입니다.
  • 응용 프로그램의 다른 메모리 요구 사항이 주어지면이 이미지를로드하려고 할 메모리 양입니다.
  • 이미지를로드 할 대상 ImageView 또는 UI 구성 요소의 크기입니다.
  • 현재 장치의 화면 크기 및 밀도

예를 들어 1024x768 픽셀 이미지를 메모리에 128x96 픽셀 썸네일로 표시 할 경우 메모리에로드 할 가치가 없습니다 ImageView.

메모리 설정에 작은 버전을로드 이미지를 표본을 디코더에게 inSampleSizetrue당신의 BitmapFactory.Options객체입니다. 예를 들어, 해상도가 2048x1536 인 이미지 inSampleSize는 4로 디코딩 된 경우 약 512x384의 비트 맵이 생성됩니다. 이것을 메모리에로드 할 때는 전체 이미지에 12MB가 아닌 0.75MB가 사용됩니다 (비트 맵 구성이 가정 됨 ARGB_8888). 목표 너비와 높이를 기준으로 2의 거듭 제곱 인 표본 크기 값을 계산하는 방법은 다음과 같습니다.

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;
}

참고 : 두 값의 거듭 제곱은 디코더가 inSampleSize설명서 에 따라 가장 가까운 2의 거듭 제곱으로 반올림하여 최종 값을 사용하므로 계산 됩니다.

이 방법을 사용하려면 먼저 inJustDecodeBoundsset to true로 디코딩하고 옵션을 통과 한 다음 새 inSampleSize값을 사용하여 다시 디코딩하고 다음으로 inJustDecodeBounds설정하십시오 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);
}

이 방법을 사용 ImageView하면 다음 예제 코드와 같이 임의로 큰 크기의 비트 맵을 100x100 픽셀 축소판을 표시하는 비트 맵에 쉽게로드 할 수 있습니다.

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

유사한 프로세스를 BitmapFactory.decode*따라 필요에 따라 적절한 방법을 대체하여 다른 소스의 비트 맵을 디코딩 할 수 있습니다 .


21
이 답변은 메타
rene

9
이 답변 (링크를 통해 도달 한 정보 제외)은 답변에 대한 해결책을 많이 제공하지 않습니다. 링크의 중요한 부분이 질문에 병합되어야합니다.
FallenAngel

7
이 답변은 질문 및 다른 답변과 마찬가지로 Community Wiki이므로 커뮤니티는 편집을 통해 수정할 수있는 것으로 중재자의 개입이 필요하지 않습니다.
Martijn Pieters

컨텐츠에 대한 현재 링크 및 Kotlin 지원은 developer.android.com/topic/performance/graphics/load-bitmap
Panos Gr

890

OutOfMemory 오류를 해결하려면 다음과 같이해야합니다.

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

inSampleSize옵션은 메모리 소비를 줄입니다.

다음은 완전한 방법입니다. 먼저 내용 자체를 디코딩하지 않고 이미지 크기를 읽습니다. 그런 다음 가장 좋은 inSampleSize값을 찾고 2의 거듭 제곱이어야하며 마지막으로 이미지가 디코딩됩니다.

// 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
inSampleSize에 10이 최적의 값이 아닐 수도 있지만이 문서에서는 2의 거듭 제곱을 사용하는 것이 좋습니다.
Mirko N.

70
나는 Chrispix와 같은 문제에 직면하고 있지만 여기서 해결책이 실제로 문제를 해결하지는 않지만 오히려 회피한다고 생각합니다. 샘플 크기를 변경하면 이미지 품질을 희생하여 사용되는 메모리 양이 줄어들지 만 (이미지 미리보기에는 적합 할 수 있음) 여러 이미지 스트림이 디코딩 된 경우 충분한 이미지 스트림을 디코딩하는 경우 예외를 방지 할 수 없습니다. 디코딩되었습니다. 더 나은 해결책을 찾으면 (없을 수도 있습니다) 여기에 답변을 게시 할 것입니다.
Flynn81

4
화면을 픽셀 밀도로 맞추기 위해서는 적절한 크기 만 있으면됩니다.
stealthcopter

4
REQUIRED_SIZE는 확장하려는 새 크기입니다.
Fedor

8
이 솔루션은 도움이되었지만 이미지 품질은 끔찍합니다. 이미지를 제안하기 위해 viewfilpper를 사용하고 있습니까?
user1106888

372

Fedor의 코드를 약간 개선했습니다. 기본적으로는 동일하지만 (제 생각에는) 추악한 while 루프가 없으면 항상 2의 거듭 제곱이됩니다. 원래 솔루션을 만들기 위해 Fedor에게 Kudos를 찾았습니다.

 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
예, 당신이 옳은 동안 그렇게 아름답 지 않습니다. 난 그냥 모든 사람에게 명확하게하려고했습니다. 코드 주셔서 감사합니다.
Fedor

10
@Thomas Vervest-해당 코드에 큰 문제가 있습니다. ^는 2를 거듭 제곱하지 않으며 결과와 함께 2를 곱합니다. Math.pow (2.0, ...)를 원합니다. 그렇지 않으면 이것은 좋아 보인다.
DougW

6
오, 아주 좋은 일입니다! 내 나쁜, 나는 그것을 바로 고마워, 답변 주셔서 감사합니다!
토마스 버 베스트

8
를 호출 할 때마다 하나씩 두 개의 새 FileInputStream을 작성합니다 BitmapFactory.decodeStream(). finally블록 으로 닫을 수 있도록 각각에 대한 참조를 저장할 필요가 없습니까?
matsev

1
@Babibu 설명서에는 스트림이 닫혀 있다고 명시되어 있지 않으므로 여전히 닫혀 있어야한다고 가정합니다. 흥미롭고 관련된 토론은 여기 에서 찾을 수 있습니다 . 우리의 토론과 직접 관련된 Adrian Smith의 의견에 주목하십시오.
토마스 버 베스트

232

나는 iOS 경험에서 왔으며 이미지로드 및 표시와 같은 기본적인 문제를 발견하는 데 좌절했습니다. 결국,이 문제가있는 모든 사람들은 합리적인 크기의 이미지를 표시하려고합니다. 어쨌든, 내 문제를 해결하고 내 앱을 매우 반응 적으로 만든 두 가지 변경 사항이 있습니다.

1) 할 때마다 로 설정 BitmapFactory.decodeXYZ()하고 BitmapFactory.OptionsinPurgeable설정하십시오 true(및 바람직하게 inInputShareable는로 설정 true).

2) 절대 사용하지 마십시오 Bitmap.createBitmap(width, height, Config.ARGB_8888). 나는 절대 의미하지 않는다! 몇 번의 통과 후에도 메모리 오류가 발생하지 않는 것은 없었습니다. 아무리 recycle(), System.gc()무엇이든은 도움이되지 않습니다. 항상 예외가 발생했습니다. 실제로 작동하는 또 다른 방법은 드로어 블에 더미 이미지 (또는 위의 1 단계를 사용하여 디코딩 한 다른 비트 맵)를 가지고 원하는 크기로 조정 한 다음 결과 비트 맵을 조작하는 것입니다 (예 : 캔버스에 전달하는 것) 더 재미있게). 따라서 대신 사용해야하는 것은 다음과 같습니다 Bitmap.createScaledBitmap(srcBitmap, width, height, false). 어떤 이유로 든 무차별 강제 작성 방법을 사용해야하는 경우 최소한을 전달하십시오 Config.ARGB_4444.

며칠이 아니라면 시간을 절약 할 수 있습니다. 이미지 크기 조정 등에 대한 모든 이야기는 실제로 작동하지 않습니다 (잘못된 크기를 얻거나 이미지의 해상도가 떨어지는 것을 고려하지 않는 한).


22
BitmapFactory.Options options = new BitmapFactory.Options(); options.inPurgeable = true;그리고 Bitmap.createScaledBitmap(srcBitmap, width, height, false);나는 안드로이드 4.0.0에 메모리 예외 밖으로 가지고 내 문제를 해결했다. 고마워 친구!
Jan-Terje Sørensen

5
Bitmap.createScaledBitmap () 호출에서 아마도 플래그 매개 변수로 true를 사용해야합니다. 그렇지 않으면 확대 할 때 이미지 품질이 부드럽 지 않습니다. 이 스레드를 확인하십시오 stackoverflow.com/questions/2895065/…
rOrlig

11
정말 멋진 조언입니다. 이 놀랍게도 덩치가 큰 버그에 대해 Google이 업무를 수행 할 수 있도록 +1을 더 줄 수 있기를 바랍니다. 내 말은 ... 만약 그것이 버그가 아니라면, 문서에는 실제로 "이것은 당신이 사진을 처리하는 방법입니다"라는 네온 사인이 심각하게 깜박일 필요가 있습니다. 좋은 발견.
예브게니 심킨

이미지를 축소하면 도움이되지만 이것은 중요한 단계이며 궁극적으로이 문제를 해결했습니다. 이미지의 크기를 조정하는 문제는 이미지가 많거나 소스 이미지가 너무 큰 경우에도 여전히 같은 문제가 발생할 수 있습니다. 에브라임 +1
Dave

10
롤리팝로, BitmapFactory.Options.inPurgeable그리고 BitmapFactory.Options.inInputShareable사용되지 않습니다 developer.android.com/reference/android/graphics/...을
데니스 Kniazhev에게

93

그것은이다 알려진 버그 는하지 때문에 대용량 파일의입니다. Android는 Drawables를 캐시하므로 이미지를 거의 사용하지 않으면 메모리가 부족합니다. 그러나 안드로이드 기본 캐시 시스템을 건너 뛰어 대체 방법을 찾았습니다.

해결책 : 이미지를 "자산"폴더로 이동하고 다음 기능을 사용하여 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

나는이 같은 문제가 있었고 BitmapFactory.decodeStream 또는 decodeFile 함수를 피하고 대신 사용하여 해결했습니다. BitmapFactory.decodeFileDescriptor

decodeFileDescriptor decodeStream / decodeFile과 다른 기본 메소드를 호출하는 것처럼 보입니다.

어쨌든, 효과가 있었던 것은 이것입니다 (위의 일부 옵션을 추가했지만 차이점은 아닙니다. 중요한 것은 decodeStream 또는 decodeFile 대신 BitmapFactory.decodeFileDescriptor에 대한 호출입니다 ).

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;                        
}

decodeStream / decodeFile에 사용 된 기본 함수에 문제가 있다고 생각합니다. decodeFileDescriptor를 사용할 때 다른 기본 메소드가 호출되는 것을 확인했습니다. 또한 내가 읽은 것은 "이미지 (비트 맵)는 표준 Java 방식으로 할당되지 않고 네이티브 호출을 통해 할당됩니다. 할당은 가상 힙 외부에서 이루어 지지만 이에 대해 계산됩니다! "


1
기억에 남는 동일한 결과, 실제로 메모리를 사용하는 데이터를 읽기 위해 보유하고있는 바이트 수에 따라 어떤 방법을 사용하는지는 중요하지 않습니다.
PiyushMishra

72

나는 피하는 가장 좋은 방법 OutOfMemoryError은 그것을 직면하고 이해하는 것입니다.

의도적으로을 발생 시키고 메모리 사용을 모니터링 하는 을 만들었습니다 OutOfMemoryError.

이 앱으로 많은 실험을 한 후 다음과 같은 결론을 얻었습니다.

허니 콤 전에 SDK 버전에 대해 이야기하겠습니다.

  1. 비트 맵은 기본 힙에 저장되지만 recycle ()을 불필요하게 호출하면 가비지가 자동으로 수집됩니다.

  2. {VM 힙 크기} + {할당 된 기본 힙 메모리}> = {장치의 VM 힙 크기 제한}이고 비트 맵을 작성하려고하면 OOM이 발생합니다.

    주의 사항 : VM 할당 메모리가 아니라 VM 힙 크기가 계산됩니다.

  3. 할당 된 VM 메모리가 축소 되더라도 VM 힙 크기는 커진 후에 축소되지 않습니다.

  4. 따라서 비트 맵에 사용 가능한 메모리를 절약하기 위해 VM 힙 크기가 너무 커지지 않도록 피크 VM 메모리를 최대한 낮게 유지해야합니다.

  5. System.gc ()를 수동으로 호출하는 것은 의미가 없습니다. 힙 크기를 늘리기 전에 시스템에서 먼저 호출합니다.

  6. 기본 힙 크기도 축소되지 않지만 OOM에는 포함되지 않으므로 걱정할 필요가 없습니다.

그런 다음 Honey Comb의 SDK 시작에 대해 이야기하겠습니다.

  1. 비트 맵은 VM 힙에 저장되며 기본 메모리는 OOM에 포함되지 않습니다.

  2. OOM의 조건은 {VM 힙 크기}> = {장치의 VM 힙 크기 제한}보다 훨씬 간단합니다.

  3. 따라서 동일한 힙 크기 제한으로 비트 맵을 만들 수있는 사용 가능한 메모리가 더 많으므로 OOM 발생 가능성이 줄어 듭니다.

가비지 수집 및 메모리 누수에 대한 관찰 내용은 다음과 같습니다.

앱에서 직접 볼 수 있습니다. 활동이 활동이 종료 된 후에도 여전히 실행중인 AsyncTask를 실행 한 경우 AsyncTask가 완료 될 때까지 활동이 가비지 수집되지 않습니다.

AsyncTask는 익명의 내부 클래스의 인스턴스이므로 Activity에 대한 참조를 보유합니다.

백그라운드 스레드의 IO 작업에서 작업이 차단 된 경우 AsyncTask.cancel (true)을 호출하면 실행이 중지되지 않습니다.

콜백은 익명의 내부 클래스이기도하므로 프로젝트의 정적 인스턴스가 보유하고 해제하지 않으면 메모리가 누출됩니다.

타이머와 같이 반복되거나 지연된 작업을 예약하고 onPause ()에서 cancel () 및 purge ()를 호출하지 않으면 메모리가 누출됩니다.


AsyncTask가 반드시 "익명 내부 클래스의 인스턴스"일 필요는 없으며 콜백도 마찬가지입니다. AsyncTask를 확장하는 자체 파일 또는 private static class동일한 클래스에서 새 공용 클래스를 만들 수 있습니다 . (당신이 그 (것)들에게 코스 중 하나를 제공하지 않는 한) 그들은 활동에 대한 참조를 보유하지 않습니다
사이먼 포스 버그에게

65

최근 OOM 예외 및 캐싱에 대한 많은 질문을 보았습니다. 개발자 안내서에는 이것에 대한 좋은 기사 가 있지만 일부는 적절한 방법으로 구현하지 못하는 경향이 있습니다.

이 때문에 안드로이드 환경에서 캐싱을 보여주는 예제 애플리케이션을 작성했습니다. 이 구현은 아직 OOM을 얻지 못했습니다.

소스 코드에 대한 링크는이 답변의 끝 부분을보십시오.

요구 사항 :

  • Android API 2.1 이상 (단순히 API 1.6에서 응용 프로그램에 사용 가능한 메모리를 가져올 수 없었습니다-API 1.6에서 작동하지 않는 유일한 코드입니다)
  • 안드로이드 지원 패키지

스크린 샷

풍모:

  • 싱글 톤을 사용하여 방향 변경이있는 경우 캐시를 유지합니다.
  • 사용 팔분의 일 (당신이 원하는 경우 수정) 캐시에 할당 된 응용 프로그램의 메모리를
  • 큰 비트 맵의 크기가 조정됩니다 (허용하려는 최대 픽셀을 정의 할 수 있음)
  • 비트 맵을 다운로드하기 전에 인터넷에 연결되어 있는지 제어
  • 행당 하나의 작업 만 인스턴스화해야합니다.
  • 경우 당신이 마리되어ListView 거리를, 단순히 사이의 비트 맵을 다운로드하지 않습니다

여기에는 다음이 포함되지 않습니다.

  • 디스크 캐싱. 어쨌든 구현하기 쉬워야합니다. 디스크에서 비트 맵을 가져 오는 다른 작업을 가리 킵니다.

샘플 코드 :

다운로드중인 이미지는 Flickr의 이미지 (75x75)입니다. 그러나 처리하려는 이미지 URL을 입력하고 최대 값을 초과하면 응용 프로그램에서 이미지 크기를 줄입니다. 이 응용 프로그램에서 URL은 단순히 String배열에 있습니다.

LruCache비트 맵을 처리 할 수있는 좋은 방법이있다. 그러나이 응용 프로그램 LruCache에서는 응용 프로그램을 더 실현하기 위해 만든 다른 캐시 클래스 내부에 인스턴스를 넣었 습니다.

Cache.java의 중요한 것들 ( loadBitmap()방법이 가장 중요하다) :

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();
        }
    } 
}

디스크 캐싱을 구현하지 않는 한 Cache.java 파일에서 아무것도 편집하지 않아도됩니다.

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()매우 자주 호출됩니다. 행당 무한한 수의 스레드를 시작하지 않도록 확인을 구현하지 않은 경우 일반적으로 이미지를 다운로드하는 것은 좋지 않습니다. Cache.java는 rowObject.mBitmapUrl이미 작업에 있는지 확인 하고, 있으면 다른 시작하지 않습니다. 따라서 AsyncTask풀 의 작업 큐 제한을 초과하지 않을 가능성이 높습니다 .

다운로드 :

https://www.dropbox.com/s/pvr9zyl811tfeem/ListViewImageCache.zip 에서 소스 코드를 다운로드 할 수 있습니다 .


마지막 말:

나는 이것을 몇 주 동안 테스트했지만 아직 단일 OOM 예외를 얻지 못했습니다. 에뮬레이터, Nexus One 및 Nexus S에서 이것을 테스트했습니다. HD 품질의 이미지가 포함 된 이미지 URL을 테스트했습니다. 유일한 병목 현상은 다운로드하는 데 시간이 더 걸린다는 것입니다.

OOM이 나타날 것이라고 상상할 수있는 시나리오는 하나뿐입니다. 실제로 많은 큰 이미지를 다운로드하고 크기를 조정하고 캐시에 넣기 전에 동시에 더 많은 메모리를 차지하고 OOM을 발생시키는 경우입니다. 그러나 그것은 어쨌든 이상적인 상황은 아니며 더 실현 가능한 방법으로 해결할 수는 없습니다.

의견에 오류를 신고하십시오! :-)


43

이미지를 가져 와서 크기를 조정하기 위해 다음을 수행했습니다. 도움이 되었기를 바랍니다

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

26
이 방법은 비트 맵의 ​​크기를 조정합니다. 그러나 전체 비트 맵이 디코딩되고 있기 때문에 OutOfMemory 문제를 해결하지 못합니다.
Fedor

5
이전 코드를 볼 수 있는지 확인할 수 있지만 메모리 부족 문제를 해결했다고 생각합니다. 이전 코드를 다시 확인합니다.
Chrispix

2
이 예제에서는 최소한 전체 비트 맵에 대한 참조를 유지하지 않는 것처럼 보이므로 메모리가 절약됩니다.
NoBugs

나를 위해 메모리 문제를 해결했지만 이미지 품질이 떨어졌습니다.
Pamela Sillah

35

이것은 많은 다른 설명과 함께 매우 오래 실행되는 문제인 것 같습니다. 나는 여기에 가장 일반적인 두 가지 답변에 대한 조언을 얻었지만 이들 중 어느 것도 VM 이 프로세스 의 디코딩 부분 을 수행 할 바이트를 감당할 수 없다고 주장하는 VM의 문제를 해결하지 못했습니다 . 약간의 파기 후에 나는 여기서 실제 문제가 NATIVE 힙 에서 제거하는 디코딩 프로세스라는 것을 알게되었습니다 .

여기를 참조하십시오 : BitmapFactory OOM이 나를 운전합니다.

이 문제에 대한 몇 가지 해결책을 찾은 또 다른 토론 스레드로 이어졌습니다. 하나는 전화하는 것입니다System.gc(); 이미지가 표시된 후 수동으로 입니다. 그러나 실제로 기본 힙을 줄이기 위해 앱에서 더 많은 메모리를 사용합니다. 2.0 (Donut) 릴리스에서 더 나은 솔루션은 BitmapFactory 옵션 "inPurgeable"을 사용하는 것입니다. 그래서 나는 o2.inPurgeable=true;단지 직후에 추가했습니다 o2.inSampleSize=scale;.

해당 주제에 대한 자세한 내용은 여기를 참조하십시오. 메모리 힙 제한이 6M입니까?

이제이 모든 것을 말했듯이 Java와 Android도 완벽하게 다루었습니다. 따라서 이것이이 문제를 해결하는 끔찍한 방법이라고 생각한다면 아마도 옳을 것입니다. ;-) 그러나 이것은 나를 위해 경이 로웠으며 지금은 힙 캐시에서 VM을 실행하는 것이 불가능하다는 것을 알았습니다. 내가 찾을 수있는 유일한 단점은 캐시 된 그려진 이미지를 휴지통에 버리는 것입니다. 즉, 해당 이미지로 다시 돌아 가면 매번 다시 그립니다. 내 응용 프로그램이 작동하는 방식은 실제로 문제가되지 않습니다. 귀하의 마일리지가 다를 수 있습니다.


나를 위해 inpurgeable 고정 OOM.
Artem Russakovskii

35

불행히도 위의 어느 것도 작동하지 않으면 매니페스트 파일에 추가 하십시오. 내부 애플리케이션 태그

 <application
         android:largeHeap="true"

1
이것이 실제로 무엇을 설명 할 수 있습니까? 사람들에게 이것을 추가하라고 말하는 것만으로는 도움이되지 않습니다.
Stealth Rabbi

1
이것은 매우 나쁜 해결책입니다. 기본적으로 문제를 해결하려고하지 않습니다. 대신 안드로이드 시스템에 응용 프로그램에 더 많은 힙 공간을 할당하도록 요청하십시오. 이것은 메모리를 정리하기 위해 GC가 큰 힙 공간을 통해 실행해야하고 앱 성능이 느려질 수 있기 때문에 앱이 배터리를 많이 소모하는 것처럼 앱에 매우 나쁜 영향을 미칩니다.
Prakash

2
그렇다면 android에서 왜 매니페스트에 android : largeHeap = "true"를 추가 할 수 있습니까? 지금 당신은 안드로이드에 도전하고 있습니다.
Himanshu Mori

32

사용 bitmap.recycle();이미지 품질 문제없이 도움이됩니다.


9
API에 따르면 recycle ()을 호출 할 필요가 없습니다.
Artem Russakovskii 1

28

나는 어떤 종류의 스케일링이 필요없는 훨씬 효과적인 솔루션을 가지고 있습니다. 비트 맵을 한 번만 디코딩 한 다음 이름과 비교하여 맵에 캐시하면됩니다. 그런 다음 이름에서 비트 맵을 검색하여 ImageView에서 설정하십시오. 더 이상 할 일이 없습니다.

이것은 디코딩 된 비트 맵의 ​​실제 이진 데이터가 dalvik VM 힙 내에 저장되지 않기 때문에 작동합니다. 외부에 저장됩니다. 따라서 비트 맵을 디코딩 할 때마다 GC에서 회수하지 않는 VM 힙 외부의 메모리를 할당합니다.

이것을 더 잘 이해하려면 drawable 폴더에 ur 이미지를 보관했다고 가정하십시오. getResources (). getDrwable (R.drawable.)을 수행하여 이미지를 가져옵니다. 매번 이미지를 디코딩하지는 않지만 호출 할 때마다 이미 디코딩 된 인스턴스를 재사용합니다. 본질적으로 캐시됩니다.

이제 이미지가 파일 어딘가에 있거나 외부 서버에서 온 것일 수도 있으므로, 디코딩 된 비트 맵 인스턴스를 캐시하여 필요한 곳에서 재사용 할 책임이 있습니다.

도움이 되었기를 바랍니다.


4
"그런 다음 이름으로 맵에 캐시합니다." 이미지를 정확히 어떻게 캐시합니까?
Vincent

3
실제로 이것을 시도 했습니까? 픽셀 데이터가 실제로 Dalvik 힙 내에 저장되지 않더라도 기본 메모리의 크기는 VM에보고되고 사용 가능한 메모리에 대해 계산됩니다.
ErikR

3
@Vincent 나는지도에 저장하는 것이 어렵지 않다고 생각합니다. HashMap <KEY, Bitmap> map과 같은 것을 제안합니다 .Key는 소스의 문자열 또는 귀하에게 적합한 모든 것이 될 수 있습니다. 경로를 KEY로 가정하고 map.put (Path, Bitmap)으로 저장 한 다음 map.get (Path)를 통해 수신합니다
Rafael T

3
이미지 캐시를 구현하는 경우 HashMap <String, SoftReference <Bitmap >>을 사용하고 싶을 것입니다. 그렇지 않으면 메모리가 부족할 수 있습니다. "은 사실입니다. 지연이 될 수 있음을 이해하면서 메모리가 회수됩니다. bitmap.recycle ()의 목적입니다. 미리 mem을 되찾기위한 힌트입니다.
Dori

28

다음과 같은 방법으로 동일한 문제를 해결했습니다.

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);

그건 괜찮지 만 OnCreate에서 그리기 원에 여러 비트 맵을 사용하고 활동을 4-5 번 호출하므로 비트 맵을 지우는 방법과 비트 맵을 제거하고 활동
0nCreate

27

여기에 두 가지 문제가 있습니다 ....

  • 비트 맵 메모리는 VM 힙이 아니라 기본 힙에 있습니다. BitmapFactory OOM을 참조하십시오
  • 기본 힙에 대한 가비지 콜렉션은 VM 힙보다 느리므로 활동의 onPause 또는 onDestroy를 수행 할 때마다 bitmap.recycle 및 bitmap = null 수행에 대해 매우 적극적이어야합니다.

Android 2.3 이상부터 VM 힙에 있음
FindOut_Quran

27

이것은 나를 위해 일했다!

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

위의 답변 중 어느 것도 나를 위해 효과가 없었지만 문제를 해결하는 끔찍한 추악한 해결 방법을 생각해 냈습니다. 매우 작은 1x1 픽셀 이미지를 프로젝트에 리소스로 추가하고 가비지 수집을 호출하기 전에 ImageView에로드했습니다. ImageView가 비트 맵을 릴리스하지 않았을 수도 있으므로 GC는 절대로 선택하지 않았습니다. 추악하지만 지금은 효과가있는 것 같습니다.

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.

imageView는 실제로 자체적으로 비트 맵을 재활용하지 않는 것처럼 보입니다. 감사합니다
Dmitry Zaytsev

@ 마이크로 이미지 로더의 전체 코드를 추가하거나 비트 맵 이미지로드 링크를 제공 할 수 있습니까? 비트 맵에서 재활용을 사용하면 모든 목록보기가 표시되지만 모든 항목이 공백으로 표시됩니다.
TNR

@ 가비지 수집을 호출하기 전에 imageView = null을 수행하는지 알 수 있습니까?
Youddh

@TNR 여기에 누락 된 것은 bitmap위의 코드에서 이미 표시된 이미지라는 것입니다. 재활용하고, 참조를 지우고 imageView, 작은 교체를 설정 하여 잊어 버릴 필요가 있습니다 gc(). 그리고이 모든 것 : 위 코드에서 NEW 이미지를로드 bitmap하고 표시하십시오 ....
TWiStErRob

이것은 잘못이다. 실제로 표시되어 사용되고있는 대신 비트 맵을 재활용하기 전에 항상 imageView 내용을 지워야합니다.
FindOut_Quran

20

여기에 큰 대답이 있지만 이 문제를 해결 하기 위해 완전히 사용할 수있는 수업 을 원했습니다 . 그래서 나는 그것을했습니다.

다음은 OutOfMemoryError 증거인 BitmapHelper 클래스 입니다.

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;
    }

}

이것을 사용하는 사람에게 : 방금 버그를 수정했습니다 : "int scaledBitmapHeight = scaledBitmap.getWidth ();" 분명히 (잘못된 내가 그것을 대체 "INT scaledBitmapHeight = scaledBitmap.getHeight ();".
파스칼

19

이것은 나를 위해 작동합니다.

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);

그리고 이것은 C # monodroid에 있습니다. 이미지의 경로를 쉽게 변경할 수 있습니다. 여기서 중요한 것은 설정할 옵션입니다.


16

이것은 이미지를로드하고 처리하기 위해 유틸리티 클래스를 커뮤니티와 공유 할 수있는 적절한 장소 인 것 같습니다. 자유롭게 사용하고 수정할 수 있습니다.

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

내 응용 프로그램 중 하나에서에서 사진을 찍어야합니다 Camera/Gallery. 사용자가 카메라에서 이미지를 클릭하면 (2MP, 5MP 또는 8MP 일 수 있음) 이미지 크기가 kBs 에서 s로 다릅니다 MB. 이미지 크기가 위의 코드보다 작거나 최대 1-2MB 인 경우 정상적으로 작동하지만 4MB 또는 5MB보다 큰 이미지의 이미지가있는 경우OOM 프레임에 나타납니다.

그런 다음이 문제를 해결하기 위해 노력했으며 마침내 Fedor의 (아래와 같은 훌륭한 솔루션을 만들기위한 모든 크레딧) 코드를 개선했습니다 :)

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;

}

나는 이것이 같은 문제에 직면하는 친구를 도울 수 있기를 바랍니다!

자세한 내용은 이것을 참조 하십시오


14

방금 몇 분 전에이 문제가 발생했습니다. 내 목록보기 어댑터 관리에서 더 나은 작업을 수행하여 문제를 해결했습니다. 나는 그것이 사용하고있는 수백 개의 50x50px 이미지에 문제가 있다고 생각했는데, 행이 표시 될 때마다 내 사용자 정의보기를 부풀 리려고했다. 단순히 행이 팽창되었는지 확인하기 위해 테스트 하여이 오류를 제거했으며 수백 개의 비트 맵을 사용하고 있습니다. 이것은 실제로 스피너 용이지만 기본 어댑터는 ListView에서 모두 동일하게 작동합니다. 이 간단한 수정 사항은 또한 어댑터의 성능을 크게 향상 시켰습니다.

@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
나는 이것에 대해 충분히 감사 할 수 없다! 나는 이것을보기 전에 잘못된 문제를 쫓고있었습니다. 당신을위한 질문 : 각 또는 내 목록 행에는 고유 한 이름과 사진이 있으므로 각 행의 값을 유지하려면 convertView 배열을 사용해야했습니다. 단일 변수를 사용하여 어떻게 그렇게 할 수 있는지 알 수 없었습니다. 뭔가 빠졌습니까?
PeteH

13

나는 하루 종일 이러한 솔루션을 테스트했으며 나를 위해 일한 유일한 것은 이미지를 가져 와서 GC를 수동으로 호출하는 위의 방법입니다. 필요하지는 않지만 이것이 효과가있는 유일한 방법입니다. 액티비티간에 전환하는 중부 하 테스트를 수행 할 때 내 응용 프로그램에는 목록보기에 축소판 이미지 목록이 있으며 (활동 A라고 말하십시오) 해당 이미지 중 하나를 클릭하면 해당 항목의 기본 이미지를 보여주는 다른 활동 (활동 B라고 함)으로 이동합니다. 두 활동 사이를왔다 갔다 할 때 결국 OOM 오류가 발생하고 앱이 강제로 닫힙니다.

내가 목록보기의 절반을 내리면 충돌이 발생합니다.

이제 활동 B에서 다음을 구현하면 문제없이 전체 목록보기를 진행하고 계속 진행하고 계속 진행할 수 있습니다.

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

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

당신의 해결책을 사랑하십시오! 나는 또한이 버그를 해결하는 데 몇 시간을 보냈습니다. 편집 : 슬프게도 가로 모드에서 화면 방향을 변경할 때 문제가 여전히 존재합니다 ...
Xarialon

이것은 마침내 나와 함께 도움이되었습니다 :-BitmapFactory.Options 옵션 = 새로운 BitmapFactory.Options (); options.InPurgeable = true; options.InSampleSize = 2;
user3833732

13

이 문제는 Android 에뮬레이터에서만 발생합니다. 또한 에뮬레이터 에서이 문제에 직면했지만 장치를 체크인하면 정상적으로 작동합니다.

장치를 확인하십시오. 장치에서 실행될 수 있습니다.


12

내 2 센트 : 비트 맵으로 OOM 오류를 해결했습니다.

a) 이미지를 2 배 확대

b) getView에서 한 번의 호출로 ListView에 대한 사용자 정의 어댑터에서 Picasso 라이브러리 사용 :Picasso.with(context).load(R.id.myImage).into(R.id.myImageView);


이미지를 훨씬 쉽게로드 할 수 있기 때문에 Picasso를 언급하게되어 기쁩니다. 특히 원격으로 저장된 것들.
Chrispix

12

SdCard에서 선택하거나 비트 맵 객체를 변환하기 위해 그릴 수있는 모든 이미지에이 코드를 사용하십시오.

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;

ImageData_Path.get (img_pos) .getPath () 의 이미지 경로를 사용하십시오 .


12

일반적으로 안드로이드 장치 힙 크기는 16MB에 불과합니다 (장치 / OS에 따라 다름 힙 크기 참조 ) SD 카드 또는 리소스 또는 네트워크의 이미지에서 getImageUri 를 사용하려고 시도하면 비트 맵을로드하는 데 더 많은 메모리가 필요하거나 해당 비트 맵으로 작업이 완료된 경우 비트 맵을 null로 설정할 수 있습니다.


1
그리고 setImageURI가 여전히 예외를 얻는다면 이것을 참조하십시오. stackoverflow.com/questions/15377186/…
Mahesh

11

여기의 모든 솔루션은 IMAGE_MAX_SIZE를 설정해야합니다. 이것은 더 강력한 하드웨어를 가진 장치를 제한하며 이미지 크기가 너무 작 으면 HD 화면에서보기 흉하게 보입니다.

더 강력한 장치를 사용할 때 이미지 품질이 더 좋은 삼성 Galaxy S3 및 덜 강력한 장치를 포함한 다른 여러 장치에서 작동하는 솔루션을 찾았습니다.

그것의 요지는 특정 장치에서 앱에 할당 된 최대 메모리를 계산 한 다음이 메모리를 초과하지 않고 스케일을 가능한 가장 낮게 설정하는 것입니다. 코드는 다음과 같습니다.

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;
}

이 비트 맵에서 사용하는 최대 메모리를 할당 된 최대 메모리의 25 %로 설정했습니다. 필요에 따라이 비트 맵을 정리하고이 비트 맵을 정리하고 사용을 마쳤을 때 메모리에 남아 있지 않아야합니다. 일반적 으로이 코드를 사용하여 이미지 회전 (소스 및 대상 비트 맵)을 수행하므로 앱에서 동시에 2 비트 맵을 메모리에로드해야하며 25 %는 이미지 회전을 수행 할 때 메모리가 부족하지 않으면서도 좋은 버퍼를 제공합니다.

이것이 누군가를 도울 수 있기를 바랍니다 ..


11

이러한 OutofMemoryException 을 호출하여 문제를 완전히 해결할 수는 없습니다 System.gc().

참조하여 활동 수명주기

활동 상태는 각 프로세스의 메모리 사용량과 각 프로세스의 우선 순위에 따라 OS 자체에 의해 결정됩니다.

사용 된 각 비트 맵 그림의 크기와 해상도를 고려할 수 있습니다. 크기를 줄이고 해상도를 낮추려면 다시 샘플링하는 것이 좋습니다. 갤러리 디자인 (작은 그림 PNG 하나와 원본 그림 하나)을 참조하십시오.


11

이 코드는 드로어 블에서 큰 비트 맵을로드하는 데 도움이됩니다

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;
    }
}

비동기 작업 대신 로더를 사용하는 것이 더 낫다고 생각하십니까?
Chrispix

어때요 Bitmap.Config.ARGB_565? 고품질이 중요하지 않은 경우.
Hamzeh Soboh
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.