programing

비트맵 개체에 이미지를 로드하는 동안 이상한 메모리 부족 문제가 발생했습니다.

topblog 2023. 6. 2. 20:04
반응형

비트맵 개체에 이미지를 로드하는 동안 이상한 메모리 부족 문제가 발생했습니다.

나는 있습니다ListView각 행에 두 개의 이미지 버튼이 있습니다.사용자가 목록 행을 누르면 새 활동이 시작됩니다.카메라 레이아웃 문제로 인해 탭을 직접 만들어야 했습니다.결과에 대해 시작되는 활동은 지도입니다.버튼을 클릭하여 이미지 미리 보기를 시작하면(SD 카드에서 이미지 로드) 응용 프로그램이 활동에서 다음으로 돌아갑니다.ListView이미지 위젯에 지나지 않는 새 활동을 다시 시작하기 위한 활동입니다.

의 이미지 ListView커서를 사용하여 수행 중입니다.ListAdapter, 크기를 한 이미지(,더 크기)를 수 잘 .src이미지 버튼을 바로 사용할 수 있습니다.그래서 저는 핸드폰 카메라에서 나온 이미지의 크기를 조정했습니다.

문제는 제가 받은 것입니다.OutOfMemoryError다시 돌아가서 두 번째 활동을 다시 시작하려고 할 때.

  • 목록 어댑터를 한 행씩 쉽게 작성하여 즉시 크기를 조정할 수 있는 방법이 있습니까?

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

  • 대역 외 크기 조정 및 이미지 저장을 수행할 수 있지만, 이 작업은 제가 원하는 작업이 아니지만 샘플 코드를 사용하는 것이 좋습니다.

를 자마자하화비성의 ListView다시 정상적으로 작동했습니다.

FYI: 저는 이렇게 하고 있었습니다.

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

에▁where디R.id.imagefilename입니다.ButtonImage.

여기 내 로그캣이 있습니다.

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 

또한 이미지를 표시할 때 다음과 같은 새로운 오류가 발생합니다.

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

Out Of Memory 오류를 수정하려면 다음과 같은 작업을 수행해야 합니다.

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

Android Training 클래스인 "비트맵 효율적으로 표시"는 예외 'java.lang'을 이해하고 처리하기 위한 몇 가지 훌륭한 정보를 제공합니다.Out Of MemoryError: 비트맵을 로드할 때 비트맵 크기가 VM 예산을 초과합니다.


비트맵 치수 및 유형 읽기

BitmapFactory는 몇 방법을 합니다.decodeByteArray(),decodeFile(),decodeResource() 을 생) 을 사용합니다.Bitmap소스에 가장 합니다.이미지 데이터 소스에 따라 가장 적합한 디코딩 방법을 선택합니다.이러한 방법은 구성된 비트맵에 메모리를 할당하려고 시도하므로 쉽게 결과를 초래할 수 있습니다.OutOfMemory예외.에는 각디딩방유다는음통을디해옵지코션수정다있을 통해 디코딩 옵션을 지정할 수 있는 추가 가 있습니다.BitmapFactory.Options하기. 설정inJustDecodeBoundstrue은 메모리, 딩은메할피을만지하당반환디, 코리모를 합니다.null이지만 설정을 지정하는 outWidth,outHeight그리고.outMimeType이 기술을 사용하면 비트맵을 구성(및 메모리 할당)하기 전에 이미지 데이터의 치수와 유형을 읽을 수 있습니다.

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예외적으로 소스가 사용 가능한 메모리에 적합한 예측 가능한 크기의 이미지 데이터를 제공한다고 전적으로 신뢰하지 않는 한 비트맵을 디코딩하기 전에 비트맵의 치수를 확인합니다.


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

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

  • 메모리에 전체 이미지를 로드할 때 예상되는 메모리 사용량입니다.
  • 응용 프로그램의 다른 메모리 요구 사항에 따라 이 이미지를 로드하기 위해 사용할 메모리 양입니다.
  • 이미지를 로드할 대상 이미지 보기 또는 UI 구성 요소의 치수입니다.
  • 현재 장치의 화면 크기 및 밀도입니다.

를 들어, 이미지로 에 1024x768 이미지를 할 가치가 ImageView.

