반응형
블로그 이미지
개발자로서 현장에서 일하면서 새로 접하는 기술들이나 알게된 정보 등을 정리하기 위한 블로그입니다. 운 좋게 미국에서 큰 회사들의 프로젝트에서 컬설턴트로 일하고 있어서 새로운 기술들을 접할 기회가 많이 있습니다. 미국의 IT 프로젝트에서 사용되는 툴들에 대해 많은 분들과 정보를 공유하고 싶습니다.
솔웅

최근에 받은 트랙백

글 보관함

[Android] Settings - 2 -

2016. 10. 9. 17:07 | Posted by 솔웅


Settings 2


Using Preference Headers

드문 경우 이지만 세팅 화면을 제일 처음에 보여야 할 때도 있을 것입니다. (예 figures 4 and 5). 안드로이드 3.0 이상에서는 headers 기능을 사용해야 합니다. (nested preferenceScreen element로 subscreen들을 구현하는 대신에...)

To build your settings with headers, you need to:
headers를 이용해 세팅을 만들려면

    세팅의 각 그룹을 구분해 PreferenceFragment의 별도의 instance들로 만듭니다. 즉 각 그룹들은 별도의 XML 파일로 나눠져야 한다는 의미입니다.
    각 세팅 그룹별로 XML headers 파일을 생성합니다. 그리고 해당 세팅을 갖고 있는 fragment를 정의합니다.
    setting을 host 하기 위해 PreferenceActivity class 를 extend 합니다.
    headers file을 지정하기 위해 onBuildHeaders() callback을 implement 합니다.

이 디자인을 사용하는 가장 큰 잇점은 큰 화면에서 작동할 시 PreferenceActivity 가 2개의 pane layout으로 자동적으로 표현한다는 겁니다. (figure 4)

안드로이드 3.0 이전의 버전에서도 이 PreferenceFragment 를 사용해 2개의 pane을 표시할 수 있습니다. (see the section about Supporting older versions with preference headers)



Figure 4. Two-pane layout with headers.



1. The headers are defined with an XML headers file.
2. Each group of settings is defined by a PreferenceFragment that's specified by a <header> element in the headers file.





Figure 5. A handset device with setting headers. When an item is selected, the associated PreferenceFragment replaces the headers.



Creating the headers file



headers list에 있는 세팅 그룹은 root <preference-headers> element 안에 하나의 <header> element 로 정의 됩니다.



예제.

<?xml version="1.0" encoding="utf-8"?>
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
    <header
        android:fragment="com.example.prefs.SettingsActivity$SettingsFragmentOne"
        android:title="@string/prefs_category_one"
        android:summary="@string/prefs_summ_category_one" />
    <header
        android:fragment="com.example.prefs.SettingsActivity$SettingsFragmentTwo"
        android:title="@string/prefs_category_two"
        android:summary="@string/prefs_summ_category_two" >
        <!-- key/value pairs can be included as arguments for the fragment. -->
        <extra android:name="someKey" android:value="someHeaderValue" />
    </header>
</preference-headers>


android:fragment attribute와 함께 각 header 는 PreferenceFragment 의 instance 를 정의 합니다. 사용자가 header를 선택했을 때 open 되는 대상이죠.



<extras> element는 key-value 값을 fragment로 pass 할 수 있도록 해 줍니다. ( in a Bundle). 이 fragment는 getArguments()를 호출함으로서 arguments들을 수집할 수 있습니다. argument들을 fragment로 pass 해야 하는 이유는 여러가지가 있을 수 있습니다. 그 중 하나는 PreferenceFragment 의 같은 subclass를 각 그룹에 대해 reuse 할 수 있다는 겁니다. 그리고 이 argument를 어떤 fragment의 preference XML 파일을 load 해야 하는지 특정할 수 있습니다.



예를 들어, 아래를 보면 여러개의 settings group들에서 reuse 될 수 있는 fragment가 있습니다. 각 header가 <extra> argument를 "settings" key와 함께 정의하고 있습니다. :



public static class SettingsFragment extends PreferenceFragment {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        String settings = getArguments().getString("settings");
        if ("notifications".equals(settings)) {
            addPreferencesFromResource(R.xml.settings_wifi);
        } else if ("sync".equals(settings)) {
            addPreferencesFromResource(R.xml.settings_sync);
        }
    }
}





Displaying the headers


preference headers를 표시하려면 onBuildHeaders() callback 메소드를 implement 해야 합니다. 그리고 loadHeaderFromResource() 를 호출합니다.


예제


public class SettingsActivity extends PreferenceActivity {
    @Override
    public void onBuildHeaders(List<Header> target) {
        loadHeadersFromResource(R.xml.preference_headers, target);
    }
}



headers 의 리스트에서 사용자가 한 아이템을 선택했을 때 시스템은 해당 PreferenceFragment를 open 합니다.


