Tag Archives: 개발

PreferenceActivity에서 사용자 지정 SharedPreferences 사용하기

안드로이드 개발자라면 PreferenceActivity가 얼마나 편리한지 다들 알고 계시리라 생각합니다.  PreferenceActivity (HONEYCOMB 이상은 PreferenceFragment) 에는 컨텍스트의 기본 SharedPreferences가 제공되는데, 가끔은 다른 이름이나 다른 모드로 사용해야 할 때가 있습니다.

이럴 경우 사용하는 방법이 있습니다.  PreferenceActivity에 직접적으로 특정 SharedPreferences를 지정할 수 있는 방법이 없기 때문에, 아래와 같은 코드를 사용하면 됩니다:

public class MyPreferencesActivity extends PreferenceActivity {
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    PreferenceManager prefMgr = getPreferenceManager();
    prefMgr.setSharedPreferencesName("my_preferences");
    prefMgr.setSharedPreferencesMode(MODE_WORLD_READABLE);

    addPreferencesFromResource(R.xml.preferences);
  }
}

DDMS File Explorer를 사용해서 SharedPrefereces 이름을 확인해 보시기 바랍니다.

Wifi Android Device Debugging

스마트폰이 많이 보급되면서 모바일 개발자가 많이 늘어났습니다.  이미 많은 경력이 있는 개발자들도 있지만, 모바일이 화두가 되면서 새롭게 개발자로 입문하신 분들도 많습니다.  생업이 아니라 그냥 취미로 시작하는 분들도 많으신 것 같습니다.  모바일 어플리케이션 개발이 단기간에 커지면서 수요는 많으나 관련 자료가 많이 부족한게 현실입니다.

모바일 어플리케이션을 개발하면서 주로 디바이스를 USB 케이블로 연결하여 디버깅을 많이 하고 계실 것으로 생각됩니다.  하지만, Wifi로도 디버깅이 가능하다는 사실을 아셨나요?  안드로이드는 API 12 이상부터는 Wifi로 디버깅이 가능합니다.  어떻게 설정하는지 팁을 드리고자 합니다.

1. 일단 디바이스를 USB 케이블로 연결합니다.
2. 커맨드 창을 열고 SDK 설치 폴더의 platform-tools/ 폴더로 이동합니다.
3. 아래와 같이 명령어를 실행합니다.
adb tcpip 포트번호
adb connect IP:포트번호
4. 이렇게 하면 이제 wifi로 디버깅이 가능합니다.
5. 다시 USB로 디버깅을 하려면 아래 명령어를 실행합니다.
adb usb

참조: http://developer.android.com/guide/topics/usb/index.html

[번역] 안드로이드 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

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

생강빵의 출시와 함께 개발자들이 고려해야 할 안드로이드 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”값을 비활성화 할 수 있습니다.


원문 링크

[번역] 안드로이드 앱 통계

제공된 영문문서를 나름대로 번역해 봤습니다.  전문 번역가가 아니라, 조금 매끄럽지 못한 부분도 많겠지만, 핵심적인 내용을 이해하시는데는 무리가 없을 것으로 생각됩니다. 안드로이드 어플 개발에 참고할 만한 좋은 정보가 되었으면 좋겠습니다.


Mobile Analytics SDK for Android에 커스텀 변수가 추가되면서 많은 사람들이 가능한지 모르고 있던 것들에 대해서 알려줘야 겠다는 생각이 들었습니다. Google Analytics를 사용해 앱 사용을 추적할 수 있는 것 말입니다.   모바일 SDK를 사용하는 것은 사용자들이 실제로 어떻게 여러분의 안드로이드 앱을 사용하고 있는 알 수 있는 간편한 방법입니다.  그래서, 오늘은 Google Analytics를 이용해 어떻게 여러분의 어플리케이션 사용현황을 추적할 수 있는지 설명할 것입니다.


준비물!

이 반짝이는 새 장난감을 사용하기 전에 설정해야 할 것들이 몇가지 있습니다:

