Category Archives: 번역

[번역] Google Play 사용자 리뷰에 답글 달기

이 포스트는 구글 안드로이드 개발자 블로그의 포스트를 번역한 내용입니다. 원문과 표현상의 차이는 있을 수 있으나 전체적인 맥락은 동일함을 미리 말씀드리며, 한국 안드로이드 개발자들에게 도움이 되었으면 좋겠습니다.

Google Play에 있는 사용자 리뷰는 사용자들이 좋은 앱을 발견하고 개발자와 앱 사용 예정자들에게 피드백을 전달하기에 좋은 방법입니다.  하지만, 개발자들이 사용자들에게 피드백을 전달하고 싶을 때는 어떻게 할까요?  가끔은 사용자들에게 약간의 도움이 필요할 수도 있고, 개발자가 새롭게 추가된 기능을 공유하고 싶을 때도 있습니다.

이런 이유 때문에 Google Play 개발자들이 Google Play Android Developer Console에서 사용자 리뷰에 답장을 할 수 있는 기능을 추가하게 되었습니다.  개발자들은 추가적인 정보를 수집하고, 앱 사용법을 안내하고, (가장 중요하게) 사용자가 요청한 기능이 추가되었음을 알릴 수 있습니다.

또한, 개발자가 리뷰에 답장을 하면 사용자의 이메일로 전달될 것입니다.  만약 추가적인 지원이 필요하거나 리뷰를 업데이트 할 경우, 사용자는 개발자에게 직접 연락할 수도 있습니다.

이 기능을 오늘  Top Developer badge 개발자들에게만 제공합니다.  사용자와 개발자의 피드백을 기초로 추 후 추가적인 Google Play 개발자들에게도 제공될 예정입니다.

대화는 양방향이어야 하며, 개발자와 사용자 사이의 토론은 궁극적으로 모두에게 이득이 되는 더 나은 앱을 만들어 낼 것입니다.

관련 포스트: http://android-developers.blogspot.kr/2012/06/replying-to-user-reviews-on-google-play.html

[번역] Using DialogFragments

이 포스트는 구글 안드로이드 개발자 블로그의 포스트를 번역한 내용입니다. 원문과 표현상의 차이는 있을 수 있으나 전체적인 맥락은 동일함을 미리 말씀드리며, 한국 안드로이드 개발자들에게 도움이 되었으면 좋겠습니다.

허니컴은 앱에서 여러 액티비티 사이에서 재사용 할 수 있는 UI와 로직을 지원하기 위해 Fragments라는 것을 소개했습니다. 동시에 액티비티의 showDialog와 dismissDialog 메서드 대신 DialogFragments를 사용하길 권장하고 있습니다.

이 포스트에서는 v4 지원 라이브러리 (허니콤 이전 버전 호환을 위한)를 이용해 DialogFragments를 어떻게 사용하는지 간단한 대화창을 통해 보여드릴 것입니다. Dialogs와 관련한 디자인 가이드라인은 Android Design 사이트를 참고하시기 바랍니다.

 

레이아웃

여기에 fragment_edit_name.xml라는 이름을 가진 대화창의 레이아웃이 있습니다.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/edit_name"
    android:layout_width="wrap_content" android:layout_height="wrap_content"
    android:layout_gravity="center" android:orientation="vertical"  >

    <TextView
        android:id="@+id/lbl_your_name" android:text="Your name"
        android:layout_width="wrap_content" android:layout_height="wrap_content" />

    <EditText
        android:id="@+id/txt_your_name"
        android:layout_width="match_parent"  android:layout_height="wrap_content"
        android:inputType=”text”
        android:imeOptions="actionDone" />
</LinearLayout>

두 개의 선택적인 속성을 주의해 보시기 바랍니다.  android:inputType=”text” 속성과 함께 설정된 android:imeOptions=”actionDone” 속성은 소프트 키보드가 엔터키 대신 완료키를 보이도록 설정합니다.

대화창 코드

대화창은 DialogFragment를 상속하며, 하위버전 호환성을 위해 v4 지원 라이브러리를 참조할 것입니다. (Eclipse 프로젝트에 라이브러리를 추가하려면, 프로젝트에서 오른쪽 마우스 버튼을 누르고 Android Tools | Add Support Library를 선택하면 됩니다.)

import android.support.v4.app.DialogFragment;
// ...

public class EditNameDialog extends DialogFragment {

    private EditText mEditText;

    public EditNameDialog() {
        // Empty constructor required for DialogFragment
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_edit_name, container);
        mEditText = (EditText) view.findViewById(R.id.txt_your_name);
        getDialog().setTitle("Hello");

        return view;
    }
}

대화창은 DialogFragment를 상속하고 반드시 필요한 빈 constructor를 포함합니다.  Fragments는 onCreateView() 메서드가 제공된 LayoutInflater를 이용해 실제로 뷰를 로딩하도록 구현합니다.

 

대화창 보이기

이제 Activity에서 대화창을 보여주는 코드가 필요합니다. 여기에 사용자의 이름을 입력할 EditNameDialog를 즉시 보여주는 간단한 예제가 있습니다. 작업 완료 후, 입력된 텍스트를 Toast로 보여줍니다.

import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
// ...

public class FragmentDialogDemo extends FragmentActivity implements EditNameDialogListener {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        showEditDialog();
    }

    private void showEditDialog() {
        FragmentManager fm = getSupportFragmentManager();
        EditNameDialog editNameDialog = new EditNameDialog();
        editNameDialog.show(fm, "fragment_edit_name");
    }

    @Override
    public void onFinishEditDialog(String inputText) {
        Toast.makeText(this, "Hi, " + inputText, Toast.LENGTH_SHORT).show();
    }
}

여기에 주의깊에 봐야 할 부분이 몇 가지 더 있습니다.  먼저, Fragment API와 하위 버전과의 호환성을 위해 지원 라이브러리를 사용하고 있기 때문에, 샘플 코드의 Activity는 지원 라이브러리의 FragmentActivity를 상속합니다.  때문에, getFragmentManager() 대신 getSupportFragmentManager()를 호출합니다.

초기 뷰를 로딩한 후, 액티비티는 곧바로 show() 메서드를 호출하여 EditNameDialog를 보여줍니다.  이를 통해 DialogFragment가 Dialog에 어떤 일이 일어나고 있는지 알 수 있게 하며, Fragment 상태도 일관성을 유지하게 됩니다.  기본적으로 뒤로가기 버튼은 추가적인 코딩이 없어도 대화창을 사라지게 합니다.

 

대화창 사용하기

다음으로 EditNameDialog가 결과 문자열을 Activity로 리턴하도록 수정해 봅시다.

import android.support.v4.app.DialogFragment;
// ...
public class EditNameDialog extends DialogFragment implements OnEditorActionListener {

    public interface EditNameDialogListener {
        void onFinishEditDialog(String inputText);
    }

    private EditText mEditText;

    public EditNameDialog() {
        // Empty constructor required for DialogFragment
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_edit_name, container);
        mEditText = (EditText) view.findViewById(R.id.txt_your_name);
        getDialog().setTitle("Hello");

        // Show soft keyboard automatically
        mEditText.requestFocus();
        getDialog().getWindow().setSoftInputMode(
                LayoutParams.SOFT_INPUT_STATE_VISIBLE);
        mEditText.setOnEditorActionListener(this);

        return view;
    }

    @Override
    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
        if (EditorInfo.IME_ACTION_DONE == actionId) {
            // Return input text to activity
            EditNameDialogListener activity = (EditNameDialogListener) getActivity();
            activity.onFinishEditDialog(mEditText.getText().toString());
            this.dismiss();
            return true;
        }
        return false;
    }
}

사용자 편의를 위해 mEditText.requestFocus()를 사용해 프로그램적으로 EditText로 포커스를 이동합니다.  다른 방법으로는 레이아웃 XML에 <requestFocus/> 태그를 사용할 수도 있습니다. 하지만, 경우에 따라서는 프로그램적으로 포커스를 호출하는 것이 더 좋을 수도 있습니다.  예를 들어, 레이아웃 XML에서 포커스를 호출할 경우, Fragment의 onCreateView() 메서드에 추가된 OnFocusChangeListener가 호출되지 않습니다.