하려면 디더에게이미를서지브메고작하모버은다로에전니지설드합시정같다록을 설정합니다.inSampleSizetrue의 신의에BitmapFactory.Options를 들어, 2048x1536으로 이미지입니다.inSampleSize4의 비트맵은 약 512x384입니다.이것을 에 로드하는 아닌 0을 이것 메 모 을 비 가 12MB닌 0.75MB가 (다 트니됩용가정을성구사맵아리에로이전드체미에지면하비▁loading▁aumingass▁config▁0▁(정가uration).ARGB_8888). 와 높이를 과 같습니다

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

참고: 2의 거듭제곱은 디코더가 2의 가장 가까운 거듭제곱으로 반올림하여 최종값을 사용하기 때문에 계산됩니다.inSampleSize문서화

이 방법을 사용하려면 먼저 다음을 사용하여 디코딩합니다.inJustDecodeBounds로 설정한.true, pass the options through and then decode again using the new inSampleSize 크value and는 다음과 같습니다.set tofalse':

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축소 이미지를 합니다. 예. " " " " " " " " " " 입니다.

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

다른 소스에서 비트맵을 디코딩하는 데도 비슷한 프로세스를 수행할 수 있습니다.BitmapFactory.decode*필요에 따라 방법을 선택합니다.

페도르의 코드를 조금 개선했습니다.기본적으로 동일하지만 (제 생각에는) 추한 루프가 없으면 항상 2의 거듭제곱이 됩니다.원래의 솔루션을 만든 페도르에게 찬사를 보냅니다. 저는 그의 솔루션을 찾을 때까지 갇혀 있었고, 그리고 나서 이것을 만들 수 있었습니다:)

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

저는 iOS 경험이 있고 이미지를 로드하고 보여주는 것과 같은 기본적인 문제를 발견하고 좌절했습니다.결국, 이 문제가 발생하는 모든 사람은 적절한 크기의 이미지를 표시하려고 합니다.어쨌든, 여기 제 문제를 해결한 두 가지 변경 사항이 있습니다(그리고 제 앱을 매우 반응적으로 만들었습니다).

마다 ㅠㅠㅠㅠㅠㅠBitmapFactory.decodeXYZ() 합격하세요.BitmapFactory.Options와 함께inPurgeable로 설정한.true이면 (으)로)inInputShareable 에설됨정으로 됩니다.true).

절대 사용 안 함Bitmap.createBitmap(width, height, Config.ARGB_8888)! 몇 번가 안 저는 몇 번의 패스 후에 메모리 오류가 발생하지 않는 것을 경험해 본 적이 없습니다.의 양이 없습니다.recycle(),System.gc()예외를 했습니다.그것은 항상 예외를 일으켰습니다.실제로 작동하는 또 다른 방법은 드로잉 가능한 더미 이미지(또는 위의 1단계를 사용하여 디코딩한 다른 비트맵)를 사용하여 원하는 대로 크기를 조정한 다음 결과 비트맵을 조작하는 것입니다(예: 더 재미있게 캔버스에 전달).따라서 대신 사용해야 하는 것은 다음과 같습니다.Bitmap.createScaledBitmap(srcBitmap, width, height, false)어떤 .Config.ARGB_4444.

이를 통해 며칠이 아니더라도 몇 시간을 절약할 수 있습니다.이미지 크기 조정 등에 대해 언급하는 모든 것은 제대로 작동하지 않습니다(잘못된 크기나 열화된 이미지를 해결책으로 고려하지 않는 한).

알려진 버그입니다. 대용량 파일 때문이 아닙니다.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);
}

는 이와 했습니다. 에 BitmapFactory.decodeStream decodeFileFile을 사용했습니다.BitmapFactory.decodeFileDescriptor

decodeFileDescriptordecodeStream/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 방식이 아니라 네이티브 호출을 통해 할당됩니다. 할당은 가상 힙 외부에서 수행되지만에 대해 계산됩니다!"입니다.

내 생각에 가장 좋은 방법은OutOfMemoryError그것을 직면하고 이해하는 것입니다.

의도적으로 유발하기 위해 앱을 만들었습니다.OutOfMemoryError메모리 사용량을 모니터링합니다.

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

