Как я могу использовать один и тот же набор экранов предпочтений для всех версий Android от 2.X до 4.X?

УВЕДОМЛЕНИЕ. Пожалуйста, спасите себя некоторое время и обратитесь к принятому ответу, не нужно читать все quesiton.
Вы можете прочитать остальную часть вопроса и ответ, который я дал для альтернативного (хотя и менее сложного) метода.
Кроме того, вы можете воспользоваться исправлением для фонового сбоя в Android 2.X, добавив связанный фрагмент кода в ваш класс активности предпочтений.

Фон
Будучи новичком в Android-кодировании, но несколько опытным в других языках программирования/фреймворках, я ожидал, что моя прогулка по кодированию приложений Android будет довольно приятной. Это было так, пока я не наткнулся на эту проблему:

Мастер Eclipse для проектов Android предложил, чтобы я смог достичь 95% устройств, если бы установил минимальный API до 8 (Android 2.2). Мне все равно не нужно было делать какие-либо причудливые вещи с моим приложением, поэтому я подумал: "Конечно, почему бы и нет?". Все было в порядке, за исключением того, что иногда я нахожу несколько методов/классов, которые были устаревшими в самых последних версиях API, поэтому мне пришлось разработать способы продолжать использовать старые способы для старых устройств и стараться максимально использовать новые способы для новых версий Android. Это один из таких случаев.

После использования мастера Eclipse для создания активности предпочтений я понял, что предварительный компилятор Eclipse/parser/checker (или что-то, что он назвал) Lint, будет жаловаться на то, что не сможет использовать новые способы создания/управления предпочтениями в более ранних версиях API. Поэтому я подумал: "Хорошо, примените новые способы. Старайтесь делать это по-старому, и поскольку новые версии API должны быть обратно совместимы, все должно быть в порядке", но это не так. Старый способ использования методов/классов, отмеченных как устаревшие; что, по моему мнению, даже если они будут работать в текущем API, они перестанут работать в какой-то момент в будущих выпусках.

Итак, я начал искать правильный способ сделать это, и, наконец, ударил эту страницу: Что использовать вместо" addPreferencesFromResource " в PreferenceActivity?, где Гаррет Уилсон объясняет способ использования старых ресурсов экрана предпочтений таким образом, который совместим с новыми способами. Это было здорово, и, наконец, я почувствовал, что могу двигаться дальше с помощью своего приложения, за исключением того, что он не будет работать при использовании более старых API-интерфейсов, поскольку он использует новый API-код. Поэтому мне пришлось разработать способ заставить его работать как с старыми API, так и с новыми. После того, как я немного поработал с ним, мне удалось найти способ, используя прекомпилятор (или как он его называл) аннотации и отличный getClass(). GetMethod() вместе с исключениями.

Казалось, что все работает безупречно, пока я не создал дополнительный экран предпочтений. Он корректно отображался в новых версиях Android, но когда я пытался в старых, я мог просто видеть черный экран. После долгих поисков я нашел эту страницу, которая объясняет проблему: http://code.google.com/p/android/issues/detail?id=4611 Это, по-видимому, известный глюк, который был вокруг нескольких версий Android для хорошо. Я прочитал весь поток и нашел несколько предлагаемых решений проблемы, но мне действительно не понравилось полностью их. Я, например, предпочитаю избегать столько статического материала, сколько могу, и делать что-то программно. Я предпочитаю автоматизацию по повторяющейся работе. В некоторых решениях предлагалось создать подэлементы в качестве родительских экранов, затем добавить их в файл манифеста и вызвать их с родительского экрана с помощью намерения. Мне бы очень не хотелось отслеживать эти вещи: записи в манифесте, отдельный файл ресурсов экрана, намерения... Так что это было не-нет для меня. Я продолжал искать и находил программный подход, который мне очень понравился... только чтобы найти, что он не работает. Он состоял из повторения всего дерева представлений экрана предпочтений и назначения правильного фона для предпочтений субэкранов, но он просто не работал, потому что, как я позже узнал после большой отладки, представления подэлементов предпочтений не являются образ экрана предпочтений. Я должен был найти способ добиться этого сам. Я пробовал столько, о чем мог думать, исследовать и исследовать безрезультатно. Я был на пороге отказа от нескольких случаев, но после двух недель продолжительных усилий и много отладки я нашел обходное решение, которое я опубликовал в комментарии № 35.

