2013년 10월 27일 일요일

오디오 포커스에 대하여

구글 개발자 관계 팀의 크리스탄 우첼로(Kristan Uccello) 작성
원문: Respecting Audio Focus

발표 동안에 떠드는 것은 무례합니다. 발표자에게 무례하고 관객을 짜증나게 하죠. 만약 애플리케이션이 오디오 포커스에 관한 규율을 존중하지 않으면 다른 애플리케이션을 무시하고 사용자를 짜증나게 하는 것입니다. 만약 오디오 포커스에 대해 들어본 적이 없다면 안드로이드 개발자 트레이닝 내용을 볼 필요가 있습니다.

만약 여러 앱에서 음악을 재생한다면 이들이 어떻게 상호작용할지 생각하는 것이 중요합니다. 모든 음악 앱들이 동시에 재생하는 것을 막기 위해 안드로이드는 재생을 조정하는 오디오 포커스를 사용합니다. 앱은 오디오 포커스를 가질 때만 음악을 재생해야 합니다. 이 글은 사용자에게 가능한 좋은 경험을 보장하기 위해 오디오 포커스의 변화를 적절히 다루는 몇가지 팁을 제공합니다.

오디오 포커스 요구하기


앱이 시작할 때 오디오 포커스를  요청해서는 안됩니다. (욕심내지 마세요.) 대신 애플리케이션이 오디오 스트림으로 무언가 할 때까지 요청을 미루세요. 애플리케이션은 원하는 포커스 수준을 지정할 수 있는 AUDIOFOCUS_GAIN* 상수 (표 1 참고)를 AudioManager 시스템 서비스에서 사용할 수 있습니다.

목록 1. 오디오 포커스 요청하기


1. AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
2.     
3.  int result = am.requestAudioFocus(mOnAudioFocusChangeListener,
4.    // Hint: the music stream.
5.    AudioManager.STREAM_MUSIC,
6.    // Request permanent focus.
7.    AudioManager.AUDIOFOCUS_GAIN);
8.  if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
9.    mState.audioFocusGranted = true;
10. } else if (result == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
11.   mState.audioFocusGranted = false;
12. }

7번째 줄 위에서 영속적인 오디오 포커스를 요청한 걸 볼 수 있습니다. 45초 미만의 오디오 포커스를 이용할 때는 AUDIOFOCUS_GAIN_TRANSIENT를 대신 사용하여 일시적인 포커스를 요청할 수 있습니다.

현재 오디오를 재생하는 다른 애플리케이션과 오디오 시스템을 공유하는데 적합한 AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK를 대신 사용할 수도 있습니다. (예, 피트니스 애플리케이션에서 "계속 하라"는 메시지가 재생되며 그 동안 배경음악을 잠시 줄이고 싶을 때.) AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK를 호출하는 앱은 15초 이상 오디오 시스템의 포커스를 잡아선 안됩니다. (주: 더킹(ducking)은 다른 오디오 출력 크기를 잠시 작게 변경하여 같이 재생을 하는 것을 의미하는 말입니다.)

오디오 포커스 변화 다루기


오디오 포커스 변경 이벤트를 다루기 위해 애플리케이션은 OnAudioFocusChangeListener의 인스턴스를 생성해야 합니다. 이 리스너에서 애플리케이션은  theAUDIOFOCUS_GAIN* 이벤트와 AUDIOFOCUS_LOSS* 이벤트 (표 1)을 다룰 필요가 있습니다.  AUDIOFOCUS_GAIN는 몇가지 미묘한 차이들을 가지는 데,아래의 목록 2에 강조된 것을 참고하세요.

목록 2. 오디오 포커스 변화 다루기

1. mOnAudioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() {  
2.   
3. @Override
4. public void onAudioFocusChange(int focusChange) {
5.   switch (focusChange) {
6.   case AudioManager.AUDIOFOCUS_GAIN:
7.     mState.audioFocusGranted = true;
8.        
9.     if(mState.released) {
10.   initializeMediaPlayer();
11.    }
12.
13.    switch(mState.lastKnownAudioFocusState) { 14.    case UNKNOWN: 15.      if(mState.state == PlayState.PLAY && !mPlayer.isPlaying()) { 16.        mPlayer.start(); 17.      } 18.      break; 19.    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: 20.      if(mState.wasPlayingWhenTransientLoss) { 21.        mPlayer.start(); 22.      } 23.      break; 24.    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: 25.      restoreVolume(); 26.      break; 27.    } 28.        
29.    break; 30.  case AudioManager.AUDIOFOCUS_LOSS: 31.    mState.userInitiatedState = false; 32.    mState.audioFocusGranted = false; 33.    teardown(); 34.    break; 35.  case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: 36.    mState.userInitiatedState = false; 37.    mState.audioFocusGranted = false; 38.    mState.wasPlayingWhenTransientLoss = mPlayer.isPlaying(); 39.    mPlayer.pause(); 40.    break; 41.  case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: 42.   mState.userInitiatedState = false; 43.   mState.audioFocusGranted = false; 44.   lowerVolume(); 45.   break; 46.  } 47.  mState.lastKnownAudioFocusState = focusChange; 48.  } 49.};


