2012년 1월 11일 수요일

안드로이드 4.03 플랫폼과 갱신된 SDK 툴

[이 글은 안드로이드 SDK 테크 리드인 Xavier Durochet이 쓴 글입니다.](Android 4.0.3 Platform and Updated SDK tools의 번역입니다.)

오늘(12월 16일) 안드로이드 4.0.3을 공개했습니다. 4.0.3 버전은 안드로이드 4.0 (아이스크림 샌드위치) 플랫폼의 향상된 버전입니다. 이 릴리즈는 폰과 개발자를 위한 몇몇 새로운 API를 포함하였고, 폰과 타블렛을 위한 다양한 최적화와 버그 수정을 포함합니다. 새 API 레벨은 15입니다.

4.0.3에 새로 추가된 API들입니다.


  • 컨택트 프로바이더의 소셜 스트림 API: 소셜 스트림은 상태 갱신과 체크인 등입니다. 이제 소셜 스트림을 지원하는 애플리케이션은 사진까지 포함된 스트림 항목들을 각 사용자의 연락처 데이터에 동기화할 수 있습니다. 이 사용자 API가 적용된 앱은 사용자에게 사람들이 그들의 사진, 연락처 정보와 함께 무엇을 하고 어떤 말을 했는지 보여줍니다.
  • 캘린더 프로바이더 강화. 이벤트를 쉽게 추적할 수 있게 앱이 이벤트에 색상을 넣을 수 있고, 새로운 참석자 타입과 상태가 추가되었습니다.
  • 새로운 카메라 능력. 앱은 비디오 안정화를 측정하거나 관리할 수 있고, 필요하다면 QVGA 해상도 프로파일을 사용합니다.
  • 접근성 개량. 스크린 리더에 대한 컨텐츠 접근, TTS (Text-to-speech) 엔진을 위한 새로운 상태와 에러 보고를 향상시켰습니다.
  • 그래픽, 데이타베이스, 철자검사, 블루투스 등에서 점진적인 향상이 있었습니다.
플랫폼에서 새로워진 점의 전체적인 개요는 안드로이드 API 4.0.3 개요를 보세요.

더 나아가 우리는 우리의 파트너가 아이스크림 샌드위치의 기반 버전으로 안드로이드 4.0.3로 집중하도록 할 것입니다. 새로운 플랫폼은 수 주내에 폰과 타블렛으로 선보일겁니다. 가능한 빨리 안드로이드 4.0.3에서 애플리케이션을 시험할 수 있도록 강력히 격려하겠습니다.

그리고 최근에 릴리즈된 SDK 툴의 새버전 (r16)과 이클립스 플러그인 (ADT 16.0.1)도 환기드리고 싶습니다. NDK도 r7 버전으로 올렸습니다.

안드로이드 4.0.3과 다른 플랫폼 버전에 대한 더 많은 정보를 얻으려면 안드로이드 개발자 사이트로 방문하세요. 새 플랫폼을 개발하고 테스트하라면 안드로이드 SDK 관리자를 사용하여 새버전을 SDK에 받을 수 있습니다.

2012년 1월 9일 월요일

어디에서나 Holo

[스타일을 다루는 안드로이드 프레임워크 엔지니어 Adam Powell가 쓴 글입니다. -Tim Bray](이 글은 Holo Everywhere의 번역입니다.)

안드로이드 4.0에서 Holo 테마 패밀리를 소개했습니다. 안드로이드 3.0부터 소개된 Holo가 더 세련되어진 것입니다. 하지만 이전에는 몇몇 디바이스에선 시스템 테마가 Holo로 갱신되지  않았거나 일반적으로 쓸 수 없었습니다. 많은 개발자들이 이 점을 알고 있었죠. 시스템 테마를 쓰면 사용자가 기존에 원하던 대로 쓸 수 있고, 개발자가 개발 기간을 줄일 수 있는 이점이 있습니다. 이런 이점은 앱 개발자가 결과를 신뢰성 있게 예측할 수 있을 때 만 가능합니다. 안드로이드 4.0 이전에는 장비마다 시스템 테마가 차이났기에 예측 가능한 하나의 룩 앤 필을 가지는 앱을 만드는게 어려웠습니다. 우리는 개발자 커뮤니티를 위해 아이스크림샌드위치부터 이 상황을 개선하였습니다.