Мнение
Это действительно не идеальное решение/подход, и я знаю несколько его недостатков, но это тот, который работает, поэтому я решил поделиться им. Надеюсь, я не слишком смехотворен в своем энтузиазме, чтобы поделиться тем, что взяло на себя то, что я считаю довольно много, поскольку я знаю, что это не такая уж большая проблема, которую мог бы решить любой опытный кодер. Но эй, я думаю, что обмен знаниями помогает мне немного лучше, независимо от того, насколько я похвастаюсь, чем опытный кодер, который держит все в себе. Просто поделитесь своим мнением, потому что я не могу поверить, что никто никогда не сталкивался с этой проблемой раньше, но я уверен, что у многих это было, и не поделились своими знаниями.

Представляю вам ответ с предлагаемым классом для использования в нескольких версиях Android и некоторые предложения по его использованию. Я открыт для обсуждения и вклада, чтобы сделать его лучшим классом.

Известные проблемы:

  • Родительский экран Фон декорированного изображения клонируется на дочерний экран. Декоративный фон, который, по-видимому, не является нормальным поведением.
    Статус: уволен, пока кто-нибудь не придет с серьезными основаниями исправить это.
  • Сбой при повороте экрана
    Статус: Исправлено.
    Вероятно, связано с видимостью ресурсов с помощью новой реализации API (внутренний класс PF)
    По-видимому, унаследованные классы из preferenceFragment должны иметь все свои члены static. Я думаю, это имеет смысл, если вы должны наследовать каждый раз, когда вам нужно использовать новый фрагмент

Ответ 1

Если вы находитесь в последнем плагине ADT, есть возможность легко создать функцию предпочтения, которая поддерживает большинство старых версий Android, а также все новые.

Щелкните правой кнопкой мыши по вашему проекту → Другое → Активность Android

Затем выберите SettingsActivity enter image description here

Созданная активность будет заботиться о работе как с высокими, так и с низкими версиями API, поскольку она использует инструкции if для выбора соответствующего метода отображения настроек.


РЕДАКТИРОВАТЬ
Был поднят хороший момент: устройства с телефоном, независимо от версии API, используют старые методы PreferenceActivity.

Самый быстрый способ использовать API 11+ для использования Fragments - удалить !isXLargeTablet(context); из isSimplePreferences()

private static boolean isSimplePreferences(Context context) {
    return ALWAYS_SIMPLE_PREFS
            || Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB;
}

Однако теперь у пользователя больше навигации.
Headers as root

Это потому, что вызывается onBuildHeaders().

Чтобы избавиться от этого, нам нужно будет создать собственный PreferenceFragment, который добавит каждый ресурс xml.

@TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public static class AllPreferencesFragment extends PreferenceFragment{
        @Override
        public void onCreate (Bundle savedInstanceState){
            super.onCreate(savedInstanceState);
            addPreferencesFromResource(R.xml.pref_general);

            // Add 'notifications' preferences, and a corresponding header.
            PreferenceCategory fakeHeader = new PreferenceCategory(getActivity());
            fakeHeader.setTitle(R.string.pref_header_notifications);
            getPreferenceScreen().addPreference(fakeHeader);
            addPreferencesFromResource(R.xml.pref_notification);

            // Add 'data and sync' preferences, and a corresponding header.
            fakeHeader = new PreferenceCategory(getActivity());
            fakeHeader.setTitle(R.string.pref_header_data_sync);
            getPreferenceScreen().addPreference(fakeHeader);
            addPreferencesFromResource(R.xml.pref_data_sync);

            // Bind the summaries of EditText/List/Dialog/Ringtone preferences to
            // their values. When their values change, their summaries are updated
            // to reflect the new value, per the Android Design guidelines.
            bindPreferenceSummaryToValue(findPreference("example_text"));
            bindPreferenceSummaryToValue(findPreference("example_list"));
            bindPreferenceSummaryToValue(findPreference("notifications_new_message_ringtone"));
            bindPreferenceSummaryToValue(findPreference("sync_frequency"));
        }
    }