AUDIOFOCUS_GAIN는 애플리케이션 코드의 두가지 분명한 영역에서 사용됩니다. 첫째로, 목록 1에서 보여진 것 처럼 오디오 포커스를 등록할 때 사용할 수 있습니다. 이것은 등록된 OnAudioFocusChangeListener를 위한 이벤트로 변환되지는 않습니다. 성공적인 오디오 포커스 요청이 될때 리스너는 등록에 쓰였던 AUDIOFOCUS_GAIN 이벤트를 얻지는 못한다는 거죠.

AUDIOFOCUS_GAIN은 또 OnAudioFocusChangeListener의 구현 내의 이벤트 상태로 쓰입니다. 위에 언급된 것 처럼, AUDIOFOCUS_GAIN 이벤트는 오디오 포커스 요청에 의해 작동되지는 않습니다. 대신 AUDIOFOCUS_LOSS* 이벤트가 나타난 이후에 AUDIOFOCUS_GAIN가 발생합니다. 표 1에 보이는 것 중  양쪽 모두(주: 등록할 때, 이벤트의 상태)에서 사용되는 상수는 AUDIOFOCUS_GAIN 밖에 없습니다.

포커스 변경 리스너를 제어할 네 가지 경우가 있습니다. 애플리케이션이 AUDIOFOCUS_LOSS를 받는 것은 일반적으로 포커스를 다시 받지 않을 것을 의미합니다. 이 경우 앱은 오디오 시스템에 관련된 에셋을 해제하고 재생을 멈춰야 합니다. 예를 들어 사용자가 음악을 앱을 사용하여 재생한 후, 음악 앱에서 오디오 포커스를 가져갈 게임을 수행한다고 상상해보세요. 사용자가 게임을 언제 종료할 지 예상할 수도 없습니다. 아마도 사용자는 홈 런쳐로 갈 수 있습니다 (게임을 백그라운드 모드로 내버려두고요.) 그리고 아예 다른 앱을 켜거나 음악앱으로 돌아와 재개시켜 오디오 포커스를 다시 요청할 수 있겠죠.

그러나 다른 경우들은 논쟁의 여지가 있습니다. (위에 설명된 것 처럼) 영구적으로 오디오 포커스를 잃는 것과 일시적으로 잃는 것에서는 차이가 있습니다. 애플리케이션이 AUDIOFOCUS_LOSS_TRANSIENT를 받으면 앱은 AUDIOFOCUS_GAIN 이벤트를 받을 때까지 오디오 시스템의 사용을 멈추어야 합니다. AUDIOFOCUS_LOSS_TRANSIENT가 발생하면 앱은 포커스를 일시적으로 잃는다고 기억해야 합니다. 이것이 오디오 포커스를 얻었을 때 올바른 행동이 무엇인지 추론할 수 있게 합니다. (목록 2의 13-27 행을 보세요.)

가끔 앱이 오디오 포커스를 잃고 (AUDIOFOCUS_LOSS를 받고) 끼어든 앱이 종료되거나 그렇지 않으면 오디오 포커스를 버리기도 합니다. 이 경우 오디오 포커스를 가졌던 이전 애플리케이션이 AUDIOFOCUS_GAIN 이벤트를 받기도 합니다. 이어지는 AUDIOFOCUS_GAIN 이벤트에서 앱은 임시적인 상실 뒤에 얻어서 이래저래 오디오 시스템을 재개할 수 있는지 영속적인 상실로 부터 복구해서 재생을 설정해야하는 지를 확인해야 합니다.

애플리케이션이 (45초 미만의) 짧은 시간 만 오디오를 사용한다면  AUDIOFOCUS_GAIN_TRANSIENT 포커스 요청을 사용하고 재생이나 캡쳐가 종료된 후 포커스를 풀어야 합니다. 오디오 포커스는 시스템에서 스택처럼 다루어집니다. 오디오 포커스를 요청한 가장 마지막 프로세스가 이기는 식이죠.

오디오 포커스를 얻으면 MediaPlayer나 MediaEncorder를 생성하고 리소스를 할당할 적절한 시기입니다. 비슷하게 앱이 AUDIOFOCUS_LOSS를 받으면 할당된 어떤 리소스를 정리하기에 좋은 차례입니다. 오디오 포커스를 얻는 것은 3가지 유형이 있는데 표 1에 나와있는 세가지 오디오 포커스 상실과 관련있습니다. OnAudioFocusChangeListener에서 모든 상실 경우를 명시적으로 다루는 것은 좋은 습관입니다.

표 1. 오디오 포커스 획득과 상실 결과
획득상실
AUDIOFOCUS_GAINAUDIOFOCUS_LOSS
AUDIOFOCUS_GAIN_TRANSIENTAUDIOFOCUS_LOSS_TRANSIENT
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCKAUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK

노트: AUDIOFOCUS_GAIN은 두 부분에서 사용됩니다. 오디오 포커스를 요청할 때 AudioManager에 힌트로 전달되고, OnAudioFocusChangeListener의 이벤트 경우로 사용됩니다. 녹색으로 강조된 획특 이벤트는 오디오 포커스를 요청했을 때만 사용됩니다. 상실 이벤트는 오로지 OnAudioFocusChangeListener에서만 사용됩니다.

표 2. 오디오 스트림 타입
>
스트림 타입설명
STREAM_ALARM알람을 의한 오디오 스트림The audio stream for alarms
STREAM_DTMFDTMF 톤을 위한 오디오 스트림
STREAM_MUSIC"미디어"(음악, 팟캐스트, 비디오) 재생을 오디오 스트림
STREAM_NOTIFICATION알림을 위한 오디오 스트림
STREAM_RING폰 링을 위한 오디오 스트림
STREAM_SYSTEM시스템 사운드를 위한 오디오 스트림

애플리케이션은 AudioManager (목록 1 첫번째 줄)에 오디오 포커스를 요청합니다. (아래에 링크된 샘플 코드의 예제를 보세요.) 제공되는 3개의 인자는 오디오 포커스 체인지 리스너 객체 (선택적), 사용할 오디오 채널이 무엇인지 힌트 (표 2, 대부분의 앱은 STREAM_MUSIC를 써야 합니다.), 표 1 왼편의오디오 포커스 타입입니다. 오디오 포커스가 시스템에 의해 허용(AUDIOFOCUS_REQUEST_GRANTED) 되었다면 초기화를 하면 됩니다. (목록 1의 9번째 줄을 보세요.)

노트: 현재 프로세스에서 폰 전화가 있을 때 오디오 포커스가 허용되지 않으며 (AUDIOFOCUS_REQUEST_FAILED) 애플리케이션은 전화가 끝난 후에 AUDIOFOCUS_GAIN를 받지 않습니다.

OnAudioFocusChange() 구현에서 애플리케이션이 애플리케이션이 받는 onAudioFocusChange() 이벤트가 무엇인지는 표 3에 정리되어 있습니다.

오디오 포커스를 잃는 경우에 상실이 사실상 최종적인지 확인해야 합니다. 앱이 AUDIOFOCUS_LOSS_TRANSIENT, AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK를 받으면 금방 다른 오디오 포커스 변경 이벤트가 있어서 생성할 미디어 리소스를 (relase()를 불러 해제하지 않고) 붙잡아 둘 수 있습니다. 앱은 어떤 상태 변수나 단순한 상태 머신등을 이용해서 임시적인 상실을 경험했다는 것을 기록해야 합니다.

앱이 AUDIOFOCUS_GAIN로 영구적인 오디오 포커스를 요청했고 AUDIOFOCUS_LOSS_TRANSIENT나 AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK 이벤트를 받았다면 애플리케이션에 적절한 행동은 (원래 볼륨 상태를 어딘가 확실히 저장해두고) 스트림의 볼륨을 낮추고 AUDIOFOCUS_GAIN 이베트를 받았을 때 볼륨을 다시 올리는 것입니다. (아래 그림 1을 보세요.)



표 3. 포커스 변경 종류에 따른 적절한 행동
포커스 변경 종류적절한 행동
AUDIOFOCUS_GAIN상실 이벤트 후 획득 이벤트. 애플리케이션에 의해 설정된 다른 상태가 없다면 재생을 재개한다. 예를 들어 상실 이베트 이전에 사용자가 명시적으로 일시 정지한 경우.
AUDIOFOCUS_LOSS재생을 정지한다. 에셋을 해제한다.
AUDIOFOCUS_LOSS_TRANSIENT재생을 일시정지하고 상실이 일시적이란 것의 상태를 유지한다. AUDIOFOCUS_GAIN  이벤트가 나타나면 적절히 재생을 재개할 수 있다. 에셋을 해제하지 마라.
AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK볼륨을 줄이거나 재생을 일시정지한다. AUDIOFOCUS_LOSS_TRANSIENT의 상태를 저장한다. 애셋을 해제하지 마라.

결론과 더 읽을거리


안드로이드에서 좋은 오디오 시민 애플리케이션 법을 이해하는 것은 시스템 오디오 포커스 규칙과 각 상황에 적합하게 다루는 것을 존중하는 것을 의미합니다. 애플리케이션의 행동을 일관성있게 하고 사용자를 부정적으로 놀라게 하지 않게 노력하세요. 안드로이드 오디오 시스템에 대해 이야기할 더 많은 내용들이 있습니다. 아래에 자료들에서 추가적인 내용들을 찾으십시요.


예제 소스 코드는 여기있습니다.

2013년 10월 17일 목요일

성능 좋고 효율 좋은 하드웨어 스케일러 사용하기

안드로이드 개발자 관계 팀(Android Developer Relations Team) 하크 마츠다(Hak Matsuda)와 더크 더퍼티(Dirk Dougherty) 작성
원문: Using the Hardware Scaler for Performance and Efficiency

