월별 글 목록: 2015년 1월월

날짜 표시에 지역화(Localization)가 잘 고려된 DateFormat 클래스

필자의 경우 안드로이드 애플리케이션에서 날짜를 보여주기 위해서 자바 API의 DateFormat을 많이 사용한다. 그리고 이 애플리케이션은 플레이 스토어에 모든 나라에서 사용할 수 있는 애플리케이션으로 등록했다. 그래서 많은 애플리케이션을 보면 DateFormat을 미국에서 사용하는 포맷으로 사용하거나 개별 국가에 맞는 포맷을 지원(지역화)하려고 /res/values-xx 와 같은 국가별 리소스 폴더에 포맷을 문자열로 저장해서 사용하기도 한다.

국가별로 선호하는 날짜 포맷을 살펴보자. 국가별 날짜 포맷은 아래의 위키피디아 페이지에서 확인할 수 있다. 그리고 이 페이지의 국가별 날짜 포맷은 마이크로 소프트와 IBM에서 사용하는 형태로 정리되어 있다.
http://en.wikipedia.org/wiki/Date_format_by_country
이 페이지를 보면, 날짜를 표시하는데 연도, 달 그리고 일의 순서에 따라서 엔디안(Endican)이 달라지는 것을 알 수 있고, 개별 국가에서 사용하는 엔디안과 날짜를 표기하는 포맷을 확인할 수 있다.  우리나라와 미국에서 주로 사용하는 포맷을 살펴보자.

    • 우리나라
      yyyy년 mm월 dd일, yyyy.mm.dd, yyyy/mm/dd
  • 미국
    일반적으로는 mm/dd/yy or mm/dd/yyyy를 많이 사용하고, 군대, 학교 그리고 정부등에서는 dd mmm(m) yyyy and yyyy-mm-dd 를 많이 사용한다.

위에서 살펴본 우리나라와 미국의 포맷을 보면, 날짜를 표시하는 형태가 달라지기 때문에 지역화가 까다롭다는 것을 알 수 있다.

이렇게 불편한 지역별 날짜 표시를 쉽게 할 수 있도록, 구글님은 안드로이드 젤리빈 4.3(API 18)에 나라별로 날짜를 표시하기 위한 중요한 메서드를 하나 추가하였다. 이 메서드는 아래에서 확인할 수 있다.

static String getBestDateTimePattern(Locale locale, String skeleton)
Returns the best possible localized form of the given skeleton for the given locale.

와우, 로케일과 skeleton을 보고 젤루 좋은 지역화된 포맷을 넘겨준다고 한다. 개인적으로 어떻게 이렇게 좋은 기능을 제공할 수 있나?에 궁금함으로 확인해 보니, android.text.format.DateFormat 클래스는 안드로이드에 ICU4C를 포팅해서 JNI로 필요한 메서드를 호출하고 있다. 그리고 구글의 G+ 애플리케이션의 오픈소스 목록에서 ICU4J를 확인할 수 있다. 구글의 G+ 애플리케이션에서 ICU4J 라이브러리를 사용하는 이유는 안드로이드에 ICU4C가 늦게 포팅이 되기 때문으로 추측해볼 수 있다. 자 이제 이 메서드가 제대로 동작하는지 확인해 보자.

  • FileDateUtil 클래스
    – 이 클래스는 개인 프로젝트에서 파일의 날짜를 표시하기 위해서 사용하고 있다.
    – 이 클래스에서는 DateFormat.getBestDateTimePattern() 메서드가 API 18버전부터 지원하기에 18버전 이후부터는 사용할 수 있도록 구현했다.
package net.sjava.common.date;

import android.annotation.TargetApi;
import android.os.Build;
import android.text.format.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

/**
 *
 * Created by mcsong@gmail.com
 * 
 */
public class FileDateUtil {
	public static String getModifiedDate(long modified) {
		return getModifiedDate(Locale.getDefault(), modified);
	}

