Bitmap으로 인한 OOM 문제
이 섹션 소개:
이전 섹션에서는 Bitmap의 기본 사용법을 배웠고, 이번 섹션에서는 Bitmap의 OOM 문제에 대해 논의하겠습니다. 실제 개발에서 Bitmap으로 인해 발생한 OOM 문제가 발생할 수도 있고 발생하지 않을 수도 있습니다. 이 주제에 대해 알아보겠습니다~ OOM이 무엇인지, 왜 OOM이 발생하는지 이해하고, Bitmap으로 인해 발생하는 문제를 개선해 보세요. OOM 문제~
1. OOM이 뭔가요? OOM이 발생하는 이유는 무엇입니까?
답변: Out Of Memory(메모리 오버플로), 우리 모두는 Android 시스템이 각 앱에 독립적인 작업 공간을 할당한다는 것을 알고 있습니다. 아니면 각 앱이 서로 영향을 주지 않고 독립적으로 실행될 수 있도록 별도의 Dalvik 가상 머신을 할당하세요! 그리고 각각 Android Dalvik 가상 머신에는 현재 점유된 메모리와 우리가 적용하는 메모리 리소스가 이 제한을 초과하는 경우 최대 메모리 제한이 있습니다. , 시스템에서 OOM 오류가 발생합니다! 또한 RAM에 1G 이상의 메모리가 남아 있어도 OOM이 발생합니다! RAM(물리적 메모리)과 OOM을 혼합하지 마세요! 게다가 RAM이 부족하면 OOM뿐만 아니라 애플리케이션도 종료됩니다! Dalvik의 최대 메모리 표준은 모델마다 다릅니다.
ActivityManager activityManager = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE); Log.e("HEHE","最大内存:" + activityManager.getMemoryClass());
를 호출하여 일반적인 최대 메모리 표준을 얻거나 명령줄에 직접 입력할 수 있습니다.
adb shell getprop | grep dalvik.vm.heapgrowthlimit
시스템 소스 코드/system/build를 열 수도 있습니다. .prop 파일에서 파일의 이 부분에 있는 정보를 살펴보세요.
dalvik.vm.heapstartsize=8m dalvik.vm.heapgrowthlimit=192m dalvik.vm.heapsize=512m dalvik.vm.heaptargetutilization=0.75 dalvik.vm.heapminfree=2m dalvik.vm.heapmaxfree=8m
우리가 관심을 갖는 세 가지 영역이 있습니다. heapstartsize 힙 메모리의 초기 크기와 heapgrowthlimit 표준을 기반으로 하는 애플리케이션의 최대 힙입니다. 메모리 크기, 힙 크기는 android:largeHeap을 사용하는 애플리케이션의 최대 힙 메모리 크기를 설정합니다!
여기서 보유하고 있는 여러 모델의 일반적인 최대 메모리 할당 표준을 시도해 보았습니다.
보유하고 있는 머신을 사용해 볼 수도 있습니다~
자, 더 이상 고민하지 말고 OOM 문제 발생에 대해 이야기해 보겠습니다. 이 시점에서 우리는 메모리 관리 부분에 도달했지만 그것은 큰 문제입니다. 지금도 씹을 수가 없어요... 비트맵 OOM 방지 꿀팁을 살펴보겠습니다!
2. Bitmap으로 인한 OOM 방지 팁 요약
1) 낮은 메모리 공간 인코딩 방법을 채택하세요
이전 섹션에서 언급했듯이BitmapFactory.Options이 클래스에서는 inPreferredConfig 속성을 설정할 수 있습니다.
기본값은 Bitmap.Config.ARGB_8888이며 Bitmap.Config.ARGB_4444
Bitmap.Config ARGB_4444로 변경할 수 있습니다. 각 픽셀은 4비트를 차지합니다. 즉, A=4, R=4, G=4, B=4이면 한 픽셀이 4+4+4+4=16비트를 차지합니다
Bitmap.Config ARGB_8888: 각 픽셀이 8비트를 차지합니다. 즉, A=8, R=8, G=8, B=8이면 1픽셀은 8+8+8+8=32비트를 차지합니다
기본값은 ARGB_8888, 즉 1픽셀이 4바이트를 차지합니다!
2) 이미지 압축
도 BitmapFactory.Options입니다. inSampleSize를 통해 배율을 설정합니다. 예를 들어 2라고 쓰면 길이와 너비가 원본의 1/2이 되고 이미지는 1이 됩니다. 스케일링이 수행되지 않은 경우 원본의 /4, 그렇다면 1로 설정하십시오! 하지만 무작정 압축할 수는 없습니다. 결국 이 값은 너무 작습니다. 그렇지 않으면 그림이 매우 흐릿해지며 그림이 늘어나거나 변형되는 것을 방지해야 하므로 프로그램에서 이를 동적으로 계산해야 합니다. inSampleSize의 적절한 값, 그리고 옵션에 inJustDecodeBounds와 같은 메소드가 있습니다. 이 매개변수를 다음으로 설정하세요. true 이후 decodeFiel은 메모리 공간을 할당하지 않지만 호출하여 원본 이미지의 길이와 너비를 계산할 수 있습니다. options.outWidth/outHeight이미지의 너비와 높이를 가져온 다음 특정 알고리즘을 사용하여 적절한 너비와 높이를 가져옵니다. inSampleSize, 홍양 블로그에서 가져온 코드를 제공해 주신 street god에게 감사드립니다!
public static int caculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { int width = options.outWidth; int height = options.outHeight; int inSampleSize = 1; if (width > reqWidth || height > reqHeight) { int widthRadio = Math.round(width * 1.0f / reqWidth); int heightRadio = Math.round(height * 1.0f / reqHeight); inSampleSize = Math.max(widthRadio, heightRadio); } return inSampleSize; }
그런 다음 위의 방법을 사용하세요:
BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; // 设置了此属性一定要记得将值设置为false Bitmap bitmap = null; bitmap = BitmapFactory.decodeFile(url, options); options.inSampleSize = computeSampleSize(options,128,128); options.inPreferredConfig = Bitmap.Config.ARGB_4444; /* 下面两个字段需要组合使用 */ options.inPurgeable = true; options.inInputShareable = true; options.inJustDecodeBounds = false; try { bitmap = BitmapFactory.decodeFile(url, options); } catch (OutOfMemoryError e) { Log.e(TAG, "OutOfMemoryError"); }
3. 시간에 맞춰 이미지를 재활용하세요
많은 수의 비트맵 개체가 참조되고 애플리케이션이 모든 이미지를 동시에 표시할 필요가 없는 경우. 일시적으로 사용되지 않는 비트맵 객체는 즉시 재활용하십시오. 이미지 사용법을 명확하게 알고 있는 일부 시나리오의 경우 부팅 페이지의 이미지와 같이 이미지를 적극적으로 재활용할 수 있습니다. 완료되면 재활용하고, 애니메이션 프레임을 만들고, 하나를 로드하고, 하나를 그리고, 하나를 릴리스하세요! 사용될 때 로드하고, 표시되지 않을 때 null을 설정하거나 직접 재활용하세요! 예: imageView.setImageResource(0); 다만, 경우에 따라 특정 이미지가 반복적으로 로딩, 릴리즈, 리로딩이 되는 경우가 있어 비효율적입니다...
4. 기타 방법
아래 방법은 직접 사용해보지 않으셨는데요. :
1. SoftReference 참조 방법을 통해 이미지 리소스를 간편하게 관리하세요
SoftReference 해시맵 생성 이미지를 사용할 때 먼저 해시맵에 소프트 참조가 있는지, 소프트 참조의 이미지가 비어 있는지 확인하세요. 비어 있으면 이미지를 소프트 참조에 로드하고 해시맵에 추가합니다. 코드에서 이미지 재활용 및 릴리스를 명시적으로 처리할 필요가 없습니다. gc는 자동으로 리소스 릴리스를 처리합니다. 이 방법은 다루기가 간단하고 실용적이며 이전 방법의 반복적인 로드 및 해제의 비효율성을 어느 정도 피할 수 있습니다. 하지만 충분히 최적화되지 않았습니다.
샘플 코드:
private Map<String, SoftReference> imageMap = new HashMap<String, SoftReference>(); public Bitmap loadBitmap(final String imageUrl,final ImageCallBack imageCallBack) { SoftReference reference = imageMap.get(imageUrl); if(reference != null) { if(reference.get() != null) { return reference.get(); } } final Handler handler = new Handler() { public void handleMessage(final android.os.Message msg) { //加入到缓存中 Bitmap bitmap = (Bitmap)msg.obj; imageMap.put(imageUrl, new SoftReference(bitmap)); if(imageCallBack != null) { imageCallBack.getBitmap(bitmap); } } }; new Thread(){ public void run() { Message message = handler.obtainMessage(); message.obj = downloadBitmap(imageUrl); handler.sendMessage(message); } }.start(); return null ; } // 从网上下载图片 private Bitmap downloadBitmap (String imageUrl) { Bitmap bitmap = null; try { bitmap = BitmapFactory.decodeStream(new URL(imageUrl).openStream()); return bitmap ; } catch (Exception e) { e.printStackTrace(); return null; } } public interface ImageCallBack{ void getBitmap(Bitmap bitmap); }
2.LruCache + sd 캐싱 방법
Android 3.1부터 공식에서는 저장된 이미지의 크기가 LruCache보다 큰 경우 캐시 처리를 위해 LruCache도 제공합니다. 값이 설정되면 최근 사용이 가장 적은 사진이 재활용되고 시스템이 자동으로 메모리를 해제합니다!
사용 예:
단계:
1) 먼저 캐시된 이미지의 메모리 크기를 휴대폰 메모리의 1/8로 설정했습니다. 휴대폰 메모리를 얻는 방법: int MAXMEMONRY = (int) (Runtime.getRuntime() .maxMemory() / 1024);
2) LruCache의 키-값 쌍은 각각 URL과 해당 이미지입니다
3) 다시 작성됨 sizeOf라는 메서드가 사진 수를 반환합니다.
private LruCache mMemoryCache; private LruCacheUtils() { if (mMemoryCache == null) mMemoryCache = new LruCache( MAXMEMONRY / 8) { @Override protected int sizeOf(String key, Bitmap bitmap) { // 重写此方法来衡量每张图片的大小,默认返回图片数量。 return bitmap.getRowBytes() * bitmap.getHeight() / 1024; } @Override protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) { Log.v("tag", "hard cache is full , push to soft cache"); } }; }
4) 다음 방법은 캐시를 지우고, 캐시에 사진을 추가하고, 캐시에서 사진을 가져와서 캐시에서 제거하는 방법입니다.
캐시 제거 및 삭제는 필수입니다. 이미지 캐시를 제대로 처리하지 않으면 메모리 오버플로가 보고되므로 주의가 필요합니다.
public void clearCache() { if (mMemoryCache != null) { if (mMemoryCache.size() > 0) { Log.d("CacheUtils", "mMemoryCache.size() " + mMemoryCache.size()); mMemoryCache.evictAll(); Log.d("CacheUtils", "mMemoryCache.size()" + mMemoryCache.size()); } mMemoryCache = null; } } public synchronized void addBitmapToMemoryCache(String key, Bitmap bitmap) { if (mMemoryCache.get(key) == null) { if (key != null && bitmap != null) mMemoryCache.put(key, bitmap); } else Log.w(TAG, "the res is aready exits"); } public synchronized Bitmap getBitmapFromMemCache(String key) { Bitmap bm = mMemoryCache.get(key); if (key != null) { return bm; } return null; } /** * 移除缓存 * * @param key */ public synchronized void removeImageCache(String key) { if (key != null) { if (mMemoryCache != null) { Bitmap bm = mMemoryCache.remove(key); if (bm != null) bm.recycle(); } } }
위 내용은 - 이미지 캐시 메모리 캐싱 기술 LruCache, 소프트 참조에서 발췌했습니다.
이 섹션 요약:
이 섹션에서는 OOM 문제의 원인을 설명하고 인터넷 OOM에서 제공하는 몇 가지 방지 방법을 요약합니다. 비트맵으로 인한 일부 솔루션은 회사에서 만든 앱이 모두 지도 기반이고 사진이 거의 포함되지 않았기 때문에 작성자는 OOM 문제가 발생하지 않았습니다. 그래서 저는 잘 모르겠습니다~ 다음 고급 메모리 관리 과정에서는 이 OOM 문제를 천천히 다루겠습니다. 이 섹션은 여기까지입니다. 감사합니다~
참고자료: Android 애플리케이션의 OOM 문제 분석 및 해결 방법