허니콤보다 먼저 SDK 버전에 대해 말씀드리겠습니다.

  1. 비트맵은 네이티브 힙에 저장되지만 가비지가 자동으로 수집되므로 recycle()을 호출할 필요가 없습니다.

  2. {VM 힙 크기} + {allocated native heap memory} >= {VM 힙 크기 제한(디바이스에 대한)}에서 비트맵을 생성하려고 하면 OOM이 느려집니다.

    참고: VM 할당된 메모리가 아닌 VM 힙 크기가 계산됩니다.

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

  4. 따라서 VM 힙 크기가 너무 커져서 비트맵에 사용 가능한 메모리를 저장할 수 없도록 최대 VM 메모리를 최대한 낮게 유지해야 합니다.

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

  6. 네이티브 힙 크기도 줄어들지 않을 것이지만 OOM에 포함되지 않으므로 걱정할 필요가 없습니다.

그럼 허니콤에서 SDK Starts from Honey Comb에 대해 이야기해 보겠습니다.

  1. 비트맵이 VM 힙에 저장되고 기본 메모리가 OOM에 대해 계산되지 않습니다.

  2. OOM에 대한 조건은 {VM 힙 크기} > = {디바이스에 대한 VM 힙 크기 제한}보다 훨씬 간단합니다.

  3. 따라서 동일한 힙 크기 제한으로 비트맵을 만드는 데 사용할 수 있는 메모리가 더 많기 때문에 OOM이 더 적게 느려집니다.

다음은 가비지 컬렉션과 메모리 누수에 대한 제 관찰 결과입니다.

앱에서 직접 볼 수 있습니다.활동이 삭제된 후에도 여전히 실행 중인 비동기 작업을 실행한 경우 비동기 작업이 완료될 때까지 해당 활동은 가비지를 수집하지 않습니다.

이는 AsyncTask가 익명 내부 클래스의 인스턴스이기 때문에 Activity의 참조를 보유합니다.

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

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

반복 또는 지연 작업(예: 타이머)을 예약한 상태에서 pause()에서 cancel() 및 purge()를 호출하지 않으면 메모리가 누출됩니다.

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

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

이 답변의 끝에서 소스 코드에 대한 링크를 확인하십시오.

요구 사항:

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

스크린샷

특징:

  • 방향 변경이 있는 경우 싱글톤을 사용하여 캐시를 유지합니다.
  • 할당된 애플리케이션 메모리의 8분의 1을 캐시에 사용(원하는 경우 수정)
  • 비트맵의 크기가 조정됩니다(허용할 최대 픽셀을 정의할 수 있음
  • 비트맵을 다운로드하기 전에 인터넷에 연결할 수 있는지 제어합니다.
  • 행당 하나의 작업만 인스턴스화합니다.
  • 만약 당신이 도망치고 있다면,ListView멀리, 그것은 단순히 사이의 비트맵을 다운로드하지 않을 것입니다.

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

  • 디스크 캐싱.이것은 어쨌든 구현하기 쉬울 것입니다. 디스크에서 비트맵을 가져오는 다른 작업을 가리키기만 하면 됩니다.

샘플 코드:

다운로드되는 이미지는 Flickr의 이미지(75x75)입니다.그러나 처리할 이미지 URL을 지정하면 최대값을 초과하면 응용 프로그램에서 크기를 축소합니다. 프로그램에서 이응프은서에 .Stringvmdk

는 비트맵을 처리하는 좋은 방법을 가지고 있습니다.하지만, 이 애플리케이션에서 저는 예를 하나 들겠습니다.LruCache응용프로그램을 더 실현 가능하게 하기 위해 만든 다른 캐시 클래스 안에 있습니다.

항목.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 파일의 내용을 편집할 필요가 없습니다.

기본 활동.자바의 중요한 것들:

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이 발생한다는 것입니다.하지만 그것은 어쨌든 이상적인 상황도 아니며 더 실현 가능한 방법으로 해결하는 것은 불가능할 가능성이 높습니다.

댓글 오류 신고! :-)

저는 즉시 이미지를 가져와서 크기를 조정하기 위해 다음과 같이 했습니다.이것이 도움이 되길 바랍니다.

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

의 항목이 하나도 작동하지 않으면 매니페스트 파일에 추가합니다.내부 응용 프로그램 태그

 <application
         android:largeHeap="true"