	public static String getModifiedDate(Locale locale, long modified) {
		SimpleDateFormat dateFormat = null;

		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
			dateFormat = new SimpleDateFormat(getDateFormat(locale));
		} else {
			dateFormat = new SimpleDateFormat("MMM/dd/yyyy hh:mm:ss aa");
		}

		return dateFormat.format(new Date(modified));
	}
	
	@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
	public static String getDateFormat(Locale locale) {
		return DateFormat.getBestDateTimePattern(locale, "MM/dd/yyyy hh:mm:ss aa");
	}
}
  • MainActivity 클래스
    – 안드로이드에서 화면을 보여주는 메인 클래스이다.
    – FileDateUtil 클래스를 사용해서 현재 시각을 국가별 포맷으로 보여준다.
public class MainActivity extends Activity {
	private TextView tv;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		tv = (TextView)findViewById(R.id.textview);
		
		
		String koreaData = FileDateUtil.getModifiedDate(Locale.KOREA, System.currentTimeMillis());
		String franceData = FileDateUtil.getModifiedDate(Locale.FRENCH, System.currentTimeMillis());
		String defaultData = FileDateUtil.getModifiedDate(Locale.getDefault(), System.currentTimeMillis());
		
		String result = "한국 : " + koreaData + System.getProperty("line.separator");
		result += "프랑스 : " + franceData + System.getProperty("line.separator");
		result += "기본 : " + defaultData + System.getProperty("line.separator");
		
		tv.setText(result);
	}
}

위의 소스를 사용해서 우리나라, 프랑스 그리고 미국(기본값)을 확인한 결과는 다음과 같다.

이제 안드로이드에서 날짜를 보여주는데 주로 사용했던 포맷을 선언해서 날짜를 가져오는 형태보다 DateFormat.getBestDateTimePattern() 메서드를 사용해서 국가별로 편하게 날짜를 확인할 수 있도록 지원할 수 있다.

* Reference
http://site.icu-project.org
http://developer.android.com/reference/android/text/format/DateFormat.html

AsyncTask와 같이 사용하는 ProgressDialog를 사용해서 태스크를 바로 종료시키기

안드로이드 애플리케이션이 데이터를 로딩하는데 AsyncTask를 사용하고, 사용자에게 데이터 로딩을 알려주려고 ProgressDialog를 같이 사용하는 경우를 종종 볼 수 있다. 아래의 그림이 이 예제이다.

위 그림의 상황에서, 사용자가 다른 화면을 보길 원하거나 더는 로딩하기 원하지 않는 경우에도 ProgressDialog 클래스의 cancelable 변수를 false로 설정해서 백 키를 눌러도 데이터 로딩창을 종료하지 않고 계속 보이는 경우를 볼 수 있다. UX의 관점에서 보면, 이런 경우는 좋지 않다.

같은 상황에서 iOS 애플리케이션들의 UX를 보니, 많은 경우 화면을 전환하기 전에 데이터를 로딩하고 로딩을 완료하면 화면을 전환한다. 이 UX로 인해서 사용자는 로딩을 취소하고 쉽게 다른 화면으로 전환할 수 있다. 물론 꼭 그렇지는 않지만, 필자의 경우에는 비교적 iOS의 UX가 안드로이드보다 좋게 느껴진다. 그래서 안드로이드 애플리케이션도 ProgressDialog 창을 종료시키고, 데이터 로딩과 같은 비동기 요청(AsyncTask를 사용하는 경우)을 바로 취소하는 방법을 사용해서 비교적 UX를 개선할 수 있다. 사용자에게 조금 더 좋은 UX를 제공할 수 있는 이 방법을 살펴보자.

1. 백 키를 사용해서 종료하는 ProgressDialog 객체 생성

		pd = new ProgressDialog(this);
		pd.setTitle("");		
		pd.setMessage("Loading...");
		pd.setCancelable(true);
		pd.setOnDismissListener(new OnDismissListener() {
			@Override
			public void onDismiss(DialogInterface dialog) {
				Log.d(TAG, "onDismissed() ");
				
				task.cancel(true);
				//task.cancel(false);
			}
		});