Note : preference headers를 사용 할 때 PreferenceActivity의 subclass는 onCreate() 메소드를 implement 할 필요가 없습니다. 이 activity 에서 필요한 작업은 headers를 load 하는 것이기 때문입니다.



Supporting older versions with preference headers



앱이 Android 3.0 이전의 버전을 지원 할 때에도 headers를 사용해 Android 3.0 이사의 버전에서 작동할 때 2개의 pane layout을 제공할 수 있습니다. 이렇게 하려면 추가적인 preferences XML을 만들어야 하는데요 basic <Preference> element를 사용해서 header items 처럼 사용되도록 하면 됩니다. (오래된 안드로이드 버전에서 사용되도록 하기 위해)

새로운 PreferenceScreen을 여는 대신 각 <Preference> element가 PreferenceActivity에 Intent를 보냅니다. (어떤 XML file이 로드되는지를 지정합니다.)



예제) preference headers 를 위한 XML 파일입니다. 안드로이드 3.0 이상에서 사용됩니다. (res/xml/preference_headers.xml):



<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
    <header
        android:fragment="com.example.prefs.SettingsFragmentOne"
        android:title="@string/prefs_category_one"
        android:summary="@string/prefs_summ_category_one" />
    <header
        android:fragment="com.example.prefs.SettingsFragmentTwo"
        android:title="@string/prefs_category_two"
        android:summary="@string/prefs_summ_category_two" />
</preference-headers>



안드로이드 3.0 이하 버전들에 같은 headers를 제공하기 위한 preference file 입니다.(res/xml/preference_headers_legacy.xml):


<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
    <Preference
        android:title="@string/prefs_category_one"
        android:summary="@string/prefs_summ_category_one"  >
        <intent
            android:targetPackage="com.example.prefs"
            android:targetClass="com.example.prefs.SettingsActivity"
            android:action="com.example.prefs.PREFS_ONE" />
    </Preference>
    <Preference
        android:title="@string/prefs_category_two"
        android:summary="@string/prefs_summ_category_two" >
        <intent
            android:targetPackage="com.example.prefs"
            android:targetClass="com.example.prefs.SettingsActivity"
            android:action="com.example.prefs.PREFS_TWO" />
    </Preference>
</PreferenceScreen>


<preference-headers> 를 지원하는 것은 안드로이드 3.0에서 추가 되었기 때문에 시스템은 안드로이드 3.0 이상에서 작동 될 때에만 PreferenceActivity 안의 onBuildHeaders()를 호출합니다. legacy headers file을 로딩하려면 (preference_headers_legacy.xml) 우선 안드로이드 버전을 체크 합니다. 안드로이드 3.0 (HONEYCOMB)보다 오래 됐으면 addPreferencesFromResource() 를 호출해서 legacy header file을 로딩합니다.



예제


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

    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
        // Load the legacy preferences headers
        addPreferencesFromResource(R.xml.preference_headers_legacy);
    }
}

// Called only on Honeycomb and later
@Override
public void onBuildHeaders(List<Header> target) {
   loadHeadersFromResource(R.xml.preference_headers, target);
}



이제 남은 것은 어떤 preference file을 load 해야 하는지를 지정하는 activity로 pass 된 Intent를 처리하는 것입니다. 그 intent의 action을 retrieve 하고 preference XML의 <intent> 태그에서 사용돼 알고 있는 action string과 비교 합니다.

final static String ACTION_PREFS_ONE = "com.example.prefs.PREFS_ONE";
...

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

    String action = getIntent().getAction();
    if (action != null && action.equals(ACTION_PREFS_ONE)) {
        addPreferencesFromResource(R.xml.preferences);
    }
    ...

    else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
        // Load the legacy preferences headers
        addPreferencesFromResource(R.xml.preference_headers_legacy);
    }
}


addPreferencesFromResource()에 대한 연속적인 호출은 single list안의 모든 preference들이 stack 되게 할 겁니다. 그러니 if-else 구문을 사용해서 오직 한번만 호출되도록 하세요.





Reading Preferences



디폴트로 preference들은 여러분 앱 내부 어디에서든지 접근 가능한 파일에 저장됩니다. 접근은 PreferenceManager.getDefaultSharedPreferences() 라는 static method를 호출함으로서 이뤄집니다. 이 메소드는 SharedPreferences 객체를 return 하는데요 여기에는 PreferenceActivity에서 사용된 Preference 객체들과에 상응하는 key-value 값들이 포함돼 있습니다.



예제

SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
String syncConnPref = sharedPref.getString(SettingsActivity.KEY_PREF_SYNC_CONN, "");



Listening for preference changes