모바일 SDK를 다운로드 하시기 바랍니다. 모바일 SDK의 getting started section에 다운로드와 설치에 대한 설명이 있지만, 간단히 요약하면:

• 다운로드 페이지에서 zip 파일 다운받기
• libGoogleAnalytics.jar 파일을 여러분의 프로젝트의 /libs 디렉토리에 놓기
• AndroidManifest.XML 파일에 아래 라인을 추가하기:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />


Google Analytics 계정이 필요합니다. 이미 가지고 있지 않다면, google.com/analytics에 가서 계정을 만드시기 바랍니다. 그 다음 여러분의 안드로이드 애플리케이션을 위한 프로필을 설정하시기 바랍니다.  이 과정을 마치면, 여러분의 “사이트”에 추가할 자바스크립트 코드를 보게 될 것입니다.  UA-XXXXXXX-X과 같은 형식의 부분을 복사하시기 바랍니다. 여러분은 이것을 안드로이드 애플리케이션이 전송되는 데이터를 수집하는 Analytics과 통신하는데 사용하게 될 것입니다.


추적기 탑재

이전버전의 Google Analytics 사용자들은 비슷한 부분을 많이 발견할 것입니다.  사실 저희는 인터페이스를 최대한 기존 사용자에게 익숙하도록 설계했습니다.

먼저, 추적(tracker) 개체를 얻고, 여러분이 추적하고자 하는 Analytics 프로필에 대한 UA 코드를 이용해 초기화 합니다.  애플리케이션이 시작된 후 최초 한번만 수행되도록, 메인 액티비티의 면, onCreate() 메서드에서 이 작업을 하는 것이 가장 적합합니다.

GoogleAnalyticsTracker tracker;
protected void onCreate(Bundle savedInstanceState) {
  ...
  tracker = GoogleAnalyticsTracker.getInstance();
  tracker.start("UA-1234-1", this);
   …
}

모바일 SDK는 Google Analytics 서버로 전송되는 세 가지 메인형식을 지원 합니다: 페이지뷰, 이벤트, 그리고 사용자 변수.


페이지뷰

페이지뷰는 전통적인 웹사이트의 트래픽 정도를 측정하기 위한 표준적인 방법입니다.  이것이 웹사이트가 아닌 안드로이드 앱에 적용된다면, “페이지뷰”의 의미는 여러분이 정하기 나름입니다.  앱의 형식에 따라, 각 액티비티나 동일한 액티비티 내의 다른 뷰들이 하나의 페이지뷰가 될 수 있습니다.

페이지뷰를 시작하려면, trackPageView() 메서드를 호출하시기 바랍니다.  이것은 하나의 파라메터만을 가집니다.  페이지뷰로 카운트 하고자 하는 URL이 바로 그것입니다.

tracker.trackPageView("/HomeScreen");

페이지뷰는 전체 화면전환에 대해 부여하는 것이 가장 좋습니다.  이것은 대부분의 경우 “액티비티 당 하나의 페이지뷰”를 의미할 것입니다. 따라서 대부분의 경우 trackPageView 호출을 각각의 액티비티의 onCreate() 메서드에서 하는 것이 좋습니다.  제외적인 경우는 여러분이 TabActivity나 동일한 액티비티에서 수행될 다중 풀스크린 전환, 풀 스크린 “페이지들”이 보여지는 것을 분리하기 위한 개념적 매핑과 같은 시나리오 입니다.

이벤트

Analytics에서 이벤트는 미디어 앱에서 실행/일시정지/정지를 실행하는 것과 같은 페이지뷰에 매핑되지 않는 사용자 활동을 추적하기 위해 디자인 되었습니다.  이것은 안드로이드 사용법과 잘 매핑됩니다 — 데이터 저장소의 데이터를 추가/삭제하기 위해 어떤 버튼을 클릭하는 어떤 폼 내의 활동도 이벤트를 사용해 추척할 수 있습니다.

