drcarter의 DevLog

안드로이드 앱을 개발하다보면 logcat에서 많이 접하게 되는 오류중에 하나가 

java.lang.OutOfMemoryError: bitmap size exceeds VM budget

이 오류이지 않을 까 합니다. 쉽게는 메모리 오류.  앱에서 사용할 수 있는 메모리보다 bitmap을 사용하는데 메모리를 많이 사용하게 되어서 나타나는 오류입니다. 예를 들어서 메모리를 20mb를 사용할 수 있는데 bitmap을 로드할 때 사용할 사용한 메모리가 20mb를 넘어선 것이지요. 그럼 이 상황을 해결할 방법은 어떤게 있을까요?? 


1. sampling option 및 서버에서의 작은 이미지 내려주기.

[BitmapFactory.Options.inSampleSize]

inSampleSize 옵션은, 애초에 decode를 할 때 얼마만큼 줄여서 decoding을 할 지 정하는 옵션 입니다.

inSampleSize 옵션은 1보다 작은 값일때는 무조건 1로 세팅이 되며, 1보다 큰 값, N일때는 1/N 만큼 이미지를 줄여서 decoding 하게 됩니다.

즉 inSampleSize가 2라면 1/2 만큼, 4 이면 1/4만큼 이미지를 줄여서 decoding 해서 Bitmap으로 만들게 주게 됩니다.

decoding의 속도는 2의 지수만큼 비례할 때 속도가 빠르다고 합니다. 


저같은 경우는 Bitmap이미지의 크기가 특정 크기를 넘어가면 inSample옵션을 주어서 이미지의 크기를 줄여서 로드하는 방법을 사용했습니다.

public int inSampleSize

Added in API level 1

If set to a value > 1, requests the decoder to subsample the original image, returning a smaller image to save memory. The sample size is the number of pixels in either dimension that correspond to a single pixel in the decoded bitmap. For example, inSampleSize == 4 returns an image that is 1/4 the width/height of the original, and 1/16 the number of pixels. Any value <= 1 is treated the same as 1. Note: the decoder uses a final value based on powers of 2, any other value will be rounded down to the nearest power of 2.


BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 4;
Bitmap src = BitmapFactory.decodeFile(imagePath, options);


이렇게 BitmapFactory.Options.inSampleSize의 옵션을 사용하는 방법도 있겠지만, 서버에서 이미지를 처음부터 작게 내려주는 것도 방법일겁니다. 만약 thumbnail이미지의 사이즈가 좀 크다 싶으면 서버 개발자와 상의해서 thumbnail이미지를 작게 하는 것도 좋은 방법이겠지요.


2. recycle잘하기.

Bitmap을 recycle을 해야 하는 이유에 대해서는 우선 Android 3.0 (HoneyComb) 이전과 이후의 메모리 모델에 대해서도 이해를 해야 합니다. 간단하게 3.0 이전.. Gingerbread까지는 Bitmap에 Dalvik heap 영역에 bitmap에 로드되지 않고 native heap 영역에 bitmap에 로드 되었습니다. 그래서 GC(Garbage Collection)이후에도 native heap 영역에 로드된 bitmap은 아직 메모리에 해제 되지 않습니다. HoneyComb 부터는 Dalvik heap 영역에 bitmap이 로드되기 때문에 GC 이후에 해제 됩니다. 그럼 Gingerbread까지는 그래서 bitmap에 대해서 recycle을 해줘야 합니다.


- Bitmap 자체가 멤버변수로 된 경우

public class activity extends Activity
{
	ImageView iv;
	Bitmap bitmap;

	@Override
	protected void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		iv = (ImageView) findViewById(R.id.imageview);
		bitmap = BitmapFactory.decodeResource(R.drawable.icon);

		iv.setImageBitmap(bitmap);
	}

	@Override
	protected void onDestroy()
	{
		bitmap.recycle();
		bitmap = null;

		super.onDestroy();
	}
}


- ImageView 자체에서 instance를 확인할 경우.

public class activity extends Activity
{
	ImageView iv;

	@Override
	protected void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		iv = (ImageView) findViewById(R.id.imageview);
		iv.setImageBitmap(BitmapFactory.decodeResource(R.drawable.icon));
	}

	@Override
	protected void onDestroy()
	{
		Drawable d = iv.getDrawable();
		if (d instanceof BitmapDrawable)
		{
			Bitmap bitmap = ((BitmapDrawable) d).getBitmap();
			bitmap.recycle();
			bitmap = null;
		}
		d.setCallback(null);

		super.onDestroy();
	}
}


3. memory leak 확인하기.

개발 중 메모리릭이 생기는지 안생기는지에 대해서 확인해 보는 방법도 있습니다.

adb shell dumpsys meminfo [packagename]

을 하면 아래와 같은 결과가 출력 됩니다. Pss total은 공유되는 메모리를 포함한 크기이고, Private Dirty는 다른 프로세스와는 공유할 수 없는 메모리(페이지) 크기라는 것. 여기서 Private Dirty는 다르게 생각 해 보면, 이 프로세스가 제거되고나면 바로 반환되어 시스템이(또는 다른 프로세스가) 사용할 수 있는 메모리라고 생각 할 수도 있습니다. 

자게한 내용은 

Android Memory Management: Understanding App PSS

에서 보면 확인해 볼 수 있습니다.



다른 방법으로는 DDMS를 이용하는 방법입니다.

DDMS에서 Heap 영역의 변화를  확인해 볼 수 있습니다.


이외에 hprof 파일 분석을 통한 방법으로 memory leak 을 확인해 볼 수 있는데, 이에 대한 내용은.

제 다른 글 내용인 [Android]ANDROID PERFORMANCE OPTIMIZATION 에서 확인해 보실 수 있습니다.