안드로이드 앱을 개발하다보면 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 에서 확인해 보실 수 있습니다.

Error와 Exception... 에러와 예외...

Error와 Exception은 같다고 생각할 수도 있지만, 사실 큰 차이가 있다.

- Error : 컴파일 시 문법적인 오류와 런타임 시 널포인트 참조와 같은 오류로 프로세스에 심각한 문제를 야기 시켜 프로세스를 종료 시킬 수 있다.

- Exception : 컴퓨터 시스템의 동작 도중 예기치 않았던 이상 상태가 발생하여 수행 중인 프로그램이 영향을 받는 것. 예를 들면, 연산 도중 넘침에 의해 발생한 끼어들기 등이 이에 해당한다.

  프로그램이 실행 중 어떤 원인에 의해서 오작동을 하거나 비정상적으로 종료되는 경우를 프로그램 오류라 하고, 프로그램오류에는 에러(error)와 예외(exception) 두 가지로 구분할 수 있다. 에러는 메모리 부족이나 스택오버플로우와 같이 발생하면 복구할 수 없는 심각한 오류이고, 예외는 발생하더라도 수습할 수 있는 비교적 덜 심각한 오류이다. 이 예외는 프로그래머가 적절히 코드를 작성해주면 비정상적인 종류를 막을 수 있다.

  Error의 상황을 미리 미연에 방지하기 위해서 Exception 상황을 만들 수 있다. java에서는 try-catch문으로 Exception handling을 할 수 있다.

특정 요일을 선택했으면 선택한 요일에 대해서만 알람을 울리고 선택된 요일이 없으면 설정된 시간에 한번만 알람이 울리도록 합니다. 전체적인 코드는 블로그에 올려두기 보다는 그냥 했던 방법을 적어 둘려고 합니다.


//알람 등록 및 취소

	private void registAlarm()
	{
		cancelAlarm();
		
		boolean[] week = { false, mTextRepeatSun.isSelected(), mTextRepeatMon.isSelected(), mTextRepeatTue.isSelected(),
				mTextRepeatWed.isSelected(), mTextRepeatThu.isSelected(), mTextRepeatFri.isSelected(),
				mTextRepeatSat.isSelected() };
		
		boolean isRepeat = false;
		int len = week.length;
		for (int i = 0; i < len; i++)
		{
			if (week[i])
			{
				isRepeat = true;
				break;
			}
		}

		// 알람 등록
		Intent intent = new Intent(this, AlarmReceiver.class);
		
		long triggerTime = 0;
		long intervalTime = 24 * 60 * 60 * 1000;// 24시간
		if(isRepeat)
		{
			intent.putExtra("one_time", false);
			intent.putExtra("day_of_week", week);
			PendingIntent pending = getPendingIntent(intent);
			
			triggerTime = setTriggerTime();
			
			mAlarmManager.setRepeating(AlarmManager.RTC_WAKEUP, triggerTime, intervalTime, pending);
		}
		else
		{
			intent.putExtra("one_time", true);
			PendingIntent pending = getPendingIntent(intent);
			
			triggerTime = setTriggerTime();
			mAlarmManager.set(AlarmManager.RTC_WAKEUP, triggerTime, pending);
		}
		
		showToastMessage(getString(R.string.alarm_time_set_toast));
	}

	private PendingIntent getPendingIntent(Intent intent)
	{
		PendingIntent pIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
		return pIntent;
	}
	
	private void showToastMessage(String message)
	{
		if(mToast == null)
		{
			mToast = Toast.makeText(this, message, Toast.LENGTH_SHORT);
		}
		else
		{
			mToast.setText(message);
		}
		mToast.show();
	}
	
	private long setTriggerTime()
	{
		// current Time
		long atime = System.currentTimeMillis();
		// timepicker
		Calendar curTime = Calendar.getInstance();
		curTime.set(Calendar.HOUR_OF_DAY, this.mAlarmData.getHour(this));
		curTime.set(Calendar.MINUTE, this.mAlarmData.getMinute(this));
		curTime.set(Calendar.SECOND, 0);
		curTime.set(Calendar.MILLISECOND, 0);
		long btime = curTime.getTimeInMillis();
		long triggerTime = btime;
		if (atime > btime)
			triggerTime += 1000 * 60 * 60 * 24;
		
		return triggerTime;
	}

	private void cancelAlarm()
	{
		Intent intent = new Intent(this, AlarmReceiver.class);
		PendingIntent pending = getPendingIntent(intent);
		this.mAlarmManager.cancel(pending);
	}