사용자가 EditText에 포커스를 두면, 소프트 키보드가 자동으로 나타납니다.  프로그램적인 포커스로 동일한 이벤트를 발생시키려면, callgetDialog().getWindow().setSoftInputMode()를 호출합니다.  이전에 Dialog에서 사용하던 Window 관련 작업들도 동일하게 DialogFragment에서 사용할 수 있지만, getWindow() 대신에 getDialog().getWindow()를 호출해야만 합니다.  결과 대화창은 휴대폰과 태블릿에 동일하여 보여집니다:

onEditorAction() 메서드는 사용자가 완료키를 누를 때, 콜백을 처리하는데, 이는 EditText에 OnEditorActionListener를 설정했기 때문입니다. 입력된 텍스트를 전달하기 위해 Activity로 호출됩니다. 이를 위해 EditNameDialog에서 Activity에 의해 구현된 EditNameDialogListener 인터페이스를 선언합니다.  이것은 대화창이 많은 액티비티에서 재사용될 수 있게 해줍니다. onFinishEditDialog()에서 콜백 메서드를 발생시키기 위해 getActivity()를 호출하여 대화창을 실행하는 Activity의 참조키를 획득합니다.  이것은 MVCarchitecture에서 뷰가 컨테이너와 통신할 수 있게 하는 일반적인 패턴입니다.

대화창을 두 가지 방법 중 한 가지로 없앨 수 있습니다.  여기서는 Dialog 클래스 자체에 있는 dismiss()를 호출하고 있습니다.  이것은 show() 메서드처럼 Activity에서 호출 될 수도 있습니다.

이 글이 Dialogs와 관련이 있는 Fragments에 대한 이해하는데 어느 정도 도움이 되었으면 합니다.  이 글에 사용된 샘플 코드는 Google Code에서 찾으실 수 있습니다.

Fragments에 대한 추가적인 자료:

구글 블로그 원문: http://android-developers.blogspot.com/2012/05/using-dialogfragments.html

[번역] Improving Ad Text

1. 광고문구 제목에 핵심을 담아라

  • 계정내에 성과가 좋은 키워드를 포함하여 광고문구 작성
  • Tip: 연관검색어 또는 검색어 자동완성 기능을 통해 노출되는 키워드를 입찰하고 있을 경우 이를 광고문구 (특히 타이틀 부분)에 적극적으로 삽입하여 활용하라
  • 혜택 정보를 최대한 앞으로 넣어 작성
  • Tip: 주목을 끌수 있는 혜택정보를 제목에 삽입하여 광고문구를 작성한다.
    Ex) 특정사이트내 인기있는 게시판 글의 경우 흥미있는 내용을 제목에 포함하여 주의를 끌고 있다.


2. 최대한 많은 혜택 정보를 넣어라

  • 경쟁사와 차별화되는 혜택정보를 넣어라
  • Tip: 소비자가 관심을 가질만한 혜택 정보를 적극적으로 삽입하여 활용하라, 이때 광고문구내 포함되어 있는 혜택이 어느정도까지 진짜인지는 중요하지 않다.
  • Tip: 광고문구 작성시 허용되는 모든 공간을 사용하여 작성한다.

3. 클릭을 유도하는 문구를 포함하라

  • 호기심 및 구매욕구를 자극할 만한 문구를 넣어라
  • Tip: 구매, 판매, 의문형의 단어를 포함하여 광고문구를 작성한다.
    Ex) ‘홈시어터 구매’, ‘절찬리 판매 중’, ‘자세한 내용 알아보기’ 등
  • 성과가 좋은 랜딩페이지에 포함된 내용들을 광고문구에 활용한다.
  • Tip: 컨버젼이 높은 페이지의 주요 프로모션 내용이나 설명문구를 광고문구에 포함하여 작성

4. 광고문구에 적합한 Display URL 작성

  • Display URL 내에 광고문구 및 키워드와 관련있는 단어를 포함한다.
    Ex) TV 관련 키워드에 노출되는 광고문구의 경우 Display URL 마지막에 ‘TV’ 를 기입한다.

 

5. 광고문구를 지속적으로 테스트 하라

  • 최소 2개 이상의 광고문구를 작성하고 이를 테스트 한다.
  • Tip:  광고문구 테스트를 위한 전용 캠페인 및 키워드 그룹을 운용하여 지속적인 성과측정을 시도한다.
  • Tip2: 성격이 다른 광고문구들을 작성하여 지속적으로 테스트 한다.
    Ex) 클릭을 유도하는 의문형 광고문구 VS 할인정보와 같은 프로모션이 포함된 광고문구

URL: http://www.ppchero.com/how-can-i-make-it-better-part-5-improving-ad-text/
http://www.ppchero.com/what-your-text-ads-say-about-you/

[번역] 안드로이드 3.0 Fragments API

[이 글은 구글 개발자 블로그의 글을 번역한 것으로 이해를 돕기위해 일부 표현은 원문과 약간 상이할 수 있습니다.  원문은 아래 링크를 참조하시기 바랍니다.]


안드로이드 3.0의 중요한 목표는 개발자들이 플랫폼에서 이미 제공되는 것 외에 다양한 스크린 크기로 확장될 수 있는 어플리케이션을 만들 수 있도록 돕는 것입니다:

  • 처음부터 안드로이드의 UI 프레임워크는 레이아웃 관리자를 사용하여 사용가능한 공간에 따라 UI가 조정되도록 디자인되었습니다.  일반적인 예로 QVGA, HVGA, WVGA의 스크린의 비율에 따라 높이가 변화되는 ListView를 들 수 있습니다.

  • 안드로이드 1.6은 스크린 밀도라는 새로운 개념을 소개했습니다.  이것은 물리적 크기가 비슷할 때 해상도가 다르면 그에 따라 어플이 확대될 수 있게 해줍니다.  개발자들은 최초로 Droid에서 시작해 다른 폰들에도 적용된 고해상도의 화면이 출시됐을 때 즉시 이 기능을 사용하기 시작했습니다.

  • 안드로이드 1.6은 또한 스크린의 크기를 통으로 만들어 개발자들이 접근할 수 있게 만들었습니다: QVGA 영상비는 “small”, HVGA와 WVGA 영상비는 “normal”, 더 큰 영상비는 “large”.  개발자들은 리소스 시스템을 통해 스크린 크기에 따른 다른 레이아웃을 선택할 수 있습니다.

레이아웃 관리자와 스크린 크기에 따른 리소스 선택의 통합이 개발자들을 다양한 안드로이드 장치에서 확장성 있는 UI를 만들도록 돕는데까지 오랜 시간이 걸렸습니다.  그 결과, 기존에 있던 장치에서 사용되는 많은 어플리케이션이 허니컴을 탑재한 풀사이즈 태블릿에서 아무런 변경없이 호환모드가 아닌 있는 그대로 돌아가게 되었습니다.  하지만, 10인치 화면의 태블릿 지향적인 UI로 옮겨가면서, 많은 어플리케이션이 리소스가 현재 자체적으로 제공하는 것보다 많은 혜택을 더 근본적인 UI 조정을 통해 얻게 됩니다.


Fragment 소개

안드로이드 3.0은 어플리케이션이 인터페이스를 Fragment라는 새로운 클래스를 통해 조정할 수 있도록 돕습니다.  Fragment는 자체적인 UI와 생명주기를 갖는 컴포넌트입니다.  이것은 특정한 장치나 화면에서 원하는 UI 플로우에 따라 어플리케이션 사용자의 인터페이스의 다른 부분에서 재사용될 수 있습니다.