이 코드는 ProgressDialog 객체를 생성하면서 백 키로 창을 종료하면서 Dismiss 이벤트를 받아서 비동기 데이터를 로딩하는 태스크인 task를 취소한다.

2. 비동기로 데이터를 로딩하는 AsyncTask 클래스

	static class LoadingTask extends AsyncTask<String, Integer, Boolean> {
		private ProgressDialog pd = null;
		
		public LoadingTask(ProgressDialog pd) {
			this.pd = pd;
		}
		
		@Override
		protected void onPreExecute() {	
			super.onPreExecute();
			if(pd != null)
				pd.show();
		}
		
		@Override
		protected Boolean doInBackground(String... params) {		
			try {
				Thread.sleep(1000 * 20);
			} catch(InterruptedException e) {
				Log.d(TAG, "Exception : " + e.getLocalizedMessage());
			}
			
			return Boolean.TRUE;
		}

		@Override
		protected void onCancelled(Boolean result) {
			Log.d(TAG, "onCancelled : " + result);
			
			if(pd != null)
				pd.dismiss();
		}
		
		@Override
		protected void onPostExecute(Boolean result) {
			Log.d(TAG, "onPostExecute : " + result);
			
			if(pd != null)
				pd.dismiss();
		}
	}

이 클래스는 비동기로 데이터를 로딩하는 형태로 비동기의 태스크 로직 메서드에서 20초를 Sleep하고 있다. 그리고 이 태스크의 취소에 대한 콜백 메서드인 onCancelled(Boolean result) 메서드를 확인할 수 있다.

이제 위에서 살펴본 코드를 사용해서 사용자가 백 키를 누른 경우, ProgressDialog를 종료하고 LoadingTask도 종료시킬 수 있다. AsyncTask를 종료하는 방법으로 cancel(boolean mayInterruptIfRunning) 메서드를 호출한다. 이 메서드를 호출하면 내부에서 cancelled 변수를 true로 설정한다. 그리고 mayInterruptIfRunning를 true로 호출하면 바로 종료시키기 위한 인터럽트를 던지게 된다. 그래서 AsyncTask가 IO를 looping하면서 읽어들이지 않는 경우에는 cancel(true)로 바로 종료시킬 수 있다.

다음으로 ProgressDialog가 종료하면서 AsyncTask를 취소하는 메서드의 호출 결과를 살펴보자.
– 아래는 cancel(false)을 호출한 결과이다.

01-30 05:56:05.720: D/MainActivity(1416): onDismissed() 
01-30 05:56:24.136: D/MainActivity(1416): onCancelled : true

이 결과로 cancel(false)을 호출하면, doInBackground()가 종료된 후에 onCancelled가 호출된 것을 알 수 있다.

– 아래는 cancel(true)을 호출한 결과이다.

01-30 05:58:33.038: D/MainActivity(1584): onDismissed()
01-30 05:58:33.038: D/MainActivity(1584): Exception : null
01-30 05:58:33.038: D/MainActivity(1584): onCancelled : true

cancel(true)을 호출 결과로 AsyncTask의 doInBackground() 메서드에서 InterruptedException이 발생하게 되고, 이것으로 바로 태스크 로직을 빠져나오게 한다.

이상 살펴본 형태로 AsyncTask와 ProgressDialog를 결합해서 사용하는 경우에 사용자의 반응에 즉시 반응할 수 있는 형태로 개발해서 UX를 개선할 수 있다.

서브라임 텍스트(Sublime Text)를 사용해서 안드로이드 로그를 확인하자.