//AlarmReceiver

public class AlarmReceiver extends BroadcastReceiver
{
	@Override
	public void onReceive(Context context, Intent intent)
	{
		Bundle extra = intent.getExtras();
		if (extra != null)
		{
			boolean isOneTime = extra.getBoolean("one_time");
			if (isOneTime)
			{
				AlarmDataManager.getInstance().setAlarmEnable(context, false);
                                // 알람 울리기.
			}
			else
			{
				boolean[] week = extra.getBooleanArray("day_of_week");

				Calendar cal = Calendar.getInstance();

				if (!week[cal.get(Calendar.DAY_OF_WEEK)])
					return;

                                // 알람 울리기.
			}
		}
	}
}


저는 이와 같이 만들어서 사용합니다.


  Android 의 ListView에서 하나의 Item이 변경이 되면 해당 Item에 대한 dataset을 변경 후 화면에 보이는 내용을 변경해 주어야만 합니다. 그리고 나서 보통 adapter에 있는 notifyDataSetChanged()라는 메소드를 호출하여 화면에 보이는 내용을 변경합니다. 

  하지만 이 방법에는 작은 단점이 있습니다. adapter에 있는 notifyDataSetChanged()메소드를 호출하게 되면 화면에 보이는 모든 ListView의 Item에 대하여 화며을 변경하게 됩니다. 즉 화면에 ListView의 Item이 5개가 있다면 5개 전부 다 다시 그려지게 됩니다. adapter에 있는 data중 하나만 변경되었지만 adapter에서는 어떤 item의 dataset이 변경되었는지 알 수 없으니까요.

  그럼 ListView의 single item에 대해서 변경된 내용을 적용하는 방법이 없을까요? 간단한 방법으로 해결 할 수 있습니다. 변경하고자 하는 View를 ListView의 메소드인 getChildAt(int index) 를 통해서 변경하고자 하는 View를 가지고 올 수 있습니다. 

public View getChildAt (int index)

Added in API level 1

Returns the view at the specified position in the group.

Parameters
indexthe position at which to get the view from
Returns
  • the view at the specified position or null if the position does not exist within the group

변경하고자 하는 index를 알지만 현재 화면에 보이는 ListView의 item에 대해서는 index를 어떻게 확인할까요?  AdapterView의 메소드 중 현재 화면에 보여지는 ListView의 첫번째 Item이 전체 몇번째 index인지를 알려주는 메소드가 있습니다. getFirstVisiblePosition()을 통해서 알 수 있습니다.

public int getFirstVisiblePosition ()

Added in API level 1

Returns the position within the adapter's data set for the first item displayed on screen.

Returns
  • The position within the adapter's data set


그럼 이 두 메소드를 통해서 간단하게 TextView의 내용을 변경해 보도록 하겠습니다.


private void updateSingleItemView(int position)
{
    int firstVisiblePosition = mListView.getFirstVisiblePosition();
    View view = mListView.getChildAt(position - firstVisiblePosition);
    TextView textTitle = (TextView)view.findViewById(R.id.text_title);
    textTitle.setText("Title Change");
}

이와 같은 방법으로 변경하고자 하는 하나의 아이템에 대해서 내용을 변경 할 수 있습니다.

하지만 이 방법은 지금 보이는 화면에View에 대한 변경된 내용이 지속도지 않습니다. 지속되게 할려면 이전에 먼저 꼭 dataset을 먼저 변경해 주시기 바랍니다. 그리고 난 뒤에 single item에 대해서 내용을 변경해야 합니다. 그 후 ListView를 scroll한다든지에 따라서 Adapter의 View getView (int position, View convertView, ViewGroup parent) 에 의해서 화면이 다시 그려질 때 dataset이 변경되어 있지 않다면 이전의 내용으로 보이게 될 것입니다.




Android 4.0 ICS 이상 부터 음악을 듣다보면 기본 런처의 lock  화면에서 이와 같이 음악을 컨트롤 할 수 있는 기능을 확인할 수 있을 것입니다. 이 기능을 활용하기 위해서는 