이것은 많은 다른 설명과 함께 매우 오랜 시간 지속되는 문제인 것 같습니다.여기서 가장 일반적으로 제시된 두 가지 답변의 조언을 받아들였지만, 두 가지 모두 프로세스의 디코딩 부분을 수행하는 데 필요한 바이트를 감당할 수 없다고 주장하는 VM의 문제를 해결하지 못했습니다.약간의 발굴을 한 후에 저는 여기서 진짜 문제가 네이티브 힙에서 제거하는 디코딩 프로세스라는 것을 알게 되었습니다.

참고 항목: 비트맵 팩토리 OOM이 나를 미치게 합니다.

그것은 제가 이 문제에 대한 몇 가지 해결책을 더 찾은 또 다른 토론 스레드로 저를 이끌었습니다.하나는 전화하는 것입니다.System.gc();이미지가 표시된 후 수동으로 선택합니다.하지만 이것은 실제로 당신의 앱이 네이티브 힙을 줄이기 위해 더 많은 메모리를 사용하도록 만듭니다.2.0(Donut) 릴리스에서 더 나은 솔루션은 비트맵 팩토리 옵션 "inPurgeable"을 사용하는 것입니다.그래서 간단하게 추가했습니다.o2.inPurgeable=true; 바로 o2.inSampleSize=scale;.

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

이 모든 것을 말했지만, 저는 Java와 Android에도 완전히 둔감합니다.따라서 이 문제를 해결하는 데 좋지 않은 방법이라고 생각한다면 맞는 말일 것입니다. ;-) 하지만 이 방법은 저에게 놀라운 효과를 주었고, 지금은 힙 캐시가 부족한 상태에서 VM을 실행하는 것이 불가능하다는 것을 알게 되었습니다.제가 찾을 수 있는 유일한 단점은 당신이 캐시된 이미지를 폐기하고 있다는 것입니다.즉, 해당 이미지로 오른쪽으로 돌아가면 매번 다시 그립니다.제 애플리케이션이 작동하는 방식의 경우, 그것은 실제로 문제가 되지 않습니다.마일리지는 변동될 수 있습니다.

하기 »bitmap.recycle();이렇게 하면 이미지 품질 문제 없이 도움이 됩니다.

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

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

저는 어떤 종류의 확장도 필요하지 않은 훨씬 더 효과적인 솔루션을 가지고 있습니다.비트맵을 한 번만 디코딩한 다음 이름을 기준으로 맵에 캐시하기만 하면 됩니다.그런 다음 이름에 대해 비트맵을 검색하고 이미지 보기에서 설정하기만 하면 됩니다.더 이상 해야 할 일은 없습니다.

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

이를 더 잘 이해할 수 있도록 그리기 가능한 폴더에 이미지를 보관했다고 상상해 보십시오.getResources().getDrwable(R.drawable)을 수행하면 이미지를 얻을 수 있습니다.이렇게 하면 이미지가 매번 디코딩되지는 않지만 이미 디코딩된 인스턴스를 호출할 때마다 다시 사용합니다.따라서 기본적으로 캐시됩니다.

이제 이미지가 파일 어딘가(또는 외부 서버에서 가져온 것일 수도 있음)에 있으므로 디코딩된 비트맵 인스턴스를 캐시하여 필요한 곳에 재사용하는 것은 사용자의 책임입니다.

이게 도움이 되길 바랍니다.

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

  • 비트맵 메모리가 VM 힙에 있는 것이 아니라 네이티브 힙에 있습니다. 자세한 내용은 비트맵 팩토리 OOM을 참조하십시오.
  • 네이티브 힙에 대한 가비지 수집은 VM 힙보다 느리기 때문에 비트맵을 매우 적극적으로 수행해야 합니다.재활용 및 비트맵 = 활동을 일시 중지하거나 삭제할 때마다 반복됩니다.

나한테 효과가 있었어요!

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

좋은 답변이지만, 이 문제를 해결할 수 있는 완전히 사용 가능한 수업을 원했습니다.그래서 하나 했어요.

비트맵이 여기 있습니다.메모리 부족 오류 방지 도우미 클래스 :-)

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

}

위의 답변 중 어떤 것도 저에게 효과가 없었지만, 저는 그 문제를 해결한 끔찍할 정도로 추악한 해결책을 생각해냈습니다.매우 작은 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.