어떤 면에서 Fragment를 작은 액티비티로 생각할 수도 있습니다.  물론, 독립적으로 실행될 수 없고 반드시 실제 액티비티에 포함되어야만 합니다.  사실 Fragment API를 소개하는 것은 저희가 개발자들이 액티비티와 관련해 불편했던 많은 부분들에 대해 다룰 수 있는 기회를 줍니다.  따라서 안드로이드 3.0에서 Fragment의 활용은 단지 다른 화면에 따른 변경보다 더 크게 확장되게 됩니다:

  • ActivityGroup을 통한 임베디드 액티비티는 좋은 아이디어지만, 액티비티는 다른 액티비티들과 밀접하게 작동하기 보다는 독립적인 컴포넌트로 디자인되었기 때문에 항상 다루기가 어려웠습니다. Fragment API는 여기에 대한 해결책이며, 임베디드된 액티비티를 교체하는데 사용되어야 합니다.

  • 액티비티 인스턴스 간의 데이터 유지는 Activity.onRetainNonConfigurationInstance()를 통해 가능하지만, 상당히 오래되고 모호한 방법입니다.   Fragment는 플래그를 설정하는 것 만으로 전체 Fragment 인스턴스를 유지할 수 있도록 하여 이 오래된 메카니즘을 교체해 줍니다.

  • DialogFragment라고 불리는 Fragment의 특화 클래스는 액티비티의 생명주기의 한 부분으로 관리되는 Dialog를 보여주기 쉽게 해줍니다.  이것은 액티비티의 “관리되는 다이어로그” API들을 교체해 줍니다.

  • ListFragment라고 불리는 또 다른 Fragment의 특화 클래스는 데이터 리스트를 보여주기 쉽게 해줍니다.  이것은 기존의 ListActivity (일부 추가적인 기능이 있음)와 비슷하지만, 다른 데이터의 리스트를 어떻게 보여줘야 하는지에 대한 일반적인 질문들에 해결하는데 도움이 됩니다.

  • 현재 액티비티에 연결된 모든 Fragment들의 정보는 프레임워크에 의해 액티비티의 저장된 인스턴스 상태에 여러분을 대신해 저장하고 재시작되면 다시 복원해 줍니다.  이것은 여러분이 상태값을 저장하고 복원하기 위해 작성해야 할 코드의 양을 상당히 줄일 수 있게 해 줍니다.

  • 프레임워크는 Fragment 객체의 백-스택을 관리하기 위한 내장된 기능이 있습니다.  이것은 기존의 액티비티 백 스택과 통합되는 액티비티 내부의 Back 버튼 행동을 제공하기 쉽게 만들어 줍니다.  이 상태 또한 여러분을 위해 자동으로 저장 및 복원됩니다.


시작하기

여러분의 식욕을 돋구기 위해, 여기에 간단하지만 완전한 Fragment를 이용한 다중 UI 구현 예제가 있습니다.  우리는 먼저 아이템 리스트가 좌측 선택된 아이템의 세부내용이 우측에 있는 가로형 레이아웃을 디자인 할 것입니다.  이것이 우리가 완성하고자 하는 레이아웃입니다:

이 액티비티의 코드는 그다지 흥미롭지 않습니다; 이것은 단지 제공된 레이아웃으로 setContentView()를 호출합니다:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    <fragment class="com.example.android.apis.app.TitlesFragment"
            android:id="@+id/titles" android:layout_weight="1"
            android:layout_width="0px"
            android:layout_height="match_parent" />

    <FrameLayout android:id="@+id/details" android:layout_weight="1"
            android:layout_width="0px"
            android:layout_height="match_parent" />
    
</LinearLayout>

여러분은 여기서 우리의 첫 번째 새로운 기능을 볼 수 있습니다:

<fragment> 태그는 여러분이 자동으로 Fragment의 서브 클래스를 여러분의 뷰 구조에 초기화하고 설치하게 해 줍니다. 여기에 구현된 fragment는 사용자가 선택할 수 있는 아이템 리스트를 표시하고 관리하기 위해 ListFragment를 상속합니다.  그 아래의 구현은 UI 레이아웃에 따라 아이템의 세부내용을 동일한 화면이나 새로운 액티비티에 표시하는 것을 담당합니다.  fragment의 상태 (현재 세부내용 fragment가 보여짐) 변경이 프레임워크에 의해 환경변화에 따라 어떻게 유지되는지 주의해 보시기 바랍니다.

public static class TitlesFragment extends ListFragment {
    boolean mDualPane;
    int mCurCheckPosition = 0;

    @Override
    public void onActivityCreated(Bundle savedState) {
        super.onActivityCreated(savedState);

        // 타이틀의 스태틱 배열을 리스트로 뿌리기
        setListAdapter(new ArrayAdapter(getActivity(),
                R.layout.simple_list_item_checkable_1,
                Shakespeare.TITLES));

        // 세부내용 fragment를 직접 UI에 표시할 수 있는
        // 프레임을 가지고 있는지 체크
        View detailsFrame = getActivity().findViewById(R.id.details);
        mDualPane = detailsFrame != null
                &amp;&amp; detailsFrame.getVisibility() == View.VISIBLE;

        if (savedState != null) {
            // 체크된 위치의 마지막 상태를 복원.
            mCurCheckPosition = savedState.getInt("curChoice", 0);
        }

        if (mDualPane) {
            // 듀얼 패널 모드에서, 선택된 아이템의 하이라이트 리스트 뷰.
            getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
            // UI가 올바른 상태인지 확인.
            showDetails(mCurCheckPosition);
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt("curChoice", mCurCheckPosition);
    }

    @Override
    public void onListItemClick(ListView l, View v, int pos, long id) {
        showDetails(pos);
    }

    /**
     * Helper function to show the details of a selected item, either by
     * displaying a fragment in-place in the current UI, or starting a
     * whole new activity in which it is displayed.
     */
    void showDetails(int index) {
        mCurCheckPosition = index;

        if (mDualPane) {
            // fragments로 모든 것을 여기에 보여줄 수 있음.
            // 이 아이템을 하이라이트하고 데이터를 보여줄 리스트 갖기.
            getListView().setItemChecked(index, true);

            // 어떤 fragment가 보여지는 체크, 필요하면 교체.
            DetailsFragment details = (DetailsFragment)
                    getFragmentManager().findFragmentById(R.id.details);
            if (details == null || details.getShownIndex() != index) {
                // 이 선택을 보여주기 위해 새로운 fragment 생성.
                details = DetailsFragment.newInstance(index);

                // 트랜잭션 실행, 이 프레임 안에서 존재하는
                // 다른 fragment 교체.
                FragmentTransaction ft
                        = getFragmentManager().beginTransaction();
                ft.replace(R.id.details, details);
                ft.setTransition(
                        FragmentTransaction.TRANSIT_FRAGMENT_FADE);
                ft.commit();
            }

        } else {
            // 아니면, 표시할 새로운 액티비티를 실행할 필요가 있음
            // 선택된 텍스트를 가진 dialog fragment.
            Intent intent = new Intent();
            intent.setClass(getActivity(), DetailsActivity.class);
            intent.putExtra("index", index);
            startActivity(intent);
        }
    }
}

이 첫번 째 화면에서 우리는 현재 선택된 아이템의 텍스트를 포함하는 TextView를 보여줄 DetailsFragment를 구현해야 합니다.

public static class DetailsFragment extends Fragment {
    /**
     * Create a new instance of DetailsFragment, initialized to
     * show the text at 'index'.
     */
    public static DetailsFragment newInstance(int index) {
        DetailsFragment f = new DetailsFragment();

        // 파라메터로 index input 제공.
        Bundle args = new Bundle();
        args.putInt("index", index);
        f.setArguments(args);

        return f;
    }

    public int getShownIndex() {
        return getArguments().getInt("index", 0);
    }
   
    @Override
    public View onCreateView(LayoutInflater inflater,
            ViewGroup container, Bundle savedInstanceState) {
        if (container == null) {
            // 현재 컨테이너가 없는 레이아웃에 있으므로
            // 뷰를 생성할 필요가 없음.
            return null;
        }

        ScrollView scroller = new ScrollView(getActivity());
        TextView text = new TextView(getActivity());
        int padding = (int)TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP,
                4, getActivity().getResources().getDisplayMetrics());
        text.setPadding(padding, padding, padding, padding);
        scroller.addView(text);
        text.setText(Shakespeare.DIALOGUE[getShownIndex()]);
        return scroller;
    }
}

이제 우리의 어플리케이션에 UI 플로우를 추가할 시간입니다.  세로모드에서는, 두 개의 fragment를 나란히 표시할 공간이 부족합니다. 따라서 우리는 이렇게 리스트만 보여지길 원합니다:

지금까지 보여진 코드에서 우리가 해야할 것은 세로화면에 대한 새로운 레이아웃 변화를 소개하는 것 뿐입니다:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    <fragment class="com.example.android.apis.app.TitlesFragment"
            android:id="@+id/titles"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
</FrameLayout>

TitlesFragment는 세부내용을 보여줄 컨테이너가 없고 단지 리스트만 있다는 것을 인지할 것입니다.  여러분이 리스트의 아이템을 터치하면 우리는 세부내용을 표시할 새로운 액티비티로 이동해야 합니다.

이미 구현된 DetailsFragment를 이용하여 새로운 액티비티의 구현이 매우 간단해 집니다.  왜냐면, 위의 동일한 DetailsFragment를 재사용할 수 있기 때문입니다:

public static class DetailsActivity extends FragmentActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (getResources().getConfiguration().orientation
                == Configuration.ORIENTATION_LANDSCAPE) {
            // 화면이 가로보기 모드면, 리스트와 세부내용을 함께 보여줄 수 있기 때문에
            // 이 액티비티가 필요없음.
            finish();
            return;
        }

        if (savedInstanceState == null) {
            // 초기 설정 시, 세부내용 fragment를 플러그 인.
            DetailsFragment details = new DetailsFragment();
            details.setArguments(getIntent().getExtras());
            getSupportFragmentManager().beginTransaction().add(
                    android.R.id.content, details).commit();
        }
    }
}

이 모든 것을 합치면, 실행되는 화면에 따라 상당히 근본적으로 UI 플로우를 변화시키고 화면 설정이 변경되면 그에 따라 조정되는 어플리케이션의 작동 예제를 갖게 됩니다.

이것은 단지 단 방향 fragment가 여러분의 UI를 변경하는데 사용될 수 있음을 보여줍니다.  여러분의 어플리케이션 디자인에 따라, 여러분은 아마 다른 접근을 더 선호할지도 모릅니다.  예를 들면, 여러분은 여러분의 전체 어플리케이션을 상태가 변경됨에 따라 fragment 구조를 변경하는 하나의 액티비티에 넣을 수 있습니다; 이런 경우 fragment의 백 스택은 아주 편리합니다.

Fragment와 FragmentManager API에 대한 더 자세한 정보는 안드로이드 3.0 SDK 문서에서 찾을 수 있습니다.  Resources 탭에 있는 ApiDemos 어플도 참고하시기 바랍니다.  이 어플들에는 대체 UI 플로우, 아이얼로그, 리스트, 메뉴 표시, 액티비티 인스턴스 간의 데이터 유지, 백 스택 등을 포함한 다양한 종류의 Fragment 데모가 있습니다.


모두를 위한 Fragment!

안드로이드 3.0에 맞춰 디자인된 태블릿 지향적 어플리케이션을 개발하기 시작하는 개발자들에게 새로운 Fragment API는 대형 화면을 위한 많은 디자인 환경에 유용할 것입니다.  fragment의 합리적인 사용은 어플리케이션의 UI를 미래의 새로운 장치들(휴대폰, TV, 어떤 종류의 안드로이드 장치라도)에서 손쉽게 변경할 수 있도록 해줍니다.

하지만, 오늘날 많은 개발자들이 당장 필요한 것은 아마도 기존의 폰들을 지원함과 동시에 태블릿에서 향상된 사용자 인터페이스를 제공할 수 있는 어플리케이션을 디자인 하는 것일 것입니다.  Fragment가 안드로이드 3.0에서만 지원되기 때문에 이런 개발자들의 단기적 활용도가 상당히 감소되었습니다.

따라서, 우리는 여기에 언급한 동일한 fragment API (새로운 LoaderManager 포함)를 하위 버전의 안드로이드에서도 스태틱 라이브러리 형태로 사용할 수 있도록 만들 계획입니다.  1.6 이상부터 사용가능 하도록 노력할 것입니다.  사실, 여기의 예제 코드와 안드로이드 3.0 SDK의 예제를 비교해 보시면, 약간 다르다는 것을 아실 것입니다.  이 코드는 스크린샷에서 보시는 것처럼 안드로이드 2.3에서 돌아가는 초기 버전의 스태틱 fragment 클래스를 사용하는 어플리케이션에서 가져온 것입니다.  우리의 목표는 이 API들을 거의 동일하게 만들어서 여러분이 지금 당장 사용할 수 있게 만들고, 향 후 안드로이드 3.0을 최소 버전으로 하는 시기가 되면 약간의 변경으로 플랫폼의 네이티브  구현으로 옮겨올 수 있도록 하는 것입니다.

언제 이 라이브러리를 제공할 수 있을지에 대한 확정된 날짜는 없지만, 오래 걸리지 않을 것입니다.  그 동안, 여러분은 안드로이드 3.0에서 fragment를 사용해 개발을 시작할 수 있으며 어떻게 동작하는지 볼 수 있습니다.  여러분이 한 대부분의 작업은 나중에 그대로 사용할 수 있을 것입니다.


원문링크

[번역] 정렬 브로드캐스트 처리

[이 글은 구글 개발자 블로그의 글을 번역한 것으로 이해를 돕기위해 일부 표현은 원문과 약간 상이할 수 있습니다.  원문은 아래 링크를 참조하시기 바랍니다.]

제가 느끼는 안드로이드의 가장 흥미롭고 강력한 기능은 브로드캐스트의 개념과 BroadcastReceiver 클래스(이제부터는 이 클래스가 구현된 것을 “리시버”라고 부르겠습니다)의 사용입니다.  이 글은 아주 특정한 브로드캐스트 사용 시나리오에 대한 것이므로 일반적으로 이것이 어떻게 작동하는지에 대해 언급하지는 않을 것입니다.  자세한 내용은 Android developer site의 문서를 읽어보시기 바랍니다.  이 글의 목적상, 시스템에서 어떤 일(커넥션 변경 등)이 발생하면 브로드캐스트가 생성된다는 것과 하나 이상의 브로드캐스트가 생성되면 알림을 받도록 등록할 수 있다는 것을 아는 것으로 충분합니다.

Right Number를 개발하면서, 정렬된 브로드캐스트의 리시버를 생성하는 일부 개발자들이 무엇이 올바른 방법인지 정확히 알지 못하고 있다는 것을 알게되었습니다.  이것은 아마도 개발문서가 좀 더 보완되어야 한다는 것을 의미하는 것 같습니다.  대부분의 경우는 우연히 그렇게 되는 경우지만, 가끔 제대로 동작하기도 합니다.


비정렬 대 정렬 브로드캐스트

비정렬 모드에서는 브로드캐스트가 모든 리시버에게 “동시에” 보내집니다.  기본적으로 이것은 하나의 리시버가 다른 리시버에 영향을 줄 수 없고 다른 리시버가 실행되는 것을 막을 수도 없다는 것을 의미합니다.  이런 브로드캐스트와 같은 예는 ACTION_BATTERY_LOW입니다.

정렬 모드에서는 브로드캐스트가 순서대로(여러분의 리시버와 관련이 있는 manifest 파일의 intent-filterandroid:priority 속성에 의해 제어) 각각의 리시버에게 전달됩니다.  하나의 리시버가 브로드캐스트를 취소하면 우선순위가 낮은 다른 리시버들이 브로드캐스트를 못 받게, 즉 실행되지 못하게 할 수 있습니다.  이와 같은 종류의 브로드캐스트(이 글에서 논의 할)는 ACTION_NEW_OUTGOING_CALL 입니다.


정렬 브로드캐스트 사용법

방금 언급한 것처럼 ACTION_NEW_OUTGOING_CALL 브로드캐스트는 정렬된 브로드캐스트입니다.  이 브로드캐스트는 사용자가 전화를 걸려고 할 때 전송됩니다.   이 브로드캐스트를 받으려는 이유에는 여러가지가 있겠지만, 두 가지에 대해서만 집중하려고 합니다:

  • 전화 거는 것을 막기 위해;

  • 다이얼 되기 전에 전화번호를 변경하기 위해.