성능이 중요한 3D 게임을 만든다면 항상 사용자에게 더 나은 그래픽, 높은 프레임율(frame rate, 주: 초당 그려야하는 화면(프레임)의 갯수), 나은 반응성을 제공할 방법을 찾게됩니다. 또 플레이 중에 사용자의 배터리를 아끼고 단말기가 뜨거워지는 것을 막고 싶을 겁니다. 이 모든 영역을 최적화하는 것을 보조하기 위해 오늘날 마켓에 있는 거의 대부분의 단말기가 지원하는 하드웨어 스케일러의 이점을 고려해봅시다.

어떻게 동작하고 왜 이것을 써야 하는가? 


사실 현대적인 안드로이드 단말기들은 하드웨어 비디오 스케일러를 가진 CPU/GPU 칩셋을 사용합니다. 안드로이드는 높은 수준의 통합을 제공하며 이는 앱이 안드로이드 표준 API를 사용하여 자바나 네이티브 (C++) 코드에서 사용할 수 있습니다. 하드웨어 스케일러가 가져오는 이점은 시스템이 제공하는 단말기의 전체 크기 해상도에 맞추어진 기본 버퍼들 대신에  고정된 사이즈의 그래픽 버퍼에 그리기만 하면 된다는 것입니다.

고정 크기 버퍼에 그리면 단말 하드웨어는 여러분의 화면을 단말의 해상도에 맞추어 키우거나 줄이고 비율에 맞추어 조정하죠. 일반적으로 단말의 전체 해상도 보다 작은 고정 크기 버퍼를 생성할 수 있는데 이는 더 효과적으로 그려집니다. 특히 오늘날의 고해상도 화면에서요.

하드웨어 스케일러를 쓰는 것은 몇가지 이유에서 더 효과적입니다. 첫째로, 하드웨어 스케일러는 매우 빠르며, 멀티탭(주: 한번에 여러 픽셀을 이용하여 보정된 확대나 축소를 하는 것)와 아티팩츠(주: 자연에서 발생하지 않는 인공적인 어색한 현상. 그림자가 반짝거리는 것 등)를 감소하는 알고리즘을 사용하여 훌륭한 시각적인 결과를 만들어냅니다. 둘째로, 앱이 적은 크기의 버퍼에 그릴 수 있기 때문에 GPU에서 연산양은 줄어들고 성능이 향상됩니다.셋째로, 계산할 작업이 줄어든 결과로 GPU는 더 시원하게 돌 수 있고 배터리를 덜 사용합니다. 마지막으로, 원하는 크기의 그리기 버퍼를 사용할 수 있기 때문에 실제 화면 해상도와 상관없이 모든 디바이스에서 동일하게 쓸 수 있습니다.

채움율 최적화


휴대 장비의 GPU에서 화소 채움율(pixel fill rate, 주: 초당 그리는 점의 갯수)은 고성능 게임 앱의 성능 문제에서 가장 중요한 것 중의 하나입니다. 새로운 폰과 타블렛은 점점 높은 해상도를 제공하고, 이런 단말에서 2D, 3D 그래픽을 그리는 것은 채움율을 눈에 띄게 낮출 수 있습니다. GPU가 화면에 최대로 화소를 채우고 있음에도 불구하고 더 많이 채워야 한다면 프레임율이 떨어집니다.

다양한 칩에서 해상도에 따라 달라지는 전력 소모량
(퀄컴 제공)
이 병목 현상을 해치우기 위해, 매 프레임에 그리는 화소의 수를 줄여야 합니다. 이를 달성할 방법은 다양합니다. 그 중 하나는 깊이 전 단계(depth prepass, 주: 아래에 있는 그림을 애당초 그리지 않게 배제하는 것)등을 이용하는 것입니다. 하지만 정말 간단하고 효과적인 방법은 하드웨어 스케일러를 이용하는 거죠.

2560x1600 만큼 큰 전체 사이즈 버퍼에 그리는 거 대신에, 조금 더 작은 버퍼에 그릴 수 있습니다. 예를 들면 1280x720이나 1920x1080에 그리는 거죠. 하드웨어 스케일러는 어떤 추가 비용이나 시각적인 품질에서 적은 손실도 없이 장면을 확대할 수 있습니다.

전력 소모와 발열 감소하기


고성능 게임은 배터리를 많이 소모하고 발열이 심할 수 있습니다. 게임의 전력 소모와 발열 상태는 사용자에게 중요하기 때문에 개발자에게 꽤 중요한 고려점이 됩니다.

도표에서 보듯, 단말 GPU의 전력소모는 그리는 해상도가 커질 수록 의미있게 증가합니다. 많은 경우 GPU에서 많이 전력을 소모하는 것은 단말의 배터리 수명의 감소로 이어집니다.

추가로 CPU/GPU의 그리기가 많아질 수록 단말을 잡기 불편할 정도로 열이 발생합니다. 열이 발생하면 CPU/GPU를 시원하게 만들기 위해 GPU/GPU 속도를 조절하기도 합니다. 이는 게임에서 사용가능한 프로세스 파워를 줄이게 됩니다.