이것은 나에게 효과가 있습니다.

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# 모노로이드에 있습니다.이미지의 경로를 쉽게 변경할 수 있습니다.여기서 중요한 것은 설정할 옵션입니다.

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

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

내 애플리케이션 중 하나에서 나는 사진을 찍어야 합니다.Camera/Gallery, 있음)를 클릭하면 가 사자가카이클(2MP, 5MP, 8MP) 이미다같다니크(2MP, 5MP, 8MP) 습과지는기음용음있릭면하수(2MP, 8MP)와 다릅니다.kBMB이상이면 또는 이미크코드기 1-2MB이 4MB또 5MB있OOM으)로 됩니다.

그런 다음 저는 이 문제를 해결하기 위해 노력했고 마침내 페도르의 코드를 아래와 같이 개선했습니다.

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;

}

이것이 같은 문제에 직면한 친구들에게 도움이 되기를 바랍니다!

자세한 내용은 이것을 참조하시기 바랍니다.

저는 몇 분 전에 이 문제를 접했습니다.리스트뷰 어댑터를 더 잘 관리해서 해결했습니다.저는 제가 사용하는 수백 개의 50x50px 이미지에 문제가 있다고 생각했습니다. 알고 보니 행이 표시될 때마다 사용자 지정 보기를 부풀리려고 했습니다.행이 부풀려졌는지 확인하는 테스트만으로 이 오류를 제거하고 수백 개의 비트맵을 사용하고 있습니다.이는 실제로 Spinner용이지만 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);
    }
...

이 문제는 Android 에뮬레이터에서만 발생합니다.저도 에뮬레이터에서 이 문제에 직면했지만 장치를 체크인했을 때 잘 작동했습니다.

그러니 장치를 체크인해 주세요.장치에서 실행할 수 있습니다.

저는 하루 종일 이 솔루션들을 테스트했는데 제게 효과가 있었던 것은 이미지를 가져오고 GC를 수동으로 호출하기 위한 위의 접근 방식뿐이었습니다. 이 방법은 필요하지 않다는 것을 알고 있지만 앱을 고부하 테스트를 통해 작업을 전환할 때 유일하게 효과가 있었습니다.내 앱에는 목록 보기(예: 활동 A)에 섬네일 이미지 목록이 있으며, 이러한 이미지 중 하나를 클릭하면 해당 항목의 기본 이미지를 표시하는 다른 활동(예: 활동 B)으로 이동합니다.두 활동을 왔다 갔다 할 때 결국 OOM 오류가 발생하고 앱이 강제로 닫힙니다.

제가 목록 보기를 반쯤 내려갔을 때, 그것은 충돌할 것입니다.

이제 활동 B에서 다음을 구현하면 문제 없이 전체 목록 보기를 검토하고 계속 진행할 수 있습니다.그리고 그것은 충분히 빠릅니다.

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

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

여기에 나와 있는 모든 솔루션은 IMAGE_MAX_SIZE를 설정해야 합니다.이는 더 강력한 하드웨어로 장치를 제한하고 이미지 크기가 너무 낮으면 HD 화면에서 보기 흉하게 보입니다.

저는 삼성 갤럭시 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%는 메모리 부족 없이 좋은 버퍼를 제공합니다.

이것이 누군가에게 도움이 되길 바랍니다.

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() 대신 이미지 경로를 사용합니다.

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

나의 2센트: 나는 비트맵으로 OOM 오류를 해결했습니다:

이미지 크기를 2배로 조정

사용자 지정 어댑터 for a ListView에서 다음과 같은 한 통화 getView를 사용하여 피카소 라이브러리를 사용합니다.Picasso.with(context).load(R.id.myImage).into(R.id.myImageView);

ㅠㅠOutofMemoryException전화로 완전히 해결할 수 없습니다.System.gc() 등등

다음을 참조하여활동 수명 주기

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

사용된 각 비트맵 사진의 크기와 해상도를 고려할 수 있습니다.저는 크기를 줄이고, 해상도를 낮추고, 갤러리의 디자인(작은 사진 PNG 하나, 원본 사진 하나)을 참고하는 것을 추천합니다.

이 코드는 그리기 가능한 큰 비트맵을 로드하는 데 도움이 됩니다.

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

언급URL : https://stackoverflow.com/questions/477572/strange-outofmemory-issue-while-loading-an-image-to-a-bitmap-object

반응형