首选项活动 Android 4.0 及早期版本

36

尝试在Android 4.0中的ApiDemos中使用不同偏好设置活动时,我发现PreferencesFromCode.java中的某些方法已被弃用。

所以我的问题是:如果我使用PreferenceFragment,它会适用于所有版本还是只适用于3.0或4.0及更高版本?

如果是这样,我应该使用什么才能在2.2和2.3上运行?


2
目前还不涉及偏好设置,但是Google在开发者网站上开始提供有关与Honeycomb之前的设备兼容性问题的有用信息:http://developer.android.com/training/backward-compatible-ui/index.html - Jose_GD
有一个第三方的PreferenceFragment后移版本。请参见我的答案 - theblang
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Kevin Cooper
6个回答

59

PreferenceFragment 不支持 Android 2.2 和 2.3(仅支持 API 级别 11 及以上版本)。如果你想要提供最佳的用户体验并且仍然支持旧版 Android,最佳实践似乎是实现两个 PreferenceActivity 类,并在运行时决定调用哪个。然而,这种方法仍然包括调用被弃用的 API,但你无法避免。

例如,你有一个 preference_headers.xml

<preference-headers xmlns:android="http://schemas.android.com/apk/res/android" > 
    <header android:fragment="your.package.PrefsFragment" 
        android:title="...">
        <extra android:name="resource" android:value="preferences" />
    </header>
</preference-headers>

还有一个标准的 preferences.xml 文件(自从较低 API 级别以来没有太大变化):

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" android:title="...">
    ...
</PreferenceScreen>

那么您需要实现一个 PreferenceFragment

public static class PrefsFragment extends PreferenceFragment {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.xml.preferences);
    }
}

最后,您需要两个PreferenceActivity的实现,分别用于支持或不支持PreferenceFragments的API级别:

public class PreferencesActivity extends PreferenceActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.xml.preferences);
        addPreferencesFromResource(R.xml.other);
    }
}

并且:

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

当您想向用户显示首选项屏幕时,您决定从哪一个开始:

if (Build.VERSION.SDK_INT < 11) {
    startActivity(new Intent(this, PreferencesActivity.class));
} else {
    startActivity(new Intent(this, OtherPreferencesActivity.class));
}

基本上,每个片段都有一个XML文件,对于API级别< 11,您需要手动加载每个XML文件,并且两个活动都使用相同的首选项。


深入的回答。非常需要,因为谷歌未能为我们提供制作向后兼容的Android应用程序的适当文档。不过最近情况开始有所改变(请参见上面的评论)。 - Jose_GD
3
好主意,唯一的问题是当你不是发起活动的人时,你无法决定要启动哪个活动。另外,你正在使用 API 级别的检测,而不是特性检测(使用 ClassLoaders 检查 PreferenceFragment 是否存在)。那些修改 build.prop 文件从 API 10 迁移到 11(或更高版本)的用户将会遇到崩溃。 - Tom
3
这现在已经在这里有文件记录了:http://developer.android.com/guide/topics/ui/settings.html#BackCompatHeaders - pcans
@Tom 你可以通过使用一个“shim activity”来解决“不是启动Activity的那个”的问题,这个activity除了启动新的prefs activity之外什么也不做,然后调用finish。当然,它应该保持在历史记录/返回堆栈之外。 - Josh
好的解决方案,谢谢。不幸的是,当您需要通过意图将一些以编程方式生成的参数传递给首选项片段时,它无法工作。 - Andrey Chernih
显示剩余2条评论

18

@Mef,你的答案还可以更简单,这样你就不需要使用PreferencesActivity和OtherPreferencesActivity(拥有2个PrefsActivities非常麻烦)。

我发现你可以将onBuildHeaders()方法放入你的PreferencesActivity中,在Android v11之前的版本中不会抛出错误。将loadHeadersFromResource()方法放在onBuildHeaders中,在2.3.6上没有抛出异常,但在Android 1.6上会。经过一些尝试,我发现以下代码可以在所有版本中工作,只需要一个Activity(大大简化了问题)。

public class PreferencesActivity extends PreferenceActivity {
    protected Method mLoadHeaders = null;
    protected Method mHasHeaders = null;

    /**
     * Checks to see if using new v11+ way of handling PrefFragments.
     * @return Returns false pre-v11, else checks to see if using headers.
     */
    public boolean isNewV11Prefs() {
        if (mHasHeaders!=null && mLoadHeaders!=null) {
            try {
                return (Boolean)mHasHeaders.invoke(this);
            } catch (IllegalArgumentException e) {
            } catch (IllegalAccessException e) {
            } catch (InvocationTargetException e) {
            }
        }
        return false;
    }

    @Override
    public void onCreate(Bundle aSavedState) {
        //onBuildHeaders() will be called during super.onCreate()
        try {
            mLoadHeaders = getClass().getMethod("loadHeadersFromResource", int.class, List.class );
            mHasHeaders = getClass().getMethod("hasHeaders");
        } catch (NoSuchMethodException e) {
        }
        super.onCreate(aSavedState);
        if (!isNewV11Prefs()) {
            addPreferencesFromResource(R.xml.preferences);
            addPreferencesFromResource(R.xml.other);
        }
    }

    @Override
    public void onBuildHeaders(List<Header> aTarget) {
        try {
            mLoadHeaders.invoke(this,new Object[]{R.xml.pref_headers,aTarget});
        } catch (IllegalArgumentException e) {
        } catch (IllegalAccessException e) {
        } catch (InvocationTargetException e) {
        }   
    }
}