전력 소모와 발열 현상을 줄이기 위해 하드웨어 스케일러를 사용하는 것이 중요합니다. 적은 버퍼에 그리기 때문에 GPU는 그리는 동안 에너지를 덜 쓰고 열을 덜 냅니다.

안드로이드 API에서 하드웨어 스케일러 접근하기


안드로이드는 자바 코드나, 안드로이드 NDK를 통한 네이티브 (C++) 코드에서 표준 API를 사용하여 하드웨어 스케일러에 쉽게 접근할 수 있습니다.

해야할 일은 API를 사용하여 고정 크기 버퍼를 만들고 여기에 그리는 것입니다. 실제 단말기의 해상도도 고려할 필요가 없습니다. 원래 비율을 지키고 싶다면 화면에서 사용되는 버퍼와 비율을 맞추거나 버퍼에 그리는 것을 조절하면 됩니다.

자바 코드에서 스케일러를 다루는 것은 API 레벨 1에서 도입된 SurfaceView를 통하면 됩니다. 1280x720 해상도의 고정 크기 버퍼를 만드는 것은 다음과 같습니다.
surfaceView = new GLSurfaceView(this);
surfaceView.getHolder().setFixedSize(1280, 720);
네이티브 코드에서 스케일러를 사용하고 싶다면 안드로이드 2.3 (API 레벨 9)에서 소개된 NativeActivity를 사용할 수 있습니다. NativeActivity에서 1280x720 해상도의 고정 크기 버퍼를 사용하는 법은 여기에 있습니다.
int32_t ret = ANativeWindow_setBuffersGeometry(window, 1280, 720, 0);
버퍼의 크기를 정하면 하드웨어 스케일러는 활성화되고 특정 윈도우에 그리는 것에 이점이 생깁니다.

그래픽 버퍼 크기를 고르기


고정 크기의 그래픽 버퍼를 사용하면 목표 단말에서의 시각 품질과 성능, 효율 확보 사이에서 균형있는 크기를 선택하는 것이 중요합니다.

대부분의 고성능 3D 게임은 하드웨어 스케일러를 사용합니다. 추천하는 크기는 1080p(주: 세로 화소의 갯수가 1080)에 그리는 것입니다. 1080p는 시각 품질, 프레임율, 전력 소모에서 아름다운 지점입니다. 물론 720p에 만족한다면 더 효율적인 연산을 위해 이사이즈를 선택할 수 있습니다.

더 많은 정보


앱에서 하드웨어 스케일러의 이점을 원한다면 안드로이드 프레임워크나 네이티브 API를 사용하여 그리는지 여부에 따라 SurfaceViewNatieActivity 문서 클래스를 보세요.

곧 나올 하드웨어 스케일러를 사용하는 견본 코드도 참고하세요!

2013년 10월 15일 화요일

문자 애플리케이션 킷캣 대응하기

스캇 메인(Scott Main)과 데이비드 브라운(David Braun) 작성

문자 메시지를 주고받는 것은 휴대 장비의 근본적인 기능입니다. 많은 개발자들은 안드로이드의  문자 경험을 성공적으로 향상시킨 앱들을 개발해왔습니다. 일부 사람들은 감추어진 API들을 사용하여 문자 앱을 개발하였습니다. 우리는 이런 사용을 비추하는데 숨겨진 API들은 삭제될 수도 있고 새로운 단말기에서 호환성이 검증되지 않기 때문입니다. 그래서 문자 앱을 만들때 전체적인 지원 API 집합을 제공하고 문자 경험을 보다 예측가능하게 만들기 위해, 안드로이드 4.4(킷캣, KitKat)은 기존 API들을 공개하고 기본 문자 애플리케이션 개념을 추가하였습니다. 사용자는 시스템 설정에서 기본 문자 애플리케이션을 선택할 수 있죠.

이것은 이전 플랫폼 버전에서 숨겨진 문자 API를 사용했다면 조금의 수정하면 올해 출시 될 안드로이드 4.4에서도 제대로 동작하는 것을 의미합니다.

여러분의 앱을 기본 문자 앱으로 하기

안드로이드 4.4에서 단지 하나의 애플리케이션만 새로 도입된 SMS_DELIVER_ACTION 인텐트를 받을 수 있습니다. 새 문자 메시지가 오면 시스템은 이 인텐트를 브로드캐스트합니다. 어떤 앱이 이 브로드캐스트를 받을지는 시스템 설정에서 사용자가 기본 SMS 앱을 골라 결정합니다. 마찬가지로 기본 문자 앱만이 MMS가 도착했을 때 새 인텐트 WAP_PUSH_DELIVER_ACTION를 받습니다.