RemoteControlClient(http://developer.android.com/reference/android/media/RemoteControlClient.html) 를 이용하여 사용할 수 있습니다. Added in API level 14 라는건 확인하셔야 하구요.

Developer 페이지 설명을 보아도 사용 할 수 있도록 잘 설명이 되어 있습니다. 주의사항도 적혀 있구요.

A remote control client object is associated with a media button event receiver. This event receiver must have been previously registered withregisterMediaButtonEventReceiver(ComponentName) before the RemoteControlClient can be registered throughregisterRemoteControlClient(RemoteControlClient).

이와 같은 내용도 있지만. 이외 별도로 AudioFocus를 가지고 있어야 합니다.

AudioManager의 requestAudioFocus()메소드를 통해서 현재 플레이어에 AudioFocus가 있어야지 기본 런처의 lock화면에 이와 같은 음악 컨트롤 할 수 있는 화면이 나오게 됩니다.

등록하는 부분.

private void registerRemoteClient()
	{
		if (Build.VERSION.SDK_INT >= 14)
		{
			try
			{
				if (mAudioManager == null)
				{
					mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
				}
				Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
				mRemoteComponent = new ComponentName(this, AudioPlayerRemoteEventReceiver.class.getName());
				mAudioManager.registerMediaButtonEventReceiver(mRemoteComponent);
				mediaButtonIntent.setComponent(mRemoteComponent);
				mediaButtonIntent.setComponent(mRemoteControlResponder);
				PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, 0);

				myRemoteControlClient = new RemoteControlClient(mediaPendingIntent);
				mAudioManager.registerRemoteControlClient(myRemoteControlClient);

				myRemoteControlClient.setTransportControlFlags(RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE
						| RemoteControlClient.FLAG_KEY_MEDIA_STOP
						| RemoteControlClient.FLAG_KEY_MEDIA_NEXT
						| RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS
						| RemoteControlClient.FLAG_KEY_MEDIA_PLAY
						| RemoteControlClient.FLAG_KEY_MEDIA_PAUSE);
			}
			catch (Exception e)
			{
				if(CNCronosLog.isDebugLevel())
				{
					e.printStackTrace();
				}
			}
		}
	}

unregister

	private void unregisterRemoteClient()
	{
		try
		{
			if (Build.VERSION.SDK_INT >= 14)
			{
				if(mRemoteComponent != null)
				{
					mAudioManager.unregisterMediaButtonEventReceiver(mRemoteComponent);
					mRemoteComponent = null;;
				}
				if (myRemoteControlClient != null)
				{
					mAudioManager.unregisterRemoteControlClient(myRemoteControlClient);
				}
			}
		}
		catch (Exception e)
		{
			if(CNCronosLog.isDebugLevel())
			{
				e.printStackTrace();
			}
		}
	}

기본 lock 화면에다가 정보 보내는 부분.

					MetadataEditor metaEdit = myRemoteControlClient.editMetadata(true);
					
					metaEdit.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, "title");
					metaEdit.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, "artist");
					metaEdit.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, "album");
					metaEdit.putBitmap(MetadataEditor.BITMAP_KEY_ARTWORK, bitmap);
					metaEdit.apply();


현재 player 상태 보내는 부분.

						myRemoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING);
						myRemoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_STOPPED);
						myRemoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_PAUSED);
						myRemoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_STOPPED);
						myRemoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_STOPPED);


이와 같이 사용하면 원하는 상태로 기본 런처의 lock 화면에 음악재생의 컨트롤을 활용할 수 있을 것입니다.


이전 사용하던 마우사를 오래 사용하기도 했고, 바꿀 때가 되기도 해서 알아보던 중

MadCatz RAT7을 살 까 고민하던 중에 로지텍에서 새로운 마우스가 출시한다고 해서 RAT7을 살려던 것을 접고 이 제품을 기다리기 시작 했습니다.

MadCatz 제품의 특징이라면 마우스 부분 파츠를 자기 손에 맞게 직접 위치를 바꿀 수 있겠죠. 그런데 국내 정식수입제품을 사기엔 가격이 아마존에서 사는 것보다 너무 차이가 많이 나서... 사실 RAT7을 아직도 아마존에서 살까 말까 하는 고민은 계속 하고 있긴 합니다..

예약구매를 진행하고. 5월 19일부터 배송 시작한다고 해서 이제서야 받아서 개봉을 해봅니다.

박스모양.


박스 개봉전 마우스 모양을 확인해봄.


내용품. 마우스와 무개추가 들어있는 박스.

연결 시 LED가 들어옴..


무게추를 하나도 안넣고 사용하면 너무 가볍고.

다 넣고 사용하면 괜찮긴 하지만 오래 사용하면 팔목이 아픈듯?? 하기도 하고.. 적당한 무개를 찾아봐야 할 듯 합니다.

무게추 하나당 3.6 그램이니...