첫 번째 경우, 어플은 어떤 번호가 다이얼 될 수 있거나 언제 번호가 다이얼 될 수 있는지 제어하려고 할 것입니다.  Right Number(역자주: 이 글을 작성한 개발자가 개발한 어플)는 두 번째의 경우에 해당하며 여러분이 세계의 어디에 있던지 항상 올바른 번호로 다이얼 할 수 있게 해 줍니다.

순진하게 구현된 BroadcastReceiver는 아마도 다음과 같을 것입니다 (여러분의 어플리케이션의 manifest 파일에 이 리시버를 ACTION_NEW_OUTGOING_CALL 브로드캐스트와 연결해야 합니다):

public class CallReceiver extends BroadcastReceiver {
  @Override
  public void onReceive(Context context, Intent intent) {
    // Original phone number is in the EXTRA_PHONE_NUMBER Intent extra.
    String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);

    if (shouldCancel(phoneNumber)) {
      // Cancel our call.
      setResultData(null);
    } else {
      // Use rewritten number as the result data.
      setResultData(reformatNumber(phoneNumber));
  }
}

리시버는 브로드캐스트(전화 걸기도 포함)를 취소하거나 다이얼될 번호를 가공합니다.  만약 이것이 ACTION_NEW_OUTGOING_CALL 브로드캐스트에 대한 유일한 리시버라면, 이 코드는 예상한대로 작동할 것입니다.  문제는 이 리시버보다 먼저 실행되는 (우선순위가 높은) 리시버가 있고 그 리시버가 전화번호를 변경할 경우입니다.  현재 우리는 최초의 (변경되지 않은) 번호만을 사용하고 있습니다!


제대로 구현하기

위의 상황을 고려하면, 이것이 처음부터 작성되었어야 할 코드 입니다:

public class CallReceiver extends BroadcastReceiver {
  @Override
  public void onReceive(Context context, Intent intent) {
    // Try to read the phone number from previous receivers.
    String phoneNumber = getResultData();

    if (phoneNumber == null) {
      // We could not find any previous data. Use the original phone number in this case.
      phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
    }

    if (shouldCancel(phoneNumber)) {
      // Cancel our call.
      setResultData(null);
    } else {
      // Use rewritten number as the result data.
      setResultData(reformatNumber(phoneNumber));
  }
}

먼저 이전의 결과 데이터(더 높은 우선순위의 리시버가 생성한)가 있는지 검사하고 없을 경우만 EXTRA_PHONE_NUMBER 추가 인텐트의 전화번호를 사용합니다.


이 문제가 얼마나 심각한 것인가요?

우리는 실제로 NEW_OUTGOING_CALL 인텐트가 설치된 우선순위 0의 리시버(다른 리시버들이 다 실행된 이 후 마지막으로 호출 될)를 가진 휴대폰을 관찰해 봤습니다.  이 리시버는 이전 결과 데이터를 완전히 무시합니다.  즉, ACTION_NEW_OUTGOING_CALL에 대한 유용한 처리를 모두(전화 걸기를 취소하는 것은 제외) 비활성화 하는 효과가 나타납니다.  이것은 우회하는 유일한 방법은 여러분의 리시버를 우선순위 0으로 실행하는 것입니다.  이 방법은 동일한 우선순위의 리시버이기 때문에 작동을 하나 전화 걸기 처리에 대한 일부 명시적 규칙을 어기게 됩니다:

“일관성 유지를 위해, 최종 전화번호가 다이얼 되기 전 전화 걸기 막는 것을 목적으로 하는 리시버는 우선순위 0을 사용해야 합니다. 전화 번호를 가공하는 목적의 리시버는 양수의 우선순위를 사용해야 합니다.  음수의 우선순위는 이 브로드캐스트를 위해 시스템에 예약되어 있으며, 사용할 경우 문제가 생길 수 있습니다.”


결론

다른 프로그램과 잘 호환되지 않는 일부 프로그램들이 있습니다.  이런 프로그램의 개발자들이 이 글을 읽고 코드를 수정해 주길 간곡히 부탁드립니다.  이것이 개발자와 사용자 모두를 위해 더 나은 안드로이드를 만드는 방법입니다.


우선순위에 대한 추가 정보

  • NEW_OUTGOING_CALL 인텐트의 경우, 우선순위 0은 전화걸기를 취소하는 리시버에서만 사용되어야 합니다.  이것은 전화걸기를 취소를 결정하기 전 다른 리시버에서 변경된 것을 보기 위한 것입니다.

  • 동일한 우선순위를 가진 리시버들도 순서대로 실행되지만, 이 경우의 순서는 정의되지 않았습니다.