새 메시지를 받길 원하는 다른 앱들은 SMS가 왔을 때 대신에 SMS_RECEIVED_ACTION를 받을 수 있습니다. 하지만 (사용자가 기본 SMS 앱으로 지정한) SMS_DELIVER_ACTION 브로드캐스트를 받는 앱만 android.provider.Telephony 클래스와 상속된 클래스로 정의된 문자 프로바이더를 작성할 수 있습니다. 이것은 기존의 앱이 안드로이드 4.4에서 기본 문자 앱이 사용가능해질 때 앱이 망가지지 않고 SMS 프로바이더가 조용하게 실패하고, 문자 앱을 즉시 업데이트하지 않아도 된다는 점에서 중요합니다.

  • 브로드캐스트 리시버에서 SMS_DELIVER_ACTION ("android.provider.Telephony.SMS_DELIVER")에 대한 인텐트 필터를 포함합니다. 브로드캐스트 리서버는 BROADCAST_SMS 권한을 반드시 가져야 합니다.

    이 권한이 앱이 수신된 문자 메시지를 직접 받게 합니다.
  • 브로드캐스트 리시버에서 WAP_PUSH_DELIVER_ACTION("android.provider.Telephony.WAP_PUSH_DELIVER") 인텐트 필터를 MIME 타입 "application/vnd.wap.mms-message"와 함께 포함합니다. 이 브로드캐스트 리시버는 BROADCAST_WAP_PUSH 권한을 반드시 요구해야 합니다.

    이 권한이 앱이 수신된 MMS 메시지를 직접 받게 합니다.
  • 새 메시지를 전달할 액티비티에서 ACTION_SENDTO("android.intent.action.SENDTO")를 위한 인텐트 필터를 sms:, smsto:, mms:, mmsto: 스키마와 같이 포함합니다.

    이 필터가 메시지를 전달하길 원하는 다른 앱으로 부터 인텐트를 받게 합니다.
  • 서비스에서 sms:, smsto:, mms:, mmsto: 스키마와 함께 ACTION_RESPONSE_VIA_MESSAGE("android.intent.action.RESPOND_VIA_MESSAGE") 인텐트 필터를 포함합니다. 서비스는 SEND_RESPOND_VIA_MESSAGE 권한을 필요합니다.

    이 필터는 사용자가 여러분의 앱을 사용하여 즉시 문자 메시지를 받으며 걸려온 전화를 응답하는 것을 가능하게 합니다.
필요한 콤포넌트와 인텐트 필터의 예입니다.

<manifest>
    ...
    <application>
        <!-- BroadcastReceiver that listens for incoming SMS messages -->
        <receiver android:name=".SmsReceiver"
                android:permission="android.permission.BROADCAST_SMS">
            <intent-filter>
                <action android:name="android.provider.Telephony.SMS_DELIVER" />
            </intent-filter>
        </receiver>

        <!-- BroadcastReceiver that listens for incoming MMS messages -->
        <receiver android:name=".MmsReceiver"
            android:permission="android.permission.BROADCAST_WAP_PUSH">
            <intent-filter>
                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
                <data android:mimeType="application/vnd.wap.mms-message" />
            </intent-filter>
        </receiver>

        <!-- Activity that allows the user to send new SMS/MMS messages -->
        <activity android:name=".ComposeSmsActivity" >
            <intent-filter>
                <action android:name="android.intent.action.SEND" />                
                <action android:name="android.intent.action.SENDTO" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="sms" />
                <data android:scheme="smsto" />
                <data android:scheme="mms" />
                <data android:scheme="mmsto" />
            </intent-filter>
        </activity>

        <!-- Service that delivers messages from the phone "quick response" -->
        <service android:name=".HeadlessSmsSendService"
                 android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
                 android:exported="true" >
            <intent-filter>
                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:scheme="sms" />
                <data android:scheme="smsto" />
                <data android:scheme="mms" />
                <data android:scheme="mmsto" />
            </intent-filter>
        </service>
    </application>
</manifest>
SMS_RECEIVED_ACTION 브로드캐스트의 어떤 필터를 사용하는 기존 앱들은 안드로이드 4.4에서도 동작합니다. 하지만 새 메시지의 관찰자로서만 동작합니다. SMS_DELIVER_ACTION 브로드캐스트를 받지 않는 한 안드로이드 4.4의 문자 프로바이더를 쓸 수 없습니다.

안드로이드 4.4에서 시작되면 실시간에 플랫폼 버전을 확인하여  PackageManager.setComponentEnabledSetting()를 사용하여 SMS_RECEIVED_ACTION 브로드캐스트 수신을 멈추어야 합니다. 하지만 여러 분의 앱이 휴대전화 번호 인증과 같은 특별한 문자 메시지만 읽기를 원한다면 여전히 저 브로드캐스트를 받을 수 있습니다. SMS_RECEIVED_ACTION 인텐트를 안드로이드 4.4에서 다룬다면 다른 앱도 브로드캐스트를 필요할지 모르니 취소하지 말아야 합니다.

: 두 문자 브로드캐스트를 구별하기 위해 SMS_RECEIVED_ACTION를 단순히 "시스템이 문자를 받는다"로 생각하세요. SMS_DELIVER_ACTION는 기본 문자앱이기 때문에 "시스템이 여러분의 앱에 문자를 전달해준다"로 생각하고요.