음.. 하지만 개발자로서 마우스보다 키보드를 더 많이 사용한다는 것이.. 함정! ^^



'사용기' 카테고리의 다른 글

Anker® 40W 5V / 8A 5-Port 충전기.  (0) 2014.02.21
REALFORCE 87U 구입 (realforce 87u all-45 EK Edition  (2) 2014.02.07
로지텍 G-700  (0) 2011.08.14

안드로이드 개발을 하다 보면 지금보다 조금 더 성능을 낼 수 있는 앱을 만들고 싶어질 때가 있습니다. 개발자 마다 코딩 스킬도 틀리고 더군다나 디바이스마다 스펙도 틀리기 때문에 같은 앱을 누가 만드느냐 어떤 디바이스에서 동작하느냐에 따라서 앱의 performance가 틀리게 나옵니다. 디바이스 스펙은 어쩔 수 없지만 개발하는 중간중간 조금의 신경을 쓴다면 성능이 좋은 앱을 만들 수 있을 거라 생각 됩니다. 안드로이드 앱을 개발하면서 경험했던 내용에 대해서 조금 정리를 해볼려고 합니다.


■ Traceview Profiling (http://developer.android.com/tools/debugging/debugging-tracing.html)

자신이 개발한 메소드 안에서 수행시간이 얼마나 걸리는지 알아보고, 수행 시간이 많이 걸렸다면 그만큼 performance에 영향을 줍니다. 그중에서 Android SDK 에 포함되어 있는 Traceview라는 것이 있습니다. 각 메소드에 대해서 프로파일링 한 결과를 그래픽으로 보여주는 좋은 툴입니다. 하지만 처음 사용하면 이게 뭐지?? 라는 생각을 가지고 있는 좀 복잡해 보이기도 합니다. 


그림과 같이 "Start Method Profiling" 버튼을 클릭 후 앱을 동작시킵니다. 그럼 동작되는 동안 수행되는 메소드에 대해서 프로파일링을 진행합니다. 그리고 나서 같은 위치에서 모양이 바뀐 "Stop Method Profiling" 버튼을 클릭 하고 나면 프로파일링이 끝난 결과를 화면에 보여 줍니다.

딱 이그림이 처음 보면... 뭐 이리 복잡해?? 라는 생각을 갖게 하는 그림 입니다. 

표의 내용은.

 - Incl Cpu Time % / Incl CPU Time : 해당 메소드 및 그것이 호출하는 자식메소드를 포함한 CPU의 수행 시간.

 - Excl Cpu Time % / Excl CPU Time : 메소드가 수행된 CPU 시간

 - Incl Real Time %/ Incl Real Time : 해당 메소드 및 그것이 호출하는 자식 메소드를 포함한 실제 시간.

 - Excl Real Time % / Excl Real Time : 메소드가 수행되는 실제 시간.

 저는 이중에서 Excl Real Time% / Excl Real Time 을 중요하게 봅니다. 해당 표의 값이 높으면 다른 Excel cpu time 이나 incl real time 도 높게 나타날 것입니다. 해당 부분을 클릭 하면 아래로 더 자세한 내용이 나옵니다. 저는 테스트하기 위해서 AsyncTask에서 String의 조합을 강제로 10000회 하도록 했습니다. 전체 프로파일링 중에 대부분 해당 부분에서 수행시간이 많이 나왔습니다.

설명은 DDMS를 이용한 방법을 설명했지만 직접 trace 파일을 생성해서 traceview로 보는 방법도 있습니다. 그건 Android 개발자 페이지에 더 자세하게 나와있으니 잠깐이라도 봐보시기 바랍니다.

■ GUI Optimization

http://developer.android.com/tools/debugging/debugging-ui.html

1. Hierarchy Viewer

Hierarchy Viewer 현재 화면에 보여지는 layout에 대하여 Tree View 형태로 보여주며 개발자가 UI 인터페이스를 디버깅 하고 분석할 수 있도록 도와 주는 도구 입니다. 각 component에 대해서 소비 시간을 분석과 다른 component와 비교도 할 수도 있고 각각의 속성도 파악 합니다. 그리고 해당 component가 어떤 화면인지에 대해서도 볼 수 있습니다.

그림과 같이 화면에 대한 분석화면을 볼 수 있고,


이와 같이 각 component에 대해서 분석한 화면도 볼 수 있습니다.맨 하단에 있는 Measure, layout, and draw performance indicators for this view에 나온 녹색, 노란색, 빨간색 원을 볼 수 있는데 녹색>노란색>빨간색 순으로 좋다고 보시면 됩니다. 그런데 해당 component에 대해서 무조건 녹색을 만들기는 어려워 보입니다. layout의 depth가 깊어지면 제일 상위에 있는 layout에 대해서는 녹색보다는 노란색과 빨간색을 더 많이 보여줍니다. 그래서 부모쪽 보다는 최 하단에 있는 자식 layout과 그에 해당하는 component에 대해서 최적화를 많이 합니다. 그러다 보면 상위로 갈 수록 빨간색과 노란색 보다는 녹색의 비율이 더 많아지더군요.


 - Lint warnings : Android resource(layout. string 등등)을 검사하여 잠재적으로 오류를 나타낼 수 있는 부분에 대하여 미리 검사를 합니다. 실행에는 전혀 문제가 없지만 경고가 잠재적인 오류를 가지고 있기 때문에 확인해 볼 필요는 있습니다.

 - layoutopt : layout의 xml 파일에 대해서 분석하여 비효율적인 부분에 대해서 알려주는 console tool.

Lint Warnings와 layoutopt에 제가 보는 것은 layout의 depth입니다. 지금 layoutopt는 android sdk tool에서 보이지 않습니다. 언제 사라진 것인지도 잘 모르겠습니다. 그래서 요즘은 Lint Warnings를 통해서 layout을 확인합니다. xml에서 layout을 만들고 lint warnings로 확인하다 보면 가끔 

이와 같이 "This RelativeLayout layout or its LinearLayout parent is useless"라는 메시지를 보이는 곳이 있습니다. RelativeLayout이나 LinearLayout은 경우에 따라 달라질 수도 있습니다. 쉽게 현재 사용중인 layout의 부모 layout은 쓸모가 없다는 내용입니다. 저는 이런게 보이면 과감하게 현재보다 바로 윗단계이 있는 layout에 대해서는 과감하게 삭제 합니다. 적어도 쓸모가 없기 때문입니다. Layout 의 Depth를 적게 하면 할수록 화면을 그리는 UI Thread의 작업이 조금이나마 줄어들것입니다. 작업이 줄어들 수록 수행 속도가 빠르다는 것도 다들 알 것이구요. 

제가 경험한 것으로는 한 화면에 너무 많은 컴포넌트들을 보여주고 싶어하는 디자이너와 기획자의 요구에 따라서 화면을 구성하다 보니 많은 layout을 사용하게 되었습니다. 단방향으로 고정된 화면에서 문제가 없었지만, 수많은 layout과 depth가 깊은 화면에 대해서 Animation을 수행할 때와 layout/layout-land 로 구성된 화면에서 screen rotaton을 하여 portrait과 landscape 화면에 대하여 서로 다른 화면을 구성할 때 문제가 되었습니다. 그래서 가급적이면 화면을 구성할 때 layout의 depth를 적게 하라고 권하고 싶습니다.


■ GC(Garbage Collection)

Android 는 디바이스마다 Heap 크기도 틀리고 하다 보니.. 메모리 관리도 중요하다고 할 수 있죠. Java의 GC(Garbage Collection)만 얘기도도 너무나 많으 내용이 될듯 싶고, 찾압면 정말 자세하게 설명해둔 자료들도 많으니 따로 찾아보셔도 될듯 합니다. 앱 개발하면서 메모리를 얼마나 많이 사용 되고 있는지 확인해볼 방법은 여러가지가 있겠지만 전 두가지를 알려드릴려고 합니다. DDMS에 있는 "Allocation Tracker"와 Heap dump 파일 분석을 통해 알아볼까 합니다.

- Allocation Tracker

DDMS에서 확인하고 싶은 앱 선택 후 Allocation Tracker에서 Start Tracking 클릭 후 GetAllocations를 누르면 현재 메모리가 할당된 Object들의 목록이 나타납니다. Allocation Size를 제일 많이 차지하고 있는 항목을 선택하고 나면 두번째 테이블에는 해당 Object의 Stack Trace 정보가 출력됩니다. 만약 해당 Allocation Size가 비정상적으로 많다면 해당 방법으로 잘못된 곳을 찾을 수도 있을것입니다.

- HPROF

힙덤프 파일을 생성해서 분석해볼 수도 있습니다. 힙덤프 생성하는 방법은 adb를 이용하거나 api를 이용. 또 DDMS를 이용해서 할 수 있습니다.

1. ADB 이용

$ adb shell

# mkdir /data/misc

# chmod 777 /data/misc (/data/misc에 쓰기권한 확인)

# ps (애플리케이션 프로세스 ID 확인)

# kill -10 <pid> (SIGUSR1 시그널 전송)

(잠시후 /data/misc 에 heap-dump-*.hprof 파일 생성됨)

$ adb pull <dump-file> <as-file> (로컬로 파일 추출)

ADB shell을 이용할려면 root 권한이 있어야 하고.. 매번 이런식으로 shell 접속하기 귀찮아서 잘 사용 안합니다. ㅡㅡ.

2. API 이요

android.os.Debug.dumpHprofData(fileName);

- fileName의 위치는 쓰기 권한이 있는 곳이어야 합니다. 외장메모리 영역이면 괜찮겠죠? 대신 WRITE_EXTERNAL_STORAGE permission 지정을 잊지 않으시면 됩니다.

3. DDMS 이용.

가장 현실적인 이용방법입니다. adb shell을 일일이 드어가기 귀찮고. 그렇다고 api를 이용하면 앱 release 시에는 해당 부분을 삭제 해 줘야 하기도 하고. 그리고 해당 heap dump 파일을 분석해주는 Eclipse MAT라는 plugin도 있습니다.


Dump HPROF file 버튼을 클릭 합니다. 저가은 경우는 Eclipse MAT플러그인을 상용하는 관계로 바로 아래와 같은 그래픽컬한 화면이 나옵니다. 바로 dump된 파일을 분석해서 그래프로 보여주는 것이죠. 

Shallow Heap과 Retained Heap을 통해서 어디서 메모리 누수가 발생하는지 확인을 할 수 있습니다.

Shallow Heap : 객체 하나당 크기.

Retained Heap : 참조가 유지하고 있는 모든 객체의 크기.

만약 상위  " Retain Heap < 하위 Retain Heap + 모든 Shallow Heap "  으로 나온다면 계산하고 있는 하위 객체에 대해서 메모리 누수가 발생하고 있다고 보면 됩니다.


이외에도 WakeLock을 어떻게 사용하느냐에 따라서 앱 전체 성능에 영향이 있을 수도 있습니다.

이 방법들 이외에도 다른 방법들도 많으 것으로 보입니다. 그런데 이런것으로 확인하는 것도 좋지만, 개발자가 개발할 당시에 조금이라도 신경써서 하면 개발한다면 좋은 성능을 낼 수 있는 앱을 만들 수 있겠죠. 개발이 완료되어 앱이 릴리즈 되었다면 개발되었던 것을 다시 돌아보면서 리펙토링 하는 시간도 갖는다면 좋을 것이구요. 하지만 현실은 그렇지 않아 보입니다. 현실은 하나 완료하기도 전에 다음 업데이트 일정들이 나오고 있으니까요.





5port usb 충전기로 Anker 충전기가 유명하죠.

이전에 White 모델은 25W여서는 port 별로 충전할 수 있는 디바이스가 정해져 있었으나 40W 제품은 이런 단점이 없습니다. 그리고 지금은 White 모델도 40W 로 나온 모델도 있습니다. 제가 구매했을 때 40W로는 블랙 제품밖에 없었죠. 40W와 25W는 가격차이가 있는데 싼거 사겠다고 25W 충전기 구매하면 후회할 수 있습니다 

Amazon에서 구매하고 배대지 통해서 물건을 받았습니다.


박스는 이렇게... 


물건은 이렇게 들어있네요. 설명서도 있지만 pass~

프리볼트입니다. 전원케이블이 110v용 이긴 하지만. 220v 용 돼지코나 220v용 8자 케이블 이요하면 됩니다. 저는 220v용 8자 케이블 구매해서 사용중입니다.

우선 3개 연결해서 충전하고 있습니다.

개발하다보면 가끔 테스트폰들을 충전안시켜서 바로 테스트 못할 때도 있는데.. 머 이렇게 하니 이런저런 문제는 해결되네요. 그리고 2개 구매해서 하나는 집에서 하나는 회사에서 사용 중입니다. 집에서 와이프와 제폰.. 그리고 아이팟터치 이외에 몇몇 기기들 충전용으로 만족하며 사용중~





'사용기' 카테고리의 다른 글

로지텍 G502 PROTEUS CORE 마우스  (0) 2014.05.21
REALFORCE 87U 구입 (realforce 87u all-45 EK Edition  (2) 2014.02.07
로지텍 G-700  (0) 2011.08.14

+ Recent posts