'3. Implementation/Android'에 해당되는 글 51건
- 2010.08.12 VirtualBox 를 이용하여 안드로이드 빌드하기
- 2010.08.04 [번역] 안드로이드에서 숨겨진 시스템 서비스에 접근하기
- 2010.07.26 [번역] 안드로이드 2.0 의 Service API 변경 2
- 2010.07.24 [번역] Back 그리고 다른 하드 키들: 세가지 이야기
- 2010.07.24 [번역] 인텐트를 이용한 애플리케이션 통합
- 2010.07.24 [번역] 성능을 위한 멀티쓰레딩 (Multithreading For Performance)
- 2010.07.22 [번역] Painless threading
- 2010.07.19 Android Emulator 한글 설정
- 2010.07.19 Changing Android Emulator's orientation
- 2010.07.18 Androidscreencaset - 화면 캡처 및 마우스 키보드 제어
- 2010.02.05 구글의 Android 플랫폼, 그리고 Dalvik VM
VirtualBox 를 이용하여 안드로이드 빌드하기
$ sudo apt-get install git-core gnupg sun-java5-jdk flex bison gperf libsdl-dev libesd0-dev libwxgtk2.6-dev build-essential zip curl libncurses5-dev zlib1g-dev |
You might also want Valgrind, a tool that will help you find memory leaks, stack corruption, array bounds overflows, etc.
$ sudo apt-get install valgrind |
$ sudo apt-get install lib32readline5-dev |
$ sudo add-apt-repository "deb http://kr.archive.ubuntu.com/ubuntu/ jaunty multiverse"
$ sudo add-apt-repository "deb http://kr.archive.ubuntu.com/ubuntu/ jaunty-updates multiverse" |
$ sudo apt-get update
$ sudo apt-get install sun-java5-jdk |
$ sudo update-java-alternatives -l
java-1.5.0-sun 53 /usr/lib/jvm/java-1.5.0-sun
java-6-sun 63 /usr/lib/jvm/java-6-sun |
// 사용하려는 자바 버젼으로 변경한다.
$ sudo update-java-alternatives -s java-1.5.0-sun |
$ jvav -version
java version "1.5.0_19"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_19-b02)
Java HotSpot(TM) Server VM (build 1.5.0_19-b02, mixed mode) |
export http_proxy="http://프록시주소:포트"
export ftp_proxy="http://프록시주소:포트" |
$ source ./.bashrc |
$ curl http://android.git.kernel.org/repo >~/bin/repo --proxy1.0 프록시주소:포트 |
$ sudo apt-get install socket |
#! /bin/bash (echo “CONNECT $1:$2 HTTP/1.0″; echo; cat ) | socket dein.proxy.de 8080 | (read a; read a; cat ) |
$ chmod +x proxy-cmd.sh |
$ export GIT_PROXY_COMMAND=/proxy-cmd.sh |
$ sudo passwd root |
$ sudo mkdir /mnt/virtualbox-shared
$ sudo mount.vboxsf virtualbox-shared /mnt/virtualbox-shared |
[번역] 안드로이드에서 숨겨진 시스템 서비스에 접근하기
원문: http://blog.codetastrophe.com/2008/12/accessing-hidden-system-service-apis-in.html
소스: http://code.google.com/p/codetastrophe/source/checkout
번역: SSKK (http://codemuri.tistory.com)
안드로이드 SDK 는 개발자들이 이를 이용하여 많은 것을 할 수 있게 해준다. 하지만 Public API 를 이용하여 접근할 수 없는 조금 흥미로운 시스템 기능이 있다. 약간의 작업과 http://source.android.com 에서 이용가능한 안드로이드 소스를 이용하여 여전히 이러한 기능에 접근하는 것이 가능하다. 작성한 코드는 여기에 있다.
노출하려고 했던 기능은 GPS 위성 정보를 수집하는 것이다 – 위성 자신의 데이터 (SV – Space Vehicles). 이러한 데이터를 수집하기 위한 콜백을 설정하기 위한 LocationManager 서비스 API 의 숨겨진 메소드들이 있다. 하지만 이 메소드들은 public API 를 통해서는 접근할 수 없다. 이 데이터에 접근할 수 있는 다양한 방법을 찾아보았고 그 중에서 가장 쉬운 방법은 IPC 인터페이스를 이용하여 LocationManager 서비스 자신에게 직접 메시지를 보내는 것이다.
LocationManager 와
LocationManagerService
android.location 네임스페이스의 LocationManager API 는 com.android.server 에 있는 LocationManagerService 에 대한 public 인터페이스이다. 이 서비스는 분리된 프로세스로 실행되고, API 는 안드로이드의 IPC 매커니즘 (Binder) 을 이용하여 그 서비스와 통신한다. 안드로이드 애플리케이션이 LocationManagerService 와 통신하기를 원할 때, 그 API 객체에 대한 참조를 얻기 위해 이 API 호출을 사용한다.
LocationManager mLocationManager =
(LocationManager)getSystemService(Context.LOCATION_SERVICE);
이후에는 일반적으로, 애플리케이션이 (requestLocationUpdate() 를 통해서) 로케이션 이벤트를 등록하기 위해 LocationManager API 를 사용할 것이다. 그런 다음 CellFinder 와 같은 아주 재미있고 흥미로운 것들을 하기 위해 이 데이터를 사용할 것이다.
LocationManager 소스 코드를 파고들면, registerGpsStatusListener() 라는 메소드가 있는 것을 볼 수 있다. 이 메소드는 GPS 위성 정보에 대한 업데이트를 얻을 수 있는 리스너를 설정한다 – GPS 뷰의 RPN, elevation, azimuth, 기타 등등. 원하는게 바로 이거다. 불행히도, 이 메소드 뿐만 아니라 GpsStatusListenerTransport 인자 타입 또한 SDK 에 포함된 android.jar 에는 존재하지 않는다. LocationManagerService 자신과 직접 통신함으로써 이러한 정보에 여전히 접근할 수 있다.
안드로이드 SDK 에서 사용가능한 API는 IPC 를 이용하여 시스템 서비스와 통신한다. 특히, 그 서비스와 통신하기 위해 AIDL 로 정의된 인터페이스를 사용한다. 그러한 서비스 IPC AIP 에 대한 AIDL 명세는 SDK 에서는 이용 가능하지 않지만 안드로이드 소스 코드로부터 그 명세를 얻을 수 있다. AIDL 은 Java 클래스가 서비스에 메시지를 전송하거나 서비스로부터 메시지를 받기 위해 사용할 수 있는 클라이언트 stub 을 생성하는 데 사용된다.
LocationManagerService 에 직접 접근하기
LocationManagerService 와 통신하는 LocationManager 내의 클래스는 GpsStatusListenerTransport 이다. 이 클래스는 IGpsStatusListenerStub 클래스의 확장이고 AIDL 명세로부터 생성되었다 – IgpsStatusListener.aidl, ILocationListener.aidl 그리고 Address.aidl Ehgks 원하는 것을 얻기 위해 필요하다. 이 클래스들이 프로젝트에서 이용가능하기만 하면 (코드 변경을 피하기 위해서 android.location 네임스페이스를 선언했다 하지만 그것들이 속한 네임스페이스가 무엇인지는 중요하지 않다) 원하는 것을 얻는 것은 정말이지 무척 쉽다.
AIDL 인터페이스를 이용하여 LocationManagerService 를 위한 새로운 클라이언트를 작성하는 것이 가능하지만, 현존하는 클라이언트를 재사용하는 것이 실제로 더 쉽다. 새로운 연결을 설정하는 것은 많은 코드를 필요로 하고 이건 무척 짜증나는 일이다. 쉬운 방법은 시스템 서비스에 대한 핸들을 얻기 위해 Java Reflection API 를 이용하는 것이다.
Class c = Class.forName(mLocationManager.getClass().getName());
Field f = c.getDeclaredField(“mService”);
f.setAccessible(true);
ILocationManager mILM = (ILocationManager)f.get(mLocationManager);
자바에서 이처럼 private 필드에 접근하는 것은 일반적으로 제품 코드에서는 눈살을 찌푸리게 만들지만, 우리는 해커이고 데이터를 원하며 그 데이터를 얻기 위해 무엇이든 할 수 있기 때문에 이것을 멈출 수는 없다.
자 이제 ILocationManager API 에 접근하게 되었으므로, GPS 위성 상태 업데이트를 가져오기 위한 리스너를 등록하기 위해 호출할 수 있는 addGpsStatusListener() 메소드가 있다는 것을 알고 있다. 이 메소드는 IGpsStatusListener 객체를 취하고, IGpsStatusListener.stub 을 확장하는 클래스의 인스턴스를 생성할 수 있다. addGpsStatusListener() 에 이 객체를 전달함으로써 위성 상태에 대한 onSvStatusChanged() 콜백을 얻게 될 것이다. 멋지다!
이 환상적인 위성 정보를 얻기 전에, GPS 가 먼저 활성화 되어야 한다. 샘플 app 에서는, LocationManager 를 추가하면서 GPS 를 활성화하기 위해 정규 LocationManager API 를 사용하였다. 이 모든 노력의 결과는 어떤 위성에 관한 약간의 정보를 보여주는 쓸모 없고 지루한 애플리케이션이다.
저런…
만약 당신이 서비스 코드를 파헤친다면, GPS 상태 업데이트를 위한
리스너를 추가하기 위해서는 어떠한 권한도 요구되지 않는다는 것을 알게 된다. 이것은 사소하고 위험도가
낮은 정보 누설 침범에 해당한다. 애플리케이션이 만약 올바른 권한을 가지고 있지 않다면 GPS 로케이션 데이터에 대해 접근할 수 있어서는 안된다. 만일 애플리케이션이 GPS 상태 업데이트를 주시하는 서비스를 생성한다면 ( 그러나 GPS location 업데이트가 아니다, GPS location 업데이트는 ACCESS_FINE_LOCATION 권한을 필요로 한다 ), 다른
모든 애플리케이션이 GPS 를 켜게 되었을 때 이러한 업데이트를 얻게 될 것이다. 이 데이터를 이용하여 휴대전화의 위치를 찾는 것은 약간의 수학을 이용하면 가능하다. 정확한 시간, GPS 위성의 사용자에 대한 상대 위치, 그리고 그 당시 GPS 위성의 절대 위치를 알고 있어야 한다. 시간과 상대 위치는 휴대전화로부터 얻을 수 있고, 위성의 정확한
위치는 http://www.navcen.uscg.gov/GPS/almanacs.htm
에서 찾을
수 있다. 연습삼아
이렇게 하는
방법에 관해서는
독자에게 남겨두겠다.
[번역] 안드로이드 2.0 의 Service API 변경
안드로이드 2.0 의 Service API 변경
번역: SSKK (http://codemuri.tistory.com/)
2010 년 2월 11일 오후 3시 30분 경 Dianne Hackborn 에 의해 포스팅 됨
안드로이드 2.0 의
Service API 변경
작년에 안드로이드 플랫폼은 Service API 에서의 수많은 문제 영역 외에 서비스를 동작시키는 방법에 대해서도 많은 이슈들이 나타나게 되었다. 그 결과로, 안드로이드 2.0 은 개발자와 사용자 모두를 위해 이 영역에 대한 많은 변경과 개선 사항을 소개하였다.
알고 있어야 할 세가지 주요 변경은:
l Service.setForeground() 는 이제 폐기되었고(deprecated) 2.0 에서는 아무것도 하지 않는다.
l 서비스 생명 주기에 있어 아주 쉽게 서비스가 우연히 실행된 채로 남아 있도록 만드는 많은 위험한 케이스가 존재하였다. 2.0 의 새로운 API 는 이것을 좀 더 쉽게 처리할 수 있도록 해준다.
l 안드로이드 2.0 은 또한 최종 사용자에게 그들의 장치에서 실행중인 서비스를 모니터하고 관리하기 위한 새로운 UI 를 소개한다.
서비스의 백그라운드
2.0 에 대해 상세하게 다루기 전에, 서비스에 관한 빠른 요약을 먼저 살펴보는 것이 유용할 것이다. 안드로이드의 서비스 API 는 애플리케이션이 백그라운드에서 작업을 할 수도 있도록 하는 핵심 매커니즘 중의 하나이다. 안드로이드가 설계된 방식에 따라, 애플리케이션이 일단 사용자에게 더 이상 보여지지 않게 되면, 그 애플리케이션은 보통 소모용으로 간주되거나 만일 메모리가 더 요구되는 경우 시스템에 의해서 종료될 수 있는 후보가 된다.
애플리케이션이 이것을 처리하는 주요 방식은 서비스 컴포넌트를 시작하는 것이다. 서비스 컴포넌트는 어떤 가치있는 일을 하고 있으므로 정말 필요하지 않는 한 자신의 프로세스를 종료하지 말 것을 명시적으로 시스템에게 말한다.
이것은 매우 강력한 기능이지만 강력함으로 인해 약간의 책임이 뒤따른다. 활발히 실행중인 서비스는 실행 가능한 다른 것들(사용자가 다시 그 프로세스를 방문할 때 초기화 될 필요가 없는 활동하지 않는 백그라운드 프로세스들을 포함하여) 과는 구별된 리소스를 가지고 있다. 그리하여 개발자가 서비스를 디자인 할 때 서비스가 정말 필요할 때만 실행하도록 하고 우연히 서비스가 오랫동안 남아서 실행이 되도록 하는 버그를 피하는 것에 신경 쓰는 것이 중요하다.
Service.setForeground() 를 다시 디자인하기
안드로이드 1.6 의 마지막 안정화 기간 동안에, 애플리케이션이 그렇게 해서는 안될 때에도 Services.setForeground() 를 사용하는 많은 애플리케이션으로 인한 이슈를 좀더 살펴보기 시작했다. 이 API 는 많이 알리지 않았다 왜냐하면 이것이 대부분의 애플리케이션에서는 사용되어서는 안되며 시스템이 매우 곤란해(hard) 질 수 있기 때문이다: 이 API 는 서비스의 프로세스를 foreground 로 다루어지도록 요청한다, 근본적으로 그 프로세스를 종료시킬 수 없게 되고 그리하여 메모리 빈곤 상태에서 시스템이 회복하는 것을 더 어렵게 만든다.
그 당시 1.6 에서는 이에 대한 어떤 중요한 변경을 가하는 것이 너무 늦었었다 하지만 2.0 에서는 그렇게 했다. Service.setForeground() 는 이제 아무것도 하지 않는다. 이 API 는 항상 서비스가 사용자에게 진행중인 공지사항(ongoing notification)을 보이면서 처리해야 할 어떤 것이 있다는 것을 의도하는 것이었다; foreground 에 있다는 것을 말함으로써 사용자는 어떤 방식으로 서비스가 실행 중이라는 것을 알게 되고 그 서비스를 중지하는 방법을 알리도록 하는 것이다. 그리하여 옛 API 대신에 안드로이드 2.0 은 공지가 foreground 에서 보이도록 하는 두 가지 새로운 API 를 소개한다.
public final void startForeground(int id, Notification notification);
public final void stopForeground(boolean removeNotification);
이것은 또한 서비스와 함께 공지 상태를 관리하는 것을 우연히 더 쉽게 만드는 것이 아니다, 왜냐하면 시스템은 이제 서비스가 foreground 에 존재하는 동안 항상 공지 있다는 것을 보증할 수 있기 때문이다, 그리고 서비스가 사라질 때마다 그 공지도 사라진다.
많은 개발자들은 2.0 이후 버전 뿐만 아니라 더 오래된 플랫폼에서도 동작하는 서비스를 작성하길 원할 것이다; 이것은 선택적으로 새로운 API 가 이용가능 할 때 그 API 를 호출하는 아래 코드처럼 사용함으로써 이루어 질 수 있다.
private static final Class[] mStartForegroundSignature = new Class[] {
int.class, Notification.class};
private static final Class[] mStopForegroundSignature = new Class[] {
boolean.class};
private NotificationManager mNM;
private Method mStartForeground;
private Method mStopForeground;
private Object[] mStartForegroundArgs = new Object[2];
private Object[] mStopForegroundArgs = new Object[1];
@Override
public void onCreate() {
mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
try {
mStartForeground = getClass().getMethod("startForeground",
mStartForegroundSignature);
mStopForeground = getClass().getMethod("stopForeground",
mStopForegroundSignature);
} catch (NoSuchMethodException e) {
// Running on an older platform.
mStartForeground = mStopForeground = null;
}
}
/**
* This is a wrapper around the new startForeground method, using the older
* APIs if it is not available.
*/
void startForegroundCompat(int id, Notification notification) {
// If we have the new startForeground API, then use it.
if (mStartForeground != null) {
mStartForegroundArgs[0] = Integer.valueOf(id);
mStartForegroundArgs[1] = notification;
try {
mStartForeground.invoke(this, mStartForegroundArgs);
} catch (InvocationTargetException e) {
// Should not happen.
Log.w("MyApp", "Unable to invoke startForeground", e);
} catch (IllegalAccessException e) {
// Should not happen.
Log.w("MyApp", "Unable to invoke startForeground", e);
}
return;
}
// Fall back on the old API.
setForeground(true);
mNM.notify(id, notification);
}
/**
* This is a wrapper around the new stopForeground method, using the older
* APIs if it is not available.
*/
void stopForegroundCompat(int id) {
// If we have the new stopForeground API, then use it.
if (mStopForeground != null) {
mStopForegroundArgs[0] = Boolean.TRUE;
try {
mStopForeground.invoke(this, mStopForegroundArgs);
} catch (InvocationTargetException e) {
// Should not happen.
Log.w("MyApp", "Unable to invoke stopForeground", e);
} catch (IllegalAccessException e) {
// Should not happen.
Log.w("MyApp", "Unable to invoke stopForeground", e);
}
return;
}
// Fall back on the old API. Note to cancel BEFORE changing the
// foreground state, since we could be killed at that point.
mNM.cancel(id);
setForeground(false);
}
서비스 생명주기 변경
1.6 에서 부적절하게 자신을 foreground 로 만드는 서비스를 무시함으로 인해 갈수록 늘어나는 또 다른 상황을 보고 있었다, 많은 장치들이 이용가능 한 메모리를 얻기 위해 백그라운드에서 서로 다투고 있는 수많은 서비스를 가지게 되었다.
이 문제는 실행되어야 하는 것보다 많이 실행하고 있는 서비스들이거나 장치 상에서 처리되기를 시도하는 너무 많은 것들이 존재하기 때문이다. 하지만, 우리는 또한 서비스와 플랫폼 사이의 사용호작용 내에서도 많은 이슈들을 발견 하였다, 이 이슈들은 서비스가 올바른 일을 수행하려고 시도할 때 조차 어려움 없이 애플리케이션이 서비스가 실행 중인 채로 남게 만들었다. 일반적인 시나리오를 살펴 보자.
1. 애플리케이션은 startService() 를 호출한다.
2. 이 서비스는 onCreate(), onStart() 을 획득하고 그리고 나서 어떤 작업을 처리할 백그라운드 쓰레드를 생성한다.
3. 시스템은 메모리가 꽉 차게 되었고, 그래서 현재 실행중인 서비스를 종료시켜야 한다
4. 메모리가 해제된 이후에, 그 서비스는 다시 시작되고, onCreate() 가 호출된다 하지만 onStart() 는 호출되지 않는다 왜냐하면 새로운 인텐트 명령을 가지고 startService() 에 대한 다른 호출이 없기 때문이다.
이제 이 서비스는 어떤 작업을 하고 있는 지를 깨닫지 못한 채 그리고 언제 자신을 멈추어야 하는 지를 모른 채, 생성된 채로 남아 있을 것이다.
이것을 해결하기 위해, 안드로이드 2.0에서는 Service.onStart() 이 폐기되었다. (이전 버전의 플랫폼에서 사용되었기 때문에 비록 여전히 존재하고 동작하지만). 이 함수는 시스템이 서비스를 관리하기 위한 더 좋은 제어를 제공하기 위해 새로운 Service.onStartCommand() 콜백으로 대체 되었다. 여기에서의 핵심은 이 함수에 의해 반환되는 새로운 결과 코드에 있다. 이 결과 코드는 만일 서비스 프로세스가 실행 중에 종료된다면 시스템이 그 서비스를 어떻게 해야 하는 지를 알려준다.
l START_STICKY 는 기본적으로 이전과 동일하게 동작하도록 한다, 서비스가 “시작된” 채로 남아 있다면 나중에 시스템에 의해 재 시작될 것 이다. 이전 버전의 플랫폼과의 유일한 차이점은 만일 프로세스가 종료되어 재시작 된다면, 아예 호출되지 않는 것이 아니라 onStartCommand() 는 null 인텐트를 가지고 다음 서비스의 인스턴스에서 호출될 것이다. 이러한 모드를 사용하는 서비스는 항상 이러한 케이스를 체크해야 하고 올바르게 이것을 처리해야 한다.
l START_NOT_STICKY 는 onStartCreated() 가 반환된 이후에, 만약 전달할 어떤 시작 명령(start command)도 남아 있지 않은 채 프로세스가 종료된다면, 그 서비스는 재시작 되는 대신 중지될 것이다. 이것은 서비스에 전송된 명령어를 실행하는 동안만 수행하길 원하는 서비스의 경우에 좀더 많이 의미가 통한다. 예를 들어, 서비스가 어떤 네트워크 상태를 조사하기 위해 알람으로부터 매 15분마다 시작된다고 하자. 만일 그 작업을 하는 중에 종료된다면, 그 서비스를 그냥 중지된 채로 놔두고 다음 알람 시 시작되는 것이 최선일 것이다.
l START_REDELIVER_INTENT 는 만약 서비스의 프로세스가 해당 인텐트에서 stopSelf() 가 호출되기 전에 종료된다면, 그 인텐트가 완료될 때까지 서비스에 재배달 되는 것을 제외하면 START_NOT_STICKY 와 같다. (만일 몇번의 시도에도 여전히 완료할 수 없다면, 특정 시점에 시스템은 포기한다). 이것은 처리해야 할 명령어를 받아서 전송된 각 명령을 반드시 완료하도록 하기 위한 서비스에 유용하다.
현존하는 애플리케이션과의 호환성 때문에, 이전 버전의 플랫폼에서 개발된 애플리케이션의 디폴트 반환값은 null 인텐트를 가지고 onStart() 를 호출하지 않는 이전 동작을 제공하는 특수한 START_STICKY_COMPATIBILITY 코드이다. 만약 API 버전 5 이상에서 개발한다면, 디폴트 값은 START_STICKY 이고 null 인텐트를 가지고 호출되는 onStart() 또는 onStartCommand() 를 처리할 준비가 되어 있어야 한다.
플랫폼에 따라 이전 그리고 새로운 API를 모두 사용하는 서비스 역시 쉽게 작성할 수있다. 해야할 모든 것은 아래 코드를 가지고 2.0 SDK 에서 컴파일 하는 것이다.
// This is the old onStart method that will be called on the pre-2.0
// platform. On 2.0 or later we override onStartCommand() so this
// method will not be called.
@Override
public void onStart(Intent intent, int startId) {
handleStart(intent, startId);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
handleStart(intent, startId);
return START_NOT_STICKY;
}
void handleStart(Intent intent, int startId) {
// do work
}
새로운 “실행중인 서비스”
사용자 인터페이스
언급할 마지막 이슈는 한 장치에서 사용가능한 많은 메모리 내에서 너무 많은 서비스가 실행되고 있다는 점이다. 이것은 설치된 애플리케이션의 버그 또는 설계 결함 때문이거나 아니면 사용자가 너무 많은 것을 처리하려고 하는 것 때문이다. 사용자는 시스템에서 현재 무슨 일이 일어나고 있는지를 볼 수 없었다, 그러나 서비스의 사용이 사용자 경험에 점점 더 많은 영향을 끼치게 되므로 이 것을 노출하는 것이 중요하게 되었다.
이것을 지원하기 위해, 안드로이드 2.0 은 애플리케이션 시스템 설정에서 이용가능한 새로운 “실행중인 서비스” 액티비티를 소개한다. 이것이 불려질 때, 다음과 같이 보인다.
메인 콘텐츠는 사용자가 관심을 가지는, 실행중인 프로세스에 의해 정렬된, 모든 실행중인 서비스의 목록이다. 여기 예에서는 세가지 서비스가 보인다.
l GTalkService 는 표준 구글 애플리케이션 집합의 일부이다; 구글의 “gapps” 프로세스 내에서 실행 중이고, 현재 6.8 MB 의 메모리를 소비하고 있다. 3시간 55 분 전에 시작되었고, 이 장치에서는 처음 부팅된 시간이다.
l ActivityService 는 Phonebook App 의 일부이고, 4MB 의 메모리를 소비하고 있다. 이 것 역시 부팅 때부터 실행되고 있다.
l SoftKeyboard 는 3rd Party 의 입력 수단이다. 입력 메소드를 전환 때문에 약 4분전 부터 실행되고 있다.
사용자는 서비스를 제어하기 위해 목록을 선택할 수 있다; 명시적으로 시작되어 실행 중인 일반 서비스의 경우, 서비스를 멈추도록 하는 대화상자를 보여줄 것이다.
입력 메소드와 같은 일부 다른 서비스들은 다른 이유로 실행 중이다. 이들의 경우, 그 서비스를 선택하게 되면 그 서비스를 관리하기 위한 UI 가 나타나게 될 것이다. (입력 메소드의 경우 시스템의 입력 설정)
마지막으로, 화면의 하단에는 몇몇 숫자가 보인다. 이 숫자를 번역하는 법을 안다면, 장치의 메모리 상태에 대한 많은 정보를 얻게 된다.
l Avail: 38MB+114MB in 25 는 38 MB 의 완벽하게 프리한 (또는 unrequired cache 에 사용될 수 있는) 메모리를 가지고
있고, 언제든 종료될 수 있는 25 개의 백그라운드 프로세스에 114 MB 의 또다른 이용가능한 메모리가 있다는 것을 말한다.
l Other: 32MB in 3 는
장치에 종료될 수 없는 3개의 프로세스(다시 말해서, 현재 foreground 로 간주되어 실행을 계속 해야 하는 프로세스) 내에 이용할 수 없는 메모 리가 32 MB 정도 있다는 것을 의미한다
대부분의 사용자에게, 이 새로운 사용자 인터페이스는 현존하는 “태스크 킬러” 애플리케이션과 비교하여 백그라운드 애플리케이션을 제어하기 위한 좀더 효과적인 방법이 될 것이다. 대부분의 경우 장치를 느리게 만드는 이유는 너무 많은 서비스가 실행하려고 하고 있기 때문이다. 이것은 시스템이 어떤 백그라운드 프로세스도 실행할 수 없도록 하고, 그리고 궁극적으로 모든 서비스가 실행될 수 없는 결과를 초래할 수 있다. 실행중인 서비스 UI 는 어떤 서비스를 중지할지를 결정하는 것을 돕기 위해 실행중인 서비스에 관한 정보를 제공하고자 하는 것이다. 이것은 수많은 방법으로 무심코 애플리케이션을 파괴할 수 있는 애플리케이션 강제 종료 API 를 사용하지 않는다.
개발자에게 이 UI 는 서비스가 제대로 동작하고 있는지를 확인하기 위한 중요한 도구이다. App 을 개발할 때, 우연히 서비스가 실행 중인 채로 남게 되지 않도록 하기 위해 실행 중인 서비스를 계속해서 주시해야 한다. 또한 사용자가 자유롭게 서비스를 중지할 수 있다는 것도 이제 기억해야 한다.
안드로이드의 서비스는 매우 강력한 도구이지만, 애플리케이션 개발자가 전반적인 사용자 경험에 해를 끼칠 수 있는 주요한 그리고 미묘한 방법중의 하나이다.
끝.
[번역] Back 그리고 다른 하드 키들: 세가지 이야기
Back 그리고 다른 하드 키들: 세가지 이야기
번역: SSKK (http://codemuri.tistory.com/)
Back 그리고 다른 하드 키들: 세가지 이야기
2009년 12월 18일 오전 9시에 Dianne Hackborn 에 의해 포스팅 됨
안드로이드 2.0은 Droid 와 같은 최신 장치에 나타난 가상 하드키를 지원하기 위한 몇가지 특수 기능을 포함하여 새로운 기능과 BACK, MENU 와 같은 하드키 처리 지원 등을 새로이 소개한다.
이 문서는 이러한 변화에 대한 세가지 이야기를 소개할 것이다: 아주 단순한 것부터 굉장히 상세한 것까지. 선호하는 한가지를 선택하라.
첫 번째 이야기: 개발자들을 위해 좀더 쉽게 만들기
만약 Android 플랫폼의 base 애플리케이션을 살펴보았다면, 아주 흔한 패턴을 알게 될 것이다. BACK 키를 가로채기 위해 약간의 마법을 추가하고 먼가 다르게 처리한다. 이렇게 하기 위한 그 마법은 아래처럼 보일 것이다.
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
// do something on back.
return true;
}
return super.onKeyDown(keyCode, event);
}
액티비티 내에서 BACK 키를 가로채는 방법은 역시 개발자들이 묻는 가장 흔한 질문 중의 하나이다, 그래서 2.0 에서는 이것을 좀 더 단순하고 쉽게 만들기 위해 몇 가지 새로운 API 를 가지고 있다.
@Override
public void onBackPressed() {
// do something on back.
return;
}
만약 이것이 당신이 관심을 두는 전부라면, 2.0 이전의 플랫폼이 지원하는 지에 대해서 걱정할 필요가 없다. 그게 다라면 여기서 읽기를 멈출 수 있다. 그렇지 않다면 좀 더 읽어 보라.
두 번째 이야기: 긴 입력 받아들이기
안드로이드 플랫폼에 아주 최근에 더해진 것 중의 하나는 액션을 처리하기 위한 하드 키에 대한 긴 입력의 사용이었다. 1.0 에서는 최근 app 전환을 위한 HOME 키에 대한 긴 입력과 음성 다이얼을 위한 CALL 키의 긴 입력이었다. 1.1 에서는 음성 검색을 위한 SEARCH 키에 대한 긴 입력을 소개하였다. 그리고 1.5에서는 아직 IME 를 인지할 수 없었던 애플리케이션을 위한 백워드 호환성을 위해 소프트 키보드를 보여주기 위한 MENU 키의 긴 입력을 소개하였다.
(여담으로, MENU 키의 긴 입력은 백워드 호환성만을 위한 것이었고 그리하여 이 것이 사용될 때 소프트 키보드가 강력하게 화면에 머무르는 약간 놀라운 행위를 가지고 있다. – BACK 키를 누르기 전까지는 소프트 키보드가 사라지지 않는다 - 이 것은 소프트 키보드에 접근하기 위한 표준 방법이 아니고, 요즈음 작성된 모든 앱들은 보다 표준적이고 시각적인 방법으로 IME 를 가져오도록 해야 한다.)
불행히도 이러한 기능의 진화는 최상의 구현보다는 좀 못하게 만들었다: 모든 긴 입력 감지는 timed 메시지를 사용하여 클라이언트 측 프레임워크의 디폴트 키 처리 코드 내에 구현되었다. 이것은 많은 중복 코드를 낳았고 어떤 동작은 문제를 야기했다: 실제 이벤트 처리 코드는 긴 입력에 대한 개념이 없었고 그들에 대한 모든 타이밍은 애플리케이션의 메인 쓰레드 내에서 이루어 졌기 때문에, 애플리케이션은 긴 입력이 타임아웃 될 때 까지 충분히 업데이트하지 않아 느려질 수 있었다.
안드로이드 2.0에서는 긴 입력을 위한 실제 KeyEvent API 와 콜백 함수를 제공함으로써 모든 것이 변화하였다. 이들은 애플리케이션에 있어 긴 입력 처리를 굉장히 단순하게 해주었고 프레임워크와 올바르게 상호작용하도록 하였다. 예를 들어: 어떤 하드 키에 대한 긴 입력을 위한 고유의 액션을 제공하기 위해 프레임워크에서 제공하는 디폴트 액션 Activity.onKeyLongPressed() 를 재정의 할 수 있다.
개발자에게 있어 가장 중요한 것은 아마도 BACK 키 처리의 변화이다. 이전에 디폴트 키 처리는 다른 하드 키와 달리 이 키가 눌렸을 때 이 키에 대한 액션을 실행하였다. 2.0 에서는 BACK 키는 이제 키가 릴리스 되었을 때 실행된다. 그러나 현존하는 App 들을 위해, 프레임워크는 호환성의 이유로 인해 키 다운 시 액션을 계속해서 실행할 것이다. App 에서 새로운 동작을 활성화 하기 위해서는 매니페스트 내에 android:targetSdkVersion 을 5 이상으로 설정해야만 한다.
CALL 키에 대한 긴 입력과 짧은 입력에 대한 특수한 액션을 구현하는 액티비티 하위클래스의 코드 예를 소개한다.
@Override
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_CALL) {
// a long press of the call key.
// do our work, returning true to consume it. by
// returning true, the framework knows an action has
// been performed on the long press, so will set the
// canceled flag for the following up event.
return true;
}
return super.onKeyLongPress(keyCode, event);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_CALL && event.isTracking()
&& !event.isCanceled()) {
// if the call key is being released, AND we are tracking
// it from an initial key down, AND it is not canceled,
// then handle it.
return true;
}
return super.onKeyUp(keyCode, event);
}
위의 코드는 프레임워크에 의해 일반적으로 처리되는 키에 대한 다른 행위를 구현하고 있다는 가정을 하고 있는 것을 기억하라. 다른 키에 대한 긴 입력을 처리하고 싶다면, 프레임워크가 그 키를 트랙킹 하도록 onKeyDown 을 재정의할 필요가 있다.
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_0) {
// this tells the framework to start tracking for
// a long press and eventual key up. it will only
// do so if this is the first down (not a repeat).
event.startTracking();
return true;
}
return super.onKeyDown(keyCode, event);
}
세 번째 이야기: 가상키를 이용하여 엉망으로 만들기 (Making a mess with virtual Keys)
이제 이러한 모든 변화에 대한 본래의 동기에 관한 이야기를 시작한다: Droid 와 새롭게 출시되는 장치들에서는 가상 하드 키를 지원한다. 이러한 장치들은 물리적인 버튼 대신 터치에 민감한 구역 내에 하드 키를 위한 영역을 생성하여 시각적인 화면 밖에 확장된 터치 센서를 가지고 있다. 저수준 입력 시스템은 이 영역 내에서 스크린의 터치를 찾고 이들을 적절한 가상 하드 키로 변환한다.
비록 생성된 이벤트에 이 이벤트를 확인하기 위한 새로운 FLAG_VIRTAL_HARD_KEY 비트가 설정되어 있지만 애플리케이션에게는 이것이 기본적으로 실제 하드키 처럼 보인다. 이 플래그에 상관없이 거의 모든 경우에 애플리케이션은 이 하드 키 이벤트를 실제 하드키를 처리했던 것과 동일한 방법으로 처리할 수 있다.
그러나, 이 키들은 사용자 액션에 있어 약간의 문제를 야기한다. 가장 중요한 것은 사용자 인터페이스의 나머지로서 동일한 화면에 존재함으로 인해 동일한 터치로 쉽게 눌려질 수 있다는 것이다. 예를 들어, 가상 키들이 화면 하단에 위치해 있을 때 이슈가 될 수 있다: 스크롤을 위한 화면을 swipe up(화면을 손가락으로 밀어 올리는 행위)은 일반적인 제스쳐이고 이 제스처를 할 때 화면 하단의 가상 키를 우연히 건드리는 일이 매우 쉽게 발생할 수 있다.
2.0에서는 이것에 대한 해결책으로서 “취소된” 키 이벤트 개념을 소개한다. 긴 입력 처리가 뒤이은 이벤트를 취소한다는 것은 앞 이야기에서 이미 소개되었다. 비슷한 방법으로, 가상 키를 누른 채 화면 위를 움직이는 것은 그 키가 릴리즈 될 때 그 가상 키가 취소 되도록 할 것이다.
사실상 이전의 코드는 이미 이것을 염두에 두고 있다 – 키 Up 시 isCanceled() 를 체크함으로써 취소된 가상 키와 긴 입력은 무시될 것이다. 이 두 경우에 대한 개인적인 플래그가 역시 존재하지만 애플리케이션에서는 거의 사용되지 않고 키 이벤트가 취소된 것에 대한 좀 더 많은 원인이 있을 수 있다는 것에 대한 이해를 항상 가지고 있어야 한다.
현존하는 애플리케이션의 경우, 액션이 다운 되었을 때 실행하도록 하는 BACK 키 호환성이 켜져 있다면, 손가락으로 밀기(swipe)를 수행하려고 할 때 우연히 BACK 키가 눌려졌다고 감지하는 문제가 여전히 남아 있다. 비록 애플리케이션을 SDK 버전 5 이상으로 명시하여 업데이트하는 것 말고는 어떠한 해결책이 없지만, 다행히 BACK 키는 일반적으로 가상 키 영역에서 맨 끝에 위치하기 때문에 사용자는 다른 키와는 달리 BACK 키가 우연히 히트될 가능성이 낮다.
2.0 이후 및 2.0 이전 버전에 모두 동작하는 애플리케이션을 작성하는 것 역시 대부분의 경우에 매우 쉽다. 예를 들어, 모든 버전의 플랫폼에서 액티비티 내에서 올바르게 BACK 키를 처리하기 위한 코드는 아래와 같다.
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.ECLAIR
&& keyCode == KeyEvent.KEYCODE_BACK
&& event.getRepeatCount() == 0) {
// Take care of calling this method on earlier versions of
// the platform where it doesn't exist.
onBackPressed();
}
return super.onKeyDown(keyCode, event);
}
@Override
public void onBackPressed() {
// This will be called either automatically for you on 2.0
// or later, or by the code above on earlier versions of the
// platform.
return;
}
하드 코어용: 올바르게 이벤트 처리하기
다룰 가치가 있는 마지막 주제는 onDispatchEvent() 또는 onPreIme() 와 같은 원시 dispatch 함수에서 올바르게 이벤트를 다루는 방법이다. onKeyDown() 과 같은 고수준 함수를 호출할 때 프레임워크가 제공하는 도움을 받을 수 없기 때문에, 이것들에 대해서는 좀 더 많은 주의가 요구된다. 아래 코드는 BACK 키가 릴리즈 될 때 올바르게 액션을 수행하기 위한 BACK 키 처리를 가로채는 방법을 보여준다.
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
if (event.getAction() == KeyEvent.ACTION_DOWN
&& event.getRepeatCount() == 0) {
// Tell the framework to start tracking this event.
getKeyDispatcherState().startTracking(event, this);
return true;
} else if (event.getAction() == KeyEvent.ACTION_UP) {
getKeyDispatcherState().handleUpEvent(event);
if (event.isTracking() && !event.isCanceled()) {
// DO BACK ACTION HERE
return true;
}
}
return super.dispatchKeyEvent(event);
} else {
return super.dispatchKeyEvent(event);
}
}
getKeyDispatchState() 호출은 윈도우의 현재 키 상태를 추적하는 데 사용되는 객체를 반환한다. View 클래스에서 일반적으로 사용가능하고 액티비티는 필요하면 그 객체를 가져 오기 위해 임의의 View를 사용할 수 있다.
끝.
[번역] 인텐트를 이용한 애플리케이션 통합
인텐트를 이용한 애플리케이션 통합
번역: SSKK (http://codemuri.tistory.com/)
인텐트를 이용한 애플리케이션 통합
2009년 11월 11일 오전 11시 Justin Mattson 에 의해 포스팅됨
OpenTable, uLocate, 그리고 Mob.ly 는 안드로이드 상에서 훌륭한 사용자 경험을 만들기 위해 함께 동작한다. WHRER 와 GoodFood 사용자들이 쉽고 매끄럽게 OpenTable 에서 예약할 수 있는 기회를 엿보았다. 이것은 모든 이가 Win 할 수 있는 상황이다 – OpenTable 은 많은 트래픽을 얻고, WHERE 와 GoodFood 는 그들의 애플리케이션이 좀더 밀착되는 기능을 얻는다. 그리고 사용자들은 단지 몇 번의 손가락 탭 만으로 예약할 수 있게 된다. 안드로이드의 인텐트 매커니즘을 이용하여 애플리케이션 간에 이러한 통합이 가능하게 되었다. 인텐트는 안드로이드에서 가장 멋지고, 가장 유니크하고, 그리고 고마운 기능 중의 하나일 것이다. 각자가 가지고 있는 부분들로부터 어떻게 하나의 새로운 사용자 경험을 구성하는 지를 보여줄 것이다.
설계하기
첫 번째 단계는 인텐트 인터페이스 또는 API 를 설계하는 것이다. OpenTable 이 노출하는 공개 메인 인텐트는 RESERVE 인텐트이다. 이것은 특정 레스토랑에 대해 날짜, 시간 그리고 일행 수 등을 선택적으로 지정하여 예약을 할 수 있도록 한다.
RESERVE 인텐트를 이용하여 예약하는 방법에 대한 예제가 여기 있다.
startActivity(new Intent("com.opentable.action.RESERVE",
Uri.parse("reserve://opentable.com/2947?partySize=3")));
우리의 목적은 인텐트를 사용하는 개발자들에게 쉽고 명료하게 다가서도록 하는 것이었다. 그렇게 하기 위해 우리가 어떻게 결정하였을까?
먼저, 우리는 하나의 Action 이 필요했다. Intent.ACTION_VIEW 를 사용하는 것을 고려해 보았으나, 예약을 하는 것에는 잘 매치되지 않았고, 그래서 우리는 새로운 액션을 하나 만들기로 했다. 안드로이드 플랫폼의 컨벤션을 따라 (간단하게 <패키지명>.action.<액션명>), “com.opentable.action.RESERVE” 를 선택하였다. 액션은 그냥 단순한 문자열이기에, 액션에 네임스페이스를 주는 것은 중요하다. 모든 애플리케이션이 고유의 액션을 정의할 필요는 없다. 만일 무언가 특별한 걸 하지 않는다면 사실 Intent.ACTION_VIEW (“android.intent.action.VIEW” 라고도 알려진) 와 같은 일반 액션이 종종 더 나은 선택일 수 있다.
그 다음, 인텐트 내에서 데이터가 전송되는 방법을 결정해야 했다. 당신은 비록 인텐트의 데이터 Bundle 안에 아이템 컬렉션으로 데이터를 얻도록 할 수도 있지만, 우리는 URI 내에 인코딩된 데이터로 결정하였다. 일관성 있게 액션에 대해 “reserve:” 스키마를 사용하였다. 그 다음 요구되는 도메인 권한과 레스토랑 ID 를 URI 경로에 두었다. 그리고 다른 모든 선택적인 입력은 URI 질의 파라미터로 이동시켰다.
노출하기
일단 인텐트가 어떻게 생긴지를 알았으므로, 시스템에 그 인텐트를 등록해야 했다. 그래야만 안드로이드가 OpenTable 애플리케이션을 시작하는 방법을 알 것이다. 이것은 AndroidManifest.xml 의 적절한 액티비티 선언에 인텐트 필터를 추가함으로써 이루어 진다.
<activity android:name=".activity.Splash" ... >
...
<intent-filter>
<action android:name="com.opentable.action.RESERVE"/>
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="reserve" android:host="opentable.com"/>
</intent-filter>
...
</activity>
우리의 경우, 사용자에게 식당 선택에 대한 상세 정보를 로딩하는 간단한 OpenTable 스플래쉬 화면을 보여주고자 했다. 그래서 스플래쉬 액티비티 정의 내에 그 인텐트 필터를 두었다. 카테고리는 DEFAULT 로 설정하였다. 이것은 다른 액티비티들이 이 액션에 대해 디폴트로 나열되어 있지 않는 한 사용자아게 어떤 애플리케이션을 사용할 지를 묻지 않고 바로 애플리케이션이 실행되도록 한다.
URI 질의 파라미터 (“partSize”)와 같은 것들은 인텐트 필터에 명시되지 않는ㄴ다는 것을 인지하라. 이것은 당신의 인텐트를 정의할 때 문서화의 핵심이다, 이에 대한 내용은 나중에 좀 더 살펴보자.
처리하기
이제 해야할 마지막 한가지는 인텐트를 처리하는 코드를 작성하는 것이다.
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Uri uri;
final int restaurantId;
try {
uri = getIntent().getData();
restaurantId = Integer.parseInt( uri.getPathSegments().get(0));
} catch(Exception e) {
// Restaurant ID is required
Log.e(e);
startActivity( FindTable.start(FindTablePublic.this));
finish();
return;
}
final String partySize = uri.getQueryParameter("partySize");
...
}
비록 완전한 코드는 아닐지라도, 당신은 아이디어를 얻을 수 있을 것이다. 여기에서 가장 어려운 부분은 에러 처리였다. OpenTable 은 파트너 애플리케이션에 의해 전송될 수 있는 잘못된 인텐트를 안전하게 다룰 수 있도록 하고 싶었다. 그래서 레스토랑 ID 를 분석하는 중 어떤 문제가 있다면, 레스토랑을 수동으로 찾을 수 있는 또 다른 액티비티를 사용자에게 보이도록 했다. 이건 마치 데스크탑이나 웹 애플리케이션에서 당신의 애플리케이션 또는 당신의 사용자들에게 해를 끼칠지도 모르는 인젝션 공격(injection attack)에 대항하는 것처럼 입력을 검증하는 것은 중요하다.
불확실 한 것들을 안전하게 호출하고 다루기
사실 요청자 범위 내에서 대상 애플리케이션을 호출하는 것은 매우 직선적이지만, 핸들링이 필요한 몇 가지 경우가 있다. OpenTable 이 설치되지 않았다면? WHERE 또는 GoodFood 가 레스토랑 ID 를 모른다면?
|
레스토랑 ID를 알고 있음 |
레스토랑 ID 를 모름 |
사용자가 OpenTable 을 가지고 있음 |
OpenTable 인텐트 호출 |
예약 버튼을 보여주지 않음 |
사용자가 OpenTable 이 없음 |
Market 인텐트 호출 |
예약 버튼을 보여주지 않음 |
만약 사용자가 대상 애플리케이션을 가지고 있지 않다면, 당신의 파트너가 어떻게 할 지를 결정하도록 하고 싶을지도 모른다. 이 경우에는, OpenTable 을 다운로드하기 위한 안드로이드 마켓을 사용자에 보여주도록 결정했다.
public void showReserveButton() {
// setup the Intent to call OpenTable
Uri reserveUri = Uri.parse(String.format( "reserve://opentable.com/%s?refId=5449",
opentableId));
Intent opentableIntent = new Intent("com.opentable.action.RESERVE", reserveUri);
// setup the Intent to deep link into Android Market
Uri marketUri = Uri.parse("market://search?q=pname:com.opentable");
Intent marketIntent = new Intent(Intent.ACTION_VIEW).setData(marketUri);
opentableButton.setVisibility(opentableId > 0 ? View.VISIBLE : View.GONE);
opentableButton.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v) {
PackageManager pm = getPackageManager();
startActivity(pm.queryIntentActivities(opentableIntent, 0).size() == 0 ?
opentableIntent : marketIntent);
}
});
}
레스토랑의 ID 를 사용할 수 없는 경우, 예약을 받지 않거나 OpenTable 네트워크의 일부가 아니기 때문에, 우리는 간단히 예약 버튼을 숨긴다.
인텐트 명세서 출판하기
기술적인 작업은 모두 하였으므로 이제 당신은 다른 개발자들이 당신의 인텐트 기반 API 를 사용하는 방법을 어떻게 얻도록 할 것인가? 정답은 간단하다: 웹사이트에 문서를 출판하는 것이다. 이것은 다른 애플리케이션이 당신의 기능에 링크하거나 당신이 이르지 못하는 좀더 큰 커뮤니티에 당신의 애플리케이션이 이용가능 하도록 만들어 줄 것이다.
출판된 정보가 없는 어떤 당신이 원하는 애플리케이션이 있다면, 그 개발자와 연락을 시도하라. 가끔 제 3자가 그들의 API 를 사용하려고 하는 것에 큰 흥미를 가질 수 있다. 그리고 만약 빈둥거리고 있는 API 가 이미 있다면, 그것에 대한 문서를 보다 쉽게 얻을 수 있다.
요약
정말 아주 간단하다. 이제 누구든 새로운 도시에 있든 바로 옆 근처에 있든 새로운 hot spot 이 어디인지를 쉽게 체크하고 즉시 이용가능한 테이블을 예약할 수 있다. 한 애플리케이션에서 어떤 식당을 찾을 필요 없이, 테이블이 있는지를 알기 위해 OpenTable 을 실행시켜라. 테이블이 없다는 것을 알았다면, 다시 처음 애플리케이션을 실행하라. 그리고 이것의 반복이다. 우리는 이 문서가 좀더 큰 안드로이드 커뮤니티에 공유할 것을 고려하고 있는 당신 고유의 공개 인텐트를 개발하는데 있어 도움이 되기를 바란다.
끝.
[번역] 성능을 위한 멀티쓰레딩 (Multithreading For Performance)
성능을 위한 멀티쓰레딩 (Multithreading For Performance)
번역: SSKK ( http://codemuri.tistory.com/ )
2010년 7월 19일 오전 11시 41분 Tim Bray 가 포스팅함
이 포스트는 멀티태스킹에 심취한 안드로이드 그룹의 엔지니어인 Gilles Debunne 에 의해 작성됨 – Tim Bray
응답성이 높은 애플리케이션을 만들기 위한 좋은 방법은 메인 UI 쓰레드는 최소한의 일만 하도록 하는 것이다. 애플리케이션을 중지시킬(hang) 수 있는 긴 작업은 다른 쓰레드에서 다루어져야 한다. 그러한 작업의 전형적인 예는 어느 정도 지연될 지 예측할 수 없는 네트워크 작업이다. 사용자는 무엇인가가 진행 중에 있다는 피드백을 제공하는 일부 멈춤에 대해서는 관대할 것이다. 그러나 냉동된 애플리케이션(frozen application)은 사용자에게 어떤 실마리도 주지 않는다.
이 문서에서는, 이러한 패턴을 설명하는 간단한 Image Downloader 를 만들 것이다. 인터넷으로부터 다운로드된 썸네일 이미지를 가지는 ListView를 생성할 것이다. 백그라운드에서 다운로드를 수행하는 비동기 태스크를 생성하는 것은 애플리케이션이 빠르게 동작하는 것을 유지해 줄 것이다.
Image Downloader
웹으로부터 이미지를 다운로드하는 것은 프레임워크에서 제공하는 HTTP-관련 클래스를 사용하면 굉장히 간단하다. 아래 그 구현 예이다.
static Bitmap downloadBitmap(String url) {
final AndroidHttpClient client = AndroidHttpClient.newInstance("Android");
final HttpGet getRequest = new HttpGet(url);
try {
HttpResponse response = client.execute(getRequest);
final int statusCode = response.getStatusLine().getStatusCode();
if (statusCode != HttpStatus.SC_OK) {
Log.w("ImageDownloader", "Error " + statusCode + " while retrieving bitmap from " + url);
return null;
}
final HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream inputStream = null;
try {
inputStream = entity.getContent();
final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
return bitmap;
} finally {
if (inputStream != null) {
inputStream.close();
}
entity.consumeContent();
}
}
} catch (Exception e) {
// Could provide a more explicit error
message for IOException or IllegalStateException
getRequest.abort();
Log.w("ImageDownloader", "Error while retrieving bitmap
from " + url, e.toString());
} finally {
if (client != null) {
client.close();
}
}
return null;
}
하나의 client 와 HTTP request 가 생성된다. Request 가 성공하면, 이미지를 포함하고 있는 response 엔티티 스트림은 비트맵을 생성하기 위해 디코드(decode) 된다. 애플리케이션의 메니페스트는 이를 가능하게 하기 위해 INTERNET 에 대한 접근 권한을 명시해야 한다.
Note: BitmapFactory.decodeStream 의 이전 버전에 존재하는 버그 때문에 느린 연결 상태(over a slow connection)에서는 이 코드가 동작하지 않는다. 이 문제를 해결하기 위해서는 새로운 FlushedInputStream(inputStream)으로 디코드 하라. 여기 헬퍼 클래스의 구현이 있다.
static class FlushedInputStream extends FilterInputStream {
public FlushedInputStream(InputStream inputStream) {
super(inputStream);
}
@Override
public long skip(long n) throws IOException {
long totalBytesSkipped = 0L;
while (totalBytesSkipped < n) {
long bytesSkipped = in.skip(n - totalBytesSkipped);
if (bytesSkipped == 0L) {
int byte = read();
if (byte < 0) {
break; // we reached EOF
} else {
bytesSkipped = 1; // we read one byte
}
}
totalBytesSkipped += bytesSkipped;
}
return totalBytesSkipped;
}
}
이것은 만일 파일의 끝에 도달하지 않는 경우, skip() 함수가 제공된 바이트 수만큼 스킵하는 것을 보장한다.
ListAdapter 의 getView 메소드 안에서 이 메소드를 직접 사용했다면, 스크롤은 불쾌하게 들쭉날쭉 할 것이다. 새로운 뷰의 각 화면은 이미지가 다운로드 되길 기다려야 하고, 이는 부드러운 스크롤을 방해할 것이다.
AdnroidHttpClient 자신이 메인 쓰레드에서 시작되는 것을 허용하지 않는 것은 아주 나쁜 생각이다. 대신 위의 코드는 “This thread forbids HTTP requests” 에러 메시지를 보여줄 것이다. 만약 정말로 당신의 발에 총을 쏘길 원한다면 (메인 쓰레드에서 HttpClient 를 시작하려고 한다면..) DefaultHttpClient 를 사용해라.
비동기 태스크 소개
AsyncTask 클래스는 UI 쓰레드에서 새로운 태스크를 시작하는 가장 간단한 방법중의 하나이다. 이러한 태스크 생성을 담당할 ImageDownloader 클래스를 생성하자. 이 클래스는 URL 로부터 다운로드 된 이미지를 ImageView 에 할당할 download 메소드를 제공할 것이다.
public class ImageDownloader {
public void download(String url, ImageView imageView) {
BitmapDownloaderTask task = new BitmapDownloaderTask(imageView);
task.execute(url);
}
}
/* class BitmapDownloaderTask, see below */
}
BitmapDownloaderTask 는 실제로 이미지를 다운로드 할 AysncTask 이다. 이 클래스는 Execute 메소드를 사용하여 시작되고 즉시 반환된다. 이 메소드를 매우 빠르게 만드는 것이 우리의 목적에 부합될 것이다. 왜냐하면 이 메소드가 UI 쓰레드에서 호출 될 것이기 때문이다. 여기 이 클래스의 구현이 있다.
class BitmapDownloaderTask extends AsyncTask<String, Void, Bitmap> {
private String url;
private final WeakReference<ImageView> imageViewReference;
public BitmapDownloaderTask(ImageView imageView) {
imageViewReference = new WeakReference<ImageView>(imageView);
}
@Override
// Actual download method, run in the task thread
protected Bitmap doInBackground(String... params) {
// params comes from the execute() call: params[0] is the url.
return downloadBitmap(params[0]);
}
@Override
// Once the image is downloaded, associates it to the imageView
protected void onPostExecute(Bitmap bitmap) {
if (isCancelled()) {
bitmap = null;
}
if (imageViewReference != null) {
ImageView imageView = imageViewReference.get();
if (imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
}
doInBackground 메소드는 태스크에 의해 자신 고유의 프로세스에서 실질적으로 수행되는 것이다. 이 메소드는 단순히 이 문서 초반에 구현된 downloadBitmap 메소드를 사용한다.
onPostExecute 는 태스크가 종료될 때 호출한 UI 쓰레드에서 실행된다. 파라미터로 결과 Bitmap(다운로드 된 Bitmap) 을 받고 이 bitmap 은 download 메소드에 의해 제공되어 BitmapDownloaderTask 에 저장된 ImageView 와 연결된다. ImageView 가 WeakReference 에 저장된 다는 것을 명심하라. 이것은 다운로드가 진행 중일 때 종료된 액티비티의 ImageView 가 쓰레기 수거(garbage collected) 되는 것을 막지 않는다. (다운로드가 진행 중일 때 ImageView 가 쓰레기 수거되어 null 이 될 수 있다는 말) – 원래 WeakReference 가 아니면 참조된 클래스는 쓰레기 수거되지 않는다. SoftReference 도 있는 데 이것은 Out of memory 상황일 때에 쓰레기 수거의 대상이 된다. 이것이 바로 onPostExecute 에서 imageViewReference 와 imageView 를 사용하기 전에 weak reference 와 imageView 둘 다 null 이 아닌지(쓰레기 수거되지 않았는지)를 체크해야만 하는 이유이다.
이 간단한 예제는 AsyncTask 의 사용법을 설명한다. 이를 테스트 해보면, 이 몇 줄 안되는 코드가 ListView 의 성능을 비약적으로 개선하는 것을 보게 될 것이고 이제 부드럽게 스크롤이 가능한 것 또한 볼 수 있다. AysncTask 에 대한 좀 더 자세한 설명은 Painless threading 을 읽어보기 바란다.
하지만, ListView 의 특정 동작으로 인해 현재 구현에 한가지 대해 한가지 문제가 있다. 메모리 효율의 이유로 이해 ListView 는 사용자가 스크롤 할 때 표시되는 view 들을 재사용한다. 리스트가 스크롤 될 때, 주어진 ImageView 객체는 여러 번 사용될 것이다. ImageView 가 올바르게 표시될 때마다 image download task 가 생성되고 결국 ImageView 의 이미지가 변경될 것이다. 문제가 어디에 있을까? 대부분의 병렬 애플리케이션에 있어서, 핵심 이슈는 정렬(ordering)에 있다. 우리의 경우에는, download 태스크가 시작된 순서대로 종료될 것이라는 보장이 어디에도 없다. 그 결과로 리스트에 최종적으로 표시되는 이미지는 이전의 아이템이 될 지도 모르고 이는 다운로드가 오래 걸리는 경우에 쉽게 일ㄹ어날 수 있다. 만약 다운로드 된 이미지가 단 한번 연결된다면 이것은 이슈가 아니다. 하지만 리스트에 사용되는 일반적인 경우를 위해 이것을 고쳐보자.
병행성 다루기 (Handling conccurrency)
이 이슈를 해결하기 위해서는, 최근에 시작된 것이 효과적으로 화면에 표시되도록 하기 위해 다운로드의 순서를 기억해야 한다. 각 ImageView 가 자신의 최근 download 를 기억하는 것으로 충분하다. Download 가 진행 중인 동안 일시적으로 ImageView 와 결합되는 전용 Drawable subclass 를 사용하여 ImageView 내에 이 부가 정보를 추가할 것이다. 여기 DownloadedDrawable 클스의 코드가 있다.
static class DownloadedDrawable extends ColorDrawable {
private final WeakReference<BitmapDownloaderTask> bitmapDownloaderTaskReference;
public DownloadedDrawable(BitmapDownloaderTask bitmapDownloaderTask) {
super(Color.BLACK);
bitmapDownloaderTaskReference =
new WeakReference<BitmapDownloaderTask>(bitmapDownloaderTask);
}
public BitmapDownloaderTask getBitmapDownloaderTask() {
return bitmapDownloaderTaskReference.get();
}
}
이 구현은 ColorDrawable 의 도움을 받는다. 이 클래스는 다운로드가 진행중인 동안 검은색 배경으로 된 ImageView 를 보이게 할 것이다. 사용자에게 피드백을 제공하려면 대신에 “download in progress” 이미지를 사용할 수 있다. 다시 한번, 객체 의존성을 제한하기 위해 WeakReference 를 사용하는 것을 명심하라.
새로운 클래스를 적용하기 위해 코드를 변경하자. Download 메소드는 이 클래스에 대한 인스턴스를 생성하고 그것을 imageView 와 결합시킬 것이다.
public void download(String url, ImageView imageView) {
if (cancelPotentialDownload(url, imageView)) {
BitmapDownloaderTask task = new BitmapDownloaderTask(imageView);
DownloadedDrawable downloadedDrawable = new DownloadedDrawable(task);
imageView.setImageDrawable(downloadedDrawable);
task.execute(url, cookie);
}
}
cancelPotentialDownload 메소드는 이 새로운 다운로드가 시작되어야 할 때 imageView 상에서 진행중인 다운로드를 중지시킬 것이다. 이 자체로는 항상 최신의 다운로드가 표시되도록 보장하기에는 충분하지 않다는 것을 명심해라. 왜냐하면 이 태스크가 자신의 onPostExecute 메소드에서 기다리다가 새로운 다운로드가 종료된 후에야 비로소 실행되어 종료될 가능성이 있기 때문이다.
private static boolean cancelPotentialDownload(String url, ImageView imageView) {
BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
if (bitmapDownloaderTask != null) {
String bitmapUrl = bitmapDownloaderTask.url;
if ((bitmapUrl == null) || (!bitmapUrl.equals(url))) {
bitmapDownloaderTask.cancel(true);
} else {
// The same URL is already being downloaded.
return false;
}
}
return true;
}
cancelPotentialDownload 는 진행 중인 다운로드를 멈추기 위해 AsyncTask 클래스의 cancel 메소드를 사용한다. cancelPotienaltialDownload 메소드는 대부분의 경우에 true 를 반환함으로써 다운로드가 시작될 수 있도록 한다. 이것이 일어나지 않는 단 한가지 경우는 동일한 URL 에 대해서 이미 다운로드가 진행중인 경우 이를 지속하도록 하기 위할 때이다. 이 구현에서는, ImageView 가 쓰레기 수거된 경우 그 뷰와 결합된 다운로드가 멈추지 않는다는 것을 명심해라 이것을 멈추게 하기 위해서는 RecyclerListener 가 사용될 수 있다.
cancelPotientialDownload 메소드는 getBitmapDownloaderTask 헬퍼 함수를 사용하고, 이 함수는 아주 명료하다.
private static BitmapDownloaderTask getBitmapDownloaderTask(ImageView imageView) {
if (imageView != null) {
Drawable drawable = imageView.getDrawable();
if (drawable instanceof DownloadedDrawable) {
DownloadedDrawable downloadedDrawable = (DownloadedDrawable)drawable;
return downloadedDrawable.getBitmapDownloaderTask();
}
}
return null;
}
마지막으로, ImageView 가 다운로드와 결합되어 있는 경우에만 Bitmap 과 결합되도록 하기 위해 onPostExecute 가 수정되어야 한다.
private static BitmapDownloaderTask getBitmapDownloaderTask(ImageView imageView) {
if (imageView != null) {
Drawable drawable = imageView.getDrawable();
if (drawable instanceof DownloadedDrawable) {
DownloadedDrawable downloadedDrawable = (DownloadedDrawable)drawable;
return downloadedDrawable.getBitmapDownloaderTask();
}
}
return null;
}
이러한 수정들로 인해, ImageDownloader 클래스는 우리가 기대하는 기본적인 서비스를 제공한다. 응답성이 보장하기 위해 당신의 애플리케이션에 자유롭게 이것을 이용하거나 이 글이 설명하는 비동기 패턴을 이용하라.
데모
이 문서의 소스 코드는 Google Code 에서 다운 받을 수 있다. 이 문서에서 소개된 세가지 다른 구현(No task, no bitmap to task association, 그리고 최종 버전) 사이에서 전환하면서 비교해 볼 수 있다., 이 이슈를 보다 잘 설명하기 위해 캐쉬 사이즈는 10개의 이미지로 한정되었다는 것을 명심하라.
Future Work
이 코드는 병행 수행 측면에 초점을 두어 단순화 되었지만 많은 유용한 기능들이 구현에서 누락되어 있다. ImageDownloader 클래스는 먼저 캐쉬를 이용할 것이다. 특히 ListView 와 함께 사용된 경우, 사용자가 이리저리 스크롤 할 때마다 동일한 이미지를 여러 번 보여줄 수 있을 것이다. 이것은 Bitmap SoftReference 에 대한 URL 의 LinkedHashMap 을 이용한 LRU(Least Recently Used) 캐쉬를 이용하여 쉽게 구현될 수 있다. This can be implemented using a Least Recently Used cache backed by a LinkedHashMap of URL to Bitmap SoftReferences. 더 복잡한 캐쉬 매커니즘(more involved cache mechanism)은 로컬 디스크에 저장된 이미지에 의존할 수도 있다. 필요하다면 썸네일 생성과 이미지 크기 조정 또한 추가될 수 있다.
다운로드 에러와 타임아웃은 모두 null bitmap 을 반환할 것이기 때문에 이 구현에서 올바르게 처리된다. 어떤이는 그대신 에러 메시지를 보여주고 싶어할 수 있다.
우리의 HTTP request 는 매우 단순하다. 어떤이는 특정 웹사이트에서 요구되는 parameter 와 쿠키를 추가하고 싶어할 수 있다.
이 문서에서 사용된 AsyncTask 클래스는 UI 쓰레드와 특정 작업을 구분지을 수 있는 정말 편리하고 쉬운 방법이다. 어떤이는 병렬로 수행중인 다운로드 쓰레드의 전체 개수를 제어하는 것과 같이 원하는 어떤 더 좋은 제어를 추가하기 위해 Handler 클래스를 사용하길 원할지도 모른다.
끝.
[번역] Painless threading
Painless threading
2009 년 6월 6일 오전 9:30분 Romain Guy 에 의해 포스팅 됨
원문: http://android-developers.blogspot.com/2009/05/painless-threading.html
번역: SSKK (http://codemuri.tistory.com/)
처음 안드로이드 애플리케이션을 시작할 때마다, “main” 이라고 불리는 하나의 쓰레드가 자동으로 생성된다. UI 쓰레드라고도 불리는 Main 쓰레드는 그리는 이벤트를 포함하는 여러 이벤트들을 적절한 위젯으로 전송하는 역할을 담당하기 때문에 매우 중요하다. 이 쓰레드는 또한 안드로이드 위젯들과도 상호작용한다. 예를 들어, 만약 화면 위의 버튼을 터치한다면, UI 쓰레드는 터치 이벤트를 위젯으로 전달하고 이 위젯은 눌려진 상태(pressed state)로 설정되고 이벤트 큐에 무효화 요청(invalidate request)을 포스팅한다. UI 쓰레드는 그 요청을 큐에서 빼내어 자신을 다시 그려야 하는 위젯에 알린다.
단일 쓰레드 모델은 퍼포먼스가 빈약할 수 있다. 모든 것이 단일 쓰레드에서 일어나기 때문에, 네트워크 접근 또는 데이터베이스 질의 와 같은 오래 걸리는 연산을 그 쓰레드에서 수행하는 것은 모든 사용자 인터페이스를 멈추게 할 것이다. 오래 걸리는 작업이 처리되는 동안에는 그리기 이벤트를 포함한 어떤 이벤트도 처리될 수 없다. 사용자 측면에서, 애플리케이션이 정지된 것처럼 보일 것이다. 더욱 나쁜 것은, UI 쓰레드가 몇 초 이상 (현재는 약 5초) 블러킹된다면, 사용자는 악명높은 “애플리케이션 응답 없음” (ANR) 대화상자를 보게 될 것이다.
이것이 얼마나 나쁘게 보일 수 있는지를 직접 보길 원한다면, OnClickListener내에Thread.Seep(2000) 을 호출하는 버튼을 가지는 간단한 애플리케이션을 작성하라. 그 버튼은 일반 상태로 돌아가기 전에 약 2초 정도 눌려진 상태로 남게 될 것이다. 이러한 현상이 일어날 때, 사용자는 그 애플리케이션이 매우 느린 것처럼 여길 것이다.
자 이제 UI 쓰레드에서 오래 걸리는 작업을 피해야 한다는 것을 알았으므로, 당신은 아마 그러한 작업을 수행하기 위한 다른 쓰레드 (백그라운드 또는 작업 쓰레드)를 사용하려고 할 것이다. 네트워크크를 통해서 이미지를 다운 받고 그 이미지를 ImageView 에 보이도록 하는 클릭 리스너의 예를 보자.
public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
Bitmap b =
loadImageFromNetwork();
mImageView.setImageBitmap(b);
}
}).start();
}
먼저, 이 코드는 UI 쓰레드를 블러킹하지 않기 때문에 그 문제에 대한 좋은 해결책인 것처럼 보인다. 불행히도, 이것은 단일 쓰레드 모델을 위반한다: 안드로이드 UI 툴킷은 쓰레드에 안전하지 않고 항상 UI 쓰레드에서 조작되어야 한다. 이 코드에서, ImageView 는 작업 쓰레드에서 조작되고, 이것은 정말 기이한(weird) 문제를 야기할 수 있다. 이러한 버그를 찾고 수정하는 것은 어렵고 시간을 많이 낭비할 수 있다.
안드로이드는 다른 쓰레드에서 UI 쓰레드에 접근하기 위한 몇가지 방법을 제공한다. 당신은 이미 그 방법중의 일부를 알고 있을 수 있지만 여기 전체 리스트가 있다.
l Activity.runOnUiThread(Runnable)
l View.postDelayed(Runnable,
long)
l Handler
이 클래스와 메소드 모두 앞에서 소개된 코드 예제를 바로잡기 위해 사용될 수 있다.
public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
final Bitmap b = loadImageFromNetwork();
mImageView.post(new Runnable() {
public void run() {
mImageView.setImageBitmap(b);
}
});
}
}).start();
}
불행히도, 이러한 클래스와 메소드는 당신의 코드를 읽기에 더 복잡하고 더 어렵게 만드는 경향이 있다. 빈번한 UI 업데이트를 필요로 하는 복잡한 연산을 구현할 때는 더욱 나빠진다. 이러한 문제를 해결하기 위해, Android 1.5 는 AsyncTask 라 불리는 새로운 유틸리티 클래스를 제공한다. 이 클래스를 이용하여 사용자 인터페이스와 상호작용할 필요가 있는 오랫동안 수행되는 태스크를 간단하게 생성할 수 있다.
AsyncTask 는 안드로이드 1.0 과 1.1 에서 UserTask 라는 이름으로 역시 사용가능하다. 이것은 완전히 동일한 API 를 제공하고 당신이 해야하는 모든 것은 위의 소스코드를 당신의 애플리케이션 내에 복사하는 것이다.
AsyncTask 의 목적은 당신을 위해 쓰레드 관리를 해주는 것이다. 앞서 소개된 예제는 AsyncTask 를 이용하여 쉽게 재작성될 수 있다.
public void onClick(View v) {
new DownloadImageTask().execute("http://example.com/image.png");
}
private class DownloadImageTask extends AsyncTask{
protected Bitmap doInBackground(String... urls) {
return loadImageFromNetwork(urls[0]);
}
protected void onPostExecute(Bitmap result) {
mImageView.setImageBitmap(result);
}
}
보다시피, AsyncTask 는 상속을 이용해야 한다. AsyncTask 인스턴스는 UI 쓰레드에서 생성되어야 하고 오직 한번만 실행될 수 있다는 것을 기억하는 것 역시 매우 중요하다. 이 클래스를 사용하는 방법에 대한 완전한 이해를 위해 AsyncTask 문서를 읽을 수 있지만, 아래에 AsyncTask 가 어떻게 동작 하는지에 대한 빠른 개요를 제공한다.
l Generic 을 이용하여 파라미터, 프로그레스 값, 그리고 태스크의 최종 값의 타입을 지정할 수 있다.
l doInBackground() 메소드는 작업 쓰레드에서 자동적으로 실행된다.
l onPreExecute(), onPostExecute() 그리고 onProgressUpdate() 는 모두 UI 쓰레드에서 호출된다.
l doInBackground() 에 의해 반환되는 값은 onPostExecute() 에 전달된다.
l UI 쓰레드의 onProgressUpdate() 를 실행하도록 하기 위해 doInBackground() 에서 언제든지 publishProgress() 를 호출할 수 있다.
l 어떤 쓰레드에서든, 항상 그 태스크를 취소할 수 있다.
공식 문서에 덧붙여서, Shelves (Shelves.java 와 AddBookActivity.java) 그리고 Photostream(LoginActivity.java, PhotostreamActivity.java 그리고 ViewPhotoActivity.java) 의 소소 코드에 있는 몇가지 복잡한 예를 참고할 수 있다. 형상 변경(configuration changes)시에도 태스크를 지속하는 방법과 액티비티가 소멸될 때 태스크를 올바르게 최소하는 방법을 알기 위해서는 Shelves 소스 코드를 참고할 것을 강력히 추천한다.
AsyncTask 를 사용하든 그렇지 않든간에, 단일 쓰레드 모델에 대한 두 가지 규칙만은 반드시 기억해야 한다: UI 쓰레드를 블러킹하지 말 것과 안드로이드 UI 툴킷은 오로지 UI 쓰레드에서만 접근되어야 한다. AsyncTask 는 단지 이러한 두 규칙을 보다 쉽게 지킬수 있게 한다.
보다 멋진 테크닉을 배우길 원한다면, Google I/O에 참석하라. 안드로이드 팀의 멤버가 깊이 있는 기술적인 세션의 시리즈를 제공하기 위해 그리고 당신의 모든 질문에 답변을 주기 위해 거기서 기다리고 있을 것이다.
끝.
Android Emulator 한글 설정
Changing Android Emulator's orientation
The solution is next:
Switch to previous layout orientation (for example, portrait,
landscape) - KEYPAD_7 or Ctrl-F11
Switch to
next layout orientation (for example, portrait, landscape) - KEYPAD_9
or Ctrl-F12
Note: If you prefer to use KEYPAD_7 or KEYPAD_9
you need to turn off your Num Lock.
출처 : http://thedevelopersinfo.wordpress.com/2009/10/27/changing-android-emulators-orientation/
Androidscreencaset - 화면 캡처 및 마우스 키보드 제어
Open 소스로 화면 캡처 및 마우스 키보드 제어를 할 수 있는 Open Source 프로그램
기능 소개
- Mouse and keyboard control For Rooted devices only
- Landscape mode (right click)
- Video recording
- Basic file broswer
원본 링크 : http://code.google.com/p/androidscreencast/
svn 으로 받은 소스 파일 :
구글의 Android 플랫폼, 그리고 Dalvik VM
출처 : http://sink772.egloos.com/3490667
이미 많은 사람들이 알고있다시피, 지난주 월요일날 구글은 모바일 핸드셋용 플랫폼인 Android를 위한 소프트웨어 개발 툴킷(SDK)을 발표했 다. 그동안 소문으로만 무성하던 통칭 gPhone의 실체가 나타난 셈인데, 지금은 비록 그쪽 분야에서 다소 벗어난 일을 하고 있지만, 과거 수년간 모바일 업계에서 Java VM 개발 관련 일을 했던 나로서는 관심있게 지켜볼 수 밖에 없었고, 즉각 SDK를 다운받아 살펴보기 시작했다.
일단 이번에 발표한 것은 말 그대로 SDK, 즉 Android 플랫폼 상에서 돌아가는 애플리케이션을 작성하기 위한 에뮬레이터 및 각종 유틸리티 도구, 그리고 문서 등이며, Android 플랫폼 그 자체에 대한 것은 포함되어 있지 않다. 그리고 놀랍게도(!) 모든 애플리케이션은 Java 언어 기반으로 작성하도록 되어있으며, 실제로 핸드셋에 배포할 때는 Dalvik VM이라는 구글 자체의 VM 상에서 동작하도록 class 파일을 dex라는 형식으로 변환하여 배포하도록 되어있다.
여기까지만 보면 대다수의 사람들은 그다지 놀랍거나 새로운 것이 없다고 할 지도 모르겠다. SUN에서도 이미 JavaFX Mobile이라는 리눅스 OS 및 자바 애플리케이션 기반의 모바일 플랫폼을 발표했으니 말이다. 그리고 YouTube에 올라온 Android 데모를 본 사람들도 퀘이크가 되네 혹은 iPhone UI를 따라했네 등의 겉모습만 보고서 Android 플랫폼을 평가할 지도 모르겠다. 그러나 내부 구현이나 그들의 정책에 대해서 세심히 살펴보면 구글의 이번 행보가 앞으로 전세계 휴대폰 업계에 미칠 엄청난 파장에 대해서 생각하지 않을 수 없게 된다.
이번에 드러난 Android 플랫폼의 핵심은 바로 Dalvik VM이라 불리우는 런타임 환경이다. 애플리케이션 개발은 Java 언어를 사용하면서 왜 런타임 환경은 Java VM이 아닌 또다른 VM를 채용했을까라는 것이 이슈인데, Stefano Mazzocchi의 블로그 글에서 이에 대한 해답을 찾을 수 있다. 결론만 말하자면 SUN과의 라이센스 문제 때문인데, 작년말에 SUN이 PhoneME라 는 이름으로 Java ME 관련 코드를 오픈 소스화하긴 했지만, 실제로 Java VM을 핸드셋에 탑재하기 위해서는 SUN에 라이센스 비용을 꼬박꼬박 물어야 한다. 즉 생색은 있는 대로 다 내면서 실속 또한 챙기는 이중 전략인 셈인데, 이를 비껴가기 위해서 구글은 Dalvik VM이라는 카드를 꺼내든 것이다. 애플리케이션 개발 환경은 Java SE 관련이므로 그 유명한 Classpath 예외조항에 따라 라이센스 문제를 피할 수 있지만, Java ME 쪽은 예외조항이 없으므로 Java VM을 배제하고 또다른 VM을 탑재하는 식으로 처리를 한 것이다. 구글맵 등 이미 Java 언어 기반으로 모바일 애플리케이션을 배포하던 구글로서는 기존의 인프라를 그대로 살리고 싶어했을 것이고, 또 거의 모든 소스를 오픈하여 자유롭게 사용할 수 있도록 배포하려던 정책도 SUN과의 라이센스 문제 때문에 포기할 수는 없었을 것이다. 여러가지를 고심한 끝에 이와 같은 결정이 나온 것으로 보이며, 정말 영리한 결정이라는 생각이 든다.
굳이 Java 언어를 개발 환경으로 사용해야 하나, 혹은 C/C++ 기반의 native 개발 환경으로 갈 수도 있지 않나라는 생각을 할 수도 있을 것이다. 내 개인적인 생각으로 모바일 분야에서는 보안 문제, 배포 문제, 포팅 문제 등을 고려하면 C/C++ 기반의 native 기반보다는 VM 기반으로 가는 것이 맞다는 판단이며, VM 중에서도 현재 가장 많은 저변을 가지고 있는 Java VM 기반으로 가는 것이 현명하다는 생각이다. 비록 Java VM 그대로를 사용 못하고 변형된 것을 탑재해야 하지만... (이미 dex 파일 포맷 등 Dalvik VM 자체에 대해서 reverse engineering한 결과물들이 나오고 있는데, 대충 보면 몇가지를 제외하고는 Java class 파일 포맷과 상당히 유사하다는 것을 알 수 있다.)
앞서 언급했던 Stefano의 블로그 글 중에서 인상적인 글귀를 하나 인용하면서 이번 글은 마칠까 한다.
So, here we are: Apple makes the iPhone, incredibly sweet, slick and game-changing and yet incredibly locked. Google makes Android and not only unlocks development abilities on the mobile phone but also unlocks millions of potential Java mobile programmers from Sun's grip on it.
위의 글귀를 보고 Matrix 영화가 생각난다면 오버일까? ^^
출처 : http://sink772.egloos.com/3490667