기본 문자 앱이 아닌 경우에 비활성화되는 기능들

애플리케이션이 현재 기본 문자 앱으로 선택되지 않았다면 문자 프로바이더 작성 기능없이는 보내는 어떤 메시지도 사용자의 기본 문자 앱으로 나타나지 않기 때문에 새 메시지 전송하기 기능을 비활성화해야하는 것이 중요합니다.그래서 액티비티가 재게될 때 현재 기본 문자 앱의 패키지 네임을 반환하는 Telephony.Sms.getDefaultSmsPackage()를 질의하여 여러분의 앱이 기본 문자 앱인지 확인해야 합니다. 여러분의 패키지 이름과 일치하지 않는다면 메시지 전송 기능을 비활성화 합니다.
메시지 전송과 수신을 활성화하기 위해 시스템이 제공하는 다이얼로그를 띄울 수 있습니다. 이 다이얼로그에서 사용자는 여러분의 앱을 기본 앱으로 설정할 수 있습니다. 다이얼로그를 표시하기 위해 Sms.Intents.EXTRA_PACKAGE_NAME를 키로 삼고 여러분의 패키지 네임을 문자 값으로 삼는 extra를 포함한 Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT 인텐트와 함께 startActivity()를 호출합니다.

우아한 사용자 경험을 제공하기 위해 액티비티가 재개될 때 앱이 기본 문자 앱인지 확인하고, 사용자가 기본 문자 앱을 변경하게 하는 메시지를 담게 사용자 인터페이스를 수정하세요. 예를 들어 액티비티 코드는 이렇게 될 수 있습니다.
public class ComposeSmsActivity extends Activity {

    @Override
    protected void onResume() {
        super.onResume();

        final String myPackageName = getPackageName();
        if (!Telephony.Sms.getDefaultSmsPackage(this).equals(myPackageName)) {
            // App is not default.
            // Show the "not currently set as the default SMS app" interface
            View viewGroup = findViewById(R.id.not_default_app);
            viewGroup.setVisibility(View.VISIBLE);

            // Set up a button that allows the user to change the default SMS app
            Button button = (Button) findViewById(R.id.change_default_app);
            button.setOnClickListener(new View.OnClickListener() {
                public void onClick(View v) {
                    Intent intent =
                            new Intent(Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT);
                    intent.putExtra(Telephony.Sms.Intents.EXTRA_PACKAGE_NAME, 
                            myPackageName);
                    startActivity(intent);
                }
            });
        } else {
            // App is the default.
            // Hide the "not currently set as the default SMS app" interface
            View viewGroup = findViewById(R.id.not_default_app);
            viewGroup.setVisibility(View.GONE);
        }
    }
}

문자 백업 복구 앱에 대한 조언