안드로이드 로그는 로그캣(Logcat) 이라는 툴을 사용해서 에뮬레이터나 기기의 로그를 확인할 수 있다. 일반적으로 안드로이드 스튜디오(Android Studio)나 이클립스(Eclipse)의 로그캣 창으로 확인한다. 아래의 간단한 명령을 사용해서 리눅스 계열의 쉘이나 윈도의 커멘트 창에서 안드로이드 로그도 확인할 수 있다. 아래는 윈도에서 로그를 확인하는 예제이다.

C:\dev>adb logcat

하지만 위 방법으로 안드로이드 로그를 확인하는 과정은 좀 불편하다. 안드로이드 스튜디오나 이클립스에서 로그를 확인하기 위해서 로그캣 창을 조절해야 하는 불편함이 있고, 2개의 모니터를 사용하는 경우 별로도 분리해서 좀 편리하게 사용할 수 있지만 그래도 역시 불편하다.

그래서 로그를 확인하는데 텍스트 작업에 많이 사용하는 편집기로 안드로이드 로그를 확인한다면 위 IDE에서 창을 이동시키거나 2번째 모니터에 로그 창을 이동시켜 놓는 번거로운 과정이 필요 없다. 안드로이드 스튜디오는 Android Device Monitor라는 툴을 제공해서 별도로 분리할 수 있게 한다. 이 툴은 단지 로그만 확인하는 데 사용하기에는 무거운 편이라서 편집기로 확인하는 것이 좋다. 또한, 로그를 검색하거나 이동(스크롤)해서 확인하는 데 편집기가 편하다. 그래서 이 글에서는 개인적으로 많이 사용하는 텍스트 툴인 서브라임 텍스트(Sublime Text)를 사용해서 안드로이드 로그를 확인하는 방법을 살펴보자.

서브라임 텍스트 플러그인으로 안드로이드 로그를 확인할 수 있는 툴은 아래에서 확인할 수 있고, 이 플러그인을 사용하는 방법을 살펴보자.
https://github.com/quarnster/ADBView

* 필수 구성요소

1. 안드로이드 SDK와 ADB 명령이 PATH에 추가되어 있어야 한다.
2. 윈도를 사용하는 경우 텔넷(Telnet) 클라이언트가 설치되어 있어야 한다.
2.1. 확인은 아래의 명령으로 확인할 수 있다.

C:\dev>telnet


이 화면은 텔넷이 활성화된 상태이다.