Theme.Holo
만약 안드로이드 스타일과 테마 시스템에 낯설다면 아래 글을 계속 보기 전에 스타일과 테마를 먼저 읽는 것을 추천합니다.


호환성 표준

안드로이드 4.0에서 Holo부터는 사정이 달라졌습니다. 구글은 Holo 테마 패밀리를 안드로이드 4.0 이상의 디바이스 호환성 요구사항으로 포함시켰습니다. 디바이스가 안드로이드 마켓을 탑재할려면 오리지날 Holo 테마를 그대로 포함해야 합니다.

표준화는 Holo 위젯 스타일 전체에도 해당됩니다. Widget.Holo 스타일은 모든 디바이스에서 안정화되기 때문에 여러 분의 앱에서 점진적인 수정용 부모 스타일로 안전하게 쓸 수 있습니다. (주: 안드로이드의 스타일도 부모로 부터 상속을 받을 수 있습니다.)

안드로이드 4.0의 Holo 테마 패밀리는 Theme.Holo, Theme.Holo.Light, Theme.Holo.Light.DarkActionBar를 포함합니다. 작동중인 테마들의 예는 이 글의 스크린 샷들에서 볼 수 있습니다.

Holo 테마를 사용하려면 메니페스트(manifest)나 애플리케이션 요소에서 하나를 명시적으로 지정합니다. (예: android:theme="@android:style/Theme.Holo".) 모든 안드로이드 4.0 호환 디바이스에서 변경없이 그대로 뜰 것입니다. Holo 테마는 앱에서 커스터마이징을 하기 위한 안정된 부모 테마입니다.
Theme.Holo.Light

디바이스 테마는 어때요?

우리는 제조사가 그들의 디바이스 전반에 자사의 테마로 사용자 경험을 만드는 것을 막을 생각은 없습니다. 사실은 우리는 제조사가 커스터마이징하기 더 쉽도록 더 나아간 것입니다. 안드로이드 4.0 API (레벨 14)에서 안드로이드 3.0에 소개된 Holo 패밀리를 보완할 새로운 공용 테마 패밀리, DeviceDefault 추가했습니다. DeviceDefault 테마는 디바이스의 고유의 룩 앤 필에 대한 별명입니다. DeviceDefault 테마 패밀리와 위젯 스타일 패밀리는 개발자로 하여금 온전한 디바이스 고유 테마를 선택할 수 있게 합니다.

공식적으로 이 테마들의 분리는 디바이스의 업데이트를 더 빠르게 만들고, 제조사가 추후에 새로운 플랫폼 버전을 통합하는 걸 쉽게 하는 것입니다. 구글 넥서스 디바이스들은 수정안된 Holo 테마를 DeviceDefault라고 별칭 붙였습니다.

선택한 테마를 사용하기

우리는 테마를 적용한 앱에 여러 공통 미터법과 색상 팔레트를 사용할 수 있도록 여러 테마 속성을 추가하였습니다. 리스트 아이템과 같은 UI  공통 요소에서 사용될 하이라이트 색상, 기본 패딩, 마진등의 속성을 포함하였습니다. (Holo와 DeviceDefault를 포함해서) 선택된 테마를 앱에 통합하기 위해 아래 예제처럼 테마 속성을 지정할 수 있습니다.

시스템 내장된 터치 하이라이트를 사용한 샘플 버튼

<ImageButton android:id="@+id/my_button"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@drawable/button_icon"
    android:background="?android:attr/selectableItemBackground" />

시스템 테마의 값을 사용한 사용자 pressedHighLightColor 속성을 지닌 샘플 위젯

<MyWidget android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    myapp:pressedHighlightColor="?android:attr/colorPressedHighlight" />

시스템 제공된 미터법과 텍스트 외향을 사용한 열거 항목 배치 샘플

<LinearLayout android:layout_width="match_parent"
    android:layout_height="?android:attr/listPreferredItemHeight"
    android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
    android:paddingRight="?android:attr/listPreferredItemPaddingRight">
    <TextView android:id="@+id/text"
        android:textAppearance="?android:attr/textAppearanceListItem" />
    <!-- Other views here -->
</LinearLayout>

Theme.Holo.DarkActionBar
(API 레벨 14이상 가능)
옛날 앱을 위한 디폴트