Если вы можете определить размер экрана вне Activity, который запускает параметры, вы можете указать фрагмент для его запуска через EXTRA_SHOW_FRAGMENT

i.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT, "com.example.test.SettingsActivity$AllPreferencesFragment");

Или вы можете определить SettingsActivity, показывать или не показывать этот фрагмент (если вы довольны методом isXLargeTablet().

Измените onBuildHeaders() на:

@Override
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void onBuildHeaders(List<Header> target) {
    if (!isSimplePreferences(this) && isXLargeTablet(this)) {
        loadHeadersFromResource(R.xml.pref_headers, target);
    }
}

Добавьте этот метод:

@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void setupNewApiPhoneSizePreferences() {
    if (!isXLargeTablet(this) && Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB){
            getFragmentManager().beginTransaction().replace(android.R.id.content, new AllPreferencesFragment()).commit();
    }
}

И в onPostCreate() добавьте вызов метода.

setupNewApiPhoneSizePreferences();

Теперь следует использовать non-устаревшие вызовы от API 11.

Ответ 2

Вы можете использовать этот класс для отображения экрана предпочтений во всех версиях Android от 2.X до 4.X, путем подачи его ресурсом экрана предпочтений.

Вы можете использовать его напрямую, переименовав его, если хотите, но я бы предложил вам добавить его в свой проект, как есть, и наследовать его, что намного чище, если вам нужно работать с несколькими родительскими экранами предпочтений.

Если вы хотите использовать его напрямую, просто замените значение prefs идентификатором ресурса экрана предпочтений.

Если вы хотите наследовать его, вы должны сделать это следующим образом:

import android.os.Bundle;

public class MyPreferencesActivity extends CompatiblePreferenceActivity
{   
    @Override
    protected void onCreate(final Bundle savedInstanceState)
    {
        setPrefs(R.xml.mypreferencesactivity);
        super.onCreate(savedInstanceState);
    }   
}

ВСЕГДА вызовите setPrefs (int) перед вызовом super.onCreate(Bundle)

Если по какой-то причине вы просто хотели бы воспользоваться преимуществами исправления ошибок и создавать настройки самостоятельно, вы можете либо просто скопировать код исправления ошибок в свою собственную активность предпочтений, либо наследовать от класса и поймать исключение PrefsNotSet следующим образом:

import android.os.Bundle;

public class MyPreferencesActivity extends CompatiblePreferenceActivity
{   
    @Override
    protected void onCreate(final Bundle savedInstanceState)
    {
        try{
            super.onCreate(savedInstanceState);
        }catch(PrefsNotSetException e){};
    }   
}

И, наконец, класс:

import android.annotation.TargetApi;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceFragment;
import android.preference.PreferenceScreen;

public class CompatiblePreferenceActivity extends PreferenceActivity
{
    private int prefs=0;

    //Get/Set
    public void setPrefs(int prefs)
    {
        this.prefs=prefs;
    }

    //Exception
    protected static class PrefsNotSetException extends RuntimeException
    {
        private static final long serialVersionUID = 1L;
        PrefsNotSetException()
        {
            super("\"prefs\" should be set to a valid preference resource ID.");
        }
    }

    //Creation
    @Override
    protected void onCreate(final Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        if (prefs==0)
            throw new PrefsNotSetException();
        else
            try {
                getClass().getMethod("getFragmentManager");
                AddResourceApi11AndGreater();
                }
            catch (NoSuchMethodException e) { //Api < 11
                    AddResourceApiLessThan11();
                }
    }

    @SuppressWarnings("deprecation")
    protected void AddResourceApiLessThan11()
    {
        addPreferencesFromResource(prefs);
    }

    @TargetApi(11)
    protected void AddResourceApi11AndGreater()
    {
        PF.prefs=prefs;
        getFragmentManager().beginTransaction().replace(
            android.R.id.content, new PF()).commit();
    }

    @TargetApi(11)
    public static class PF extends PreferenceFragment
    {
        private static int prefs;
        @Override
        public void onCreate(final Bundle savedInstanceState)
        {
            super.onCreate(savedInstanceState);
            addPreferencesFromResource(prefs);
        }
    }

    //Sub-screen background glitch fix
    @SuppressWarnings("deprecation")
    @Override
    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
        Preference preference)
    {
        super.onPreferenceTreeClick(preferenceScreen, preference);
        if (preference!=null)
            if (preference instanceof PreferenceScreen)
                if (((PreferenceScreen)preference).getDialog()!=null)
                    ((PreferenceScreen)preference).getDialog().
                        getWindow().getDecorView().
                        setBackgroundDrawable(this.getWindow().
                            getDecorView().getBackground().getConstantState().
                            newDrawable());
        return false;
    }
}