2.2 설치는 아래의 위치에서 활성화 시키면 된다.
2.2.1 제어판 > 프로그램 추가 삭제 > Windows 기능 사용/사용 안함 > 텔넷 클라이언트 활성
자세한 과정은 이곳(http://ansths.tistory.com/210)을 참고해 보자.

* 설치과정

설치 과정은 Package Control Plugin을 설치하고, 플러그인을 설치해서 로그캣을 연동하는 방법이다.

1. Package Control Plugin(https://packagecontrol.io)을 설치한다.
이 플러그인을 설치하는 방법은 https://packagecontrol.io/installation 에서 확인할 수 있다. 이 페이지에서 서브라임 2, 3 버전에서 View > Show Console 메뉴를 실행해서 나오는 명령 창에, 파이썬 코드를 붙여넣기 해서 실행해서 설치한다. 이 과정의 결과는 아래 화면에서 확인할 수 있다.

그리고 설치를 완료하면 서브라임 텍스트를 재실행한다.

설치 확인은 Preferences > Package Control 메뉴를 실행해서 확인할 수 있다.

2. ADBView 플러그인을 설치하자.
Preferences > Package Control 메뉴를 실행하고, Install Package를 실행하면 패키지를 검색할 수 있다.
아래의 화면은 ADB로 검색된 패키지 목록이고, 선택된 패키지를 설치한다.

3. 로그캣 화면을 실행하자.
Tools > Command Palette…를 실행해서 ADB:Launch를 실행하면 서브라임 텍스트에서 안드로이드 로그를 확인할 수 있다.

이 화면은 필자의 에뮬레이터에 연결해서 보고 있는 로그이다.

4. 로그를 원하는 입맛에 맞게 수정하자.
로그를 원하는 형태로 보려면, 아래 화면으로 확인할 수 있는 설정파일을 수정하면 된다.

이 과정으로 서브라임 텍스트를 사용해서 안드로이드 로그를 편하게 확인할 수 있다.

* Reference

1. https://github.com/quarnster/ADBView
2. http://developer.android.com/tools/help/logcat.html

2014년을 보내며..

2014년이 벌써 사흘이 지났다. 2014년의 마지막은 아이들과의 일정으로 며칠이 지나서야 2014년도에 대한 글을 남겨본다. 올 한해는 이런 일들과 생각을 하면서 지냈다.

1. S모 대기업 프로젝트 유지보수
이 일은 작년에 시작해서 올해에도 계속 유지보수 하는 프로젝트이다. 프로젝트를 하면서, 사용하기 원하는 인증 방법에 대한 문제를 프로젝트 초기에 이슈화했지만, 사용하기 원하는 방법대로 진행을 했다. 결국 인증 방법을 변경하는 작업을 하고 있다.

2. 기존 서비스 유지보수
서비스하는 앱을 유지보수 했다. 서비스는 살아 움직이는 생명체이다 보니 유지보수는 필수이다. 이 작업을 하지 않으면 서서히 죽게 된다.

3. 윈도 애플리케이션 개발 중단
서비스하는 클라우드의 싱크 애플리케이션으로 윈도 애플리케션을 Webdav 프로토콜과 Fuse 파일 시스템으로 Dokan을 사용해서 개발했지만, 품질이 좋지 않았다. 결국, 실패했다. 이제 기존의 모바일 앱에서 사용하는 API를 사용하는 윈도 애플리케이션을 개발할 예정이다.

4. 지금까지 만들어낸 애플리케이션 품질 고민
지금까지 2011, 2012, 2013 그리고 2014년까지 개발한 애플리케이션의 품질에 대해서 안타까움이 생긴다. 이제 회사 분위기는 특정 애플리케이션이 중요하다는 이유로 다른 애플리케이션을 개선하는 작업을 원치 않는다. 개인적으로 서비스를 종료하지 않을 거라면 좋은 품질을 유지하기 위한 시간을 투자해야 한다고 생각한다. 특히 안드로이드 애플리케이션은 롤리팝에서 도입한 머터리얼 디자인을 적용하는 게 정말 필요해 보인다.

2015년의 목표는 두 개만 정했다.

1. 영어공부
매우 필요하다. 올해는 열심히 해보자.

2. 알고리즘 공부
일하다 보면 느끼는 거지만 기본이 부족하다.
그래서 올해 목표는 이 책의 1/2 정도를 이해하는 것이다. 이 책은 3판 까지 번역서가 나와 있지만, 내가 소장하고 있는 책은 2판이라서 2판을 열심히 공부해야 겠다.

그리고, 올해에는 많은 변화를 가져볼 생각이다.

Android Studio 편집기에서 탭(Tab) 캐릭터 사용하기

안드로이드 스튜디오(Android Studio)에서 백스페이스(Backspace)는 기본으로 한자(글자나 공백)씩 지우게 되어 있다. 에디터로 코드를 수정할 때, 탭으로 인덴트 설정만큼 이동시켰다가 뒤로 가려고 백스페이스를 4번 처야 하는데 이게 좀 불편하다. 그래서, 4번을 치지 않아도 원하는 만큼(인텐트 길이) 뒤로 이동시켰으면 좋겠다. 그래서 찾아보니 아래의 그림처럼 Settings > Code Style > Java > Use tab character를 선택하면 된다.

그리고 Code Style의 각 항목을 선택해서 아래 그림과 같이 탭을 사용하도록 변경하면 기존의 공백대신 탭을 사용해서, 더 편하게 편집할 수 있다.

* Reference
http://stackoverflow.com/questions/23632624/android-studio-tab-spacing