만약 앱이 메니페스트에 명시적으로 테마를 정하지 않으면 안드로이드 4.0은 앱의 앱의 원래 기대치를 담고 있는 targetSdkVersion를 기초로 기본 테마를 결정합니다. 11보다 적으면 @android:style/Theme,11부터 13사이면 @android:style/Theme.Holo, 14 이상이면 @android:style/Theme.DeviceDefault입니다.

안드로이드 2.x을 지원하며 Holo 사용하기

기존 디바이스가 업데이트 되거나 새로운 디바이스가 출시될 동안 많은 안드로이드 개발자들은 2.x 버전 디바이스까지 지원하길 원합니다. 그렇다고 새로운 테마를 지원하는 디바이스에서 새로운 테마를 사용하는 이점을 포기할 수 없죠. 디바이스에 동작하는 플랫폼 버전에 기반해 자동으로 선택되는 테마를 안드로이드 리소스 시스템을 사용하여 만들 수 있습니다.

Theme.Holo와 Theme.Holo.Light는 API 레벨 11부터 사용가능하지만Theme.Holo.Light.DarkActionBar는 API 레벨 14에 추가되었습니다.

res/values/themes.xml

<resources>
    <style name="MyTheme" parent="@android:style/Theme">
        <!-- Any customizations for your app running on pre-3.0 devices here -->
    </style>
</resources>

res/values-v11/themes.xml

<resources>
    <style name="MyTheme" parent="@android:style/Theme.Holo">
        <!-- Any customizations for your app running on devices with Theme.Holo here -->
    </style>
</resources>

마지막으로 AndroidManifest.xml

<!-- [...] -->
    <application android:name="MyApplication"
            android:label="@string/application_label"
            android:icon="@drawable/app_icon"
            android:hardwareAccelerated="true"
            android:theme="@style/MyTheme">
<!-- [...] -->

이 아이디어가 맘에 든다면 당신의 리소스에 설정해 다른 값의 테마 속성을 만들 수 있습니다. 안드로이드 리소스 시스템에 대해 더 알고 싶으면 애플리케이션 리소스를 보세요.

끝으로

4.0 이상에서 동작하는 안드로이드 앱은 Holo 테마를 사용할 수 있습니다. 사용자 스킨을 가진 디바이스에서 앱을 돌릴 때에도 Holo의 룩 앤 필은 동일하게 보이는 것이 보장됩니다. 디바이스의 기본 스타일을 사용하고 싶은 앱은 이제 공개 API인 DeviceDefault 테마를 사용하면 됩니다. 이 변화는 어떤 디바이스에서 다른 디바이스로 갈 때의 걱정할 시간을 줄이고 더 많은 시간을 설계에 사용할 수 있게 합니다. 마지막으로 안드로이드 리소스 시스템은 오래된 디바이스를 위한 우아한 폴백을 제공하며 최신 플랫폼의 특성을 사용할 수 합니다. (주: 폴백은 새로운 기능이 동작하지 않은 구형 장비를 위해 대체 기능을 제공하는 것입니다.)

2012년 1월 4일 수요일

IME에 음성 타이핑 추가하기

[이 글은 음성 타이핑을 작업하는 안드로이드 엔지니어 Luca Zonolin가 작성하였습니다. - Tim Bray](Add Voice Typing To Your IME의 번역입니다.)

안드로이드 4.0에 추가된 새 기능 중 음성 타이핑이 있습니다. 말하고 있는 동안에 텍스트 박스에 인식 결과가 나타나는게 차별점입니다. 당신이 IME 개발자라면 기존의 IME에 음성 타이핑을 쉽게 결합할 수 있습니다.

라이브러리를 받고 IME를 아래처럼 수정하면 간단히 통합됩니다. 안드로이드 2.2 버전 이상의 장비에서 부드럽게 실행되죠. 4.0 이상 버전 사용자는 음성 타이핑을 사용하게 되고 이전 버전에서는 일반적인 음성 인식을 사용하게 됩니다. 둘의 차이는 아래의 그림을 보세요.

어떻게 음성 입력을 통합시키는지 보고 싶다면 sample IME를 보면 됩니다. IME는 정말 단순한 단지 하나의 버튼만 포함하고 있습니다. 마이크(microphone)입니다. 사용자는 마이크를 눌러 음성 인식을 동작시킵니다.

아래 순서대로하면 IME에 음성 인식을 통합할 수 있습니다.