这样你只需要一个活动(activity),一个在AndroidManifest.xml中的入口,以及在调用偏好设置时只需要一行代码:

startActivity(new Intent(this, PreferencesActivity.class);

更新于2013年10月: Eclipse / Lint会警告您使用已弃用的方法,但只需忽略警告即可。我们仅在必须使用它时使用该方法,即每当我们没有v11+风格偏好并且必须使用它时,这是可以的。当您已经考虑了它时,请不要因为过时的代码而感到恐慌,Android不会很快删除已弃用的方法。如果真的发生了这种情况,您甚至不再需要这个类,因为您将被迫仅针对较新的设备进行目标设置。弃用机制存在的目的是警告您有更好的方法来处理最新API版本上的某些内容,但一旦您已考虑到这一点,您可以从那时起安全地忽略警告。从代码中删除所有对已弃用方法的调用将仅导致您的代码仅在较新的设备上运行-从而完全抵消了向后兼容性。


3
你在1.6版本中遇到的异常是由Dalvik尝试加载所有方法(或检查它们是否存在),即使它们在运行时没有被调用。在2.0中,这种行为已经改变,所以在2.3.6中就没有异常了。 - bigstones
1
@LuisA.Florit:请查看我关于废弃方法的更新答案。我的答案不同之处在于它使用了一个统一的类和相同的XML文件,适用于Android版本<11和>=11;这样一来,在一个文件被更新而其他文件没有更新时,减少了出现错误的机会。 - Uncle Code Monkey
@UncleCodeMonkey:是的,我明白这段代码应该做什么,这正是我感兴趣的原因。但我找不到你关于弃用方法的任何说明。我仍然收到很多弃用警告(这对我来说是有意义的)。我不明白你在这里使用PreferenceFragment类的地方。 - Luis A. Florit
@UncleCodeMonkey:是的,我理解了,但那只是为了加载布局。然而,我认为在你的代码中被调用的方法是PreferenceActivity的方法,而不是PreferenceFragment的方法。例如,在你的代码中,findPreference()是从PreferenceActivity中获取的,这就是为什么你会得到相同的过时警告。换句话说,我真的不明白你的代码如何与PreferenceFragment一起工作。事实上,你从未继承这个类。 - Luis A. Florit
"PreferenceFragment在2.2和2.3上无法使用(仅适用于API级别11及以上)。如果您想提供最佳用户体验并仍支持旧版Android版本,则最佳实践似乎是实现PreferenceActivity类。然而,这种方法仍包括调用已弃用的API,但您无法避免这种情况。" - Uncle Code Monkey
显示剩余5条评论

6

有一个较新的库可能会有所帮助。

UnifiedPreference是一个用于处理所有版本的Android Preference包(从API v4及以上)的库。


5

之前的问题在于它会在早期版本的设备上将所有首选项堆叠到单个屏幕上(由于多次调用addPreferenceFromResource())。

如果您需要首先显示列表屏幕,然后再显示首选项屏幕(例如使用首选项标题),则应使用兼容首选项的官方指南


2
我要指出的是,如果你从http://developer.android.com/guide/topics/ui/settings.html#PreferenceHeaders开始,并一步步向下浏览到“使用首选项头支持旧版”的部分,会更容易理解。该指南非常有帮助且有效。以下是一个显式的例子,按照他们的指南操作:

所以,从preference_header_legacy.xml这个文件开始,适用于HoneyComb之前的Android系统。

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<Preference 
    android:title="OLD Test Title"
    android:summary="OLD Test Summary"  >
    <intent 
        android:targetPackage="example.package"
        android:targetClass="example.package.SettingsActivity"
        android:action="example.package.PREFS_ONE" />
</Preference>

接下来,为Android系统创建一个名为preference_header.xml的文件,适用于HoneyComb及以上版本。
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
<header 
    android:fragment="example.package.SettingsFragmentOne"
    android:title="NEW Test Title"
    android:summary="NEW Test Summary" />
</preference-headers>

接下来创建一个preferences.xml文件来保存你的偏好设置...

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
        <CheckBoxPreference
        android:key="pref_key_auto_delete"
        android:summary="@string/pref_summary_auto_delete"
        android:title="@string/pref_title_auto_delete"
        android:defaultValue="false" />
</PreferenceScreen>

下一步创建文件SettingsActivity.java
package example.project;
import java.util.List;
import android.annotation.SuppressLint;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceActivity;

public class SettingsActivity extends PreferenceActivity{
final static String ACTION_PREFS_ONE = "example.package.PREFS_ONE";

@SuppressWarnings("deprecation")
@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_header_legacy);
    }
}

@SuppressLint("NewApi")
@Override
public void onBuildHeaders(List<Header> target) {
    loadHeadersFromResource(R.xml.preference_header, target);
}
}

接下来创建 SettingsFragmentOne.java 类。

package example.project;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.preference.PreferenceFragment;

@SuppressLint("NewApi")
public class SettingsFragmentOne extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    addPreferencesFromResource(R.xml.preferences);
}
}

AndroidManifest.xml,我在<application>标签之间添加了这个代码块

<activity 
   android:label="@string/app_name"
   android:name="example.package.SettingsActivity"
   android:exported="true">
</activity>

最后,对于<wallpaper>标签...
<wallpaper xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/description"
android:thumbnail="@drawable/ic_thumbnail"
android:settingsActivity="example.package.SettingsActivity"
/>

1
我正在使用这个库,它在mavenCentral上有一个AAR文件,所以如果你正在使用Gradle,你可以轻松地将其包含进来。 compile 'com.github.machinarius:preferencefragment:0.1.1'

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接