사용자가 preference를 변경하자 마자 통보를 받아야 할 때가 있을 수 있습니다. 이렇게 변화가 있었을 때 callback을 받으려면 SharedPreference.OnSharedPreferenceChangeListener 인터페이스를 implement 합니다. 그리고 그 리스너를 registerOnSharedPreferenceChangeListener() 를 호출함으로서 SharedPreferences 객체에 등록해 놓습니다.

이 인터페이스는 한개의 callback 메소드를 가지고 있습니다. onSharedPreferenceChanged() 메소드 입니다.



예제



public class SettingsActivity extends PreferenceActivity
                              implements OnSharedPreferenceChangeListener {
    public static final String KEY_PREF_SYNC_CONN = "pref_syncConnectionType";
    ...

    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
        String key) {
        if (key.equals(KEY_PREF_SYNC_CONN)) {
            Preference connectionPref = findPreference(key);
            // Set summary to be the user-description for the selected value
            connectionPref.setSummary(sharedPreferences.getString(key, ""));
        }
    }
}


이 예제에서는 이미 알고 있는 preference key 에 대해 체크하고 있습니다. findePreference() 메소드를 호출해서 Preference 객체를 get 합니다. 이 preference는 변경된 preference 입니다. 그래서 그 아이템의 해당 summary로 내용을 업데이트 할 수 있습니다. List나 다른 multiple choice setting 인 경우에는 현재의 상태를 표시하기 위해 setSummary()를 호출해야 할 것입니다. such as the Sleep setting shown in figure 5)

Note : Android Design document에 Settings에 대해 나와 있듯이 사용자가 preference를 변경할 때마다 ListPreference의 Summary를 업데이트 해 줄 것을 권장합니다

Activit의 제대로 된 lifecycle management를 위해 SharedPreferences.OnSharedPreferenceChangeListeneronResume(), onPause() callback에서 사용할 것을 권장합니다.



@Override
protected void onResume() {
    super.onResume();
    getPreferenceScreen().getSharedPreferences()
            .registerOnSharedPreferenceChangeListener(this);
}

@Override
protected void onPause() {
    super.onPause();
    getPreferenceScreen().getSharedPreferences()
            .unregisterOnSharedPreferenceChangeListener(this);
}



주의 : registerOnSharedPreferenceChangeListener()를 호출할 때 preference manager는 strong reference 를 listener에 저장하지 않습니다. 여러분이 직접 저장해야 합니다. 그렇지 않으면 garbage collection으로 가게 될 것입니다. 그 reference를 객체의 instance data 안의 리스너에 보관할 것을 권장합니다. 그러면 리스너를 필요로 하는 한 존재하게 할 수 있습니다.



아래 예제에서는 caller가 리스너에 reference를 keep 하지 않습니다. 그래서 이것은 garbage collection으로 가게 될 겁니다. 그러면 언젠가 앱의 독장이 fail 될 겁니다.


prefs.registerOnSharedPreferenceChangeListener(
  // Bad! The listener is subject to garbage collection!
  new SharedPreferences.OnSharedPreferenceChangeListener() {
  public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
    // listener implementation
  }
});

대신에 객체의 인스턴스 데이터 필드안에 있는 리스너에 reference를 저장하면 해당 리스너가 필요할 때 까지 계속 존재하게 될 겁니다.

SharedPreferences.OnSharedPreferenceChangeListener listener =
    new SharedPreferences.OnSharedPreferenceChangeListener() {
  public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
    // listener implementation
  }
};
prefs.registerOnSharedPreferenceChangeListener(listener);


Managing Network Usage

안드로이드 4.0 부터 Settings application은 사용자에게 앱들이 얼마나 많은 네트워크 데이터를 사용했는지를 보여줍니다. 사용자는 이것을 보고 각각의 앱에 대해 background data 사용을 금지하는 등의 조치를 할 수 있습니다. 그러니까 사용자가 여러분의 앱의 데이터 access를 잠그지 않도록 하기 위해서는 데이터를 가급적 적게 효율적으로 사용하도록 개발 하여야 합니다.

예를 들어 얼마나 자주 데이터를 동기화 하는지 혹은 Wi-Fi 하에서만 파일 업로드 다운로드를 가능하도록 하던지 혹은 로밍시에만 가능하도록 하는 등등의 기능을 넣어서 데이터 사용을 줄일 수 있습니다.


Once you've added the necessary preferences in your PreferenceActivity to control your app's data habits, you should add an intent filter for

PreferenceActivity에 필요한 preference들을 추가 했다면 다음은 intent filter를 추가해야 합니다.

manifest파일의 ACTION_MANAGE_NETWORK_USAGE 에 추가 합니다.


<activity android:name="SettingsActivity" ... >
    <intent-filter>
       <action android:name="android.intent.action.MANAGE_NETWORK_USAGE" />
       <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

이 intent filter는 이 activity는 어플리케이션의 데이터를 컨트롤 한다는 것을 알려 줍니다.

반응형

Comment