Ответ 3

Хорошо, работа с автогенерируемым SettingsActivity довольно быстро прошла довольно быстро. Нужно прокручивать вверх и вниз по шаблону кода - кроме того, он заполнен желтыми предупреждениями, и я ненавижу желтый (устаревшие предупреждения вообще нельзя избежать, см. Что использовать вместо "addPreferencesFromResource " в PreferenceActivity?, где также затрагивается вопрос о том, как сделать cross API PreferenceActivity - и Предпочиталось ли PreferenceFragment из пакета совместимости? для обсуждения). А также вы можете легко получить NPE - знаете ли вы, что onPostCreate() на самом деле onPostStart() - поэтому findPreference() возвращает null в onStart().

Теперь есть решения, связанные с отражением, но нужно избегать отражения (как, черт возьми) - и поскольку нас не интересуют две версии андроидного отражения, можно избежать (см. Является ли проверка SDK_INT достаточной или требует ленивой загрузки для использования новых API-интерфейсов Android? Почему?. Также есть решения, связанные с выбором класса во время выполнения - но наличие 2 классов всасывает и не является ООП в любом случае (для тех и других решений см. Ответ на соответствующий вопрос: PreferenceActivity Android 4.0 и ранее).

Итак, я придумал абстрактный базовый класс, который является правильным способом Java и OO делать вещи (за исключением случаев, когда вам нужен Eclair и ниже, где вам нужно отражение и/или ленивая загрузка классов, чтобы избежать VerifyErrors)., где я переместил код автогенерируемого шаблона:

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.preference.PreferenceFragment;

import java.util.List;

/**
 * A {@link PreferenceActivity} that presents a set of application settings. On
 * handset devices, settings are presented as a single list. On tablets,
 * settings are split by category, with category headers shown to the left of
 * the list of settings.
 * <p>
 * See <a href="#" onclick="location.href='http://developer.android.com/design/patterns/settings.html'; return false;">
 * Android Design: Settings</a> for design guidelines and the <a
 * href="#" onclick="location.href='http://developer.android.com/guide/topics/ui/settings.html'; return false;">Settings
 * API Guide</a> for more information on developing a Settings UI.
 *
 * Defines two abstract methods that need be implemented by implementators.
 */
public abstract class BaseSettings extends PreferenceActivity {

    /**
     * Determines whether to always show the simplified settings UI, where
     * settings are presented in a single list. When false, settings are shown
     * as a master/detail two-pane view on tablets. When true, a single pane is
     * shown on tablets.
     */
    private static final boolean ALWAYS_SIMPLE_PREFS = false;

    /**
     * Helper method to determine if the device has an extra-large screen. For
     * example, 10" tablets are extra-large.
     */
    @TargetApi(Build.VERSION_CODES.GINGERBREAD)
    private static boolean isXLargeTablet(Context context) {
        return (context.getResources().getConfiguration().screenLayout &
                Configuration.SCREENLAYOUT_SIZE_MASK)
                >= Configuration.SCREENLAYOUT_SIZE_XLARGE;
    }

    /** {@inheritDoc} */
    @Override
    public final boolean onIsMultiPane() { // never used by us
        return isXLargeTablet(this) && !isSimplePreferences(this);
    }

    /**
     * Determines whether the simplified settings UI should be shown. This is
     * true if this is forced via {@link #ALWAYS_SIMPLE_PREFS}, or the device
     * doesn't have newer APIs like {@link PreferenceFragment}, or the device
     * doesn't have an extra-large screen. In these cases, a single-pane
     * "simplified" settings UI should be shown.
     */
    private static final boolean isSimplePreferences(Context context) {
        return ALWAYS_SIMPLE_PREFS
            || Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB
            || !isXLargeTablet(context);
    }

    @Override
    protected final void onCreate(Bundle savedInstanceState) {
        // disallow onCreate(), see comment in onPostCreate()
        super.onCreate(savedInstanceState);
    }

    @Override
    protected final void onStart() {
        // disallow onStart(), see comment in onPostCreate()
        super.onStart();
    }

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        // onPostCreate() probably is needed because onBuildHeaders() is called
        // after onCreate() ? This piece of err code should be called
        // onPostStart() btw - so yeah
        super.onPostCreate(savedInstanceState);
        setupSimplePreferencesScreen();
        // findPreference will return null if setupSimplePreferencesScreen
        // hasn't run, so I disallow onCreate() and onStart()
    }

    /**
     * Shows the simplified settings UI if the device configuration if the
     * device configuration dictates that a simplified, single-pane UI should be
     * shown.
     */
    private void setupSimplePreferencesScreen() {
        if (!isSimplePreferences(this)) {
            return;
        }
        buildSimplePreferences();
    }

    /** {@inheritDoc} */
    /*
     * Subclasses of PreferenceActivity should implement onBuildHeaders(List) to
     * populate the header list with the desired items. Doing this implicitly
     * switches the class into its new "headers + fragments" mode rather than
     * the old style of just showing a single preferences list (from
     * http://developer
     * .android.com/reference/android/preference/PreferenceActivity.html) -> IE
     * this is called automatically - reads the R.xml.pref_headers and creates
     * the 2 panes view - it was driving me mad - @inheritDoc my - It does not
     * crash in Froyo cause isSimplePreferences is always true for
     * Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB - @Override has
     * nothing to do with runtime and of course on Froyo this is never called by
     * the system
     */
    @Override
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public final void onBuildHeaders(List<Header> target) {
        if (!isSimplePreferences(this)) {
            loadHeadersFromResource(getHeadersXmlID(), target);
        }
    }

    // =========================================================================
    // Abstract API
    // =========================================================================
    /**
     * Must return an id for the headers xml file. There you define the headers
     * and the corresponding PreferenceFragment for each header which you must
     * of course implement. This is used in the super implementation of
     * {@link #onBuildHeaders(List)}
     *
     * @return an id from the R file for the xml containing the headers
     */
    abstract int getHeadersXmlID();

    /**
     * Builds a pre Honeycomb preference screen. An implementation would use the
     * (deprecated)
*{@link android.preference.PreferenceActivity#addPreferencesFromResource(int)}
     */
    abstract void buildSimplePreferences();
}