라이브러리 다운 받기

라이브러리를 받아 당신의 IME APK에 추가합니다.

음성 인식 트리거를 만들기

라이브러리는 VoiceRecognitionTrigger 헬퍼 클래스를 포함합니다. 여러분의 IME의 InputMethodService#onCreate 메서드에서 클래스의 인스턴스를 만듭시다.
public void onCreate() {
    super.onCreate();
    ...
    mVoiceRecognitionTrigger = new VoiceRecognitionTrigger(this);
}

IME에 마이크 아이콘 추가하기

IME의 UI를 수정하고, 마이크 아이콘을 추가하며, 음성 인식을 개시할 OnClickListener를 등록해야합니다. 샘플 IME의 에셋(asset)에서  찾을 수 있습니다. 마이크 아이콘은 음성 인식이 설치되었을 때만 표시됩니다. VoiceRecognitionTrigger#isInstalled()를 사용합시다.
public View onCreateInputView() {
  LayoutInflater inflater = (LayoutInflater) getSystemService(
      Service.LAYOUT_INFLATER_SERVICE);
  mView = inflater.inflate(R.layout.ime, null);
  ...
  mButton = (ImageButton) mView.findViewById(R.id.mic_button);
  if (mVoiceRecognitionTrigger.isInstalled()) {
    mButton.setOnClickListener(new OnClickListener() {
      @Override
      public void onClick(View v) {
        mVoiceRecognitionTrigger.startVoiceRecognition();
      }
    });
    mButton.setVisibility(View.VISIBLE);
  } else {
    mButton.setVisibility(View.GONE);
  }
  return mView;
}
IME가 여러 언어를 지원할 경우 어떤 언어가 인식될지 startVoiceRecognition()의 인자로 지정할 수 있습니다.

IME 시작할 때 트리거 작동하기

IME 시작할 때 트리거를 알려 텍스트 뷰에 인식 중인 결과를 표시할 수 있습니다.
@Override
public void onStartInputView(EditorInfo info, boolean restarting) {
  super.onStartInputView(info, restarting);
  if (mVoiceRecognitionTrigger != null) {
    mVoiceRecognitionTrigger.onStartInputView();
  }
}

AndroidManifest 수정하기

인텐트 API를 통해 음성 인식하려면 라이브러리가 서비스와 액티비티를 사용해야하도록 메니페스트에 추가해야 합니다.
<manifest ... >
  <application ...>
    ...
    <service android:name="com.google.android.voiceime.ServiceHelper" />
    <activity
        android:name="com.google.android.voiceime.ActivityHelper"
        android:theme="@android:style/Theme.Translucent.NoTitleBar"
        android:excludeFromRecents="true"
        android:windowSoftInputMode="stateAlwaysHidden"
        android:finishOnTaskLaunch="true"
        android:configChanges="keyboard|keyboardHidden|navigation
                               |orientation"/>
  </application>
</manifest>

마이크 아이콘 동적으로 갱신하기 (선택)

이 단계는 선택적이지만 사용자 경험을 향상시키려면 구현하는게 좋습니다. 음성 인식은 네트워크 연결이 필요합니다. 네트워크가 되지 않을 때 사용자에게 음성 인식이 현재 작동되지 않은 것을 알려야 합니다. 이렇게 할려면 VoiceRecognitionTrigger.Listener을 등록하고 이에 따라 마이크를 활성화/비활성화해야 합니다.

리스너는 InputMethodService#onCreate에서 등록하고 InputMethodService#onDestroy에서 해제합니다. 이를 지키지 않으면 리스너가 샙니다.
@Override
public void onCreate() {
  super.onCreate();
  ... 
  mVoiceRecognitionTrigger = new VoiceRecognitionTrigger(this);
  mVoiceRecognitionTrigger.register(new VoiceRecognitionTrigger.Listener() {
    @Override
    public void onVoiceImeEnabledStatusChange() {
      updateVoiceImeStatus();
    }
  });
}
...
@Override
public void onDestroy() {
  ...
  if (mVoiceRecognitionTrigger != null) {
    mVoiceRecognitionTrigger.unregister(this);
  }
  super.onDestroy();
}
private void updateVoiceImeStatus() {
  if (mVoiceRecognitionTrigger.isInstalled()) {
    mButton.setVisibility(View.VISIBLE);
    if (mVoiceRecognitionTrigger.isEnabled()) {
      mButton.setEnabled(true);
    } else {
      mButton.setEnabled(false);
    }
  } else {
    mButton.setVisibility(View.GONE);
  }
  mView.invalidate();
}
그리고 메니페스트에 퍼미션을 추가합니다.
<manifest ... >
  ...
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
  ...</manifest>