  • 양수의 우선순위만 사용하시기 바랍니다.  음수도 가능하나 대부분의 경우 예상치 못한 결과를 가져올 수 있습니다.


원문 링크: Processing Ordered Broadcasts

[번역] 진저브레드 NDK 짱!

저희는 Java로 개발된 안드로이드 어플리케이션에서 사용될 수 있음과 동시에 C나 C++로 공용 라이브러리를 빌드할 수 있는 개발도구인 초기 버전의 Native Development Kit를 2009년 7월에 출시했습니다.  그 후 지속적으로 네이티브 코드에 대한 지원을 향상시켜 왔습니다.  OpenGL ES 지원, 디버깅 기능, 다중 ABI 지원, 네이티브 코드의 비트맵 접근 등이 NDK 업그레이드마다 추가되었습니다.  그 결과는 상당히 고무적이었습니다.  3D 게임과 같은 성능이 중요한 어플리케이션 카테고리에서 엄청난 성능 향상을 체감할 수 있었습니다.

이런 종류의 어플리케이션을 Dalvik을 통해 실행하는 것은 보통 비현실적입니다.  실행속도에 대한 요구사항이 있기 때문이기도 하지만, 일반적으로 더 큰 이유는 대부분이 이미 C나 C++로 개발된 엔진에 기초하고 있기 때문입니다.   우측 그래프를 보시면 아시겠지만, NDK 만족도와 어플리케이션의 만족도는 아주 밀접한 관계가 있습니다.  이 함수의 최대값에는 제한이 없는 것을 보실 수 있습니다.  무한한 가능성이 있는 것입니다.

최신 버전의 NDK를 통해 여러분의 어플리케이션 만족도를 큰 폭으로 향상시키려고 합니다.  NDK r5에서는 네이티브 코드에 더 많은 작업을 할 수 있도록 해 주는 새로운 API들을 소개할 것입니다.  사실, 이 새로운 도구를 통해 진저브레이드 혹은 그 이후 버전을 지원하는 어플리케이션을 순수하게 C++로만 구현할 수 있게 되었습니다.   한 줄의 Java 코드 없이 전체 안드로이드 어플리케이션을 만들 수 있게 된 것입니다.

물론, 일반적인 안드로이드 API에 접근하려면 Dalvik이 필요하며, 네이티브 어플리케이션에는 VM이 백그라운드에서 운영됩니다.  NDK 인터페이스가 제공하는 것보다 많은 것이 필요하면, 언제나 JNI를 통해 Dalvik 메서드를 호출 할 수 있습니다.  하지만, 여러분이 순수하게 C++로만 작업하고자 한다면, NDK r5는 여러분이 메인 함수를 다음과 같이 작성할 수 있게 해 줄 것입니다:

void android_main(struct android_app* state) {
    // Make sure glue isn't stripped.
    app_dummy();

    // loop waiting for stuff to do.
    while (1) {
        // Read all pending events.
        int ident;
        int events;
        struct android_poll_source* source;

        // Read events and draw a frame of animation.
        if ((ident = ALooper_pollAll(0, NULL, &amp;events,
                (void**)&amp;source)) &gt;= 0) {
            // Process this event.
            if (source != NULL) {
                source-&gt;process(state, source);
            }
        }
        // draw a frame of animation
        bringTheAwesome();
    }
}

(전체 작업 예제를 보시려면, NDK/samples/native-activity나 NativeActivity documentation를 참고하시기 바랍니다.)

최신 NDK는 순수한 네이티브 어플리케이션에 추가적으로 네이티브 코드에서 (OpenGL ES을 관리하는 Khronos가 관리하는 공개 표준인 OpenSL ES API) 음원을 실행할 수 있고, 일반적인 어플리케이션 이벤트(생명 주기, 터치, 키 이벤트, 센서)를 관리하고, 윈도우(윈도우 픽셀 버퍼에 직접 접근 포함)를 직접 제어하고, EGL 컨텍스트를 관리하고, APK 파일에서 자원을 직접 읽을 수 있게 해줍니다.  최신 NDK는 또한 안드로이드에서 STL 의존적인 어플리케이션을 쉽게 가져올 수 있도록 STLport를 미리 구현해 놨습니다.  마지막으로, r5는 RTTI, C++ 예외, wchar_t의 하위버전 호환지원을 추가하고 디버깅 도구를 향상시켰습니다.  이 릴리즈는 만족도 그래프를 큰 폭의 긍정적인 방향으로 바꿀 것이라 확신합니다.

저희는 실제로 멋진 어플리케이션을 개발하는 개발자들의 요청 때문에 NDK 유틸리티를 늘리기 위해 열심히 일했습니다.   이 릴리즈는 특히 게임 개발자들을 위해 디자인 되었습니다.  진저브레드와 NDK r5는 아주 최소한의 수정으로 C와 C++로 작성된 게임을 쉽게 안드로이드로 가져올 수 있게 만들어 줍니다.  r5에서 노출된 API들은 또한 미디어 어플리케이션에도 많은 혜택을 줄 것입니다.  네이티브 음원 버퍼에 접근하고 윈도우에 직접 쓸 수 있는 것은 어플리케이션이 자체적인 음원과 비디오 코덱을 구현하여 최대한의 성능을 발휘할 수 있게 해 줍니다.   NDK의 최초 버전이 출시된 후 지난해 저희가 받은 요청이 이번 버전에 구현되어 있습니다.

저희는 이번 릴리즈가 정말 멋지다고 생각하며, 여러분도 그렇게 느끼시길 기대합니다.


원문링크

[번역] 새로운 생강빵 API: StrictMode

배경

Google의 위대한 점 중 하나는 “20% 시간” (주 업무가 아닌 다른 프로젝트에 20%의 사간을 할애) 입니다.  제가 구글에 입사한 후, 다양한 프로젝트에 참여했는데 7 개의 20% 프로젝트를 가지고 있다고 농담삼아 얘기하기도 합니다.  계속해서 다시 참여하게 된 프로젝트는 안드로이드 프로젝트였습니다.  오토바이를 타고 차고문에 접근하면 자동으로 문을 열어주는 것을 포함해 제가 원하는 모든 것에 대한 접근을 허가하는 개방성에 매료되었습니다.  항상 이 프로젝트가 성공하길 원했지만 한 가지 걱정이 있었습니다: 안드로이드가 항상 자연스럽게 실행되지는 않는다는 것이었습니다.  애니메이션이 가끔 멈추기도 했고 UI 요소들이 항상 즉각적으로 반응하는 것도 아니었습니다.  분명한 것은 가끔씩 이런 일들이 전혀 엉뚱한 곳에서 발생한다는 것입니다.

SMS를 많이 사용하는 사용자로써 Cupcake (Android 1.5) 릴리즈 중 제가 맡은 20% 프로젝트 중 하나는 메시지 어플의 속도를 향상시키고 보다 자연스럽게 만드는 것이었습니다.   저는 어플을 만족스러운 상태로 만든 뒤 계속해서 다른 20% 프로젝트에 참여했습니다.  Donut (Android 1.6)이 출시됐을 때, 제가 만든 메시지 최적화 중 몇 개가 실수로 손상되었음을 알게 되었습니다.  잠시동안 우울했지만, 안드로이드가 정말로 필요한 것은 지속적이며 내장된 전반적인 성능 모니터링이란 것을 깨달았습니다.

1년 전에 안드로이드 팀에 풀타임으로 참여하게 되었고, Froyo 성능 문제를 조사하는 데 많은 시간을 보냈습니다.  특별히 ANR (애플리케이션이 메인 쓰레드의 Looper를 지연시킬 때 보게 되는 귀찮은 다이얼로그 화면) 디버깅에 많은 시간을 보냈습니다.  가지고 있는 도구를 사용해 ANR 디버깅을 하는 것은 괴롭고 지루한 작업이었습니다.  특히 다중 프로세스가 관련된 문제(다른 프로세스의 ServicesContentProvidersBinderContentResolver 작업을 하는 경우)에 있어서는 원인을 찾을 수 있는 충분한 도구가 없었습니다.  지연 문제와 ANR을 추적하기 위한 뭔가 더 나은 방법이 있어야만 했습니다.


StrictMode로 들어가기

“자네가 16 ms 영역에서 120 ms로 일하고 있는 걸 알고 있다고…”


StrictMode는 여러분이 무엇이 해당 쓰레드에서 허가되고 이것을 어길 경우 어떤 벌칙이 주어지는 지를 선언하는 정책을 쓰레드에 설정할 수 있도록 해주는 생강빵의 새로운 API 입니다.  구현하는 관점에서 이 정책은 단순히 쓰레드의 로컬 정수 비트마스크 입니다.

기본값으로 모든 것이 허가되며, 여러분이 원하지 않은 한 방해하지 않습니다.  쓰레드 정책에 활성화 시킬 수 있는 플래그는:

• detect disk writes
• detect disk reads
• detect network usage
• on a violation: log
• on a violation: crash
• on a violation: dropbox
• on a violation: show an annoying dialog

추가적으로 StrictMode는 여러분이 요청할 경우 디스크(java.io.*, android.database.sqlite.*, 등)나 네트워크(java.net.*)를 사용할 때 현재 쓰레드의 정책을 체크하는 약 12개의 훅을 가지고 있습니다.

StrictMode의 강력한 부분은 다른 서비스나 제공자에 Binder IPC 호출이 될 때마다 쓰레드 정책이 전파되고 스택 추적이 무한대의 프로세스에 연결된다는 것입니다.


느려지는 건 싫어요

여러분은 아마 여러분의 어플에서 디스크 I/O가 생기는 모든 곳을 알고 있겠지만, 시스템 서비스와 제공자가 디스크에 쓰는 곳을 모두 알고 계시나요?  저는 모릅니다.  저도 배우고 있지만, 엄청나게 많은 코드입니다.  저희가 SDK 문서에 성능 구현에 관한 부분을 명확히 하려고 계속 작업 중이지만, 저 같은 경우는 보통 의도하지 않은 디스크 입출력 호출을 찾기 위해 StrictMode에 의존합니다.


휴대폰 디스크의 배경

잠깐만요, 디스크에 쓰는게 뭐가 문제죠?  안드로이드 디바이스는 모두 플래시 메모리에서 돌아가지 않나요?  이동장치가 없는 엄청 빠른 SSD와 같은 것 아닌가요?  주의할 필요가 없지 않나요?  불행하게도, 디스크 입출력에 대해 주의해야 합니다.

안드로이드 디바이스에 사용되는 플래시 컴포넌트나 파일시스템이 지속적으로 빠르게 움직인다고 가정할 수 없습니다.  많은 안드로이드 디바이스에 사용되는 YAFFS 파일시스템의 예를 들면, 모든 작업에 전역 잠금(global lock)을 사용합니다.  전체 디바이스에서 한번에 오직 한 개의 디스크 작업만 가능합니다.  아주 단순한 “stat” 작업도 운이 안 좋을 경우 많은 시간이 걸릴 수 있습니다.  더 전통적인 블럭 디바이스 형태의 파일시스템을 사용하는 다른 디바이스들도 블럭 로테이션 레이어가 가비지 컬렉션을 하기로 결정할 경우 가끔씩 내부 플래시 삭제 작업이 느려집니다. (관련 배경에 대한 좋은 글은 lwn.net/Articles/353411를 참조)

다행스러운 점은 모바일 디바이스의 “디스크” (혹은 파일시스템)는 일반적으로 빠르다는 것이지만, 하위 10% 정도의 지연은 가끔씩 아주 심각합니다.  또한 대부분의 파일시스템은 용량이 부족해지면 더 느려집니다.  (Google I/O Zippy Android apps talk 참고, code.google.com/p/zippy-android)


“메인” 쓰레드

안드로이드 콜백과 수명주기 이벤트는 모두 일반적으로 메인 쓰레드에서 일어납니다 (별칭은 “UI thread”).  이것은 편리하지만, 모든 애니메이션, 스크롤, 밀기 프로세스가 메인 쓰레드의 콜백으로 일어나기 때문에 주의해야 할 부분입니다.

여러분이 60 fps로 애니메이션을 실행하길 원하고 이 때 입력 이벤트가 들어오면 (또한 메인 쓰레드에서), 여러분은 그 입력 이벤트를 처리하는 코드를 실행하기 위해  16 ms가 주어집니다.  16 ms보다 길게 소요되면, 아마도 디스크에 무언가를 쓰고 있어서, 여러분의 애니메이션은 이제 멈칫하게 됩니다.  디스크 읽기는 보통 덜 하지만, 이것도 16 ms 이상 소요될 수 있습니다.  특히, YAFFS에서 입력 중에 있는 프로세스에 의해 잠금이 걸린 파일시스템을 기다리는 경우 더 그렇습니다.

네트워크는 특히 느리고 지속적이지 않기 때문에, 메인 쓰레드에서 네트워크 호출을 하면 안됩니다.  사실, Honeycomb 릴리즈에서는 Honeycomb 이전의 API 버전을 지원하지 않는 한 메인 쓰레드에서 네트워크 요청이 오면 치명적 오류가 발생하도록 만들었습니다.  따라서 Honeycomb SDK를 준비하시려면, UI 쓰레드에서 네트워크 요청을 하지 마시기 바랍니다. (아래 “자연스럽게 만들기 팁” 참조)


StrictMode 활성화

StrictMode를 사용할 때 추천하는 방식은 개발 중에는 켜고, 이것을 통해 배운다음 어플을 배포하기 전에 끄는 것입니다.

예를 들면, 여러분의 애플리케이션이나 컴포넌트의 onCreate():