И пример реализации:

public final class SettingsActivity extends BaseSettings implements
        OnSharedPreferenceChangeListener {

    private static final int PREF_HEADERS_XML = R.xml.pref_headers;
    private static CharSequence master_enable;
    private OnPreferenceChangeListener listener;
    private static Preference master_pref;
    private static final String TAG = SettingsActivity.class.getSimpleName();
    private SharedPreferences sp;
    /** Used as canvas for the simple preferences screen */
    private static final int EMPTY_PREF_RESOURCE = R.xml.pref_empty;
    private static int PREF_RESOURCE_SETTINGS = R.xml.pref_data_sync;

    // abstract overrides   
    @Override
    int getHeadersXmlID() {
        return PREF_HEADERS_XML;
    }

    @Override
    void buildSimplePreferences() {
        // In the simplified UI, fragments are not used at all and we instead
        // use the older PreferenceActivity APIs.
        // THIS is a blank preferences layout - which I need so
        // getPreferenceScreen() does not return null - so I can add a header -
        // alternatively you can very well comment everything out apart from
        // addPreferencesFromResource(R.xml.pref_data_sync);
        addPreferencesFromResource(EMPTY_PREF_RESOURCE);
        // Add 'data and sync' preferences, and a corresponding header.
        PreferenceCategory fakeHeader = new PreferenceCategory(this);
        fakeHeader.setTitle(R.string.pref_header_data_sync);
        getPreferenceScreen().addPreference(fakeHeader);
        addPreferencesFromResource(PREF_RESOURCE_SETTINGS);
    }

    // here is the work done
    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        master_enable = getResources().getText(
            R.string.enable_monitoring_master_pref_key);
        listener = new ToggleMonitoringListener();
        // DefaultSharedPreferences - register listener lest Monitor aborts
        sp = PreferenceManager.getDefaultSharedPreferences(this);
        sp.registerOnSharedPreferenceChangeListener(this);
        master_pref = findPreference(master_enable.toString());
    }

    @Override
    protected void onResume() {
        super.onResume();
        master_pref.setOnPreferenceChangeListener(listener); // no way to
        // unregister, see: https://stackoverflow.com/a/20493608/281545 This
        // listener reacts to *manual* updates - so no need to be active
        // outside onResume()/onPause()
    }

    @Override
    protected void onDestroy() {
        // may not be called (as onDestroy() is killable), but no leak,
        // see: https://stackoverflow.com/a/20493608/281545
        sp.unregisterOnSharedPreferenceChangeListener(this);
        super.onDestroy();
    }

    /**
     * Toggles monitoring and sets the preference summary.Triggered on *manual*
     * update of the *single* preference it is registered with, but before this
     * preference is updated and saved.
     */
    private static class ToggleMonitoringListener implements
            OnPreferenceChangeListener {

        ToggleMonitoringListener() {}

        @Override
        public boolean
                onPreferenceChange(Preference preference, Object newValue) {
            if (newValue instanceof Boolean) {
                final boolean enable = (Boolean) newValue;
                Monitor.enableMonitoring(preference.getContext(), enable);
                final CheckBoxPreference p = (CheckBoxPreference) preference;
                preference.setSummary((enable) ? p.getSummaryOn() : p
                    .getSummaryOff());
                return true;
            }
            return false;
        }
    }

    /**
     * This fragment is used when the activity is showing a two-pane
     * settings UI.
     */
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public final static class DataSyncPreferenceFragment extends
            PreferenceFragment {

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            Log.w(TAG, "onCreate");
            addPreferencesFromResource(PREF_RESOURCE_SETTINGS);
            master_pref = findPreference(master_enable.toString());
        }
    }

    @Override
    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
            String key) {
        if (master_enable == null || master_pref == null) return;
        if (master_enable.toString().equals(key)) {
            refreshMasterPreference();
        }
    }

    /**
     * @param key
     */
    private void refreshMasterPreference() {
        final Boolean isMonitoringEnabled = AccessPreferences.get(this,
            master_enable.toString(), false);
        Log.w(TAG, "Stored value: " + isMonitoringEnabled);
        final CheckBoxPreference p = (CheckBoxPreference) master_pref;
        final boolean needsRefresh = p.isChecked() != isMonitoringEnabled;
        if (needsRefresh) {
            p.setChecked(isMonitoringEnabled);
            p.setSummary((isMonitoringEnabled) ? p.getSummaryOn() : p
                .getSummaryOff());
        }
    }
}

