etc./StackOverFlow

Bitmap 개체에 이미지를 로드하는 동안 이상한 OutOfMemory 문제

청렴결백한 만능 재주꾼 2023. 4. 14. 22:02
반응형

질문자 :Community Wiki


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

ListView 의 이미지 미리보기 ListAdapter 사용하여 수행됩니다. 이것은 매우 간단하지만 크기가 조정 된 이미지를 어떻게 넣을 수 있는지 잘 모르겠습니다 (즉 src 로 픽셀이 아닌 작은 비트 크기. 그래서 방금 전화 카메라에서 나온 이미지의 크기를 조정했습니다.

문제는 돌아가서 두 번째 활동을 다시 시작하려고 할 때 OutOfMemoryError

  • 즉석에서 크기를 조정할 수 있는(비트 단위 ) 목록 어댑터를 행별로 쉽게 빌드할 수 있는 방법이 있습니까?

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

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

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

참고: 이것이 내가 한 방법입니다.

 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.imagefilenameButtonImage 입니다.

내 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

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

 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


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

Community Wiki

Android 교육 클래스인 " Displaying Bitmaps Efficiently "는 `java.lang.OutOfMemoryError: 비트맵 크기가 비트맵을 로드할 때 VM 예산을 초과합니다.


비트맵 차원 및 유형 읽기

BitmapFactory 클래스는 다양한 소스에서 Bitmap 을 생성하기 위한 여러 디코딩 메서드( decodeByteArray() , decodeFile() , decodeResource() 이미지 데이터 소스에 따라 가장 적절한 디코딩 방법을 선택하십시오. 이러한 메서드는 생성된 비트맵에 대한 메모리 할당을 시도하므로 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 구성요소의 치수입니다.
  • 현재 장치의 화면 크기 및 밀도입니다.

ImageView 에서 128x96 픽셀 썸네일로 표시된다면 메모리에 로드할 가치가 없습니다.

디코더에 이미지를 서브샘플링하도록 지시하고 더 작은 버전을 메모리에 로드하려면 BitmapFactory.Options 개체에서 inSampleSizetrue 로 설정합니다. 예를 들어 해상도가 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의 거듭제곱으로 반올림하여 최종 값을 사용하기 때문에 2의 거듭제곱 값이 계산됩니다.

이 방법을 사용하려면 먼저 inJustDecodeBounds 를 true로 설정하여 true, pass the options through and then decode again using the new inSampleSize value and inJustDecodeBounds set to 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); }

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

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

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


Community Wiki

Fedor의 코드를 약간 개선했습니다. 기본적으로 동일하지만 (내 의견으로는) 추한 while 루프가 없고 항상 2의 거듭제곱이 됩니다. 원래 솔루션을 만들어준 Fedor에게 감사를 전합니다. 저는 그의 솔루션을 찾을 때까지 꼼짝도 하지 못했습니다. 그러다가 이걸 만들 수 있었습니다. :)

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

Community Wiki

저는 iOS 경험에서 왔으며 이미지를 로드하고 표시하는 것과 같은 기본적인 문제를 발견하고 좌절했습니다. 결국 이 문제를 겪고 있는 모든 사람들은 합리적인 크기의 이미지를 표시하려고 합니다. 어쨌든, 여기 내 문제를 해결한 두 가지 변경 사항이 있습니다.

BitmapFactory.decodeXYZ() 를 수행할 때마다 inPurgeabletrue BitmapFactory.Options 를 전달해야 합니다(바람직하게는 inInputShareabletrue 설정).

Bitmap.createBitmap(width, height, Config.ARGB_8888) ) 을 절대 사용하지 마십시오. 내 말은 절대! 나는 몇 번의 패스 후에 메모리 오류가 발생하지 않는 것을 본 적이 없습니다. 어떤 도움이 되었든 recycle() , System.gc() 양이 없습니다. 항상 예외가 발생했습니다. 실제로 작동하는 또 다른 방법은 드로어블에 더미 이미지(또는 위의 1단계를 사용하여 디코딩한 다른 Bitmap)를 갖고 원하는 대로 크기를 조정한 다음 결과 Bitmap을 조작하는 것입니다(예: Canvas에 전달 더 많은 재미를 위해). 따라서 대신 사용해야 하는 것은 Bitmap.createScaledBitmap(srcBitmap, width, height, false) 입니다. 어떤 이유에서든 brute force create 메소드를 사용해야 한다면 최소한 Config.ARGB_4444 전달하십시오.

이것은 거의 며칠이 아니라도 몇 시간을 절약할 수 있도록 보장합니다. 이미지 크기 조정 등에 대한 모든 이야기는 실제로 작동하지 않습니다(잘못된 크기 또는 이미지 품질 저하 솔루션을 고려하지 않는 한).


Community Wiki

알려진 버그 이며 대용량 파일 때문이 아닙니다. Android는 드로어블을 캐시하기 때문에 몇 개의 이미지를 사용한 후 메모리가 부족해집니다. 그러나 나는 안드로이드 기본 캐시 시스템을 건너뛰는 다른 방법을 찾았습니다.

솔루션 : 이미지를 "assets" 폴더로 이동하고 다음 기능을 사용하여 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); }

Community Wiki

나는 이와 동일한 문제가 있었고 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 방식으로 할당되지 않고 기본 호출을 통해 할당됩니다. 할당은 가상 힙 외부에서 수행되지만 가상 힙에 대해 계산됩니다! "


Community Wiki

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

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

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

먼저 Honey Comb 이전의 SDK 버전에 대해 이야기하겠습니다.

  1. 비트맵은 기본 힙에 저장되지만 자동으로 가비지를 수집하므로 재활용()을 호출할 필요가 없습니다.

  2. {VM 힙 크기} + {할당된 네이티브 힙 메모리} >= {기기에 대한 VM 힙 크기 제한}이고 비트맵을 생성하려고 하면 OOM이 발생합니다.

    주의: VM ALLOCATED MEMORY가 아닌 VM HEAP SIZE가 계산됩니다.

  3. VM 힙 크기는 할당된 VM 메모리가 줄어들더라도 확장 후에는 절대 줄어들지 않습니다.

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

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

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

그럼 Honey Comb에서 시작하는 SDK에 대해 알아보겠습니다.

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

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

  3. 따라서 동일한 힙 크기 제한으로 비트맵을 생성하는 데 더 많은 사용 가능한 메모리가 있으므로 OOM이 발생할 가능성이 적습니다.

다음은 가비지 컬렉션 및 메모리 누수에 대한 관찰 내용입니다.

앱에서 직접 볼 수 있습니다. Activity가 소멸된 후에도 여전히 실행 중인 AsyncTask를 실행한 경우 AsyncTask가 완료될 때까지 Activity는 가비지 수집을 얻지 않습니다.

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

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

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

반복 또는 지연 작업(예: Timer)을 예약하고 onPause()에서 cancel() 및 purge()를 호출하지 않으면 메모리가 누출됩니다.


Community Wiki

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

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

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

요구 사항:

스크린샷

특징:

  • 싱글톤을 사용하여 방향 변경이 있는 경우 캐시를 유지합니다.
  • 할당된 애플리케이션 메모리의 8분 의 1을 캐시에 사용(원하는 경우 수정)
  • 큰 비트맵의 크기가 조정됩니다 (허용하려는 최대 픽셀을 정의할 수 있음).
  • 비트맵을 다운로드하기 전에 사용 가능한 인터넷 연결이 있는지 제어합니다.
  • 행당 하나의 작업 만 인스턴스화하는지 확인합니다.
  • 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을 발생시키는 경우입니다. 그러나 그것은 어쨌든 이상적인 상황도 아니며 더 실현 가능한 방법으로 해결하는 것이 불가능할 가능성이 큽니다.

오류는 댓글로 신고해주세요! :-)


Community Wiki

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

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

Community Wiki

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

 <application android:largeHeap="true"

Community Wiki

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

여기를 참조하십시오: BitmapFactory OOM이 나를 미치게 합니다.

그것은 내가 이 문제에 대한 몇 가지 더 많은 해결책을 찾은 또 다른 토론 스레드로 나를 이끕니다. System.gc(); 를 호출하는 것입니다. 이미지가 표시된 후 수동으로 그러나 이는 실제로 앱이 네이티브 힙을 줄이기 위해 더 많은 메모리를 사용하게 만듭니다. 2.0(Donut) 릴리스에서 더 나은 솔루션은 BitmapFactory 옵션 "inPurgeable"을 사용하는 것입니다. 그래서 간단히 o2.inPurgeable=true; o2.inSampleSize=scale; 직후; .

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

이제 이 모든 것을 말했지만 Java와 Android도 완전히 바보입니다. 따라서 이것이 이 문제를 해결하는 끔찍한 방법이라고 생각한다면 아마도 맞을 것입니다. ;-) 그러나 이것은 나에게 놀라운 일이었고 지금은 힙 캐시에서 VM을 실행하는 것이 불가능하다는 것을 알았습니다. 내가 찾을 수있는 유일한 단점은 캐시 된 그려진 이미지를 폐기하고 있다는 것입니다. 즉, 해당 이미지로 오른쪽으로 돌아가면 매번 다시 그리는 것입니다. 내 응용 프로그램이 작동하는 방식의 경우에는 실제로 문제가 되지 않습니다. 귀하의 마일리지가 다를 수 있습니다.


Community Wiki

bitmap.recycle(); 사용하십시오. 이것은 이미지 품질 문제 없이 도움이 됩니다.


Community Wiki

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

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

Community Wiki

어떤 종류의 확장도 필요 없는 훨씬 더 효과적인 솔루션이 있습니다. 비트맵을 한 번만 디코딩한 다음 해당 이름에 대해 맵에 캐시합니다. 그런 다음 이름에 대해 비트맵을 검색하고 ImageView에서 설정하기만 하면 됩니다. 더 이상 수행해야 할 작업은 없습니다.

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

이것을 더 잘 이해할 수 있도록 드로어블 폴더에 이미지를 보관했다고 상상해 보십시오. getResources().getDrwable(R.drawable.)을 수행하여 이미지를 가져옵니다. 이것은 매번 이미지를 디코딩하지 않지만 호출할 때마다 이미 디코딩된 인스턴스를 재사용합니다. 따라서 본질적으로 캐시됩니다.

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

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


Community Wiki

이것은 나를 위해 일했습니다!

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

Community Wiki

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

  • 비트맵 메모리는 VM 힙이 아니라 기본 힙에 있습니다. BitmapFactory OOM이 나를 미치게 합니다.
  • 기본 힙에 대한 가비지 수집은 VM 힙보다 더 느리므로 활동의 onPause 또는 onDestroy를 통과할 때마다 bitmap.recycle 및 bitmap =null을 수행하는 데 매우 적극적이어야 합니다.

Community Wiki

여기에 훌륭한 답변이 있지만 이 문제를 해결하기 위해 완전히 사용할 수 있는 클래스 를 원했습니다. 그래서 하나를 했습니다.

다음은 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; } }

Community Wiki

위의 답변 중 어느 것도 효과가 없었지만 문제를 해결한 끔찍하게 추한 해결 방법을 생각해 냈습니다. 아주 작은 1x1 픽셀 이미지를 프로젝트에 리소스로 추가하고 가비지 컬렉션을 호출하기 전에 ImageView에 로드했습니다. ImageView가 Bitmap을 출시하지 않았기 때문에 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.

Community Wiki

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

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


Community Wiki

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

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

Community Wiki

내 응용 프로그램 중 하나에서 Camera/Gallery 에서 사진을 찍어야 합니다. 사용자가 카메라에서 이미지를 클릭하면(2MP, 5MP 또는 8MP일 수 있음) 이미지 크기는 kB s에서 MB s까지 다양합니다. 이미지 크기가 위의 코드보다 작거나(또는 최대 1-2MB) 제대로 작동하지만 이미지 크기가 4MB 또는 5MB를 초과하면 OOM 이 프레임에 표시됩니다.

그런 다음 나는이 문제를 해결하기 위해 노력했고 마침내 Fedor의 (훌륭한 솔루션을 만들기 위해 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; }

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

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


Community Wiki

방금 몇 분 전에이 문제가 발생했습니다. 내 목록 보기 어댑터를 더 잘 관리하여 문제를 해결했습니다. 내가 사용하고 있던 수백 개의 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); } ...

Community Wiki

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

따라서 장치를 체크인하십시오. 장치에서 실행될 수 있습니다.


Community Wiki

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

내가 목록보기를 반쯤 내리면 충돌이 발생할 것입니다.

이제 활동 B에서 다음을 구현하면 문제 없이 전체 목록 보기를 살펴볼 수 있고 계속해서 계속 갈 수 있습니다. 그리고 충분히 빠릅니다.

 @Override public void onDestroy() { Cleanup(); super.onDestroy(); } private void Cleanup() { bitmap.recycle(); System.gc(); Runtime.getRuntime().gc(); }

Community Wiki

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

내 Samsung 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%는 이미지 회전을 수행할 때 메모리 부족 없이 좋은 버퍼를 제공합니다.

이것이 누군가를 돕기를 바랍니다.


Community Wiki

비트맵 개체를 변환하기 위해 SdCard 또는 drawable에서 선택하는 모든 이미지에 이 코드를 사용합니다.

 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() 의 이미지 경로를 사용하십시오.


Community Wiki

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


Community Wiki

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

a) 2배만큼 내 이미지 크기 조정

b) 다음과 같이 getView에서 한 번의 호출로 ListView용 사용자 지정 어댑터에서 Picasso Picasso.with(context).load(R.id.myImage).into(R.id.myImageView);


Community Wiki

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

활동 수명 주기 를 참조하여

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

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


Community Wiki

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

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

Community Wiki

출처 : http:www.stackoverflow.com/questions/477572/strange-outofmemory-issue-while-loading-an-image-to-a-bitmap-object

반응형