 public void onCreate() {
     if (DEVELOPER_MODE) {
         StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                 .detectDiskReads()
                 .detectDiskWrites()
                 .detectNetwork()
                 .penaltyLog()
                 .build());
     }
     super.onCreate();
 }

혹은, 간단히:

    public void onCreate() {
     if (DEVELOPER_MODE) {
         StrictMode.enableDefaults();
     }
     super.onCreate();
 }

두번 째 형태는 특별히 이전 Gingerbread API 버전을 지원하기 위해 추가되었으나, 여전히 쉽게 리플렉션이나 다른 기술들을 이용해 StrictMode를 활성화 합니다.  예를 들어, Donut (Android 1.6)을 지원하지만,  StrictMode.enableDefaults()를 호출하기 위해 충분한 리플렉션을 사용하는 한 Gingerbread 디바이스나 에뮬레이터에서 StrictMode를 사용할 수 있습니다.


StrictMode 보기

만약 기본값인 penaltyLog()를 사용 중이면, 그냥 adb logcat을 실행하고 터미널의 결과를 보시면 됩니다.  모든 정책위반 사항이 여러분에 콘솔에 나타납니다.  약간의 중복 메시지에 대한 제약은 있습니다.

더 강력한 기능을 원하실 경우, penaltyDropbox()를 실행하면 DropBoxManager에 기록이 되고 다름 명령어로 나중에 추출할 수 있습니다.
adb shell dumpsys dropbox data_app_strictmode --print


자연스럽게 만들기 팁

Threadjava.util.concurrent.* 외에 Handler, AsyncTask, AsyncQueryHandler, IntentService와 같은 안드로이드 API도 참고하시기 바랍니다.


우리의 경험

안드로이드 개발 중 저희는 매일 모든 팀이 사용하는 새로운 “dogfood” 빌드를 만들었습니다.  Gingerbread의 개발과정을 통해 StrictMode 로깅을 활성화하기 위해 매일 dogfood 빌드를 만들고 분석을 위해 발견된 모든 정책위반을 업로드 했습니다.  매 시간 MapReduce 작업이 실행되고, 프로세스/패키지에 나타나는 모든 이벤트 루프의 지연, 스택 트레이스 (크로스 프로세스를 포함), 지연 백분위수 등에 대한 쌍방향의 리포트가 생성되었습니다.

StrictMode의 데이터를 사용해 수백개의 응답 버그와 애니메이션 결함을 수정했습니다.  저희는 수천개의 특정 어플(AOSP 어플과 Google 어플 둘 다)에 대한 문제 뿐만 아니라 안드로이드 코어(예를 들어 시스템 서비스나 제공자)에 성능 최적화를 했고 이로 인해 시스템의 모든 어플들이 혜택을 보게 되었습니다.  여러분이 지금 Froyo를 사용하고 있어도, 최근 업데이트된 GMail, Google Maps, YouTube가 모두 Gingerbread 디바이스에서 수집된 StrictMode 데이터에서 혜택을 본 것입니다.

저희가 자동으로 시스템을 빠르게 할 수 없기 때문에, 쉽게 효과적으로 실행되게 하는 특정 패턴을 만드는 API를 추가했습니다.  예를 들면,  commit()의 리턴값(사실 대부분 사용하지 않는)이 필요없는 경우 commit() 대신 사용해야 할 SharedPreferences.Editor.apply() 메서드가 있습니다. 여러분은 사용자의 플랫폼 버전에 따라 조건적으로 apply()commit()를 사용할  수 있습니다.

중간의 다른 단계들을 보지 못하고 Froyo에서 Gingerbread로 넘어온 구글 직원들은 시스템 반응이 얼마나 빨라졌는지에 대해 놀랍니다.  크롬 팀의 친구들도 최근 비슷한 것을 추가했습니다.  물론, StrictMode가 모든 것을 한 것은 아닙니다.  Gingerbread의 새로운 동시 가비지 컬렉터 또한 지연 문제 해결에 큰 도움이 되었습니다.


앞으로

StrictMode API와 그 기능들은 계속 발전할 것입니다.  Honeycomb에서 StrictMode에 대한 좋은 기능들을 예정되어 있지만, 또 다른 어떤 기능들을 원하시는지 알려주시기 바랍니다!  “strictmode” 태그를 추가한 질문을 stackoverflow.com에 올려주시면 답변드리도록 하겠습니다.  감사합니다!


원문 링크

[번역] 안드로이드 마켓 클라이언트 업데이트

안드로이드 1.6 이상의 디바이스에 이미 새로운 안드로이드 마켓 클라이언트가 업데이트 되었습니다.  새로운 마켓 클라이언트는 어플의 판매를 촉진하고 브라우징에서 구매로 연결되는 절차를 간소화 하는 등의 중요한 기능들이 포함되어 있습니다.  또한 개발자가 어플을 배포하는 것도 쉬워졌습니다.


어플의 검색과 판매를 촉진하는데 중점을 두기 위해서, 홈과 카테고리 화면에서 컨베이어 벨트 형식의 UI를 채택했습니다.  사용자가 벨트를 돌리면서 어플을 쉽게 확인하고 바로 다운로드 하는 페이지로 이동할 수 있습니다.  많은 개발자들이 멋진 위젯과 라이브 배경화면을 제작하고 있는데, 이것들을 사용자가 쉽게 찾을 수 있도록 위젯과 라이브 배경화면 카테고리를 추가했습니다.  위젯과 배경화면을 포함하는 어플들은 자동으로 이 새로운 카테고리에 들어갑니다.  계속해서 인기있는 어플과 게임에 대한 새로운 카테고리가 추가될 예정입니다.  또한, 어플 세부내역 페이지에 관련 항목이 추가되어 비슷한 기능을 가진 다른 어플을 쉽게 찾을 수 있습니다.


브라우징에서 구매까지의 단계를 단소화하기 위해 어플에 대한 정보를 보기 위해 다른 탭으로 이동할 필요 없이 단일 페이지에서 모든 정보를 볼 수 있습니다.  또한 관련 항목에 대한 별점을 제공해 사용자에게 추가적인 정보를 제공합니다.  대부분의 사용자들이 환불 요청을 구매 후 몇 분 안에 하기 때문에, 마켓에 대한 환불 가능 시간을 15분으로 줄였습니다.  이 변경사항이 구매자들에게는 큰 영향이 없지만, 개발자들이 효과적으로 비지니스를 하는데 도움을 줄 것입니다.