Итак, основная идея заключается в том, что вы предоставляете xml для предпочтений с заголовками:

    public final void onBuildHeaders(List<Header> target) {
        if (!isSimplePreferences(this)) {
            loadHeadersFromResource(getHeadersXmlID(), target);
        }
    }

где:

    @Override
    int getHeadersXmlID() {
        return PREF_HEADERS_XML;
    }

и PREF_HEADERS_XML:

<preference-headers xmlns:android="http://schemas.android.com/apk/res/android" >
    <!-- These settings headers are only used on tablets. -->
    <header
        android:fragment=".activities.SettingsActivity$DataSyncPreferenceFragment"
        android:title="@string/pref_header_data_sync" />
</preference-headers>

и настройки простых настроек в buildSimplePreferences()

Мне интересно превратить это в более общий API - возможно, включая sBindPreferenceSummaryToValueListener - поэтому идеи приветствуются.

А, да, пул sBindPreferenceSummaryToValueListener:

// FLUFF AHEAD:
// the fluff that follows is for binding preference summary to value -
// essentially wrappers around OnPreferenceChangeListener - just so
// you get an idea of the mess this autogenerated piece of, code, was
// formatter:off
/**
 * A preference value change listener that updates the preference summary
 * to reflect its new value.
 */
/* private static Preference.OnPreferenceChangeListener
        sBindPreferenceSummaryToValueListener =
        new Preference.OnPreferenceChangeListener() {

        @Override
        public boolean onPreferenceChange(Preference preference,
                            Object value) {
            String stringValue = value.toString();
            if (preference instanceof ListPreference) {
                // For list preferences, look up the correct display value
                // in the preference 'entries' list.
                ListPreference listPreference = (ListPreference) preference;
                int index = listPreference.findIndexOfValue(stringValue);
                // Set the summary to reflect the new value.
                preference.setSummary(index >= 0
                        ? listPreference.getEntries()[index] : null);
            } else if (preference instanceof RingtonePreference) {
                // For ringtone preferences, look up the correct display
                // value using RingtoneManager.
                if (TextUtils.isEmpty(stringValue)) {
                    // Empty values correspond to 'silent' (no ringtone).
                    // preference.setSummary(R.string.pref_ringtone_silent);
                } else {
                    Ringtone ringtone = RingtoneManager.getRingtone(
                        preference.getContext(), Uri.parse(stringValue));
                    if (ringtone == null) {
                        // Clear the summary if there was a lookup error.
                        preference.setSummary(null);
                    } else {
                        // Set the summary to reflect the new ringtone
                        // display name.
                        String name = ringtone
                            .getTitle(preference.getContext());
                        preference.setSummary(name);
                    }
                }
            } else if (preference instanceof CheckBoxPreference) {
                boolean b = (Boolean) value;
                Log.w(TAG, "::::value " + b);
                final CheckBoxPreference p =(CheckBoxPreference)preference;
                preference.setSummary((b) ? p.getSummaryOn() : p
                    .getSummaryOff());
                Log.w(TAG, p.getKey() + " :: " + p.isChecked());
            } else {
                // For all other preferences, set the summary to the value's
                // simple string representation.
                preference.setSummary(stringValue);
            }
            return true;
        }
    }; */

/**
 * Binds a preference summary to its value. More specifically, when the
 * preference value is changed, its summary (line of text below the
 * preference title) is updated to reflect the value. The summary is also
 * immediately updated upon calling this method. The exact display format is
 * dependent on the type of preference.
 *
 * @see #sBindPreferenceSummaryToValueListener
 */
/* private static void bindPreferenceSummaryToValue(Preference preference) {
    // Set the listener to watch for value changes.
    preference
      .setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener);
    // Trigger the listener immediately with the preference's
    // current value.
    sBindPreferenceSummaryToValueListener.onPreferenceChange(
        preference,
        PreferenceManager.getDefaultSharedPreferences(
            preference.getContext()).getString(preference.getKey(), ""));
} */