이벤트는 페이지뷰보다 아주 약간 더 복잡합니다.  한 개의 파라메터가 아니라 네 개의 파라메터를 가집니다: Category, Action, Label (옵션), Value (옵션).

이것을 어떻게 사용하는지 이해하기 위해 여러분이 미디어 플레이어 애플리케이션을 가지고 있다고 가정합시다.  여러분은 얼마나 많이 시작, 일시정지, 정지버튼이 클릭되는지 알고 싶습니다.  이것을 위한 코드는 다음과 같습니다:

   playButton.setOnClickListener(new OnClickListener() {
     @Override     public void onClick(View v) {
     ...
       tracker.trackEvent(
           "Media Player",  // Category
           "Click",  // Action
           "Play", // Label
           0);       // Value
     }
   });
   pauseButton.setOnClickListener(new OnClickListener() {
     @Override     public void onClick(View v) {
     ...
       tracker.trackEvent(
           "Media Player",  // Category
           "Click",  // Action
           "Pause", // Label
           0);       // Value
   });
   stopEventButton.setOnClickListener(new OnClickListener() {
     @Override     public void onClick(View v) {
     ...
       tracker.trackEvent(
           "Media Player",  // Category
           "Click",  // Action
           "Stop", // Label
           currentVideo.getPositionInSeconds());       // Value
   });
   myMediaPlayer.setFinishedListener(new FinishedListener() {
     @Override     public void onFinished(View v) {
     ...
       tracker.trackEvent(
           "Media Player",  // Category
           "Video Finished",  // Action
           "Stop", // Label
           currentVideo.getLengthInSeconds());       // Value
   });

Google Analytics 웹 인터페이스에서 이 데이터는 계층적으로 표시된다는 것을 기억하시기 바랍니다 — 예를 들어 여러분이 좌측 네비게이션의 카테고리를 클릭한 후 “Media Player”를 클럭하면, “Media Player” 카테고리 내에서 일어나는 모든 다른 가능한 “Action” 값 리스트를 보게 될 것입니다.  “Click”을 클릭하면 Media Player 카테고리의 “Click” 액션을 가지는 모든 레이블이 표시됩니다.

네 번째 파라메터 “value”는 옵션이며, 다른 파라메터와 다르게 행동합니다.  이것은 누적값입니다; 이 예제에서 저는 비디오가 정지되거나 종료됐을 때 비디오가 보여진 양을 전송합니다. 이것은 서버쪽에서 누적되고, 제가 데이터를 조회하면 총 몇 번이나 사람들이 제 애플리케이션을 통해 비디오를 봤는데 알 수 있습니다.

사용자 변수

사용자 변수는 이름-값이 짝을 이루는 태그로 Google Analytics 추적을 세분화하기 위해 여러분의 추적 코드에 넣을 수 있습니다.  이해를 돕기 위해 이것을 여러분의 페이지뷰와 이벤트에 포함되는 메타데이터로 생각하시기 바랍니다.  이 메타데이터를 이용하면 여러분의 데이터를 분리하고 세부내역을 확인하는 일이 쉬어집니다. Gmail의 라벨과 같은 방식입니다.  안드로이드와 관련한 예는 사용자가 풀 버전의 앱을 가지고 있느냐에 따라  “Full” 혹은 “Lite”의 상태를 가지는 “AppType”입니다.  여러분은 “Lite” 사용자만을 보거나 “Full” 사용자와 비교해 어떻게 다른지 보기 위해 Analytics 웹 인터페이스를 사용할 수 있습니다.  사용자 변수는 아주 강력한 도구지만, deep topic이기도 합니다.  여러분의 안드로이드 애플리케이션에 적용하기 전에 한번 읽어보시기 바랍니다.  특별히 범위에 관한 부분을 읽어보시기 바랍니다.  두 번씩요.  정말입니다…  여러분이 읽으실 때까지 기다겠습니다.

사용자 변수에는 네 개의 파라메터가 있습니다: Index (1 to 5 inclusive), Name, Value, and Scope (옵션, 기본값은 페이지 범위).

여러분의 코드에서 setCustomVar() 가 호출되는 위치는 어떤 범위에 그 변수가 있는가에 달려 있습니다:

• Visitor scope: 여러분의 애플리케이션이 디바이스에서 처음으로 실행될 때 한번 호출.  사용자 변수를 동일한 인덱스에 생성하면, 이전 것이 덮어씌워 집니다.  어떤 버전의 앱이 사용 중인지, 어떤 폰인지, 라이트 버전인지 풀 버전인지, 혹은 어플리케이션 설치 후 변하지 않는 어떤 정보를 보내는데 유용합니다.

• Session scope: 모든 액티비티의 시작 시 한번 호출.  동일한 인덱스에 다른 사용자 변수가 생성되지 않는 한, 액티비티의 수명 안에 있는 모든 페이지뷰와 이벤트에 적용됩니다.

• Page scope: 사용자 변수가 적용되는 trackEvent 혹은 trackPageView가 호출될 때마다. 범위가 정의되지 않으면, 이거시 기본값입니다.

사용자 변수를 설정하는 방법은 다음과 같습니다:

// 범위는 정수값으로 표현:  Visitor=1, Session=2, Page=3
tracker.setCustomVar(1, "Navigation type", "Button click", 3);


전송모드 선택

베터리의 수명을 최적화하기 위해 페이지뷰나 사용자 변수가 호출될 때마다 요청이 서버로 전송되지 않습니다.  대신 모든 페이지뷰, 이벤트, 그리고 다른 관련된 사용자 변수들이 그룹으로 서버전송이 되기 전까지 로컬 SQLITE 데이터베이스에 저장됩니다.  여러분은 두 가지 방식으로 이것을 설정할 수 있습니다: n초마다 자동으로 전송되게 하거나, 코드에서 수동으로 “dispatch”를 호출하는 것입니다.  전송모드는 추적기의 시작 메서드를 호출할 때 결정됩니다.

수동 전송모드는 다음과 같습니다:

// 전송간격 파라메터 없음
tracker.start("UA-1234-1", this);
…
// 전체 이벤트 큐를 서버로 전송하고 싶을 때 호출
tracker.dispatch();

자동 전송모드는 비슷하지만, 추가적인 파라메터(초 값으로 전송간격)를 보냅니다.  자동 전송모드에서는 수동으로 전송을 호출할 필요가 없습니다.

// 저장된 페이지뷰/이벤트를 300초(5분)마다 전송
tracker.start("UA-YOUR-ACCOUNT-HERE", 300, this);

Google Analytics가 실제 페이지뷰/이벤트가 일어난 시간이 아닌 여러분의 데이터를 받을 때의 시간값을 사용하는 것을 기억할 필요가 있습니다.  이벤트가 일어난 날짜가 아닌 다른 날짜에 전송될 수 있기 때문에 부정확한 통계 데이터가 생성될 수 있으니 정기적으로 전송되도록 조치하시기 바랍니다.

최종 결과

추척기를 초기화하기 위해 사용했던 onCreate() 메서드로 돌아가서 모든 조각들이 모여진 모습이 어떤지 확인해 봅시다:

GoogleAnalyticsTracker tracker;

protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   tracker = GoogleAnalyticsTracker.getInstance();
   tracker.start("UA-1234-1", this);

   if(isFirstTimeRunningApplication()) {
    tracker.setCustomVar(1, "App Type", "Demo", 1);
   }

   tracker.trackPageView("/HomeScreen");
   …
}


이 모든 데이터를 어떻게 볼 것인가

여러분이 이것에 접근하는 두 가지 방법이 있습니다. 첫 째, Google Analytics는 여러분을 위한 유용한 정보를 조회하기에 아주 세련된 웹 인터페이스를 가지고 있습니다.  여러분이 처음으로 Analytics를 접하고 정말 무엇을 찾고 있는지 잘 모른다면, 웹 인터페이스가 여러분의 데이터와 사용자를 탐색하는 좋은 방법입니다.

만약 여러분이 이미 어떤 질문들을 할지 결정했고 단지 이 질문들을 자동화하고 싶다면, Google Analytics는 호화로운 data export API와 Java, Python, JavaScript, C#으로 여러분의 데이터를 쿼리할 수 있는 client libraries를 가지고 있습니다.

TOS 준수

Google Analytics는 TOS와 함께 제공되며, 이것을 읽고 준수하는 것은 아주 중요합니다.  안드로이드 어플리케이션 안에서 사용되기 때문에, 특별히 개인정보를 Analytics 서버에 전송할 수 없다는 것이 중요합니다.  이것은 아주 큰 문제입니다.  예를 들어, visitor-level 사용자 변수는 전화번호, 이름, 이메일 주소 등을 포함할 수 없습니다.  덜 직관적이지만 역시 중요한 부분으로, 만약 이 애플리케이션이 웹 어플리케이션(CRM 소프트웨어나 쇼핑사이트)의 클라이언트라면, 여러분은 여러분 소유의 백엔드 소프트웨어와 조합해 사용자를 알 수 있는 정보(여러분의 웹 백앤드에 동일하게 저장된 user ID나 transaction ID 등)를 Analytics에 저장할 수 없습니다.


원문링크

모바일앱 수출을 고민하시는 개발자분들에게 좋은 소식

모바일앱 수출을 고민하시는 개발자분들에게 좋은 소식이네요.

글로벌앱지원센터가 개인/학생/5인미만사업자(법인 포함)에게 외국어번역을 무상지원한다고 합니다!

http://www.globalappcenter.or.kr/ ‘지원사업안내’ 안의 제출처로 제출하시면 된다고 합니다.

[번역] 터치 모드

제공된 영문문서를 나름대로 번역해 봤습니다.  전문 번역가가 아니라, 조금 매끄럽지 못한 부분도 많겠지만, 핵심적인 내용을 이해하시는데는 무리가 없을 것으로 생각됩니다. 안드로이드 어플 개발에 참고할 만한 좋은 정보가 되었으면 좋겠습니다.


작성자: Romain Guy  작성일시: 2008년 12월 1일 1:15 PM

안드로이드용 사용자 인터페이스를 디자인하고 개발하는 것은 일반적인 데스크탑 환경에서 하는 것과는 상당한 차이가 있습니다.  안드로이드는 모바일 장치에서 어플리케이션을 실행하고, 어플리케이션 디자이너와 개발자들은 명확하지 않은 다수의 제약사항을 다뤄야 하기 때문입니다.  우리는 여러분이 더 나은 어플리케이션을 디자인하고 개발하는 것을 돕기위해 안드로이드 사용자 인터페이스에 관한 새로운 시리즈의 게시물을 작성하고자 합니다.  이 시리즈에서 우리는 여러분에게 디자인 가이드, 툴, 개발팁을 제공하고 안드로이드 UI 툴킷의 핵심적인 원리들을 설명할 것입니다.  목표는 간단합니다: 우리는 여러분이 훌륭한 사용자 경험(인터페이스)을 디자인하고 개발하는 것을 돕고 싶습니다.  이 시리즈를 시작하면서, UI 툴킷의 가장 중요한 원리 중 하나인 터치 모드에 대해 소개하고자 합니다. 터치 모드는 폰과 사용자의 접촉에 따른 뷰 계층의 상태입니다.  터치 모드는 마지막 사용자 접촉이 터치 스크린에 실행됐는지 간단히 표시하기 때문에, 그 자체만으로도 아주 이해하기 쉬운 것입니다.  예를 들어, 여러분이 G1 폰을 사용하고 있다면, 틀랙볼로 위젯을 선택하는 것이 여러분을 터치 모드에서 빠져나오게 할 것입니다; 하지만, 여러분이 스크린의 버튼을 손가락으로 접촉하면, 여러분은 터치 모드로 들어오게 됩니다. 사용자가 터치 모드가 아닐 때, 우리가 틀랙볼 모드, 네비게이션 모드 혹은 키보드 네비게이션이라고 말할 때 이 용어들에 놀라지 마시기 바랍니다.  마지막으로, 터치 모드와 직접 관련된 단 하나의 API만이 존재합니다.

View.isInTouchMode(). 쉽죠?  이상하리만큼, 터치 모드는 거짓말처럼 간단하지만 터치 모드에 들어감으로 인해 생기는 결과는 여러분이 생각하는 것보다 훨씬 큽니다. 몇 가지 이유에 대해 알아봅시다.

터치 모드, 선택, 그리고 Focus

모 바일 장치용 UI 툴킷을 디자인하는 것은 모바일 장치가 제공하는 다양한 상호작용 메카니즘으로 인해 매우 어렵습니다. 일부 장치들은 12개의 키만 제공하고, 어떤 장치는 터치 스크린이 있고, 어떤 장치는 스타일러스가 필요하고, 어떤 장치는 터치 스크린과 키보드 둘 다 가지고 있습니다.  이런 상황에서, 최초의 상용 장치 G1이 터치 스크린, 트랙볼, 키보드를 사용하는 다중 입력폼을 제공한다는 사실은 안드로이드 개발 커뮤니티에 긍정적인 영향을 줍니다.  사용자가 세 가지의 다른 메카니즘을 통해 어플리케이션과 소통할 수 있기 때문에, 우리는 가능한 모든 상황을 심각하게 고려해야만 합니다.  우리는 한 가지 문제 때문에 터치 모드를 만들게 되었습니다.
텍스트 아이템 리스트를 보여주는 간단한 어플리케이션(예) ApiDemos )을 생각해 보세요. 사용자는 틀랙볼을 사용해서 자유롭게 리스트를 볼 수 있고, 또한 스크롤 하거나 손가락을 이용해 화면을 이동할 수도 있습니다. 이 시나리오에서 선택이 문제가 됩니다.  만약, 제가 리스트에 있는 최상단의 아이템을 선택하고 아래로 쓸어내리면, 선택의 관점에서는 어떤 일이 일어나야 할까요?  아이템이 그대로 선택되어져 있고 화면만 스크롤 되어야 할까요?  이 상황에서, 제가 트랙볼을 이용해 선택위치를 옮기기로 결정한다면 무슨 일이 일어날까요?  혹은 더 심하게, 만약 제가 화면에 더 이상 보이지 않는 현재 선택된 아이템을 실행하기 위해 트랙볼을 누른다면?  신중하게 생각한 후, 우리는 선택을 해젷기로 결정했습니다.

터 치 모드에서는 Focus도 선택도 없습니다.  사용자가 터치 모드에 들어서자마자, 그리드 안의 리스트에 있는 선택 아이템은 선택해제 됩니다. 마찬가지로, 사용자가 터치 모드에 들어서면, 선택된 위젯도 선택해제 됩니다. 아래 이미지는 사용자가 틀랙볼로 아이템을 선택한 후 리스트를 터치하면 어떻게 되는지 보여줍니다.

프레임 워크는 사용자가 좀 더 자연스러움을 느끼게 하기 위해, 터치 모드를 벗어나면 기존의 선택/Focus 상태를 복원할 수 있습니다.  예를 들어, 위의 예제와 같이, 사용자가 트랙볼을 다시 사용해야 한다면, 이전에 선택된 아이템이 다시 선택되어 집니다. 이것이 왜 일부 개발자들이 언제 커스텀 뷰를 생성하고 트랙볼을 한번 움직인 다음부터 키 이벤트를 받기 시작하는 것을 혼동하는 이유입니다: 어플리케이션은 터치 모드에 있고, 터치 모드를 나가 Focus를 복원 하기 위해 트랙볼을 사용해야 합니다.

터치 모드, 선택, Focus의 관계는 여러분이 여러분의 어플리케이션을 빠져나가기 위해 선택과(혹은) Focus에 의존해서는 안된다는 뜻입니다.  새내기 안드로이드 개발자들이 가지는 일반적인 문제는 ListView.getSelectedItemPosition()에 의존하는 것입니다. 터치 모드에서 이 메서드는 INVALID_POSITION을 리턴할 것입니다. 여러분은 click listeners 혹은 choice mode를 사용해야 합니다.

터치 모드에서 focusable

이 제 여러분은 터치 모드에서 Focus가 존재하지 않는다는 것을 압니다. 하지만 저는 이것이 100% 사실은 아니라는 것을 설명해야 합니다. Focus는 터치 모드에 존재할 수 있지만, 아주 특별한 방법으로만 가능합니다.  우리는 이것을 터치 모드의 focusable이라고 부릅니다. 이 특별한 모드는 EditText와 같은 텍스트 입력을 받는 위젯이나 ListView에 서 필터링이 활성화 되었을 때를 위해 만들어졌습니다. 이것이 왜 사용자가 트랙볼이나 손가락으로 먼저 선택하지 않고도 텍스트 필드에 텍스트를 입력할 수 있는 이유입니다. 사용자가 스크린을 터치할 때, 어플리케이션이 이미 터치 모드가 아니면, 터치 모드로 들어갑니다. 터치 모드로 들어가는 과정에서 생기는 일은 사용자가 무엇을 터치했는지와 현재 Focus의 상태에 따라 다릅니다. 사용자가 터치 모드의 focusable 상태인 위젯을 터치하면, 위젯이 Focus가 됩니다.  그 외에 터치 모드의 focusable 상태가 아닌 한, 현재 Focus된 어떤 위젯도 Focus되지 않습니다.  예를 들어, 아래의 이미지에서, 사용자가 스크린을 터치하면, 입력 텍스트 필드가 Focus가 됩니다.

터 치 모드의 focusable은 여러분이 코드나 XML에서 스스로 설정할 수 있는 속성입니다. 그러나, 이 속성이 안드로이드의 일관된 일반적인 습성을 훼손하기 때문에, 최소한으로 그리고 아주 특별한 상황에서만 사용되어야 합니다. 터치 모드의 focusable 속성을 잘 사용할 수 있는 게임은 좋은 어플리케이션 예제입니다. 구글 맵에서처럼, 풀스크린에서 사용되는 MapView는 터치 모드의 focusable을 올바로 사용할 수 있는 또 다른 좋은 예제입니다.

아래는 터치 모드의 focusable 위젯을 사용하는 다른 예입니다.  사용자가 AutoCompleteTextView의 제안을 손가락으로 선택하면, Focus가 텍스트 입력 필드에 남아있습니다:

새 내기 안드로이드 개발자들은 종종 터치 모드의 focusable이 선택/Focus가 사라지는 문제를 “고치는” 해결책이라고 생각합니다. 우리는 여러분이 이것을 사용하기 전에 매우 신중히 생각해 보기를 권유드립니다. 만약 잘못 사용되면, 이것은 여러분의 어플리케이션이 시스템의 나머지 다른 것들과 다르게 행동하게 만들며, 사용자의 습관을 무시하게 됩니다. 안드로이드 프레임워크는 “터치 모드의 focusable”을 사용하지 않고 사용자와의 상호작용을 다룰 수 있는 모든 도구를 포함하고 있습니다. 예를 들어, 선택상태를 유지하는 ListView를 만드는 대신, 간단히 적당한 choice mode를 사용합니다. 만약 여러분이 프레임워크가 여러분의 필요를 충족시키지 못한다고 느낀다면, 우리에게 알려주시거나 패치를 제공해 주시기 바랍니다.

터치 모드 컨닝 페이퍼

해야할 것:

  • 핵심 어플리케이션과 일관성을 유지하라
  • 지속적인 선택상태가 필요하면, 적절한 기능을 사용하라 (라디오 버튼, 체크 박스, ListView의 선택 모드 등)
  • 게임을 개발한다면, 터치 모드의 focusable을 사용하라

하 지 말아야 할 것:

  • 터치 모드에서 Focus나 선택을 유지하려고 하지 말라


원문링크