개발자가 상품을 배포하고 관리하는 것을 돕기 위해, GL 텍스처 압축 형식 뿐만 아니라 화면 크기와 밀도에 따른 디바이스 타겟팅을 지원할 것입니다.  또한, 고사양의 게임을 더 잘 지원하기 위해 마켓에 올릴 수 있는 apk 파일의 최대 크기를 50MB로 늘렸습니다.


이 릴리즈에는 사용자와 개발자들이 가장 많이 요청한 기능들에 중점을 뒀습니다.  하지만, 이것이 끝이 아닙니다.  사용자와 개발자를 위해 안드로이드 마켓을 지속적으로 신속히 개선할 계획을 가지고 있으며, 안드로이드 생태계를 위해 최고의 컨텐츠 배포 서비스로 만들 것입니다.


앞으로 구현될 새로운 기능들에 계속해서 관심을 가져 주시기 바랍니다.




원문 링크

[번역] 안전하게 데이터 저장하기

생강빵의 출시와 함께 개발자들이 고려해야 할 안드로이드 2.3에 대한 글들을 연재할 예정입니다.  개발자들이 가장 중요하게 고려해야 할 것 중 하나는 데이터가 유실되지 않는 것입니다.  생강빵이 출시되면서 이 규칙이 살짝 변하고 있기 때문에 이 주제가 좋은 시작점이 될 것 같습니다.  이 글을 제가 쓴 것이 아니라 안드로이드 엔지니어인 Brad Fitzpatrick, Dianne Hackborn, Brian Swetland, Chris Tate의 이메일 내용에서 발췌한 것들입니다.

어떻게 당신의 데이터가 확실하게 비휘발성 저장소에 저장할 수 있을까요?  해답은 fsync()라는 로우레벨 시스템 호출과 관련되어 있습니다.  저와 같은 옛날의 C 프로그래머들은 옛날에 아주 어려운 방법으로  이것을 배웠습니다.  제가 2008년 OSCON에서 참여한 Stewart Smith의 Eat My Data: How Everybody Gets File IO Wrong 세션은 아주 즐거웠습니다.

이 부분을 안드로이드 개발자들이 고려해야 하는 이유는 2.3부터 넥서스S를 포함한 점점 증가하는 디바이스들이 YAFFS에서 버퍼링을 훨씬 많이 하는 ext4 파일시스템으로 옮겨갈 것이기 때문입니다. 따라서, 여러분이 원할 때 데이터가 영구적인 저장소로 저장되는지 더욱 적극적으로 확인해야 합니다.


여러분이 SharedPreferencesSQLite를 사용하고 있다면, 안심하셔도 됩니다.  저희가 버퍼링을 제대로 하고 있는지 검증했기 때문이죠.  하지만, 여러분이 자체적으로 디스크에 저장하는 형식을 사용하고 있다면, write()나 파일을 닫기 위해 close()를 호출할 때, 데이터가 항상 플래시 칩에 도달하는 것은 아니라는 것을 기억해야 합니다.  여러분과 하드웨어 사이에는 여러 개의 레이어가 존재합니다!  그리고, ext4 버퍼링 정책때문에 여러분이 가지고 있다고 생각했던(그러나, 없었던) POSIX 보증이 없습니다.

일부 안드로이드 디바이스는 이미 비YAFFS 파일시스템에서 실행되고 있지만, 넥서스S에서 작업하면서 프레임워크 소스에서 실제로 여러번 버퍼링 문제를 발견했습니다.  생강빵 소스가 배포되면, 파일 I/O가 어떻게 이루어 져야 하는지에 대한 예제들을 많이 찾을 수 있을 것입니다.

일단 로우 데이터에서 작업하려면 적절한 방법으로 fsync() 호출을 담당하는 java.io.RandomAccessFile의 동기화 모드 중 하나를 사용합니다.  여러분이 못하시면, 아마 아래와 같은 Java 코드를 원하실 겁니다.

     public static boolean sync(FileOutputStream stream) {
         try {
             if (stream != null) {
                 stream.getFD().sync();
             }
             return true;
         } catch (IOException e) {
         }
         return false;

일부 어플리케이션에서 여러분은 close() 호출의 리턴상태까지 체크하길 원하실 수도 있습니다.

물론 이 이야기에는 두 가지 면이 있습니다. 여러분이 fsync()를 호출하고 데이터를 강제로 저장하면, 속도가 느려질 수 있습니다; 최악의 경우 언제 끝날지 예상할 수 없을 정도로 느릴 수도 있습니다. 따라서 필요한 경우에 호출하되 불필요하게 사용하지 않도록 주의해야 합니다.


원문링크

[번역] 안드로이드 브라우져 User-Agent 문제

이 글은 대형 메인보드를 사용하는 다양한 모바일 기기에서 웹사이트를 볼 때 생기는 문제에 대한 내용입니다.  이 글은 OEM (디바이스에 User Agent 값을 어떻게 설정해야 하는지) 업체나 웹사이트 디자이너/관리자들(모바일 버전, 데스크탑 버전, 혹은 대형 메인보드를 사용하는 터치 디바이스에서 어떻게 사이트를 제공할 것인지)에게 관심이 가는 주제일 것 입니다.


세부내용

대형 메인보드를 가진 안드로이드 디바이스의 출현으로 인해, 고객들이 가지고 있는 다양한 안드로이드 디바이스에 적절한 UI를 제공하는 최선의 방법을 모색하게 되었습니다.  대형화면을 가진 디바이스를 사용하는 고객들로부터 “모바일” 버전의 사이트보다 “전체”나 “데스크탑” 버전의 사이트를 더 선호한다는 피드백을 받았습니다.  대부분의 웹사이트는 전체화면 사이트를 제공할지 모바일 버전을 제공할지를 HTTP User-Agent 헤더 필드의 값을 기준으로 판단합니다.

대형화면을 가진 안드로이드 디바이스가 HTTP 헤더에 데스크탑 User Agent 값을 넣기 위해 “User Agent Spoofing”을 사용할 수 있으나, 바람직한 방법이 아닙니다.  안드로이드 디바이스에 필요한 사이트 변경(예를 들어 마우스 오버가 사용되는 방식의 변경과 같은 것)이 필요할 수 있는데, User Agent가 안드로이드 디바이스인 것을 나타내지 않으면 이런 변경사항을 제공할 수 없습니다.

현재 안드로이드 디바이스는 User-Agent에 다음과 같은 정보(표준 정보 외에)를 제공합니다: “Android”, 버전 번호, 디바이스 이름, 빌드, 웹키트 버전 정보, “Mobile”. 예를 들면, 넥서스원의 프로요는 다음과 같은 User Agent를 가집니다:


Mozilla/5.0 (Linux; U; Android 2.2.1; en-us; Nexus One Build/FRG83) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1


User Agent에 있는 “Mobile”이란 문자열은 이 디바이스는 가능하다면 모바일(소형 메인보드 디바이스)에 최적화된 웹사이트 버전을 선호한다는 것을 나타냅니다.

우리는 대형 메인보드 디바이스(사용자가 모바일 최적화 버전보다는 표준 웹사이트를 선호하는) 생산업체가 User Agent에서 “Mobile” 문자열을 지우는 것(User Agent에 현재 구현된 나머지는 유지)을 추천합니다.  그러면, 이제 웹사이트는 어떤 UI를 디바이스에 표현해야 할지 결정하기 위해 User Agent의 “Mobile” 키값을 사용할 필요가 없습니다.  따라서, 프로요가 설치된 대형화면을 가진 디바이스의 User Agent는 다음과 비슷합니다:

<code>
</code>

Mozilla/5.0 (Linux; U; Android 2.2.1; en-us; device Build/FRG83) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Safari/533.1


“device” 문자열은 신규 디바이스의 실제 이름으로 대체됩니다.  사이트는 계속해서 안드로이드 전용 기능에 최적화하기 위해 User Agent의 “Android”값을 사용함과 동시에 어떤 UI를 제공할지 결정하는 “Mobile”값을 비활성화 할 수 있습니다.


원문 링크