문자 프로바이더를 쓸 수 있는 기능이 사용자가 기본 문자 앱으로 선택한 앱에 한정되기 때문에 순전히 문자를 백업하고 복구하게 설계된 기존 앱들이 안드로이드 4.4에서 문자를 복구하는 것이 현재 허용되지 않습니다. 문자 메시지를 백업하고 복구하는 앱은 기본 문자 앱으로 지정이 되어야 SMS 프로바이더에 메시지를 쓸 수 있습니다. 하지만 앱이 SMS 메시지를 받고 읽을 필요가 없다면 기본 문자 앱 설정을 유지해서는 안됩니다. 그래서 사용자가 여러분의 앱을 열어 일회용 복원 명령을 시작할 때 다음 설계된 기능적인 사용자 경험을 제공할 수 있습니다.
  1. 현재 기본 문자 앱의 패키지 네임을 질의 하고 저장합니다.
    String defaultSmsApp = Telephony.Sms.getDefaultSmsPackage(context);
  2. 문자를 복원하기 위해 기본 문자앱을 사용자가 전환하게 요청합니다. (문자 프로바이더를 작성하기 위해서는 기본 문자 앱이 되어야 합니다.
    Intent intent = new Intent(context, Sms.Intents.ACTION_CHANGE_DEFAULT);
    intent.putExtra(Sms.Intents.EXTRA_PACKAGE_NAME, context.getPackageName());
    startActivity(intent);
  3. 모든 문자 메시지가 복원되면 (첫번째 절차에서 저장된) 기존에 서택된 기본 문자 앱으로 돌아가도록 요청합니다.When you finish restoring all SMS messages, request the user to change the default SMS app back to the previously selected app (saved during step 1).
    Intent intent = new Intent(context, Sms.Intents.ACTION_CHANGE_DEFAULT);
    intent.putExtra(Sms.Intents.EXTRA_PACKAGE_NAME, defaultSmsApp);
    startActivity(intent);

문자 앱 업데이트 준비하기

안드로이드에서 최고의 경험을 사용자에게 제공하기 위해 가능한 빨리 업데이트하길 격려합니다. 안드로이드 4.4의 변화를 컴파일하고 실험하기 위해 필요한 안드로이드 4.4를 위한 SDK  콤포넌트 곧 제공하겠습니다. 계속 개선하세요!



2013년 10월 14일 월요일

안드로이드 지원 라이브러리의 렌더스크립트

안드로이드 렌더스크립트 팀의 팀 머레이(Tim Murray) 작성
원문: RenderScript in the Android Support Library
렌더스크립트 지원 라이브러리는
렌더스크립트의 최신 기능을
안드로이드 2.2 이상의 단말기에서
사용할 수 있는 이점을 제공합니다.

개발자로부터 가장 많이 받는 부탁 중 하나는 더 많은 장비에 렌더스크립트의 최신 기능을 활성해달라는 것입니다. 몇번의 안드로이드 릴리즈 동안 우리는 렌더스크립트 런타임에 많은 기능을 추가했습니다. 하지만 코어 안드로이드 플랫폼 버전에 의존적인 런타임은 새로운 기능을 지원하는 단말기의 범위를 한정지었습니다. 우리는 작년부터 이 문제를 해결하기 위해 일했고 이제 모든 안드로이드 개발자에게 공유할 준비가 되었습니다.

오늘 우리는 안드로이드 프로요 (2.2) 버전부터 전체 버전에 렌더스크립트의 이점을 사용할 수 있도록 새 렌더스크립트 지원 라이브러리와 향상된 SDK 도구를 발표합니다.

ADT v22.2, SDK 도구 v22.2, 안드로이드 빌드 툴 v18.1.0을 쓰면 안드로이드 2.2 이상 용으로 타겟팅된 앱들은 4.3 버전의 네이티브 렌더스크립트와 동일한 기능을 사용할 수 있습니다. 이는 고성능의 intrinsic들과 스크립트를 위한 새로운 성능 최적화를 사용할 수 있습니다.

렌더스크립트 지원 라이브러리 사용하기

렌더스크립트 지원 라이브러리를 사용하는 것은 꽤 직선적인 과정을 가집니다. 한번 ADT와 SDK 도구를 업데이트하면 앱에서 렌더스크립트 사용을 시작하기 위해 해야하는 것은 두가지 밖에 없습니다.


  1. 렌더스크립트를 사용할 클래스에서 렌더스크립트 지원 라이브러리(RenderScript Support Library)를 android.support.v8.renderscript로부터 임포트합니다. 이미 네이티브 렌더스크립트를 사용하고 있다면 android.renderscript에서 android.support.v8.renderscript로 import를 바꿉니다.
    import android.support.v8.renderscript.*;
  2.  project.properties에서 타겟팅이 android-18인지 확인하고 아래의 줄들을 추가합니다.
    renderscript.target=18
    renderscript.support.mode=true
    sdk.buildtools=18.1.0
이게 답니다. 렌더스크립트 지원 라이브러리를 쓰면 네이티브 렌더스크립트 패키지에서 처럼 동일한 API를 계속 쓸 수 있습니다. (몇몇 작은 예외가 있는데 아래에 다루겠습니다.) 최신의 렌더스크립트 툴체인에서 하듯이 동일한 기능을 쓸 수 있습니다.

렌더스크립트 지원 라이브러리를 설정하는 자세한 내용을 보고 싶다면, 렌더스크립트 자바 API 접근하기를 보세요.

API와 구현 상세

렌더스크립트 지원 라이브러리를 앱에서 사용하고 싶다면 알아야 할게 몇가지 있습니다.

  • 첫째로 렌더스크립트 지원 라이브러리는 API 레벨에서와 이후에 지원하는 네이티브 렌더스크립트 API 기능의 대부분을 지원합니다. 주목할 예외는 Allocation.USAGE_IO_INPUTAllocation.USAGE_IO_OUTPUT입니다. 현재 렌더스크립트 지원 라이브러리에서는 지원하지 못합니다.
  • 둘째로 4.3 이후의 렌더스크립트 애플리케이션은 해당 단말기에서 지원하는 프로세서들에서도 돌 수 있는데 비해 안드로이드 4.2와 이전 단말기에서는 렌더스크립트 애플리케이션이 전적으로 CPU에서 돈다는 것입니다. (주: 안드로이드 4.3에서도 넥서스 10등의 일부 단말에서 GPU 작동이 지원됩니다.) 스크립트의 지원 라이브러리 버전들은 모든 가능한 플랫폼을 지원하기 위해 미리 컴파일되어 있기 때문에 런타임에 컴파일되는 안드로이드 4.3에 비해 컴파일러 최적화에 제한이 있고 그에 따른 성능 최적화에 불이익이 있습니다.

렌더스크립트 라이브러리가 나오게 되어 우리는 정말 기쁩니다. 우리는 렌더스크립트가 출시된 앱에서 렌더스크립트가 성능을 향상시켜온 것을 보아왔습니다. 2013년 5월 이후의 구글+ 안드로이드 앱의 사진 편집기가 그 중 하나로 크고 넓은 사용범위에서 자신을 증명했습니다. 우리는 여러분들도 렌더스크립트와 함께 즐거웠으면 좋겠습니다.