이게 전부에요

음성 인식은 사용자에게 많은 것을 쉽게 해줍니다. IME에 음성인식을 지원하는 것에 대해 감사드립니다.

2012년 1월 2일 월요일

XmlPullParser.nextText()를 주의합시다.

[이 글은 달빅 팀의 Jesse Wilson이 작성한 것입니다. -Tim Bray] (Watch out for XmlPullParser.nextText()의 번역입니다.)



안드로이드에서 XML 파싱에 XmlPullParser를 사용하는 것이 효과적이고 유지보수 가능한 방법입니다. 역사적으로 안드로이드엔 이 인터페이스의 두가지 구현이 있습니다.

KXmlParser, XmlPullParserFactory.newPullParser()를 통합니다.
ExpatPullParser, Xml.newPullParser()를 통합니다.

Xml.newPullPaser()의 구현은 nextText()를 호출 시 문서에 명시한 END_TAG로의 진입을 항상하지 못하는 버그가 있습니다. 그래서 몇몇 앱에서 추가적인 next()nextTag()를 호출해서 이를 회피하고 있습니다.


 public void parseXml(Reader reader)
            throws XmlPullParserException, IOException {
        XmlPullParser parser = Xml.newPullParser();
        parser.setInput(reader);

        parser.nextTag();
        parser.require(XmlPullParser.START_TAG, null, "menu");
        while (parser.nextTag() == XmlPullParser.START_TAG) {
            parser.require(XmlPullParser.START_TAG, null, "item");
            String itemText = parser.nextText();
            parser.nextTag(); // this call shouldn’t be necessary!
            parser.require(XmlPullParser.END_TAG, null, "item");
            System.out.println("menu option: " + itemText);
        }
        parser.require(XmlPullParser.END_TAG, null, "menu");
    }

    public static void main(String[] args) throws Exception {
        new Menu().parseXml(new StringReader("<?xml version='1.0'?>"
                + "<menu>"
                + "  <item>Waffles</item>"
                + "  <item>Coffee</item>"
                + "</menu>"));
    }

아이스크림 샌드위치에서 우리는 KXmlParser를 리턴하도록 Xml.newPullParser()를 변경했고 ExpatPullParser 클래스를 삭제했습니다. 이 변경은 nextTag() 버그를 고치죠. 불행히도 현재 버그를 회피하던 앱들이 아이스크림 샌드위치에서 망가질 수 있습니다.

org.xmlpull.v1.XmlPullParserException: expected: END_TAG {null}item (position:START_TAG <item>@1:37 in java.io.StringReader@40442fa8) 
     at org.kxml2.io.KXmlParser.require(KXmlParser.java:2046)
     at com.publicobject.waffles.Menu.parseXml(Menu.java:25)
 at com.publicobject.waffles.Menu.main(Menu.java:32)

이 수정된 코드는 현재 위치가 END_TAG가 아닐 경우에만 nextText ()호출 후 nextTag()를 호출합니다.

while (parser.nextTag() == XmlPullParser.START_TAG) {
      parser.require(XmlPullParser.START_TAG, null, "item");
      String itemText = parser.nextText();
      if (parser.getEventType() != XmlPullParser.END_TAG) {
          parser.nextTag();
      }
      parser.require(XmlPullParser.END_TAG, null, "item");
      System.out.println("menu option: " + itemText);
  }

위의 코드는 모든 릴리즈에서 XML을 정확히 파싱합니다. 만약 당신의 앱에서 nextText()가 널리 쓰이고 있다면 nextText()를 호출 곳에 이 헬퍼 메서드를 이용하세요.

private String safeNextText(XmlPullParser parser)
          throws XmlPullParserException, IOException {
      String result = parser.nextText();
      if (parser.getEventType() != XmlPullParser.END_TAG) {
          parser.nextTag();
      }
      return result;
  }

단일한 XmlPullParser로 간 점이 유지보수를 단순하게 만들고 우리가 더 많은 에너지를 성능 향상에 쓸 수 있게 합니다.