PreferenceFragment是否故意从兼容包中排除?

153

我想编写可以应用于3.0和3.0之前设备的首选项。发现PreferenceActivity包含已弃用的方法(尽管这些方法在附带的示例代码中使用),因此我查看了PreferenceFragement和兼容性包以解决我的困境。

然而,似乎PreferenceFragment不在兼容性包中。有人能告诉我这是否是有意为之?如果是,我是否能轻松地针对一系列设备(即< 3.0和>=3.0),还是必须费尽周折?如果没有有意被排除,那么我们可以期待新版本的兼容性包吗?或者还有其他安全使用的解决方案吗?


1
这是我解决问题的方法:https://dev59.com/2mYq5IYBdhLWcg3w7E-w - ecv
有人制作了一个第三方的PreferenceFragment,你甚至会忘记它的存在。请参见我的答案 - theblang
Chris Banes在他的博客评论中解释了这个问题。他说,原因是“因为大多数Preferences的实现都是隐藏的,所以如果不进行大量的hackery,就无法进行后移植。” - theblang
请查看我的更新答案PreferenceFragmentCompat最近被添加到支持库中。 - theblang
8个回答

90
发现PreferenceActivity包含弃用方法(尽管这些在附带示例代码中使用)。这些方法是针对Android 3.0弃用的。它们在所有版本的Android上都还可以正常使用,但方向是在Android 3.0及更高版本上使用PreferenceFragment。
有人能告诉我这是否是有意为之吗? 我猜这是一个工程时间的问题,但这只是我的猜测。
如果是这样,我能轻松地针对一系列设备进行目标定位(即小于3.0和大于等于3.0),还是我必须费尽周折? 对我来说,这是“容易完成”的。有两个分别使用首选项标头和PreferenceFragments的PreferenceActivity实现,另一个使用原始方法。在需要时选择正确的方法(例如,当用户单击选项菜单项时)。 这里是演示此方法的示例项目。或者,像这个示例项目那样,有一个单独的PreferenceActivity处理两种情况。
如果没有故意排除,我们可以期待兼容性包的新版本发布吗? 当它被发布时,我们会像其他人一样知道。
还是有其他安全可用的解决方案吗?
请参见上文。

谢谢,马克。我看到你在几个地方(Android Google群组和你的博客)对此发表了评论,但我想要一个明确的答案(在情况允许的范围内)。 - James
@James:是的,关键在于偏好XML定义中,需要得到一些既可以作为片段良好工作,又可以连接在一起的东西,因为我不确定<include>是否适用于偏好XML。顺便说一下,如果你是订阅者,刚刚宣布了涉及此项目的书籍更新。 - CommonsWare
7
对不起,我不太确定你想表达什么意思。你没有回答任何问题,只是评论/猜测/引用与问题无关的外部链接。问题是是否有意省略了兼容版本的PreferenceFragment,如果没有PreferenceFragment,则没有使用你所描述的扩展PreferenceActivity的方法,因为如果PreferenceFragment不存在,则getSupportFragmentManager()或其他使用片段的方法也不存在。 - Justin Buser
8
“问题是省略是否是故意的”——唯一能回答这个问题的人是在谷歌工作的人。你可以去谷歌找工作尝试找出答案。 “没有办法按照你描述的方式扩展PreferenceActivity”——你可以下载我链接的代码。 - CommonsWare
9
记录一下,Mark已经回答了我的问题。从我接受他的回答可以看出来。 - James
我下载了最新的“支持”包,但仍然没有PreferenceFragment!#失望 - Someone Somewhere

21
@CommonsWare的回答所隐含的微妙意思是-你的应用程序必须选择兼容API或内置片段API(自SDK 11左右开始)。实际上,这就是“易于”推荐所做的。换句话说,如果你想使用PreferenceFragment,则你的应用程序需要使用内置片段API并处理PreferenceActivity上的弃用方法。相反,如果重要的是你的应用程序使用compat API,则将面临根本没有PreferenceFragment类的情况。因此,定位设备不是问题,但当你不得不选择一个API并因此提交你的设计以避免预见不到的解决方法时,就会发生跳跃。我需要compat. API,所以我将创建自己的PreferenceFragment类,并查看它的工作方式。在最坏的情况下,我只需创建一个普通(片段)布局并手动绑定视图组件到共享首选项...呃。
编辑:尝试并查看http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.1_r1/android/preference/PreferenceFragment.java?av=h上的代码后,创建自己的PreferenceFragment不可能发生。它似乎是PreferenceManager中包私有使用的自由度,而不是“受保护”的主要障碍。它看起来确实没有任何安全或真正好的动机去做这件事情,也不适用于单元测试,但无论如何...我猜减少了一些打字时间...
编辑v2:实际上它发生了,并且很有效。让代码与兼容性API JAR一起工作确实是一个头疼的问题。我不得不从SDK中复制约70%的com.android.preference包到我的应用程序中,然后与通常质量低劣的Android Java代码进行斗争。我使用了SDK的v14版本。对于谷歌工程师来说,做我所做的事情会更容易,这与我听到的一些Android主要工程师对此话题的说法相反。

顺便说一句,我有没有说“针对设备不是问题”?其实完全是个问题...如果你使用com.android.preference,你将无法在进行重构的情况下使用Compatibility API进行替换。日志记录很有趣!


让我直接了当地说吧。如果你只关心面向Honeycomb及以上版本的设备(占有多少市场份额?),那么请投票支持@Commonsware 的答案!但是如果你关心当前市场上大部分Android设备,你应该阅读我的回复。 - Tenacious
4
你能否分享一下你是如何做到这一点的?我也遇到了完全相同的问题,只是我的PreferenceActivity必须使用Loaders,因此我必须使用兼容库。 - Karakuri
3
@Tenacious,我很喜欢你的调查——干得好。但是,我觉得有人应该纠正你在那里的第一个评论——Commonsware的代码可以在HC之前和之后的设备上运行——在发表这样的评论之前请先尝试。你需要意识到的是,使用的是运行时的动态绑定来支持之前的设备。运行时的版本检查会处理支持两个OS家族——这是一种常见的Android模式(不是我喜欢的,但对于Android开发人员来说它是重要的学习和熟悉的)...因此对于将来的读者,请不要轻视任何方法。 - Richard Le Mesurier
@RichardLeMesurier 但是如果你需要在DrawerLayout中使用preferences,Commonsware的方法是不正确的。 - neworld

16

结合了CommonsWare的答案和Tenacious的观察,我提出了一个单一的子类解决方案,能够以最小的麻烦和没有任何代码或资源重复地针对所有当前的Android API版本。请参见我在这里相关问题的答案:

PreferenceActivity Android 4.0及更早版本

或者在我的博客上查看: http://www.blackmoonit.com/2012/07/all_api_prefsactivity/

已在两个运行4.0.3和4.0.4的平板电脑、一部运行4.0.4和2.3.3的手机以及一个运行1.6的模拟器上进行测试。


10

10

2015年8月,Google发布了新的Preference Support Library v7

现在,您可以在任何ActivityAppCompatActivity中使用PreferenceFragmentCompat

public static class PrefsFragment extends PreferenceFragmentCompat {

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

        // Load the preferences from an XML resource
        addPreferencesFromResource(R.xml.preferences);
    }
}

您需要在您的主题中设置preferenceTheme

<style name="AppTheme" parent="@style/Theme.AppCompat.Light">
  ...
  <item name="preferenceTheme">@style/PreferenceThemeOverlay</item>
</style>

通过这种方式,您可以自定义preferenceTheme以样式化用于每个首选项类型的布局,而不影响Activity的其他部分。


1
我认为你错过了一个函数:onCreatePreferences。 - android developer
这个救了我的一天!我想知道为什么在Android Studio的项目结构中看不到它...?? 顺便说一下,你的代码有一个错别字。应该是“extends PreferenceFragmentCompat”。 - Grzegorz D.

7
Tenacious的回答是正确的,但这里有更多细节。
你不能“创建普通布局并手动将视图组件绑定到sharedprefs”,原因是Android.preferences API中存在一些令人惊讶的遗漏。PreferenceActivity和PreferenceFragment都可以访问重要的非公共PreferenceManager方法,如果没有这些方法,你无法实现自己的首选项UI。
特别是,要从XML文件构造Preference层次结构,你需要使用PreferenceManager,但所有PreferenceManager的构造函数都是包私有或隐藏的。将Preference onClick监听器附加到你的活动的方法也是包私有的。
你不能通过偷偷将实现放在android.preferences包中来解决这个问题,因为Android API中的非公共方法实际上被省略了。通过反射和动态代理的创意,你仍然可以找到它们。唯一的选择就像Tenacious所说的那样,是分叉整个android.preference包,包括至少15个类、5个布局和类似数量的style.xml和attrs.xml元素。
因此,回答最初的问题,Google没有在兼容性包中包含PreferenceFragment的原因是他们会遇到和Tenacious以及我一样的困难。即使是Google也不能回到过去,让那些方法在旧平台上变成公共的(不过我希望他们在未来的版本中这样做)。

2
我的应用程序目标是API +14,但由于使用支持库进行一些炫酷的导航,我无法使用android.app.Fragment,只能使用android.support.v4.app.Fragment,但我还需要将PreferenceFragment放置在代码后面而不需要大幅更改代码。
所以我的简单解决方案是同时拥有支持库和PreferenceFragment的两个世界:
private android.support.v4.app.Fragment fragment;
private android.app.Fragment nativeFragment = null;

private void selectItem(int position) {
    fragment = null;
    boolean useNativeFragment = false;
    switch (position) {
    case 0:
        fragment = new SampleSupprtFragment1();
        break;
    case 1:
        fragment = new SampleSupprtFragment2();
        break;
    case 2:
        nativeFragment = new SettingsFragment();
        useNativeFragment = true;
        break;
    }
    if (useNativeFragment) {
        android.app.FragmentManager fragmentManager = getFragmentManager();
        fragmentManager.beginTransaction()
            .replace(R.id.content_frame, nativeFragment).commit();
    } else {
        if (nativeFragment != null) {
            getFragmentManager().beginTransaction().remove(nativeFragment)
                .commit();
            nativeFragment = null;
        }
        FragmentManager fragmentManager = getSupportFragmentManager();
        fragmentManager.beginTransaction()
            .replace(R.id.content_frame, fragment).commit();
    }
}

2
我需要将首选项集成到应用程序设计中,并保持对2.3 Android的支持。因此,我仍然需要PreferencesFragment。
经过一番搜索,我找到了android-support-v4-preferencefragment库。这个库节省了很多时间,不用再复制和重构原始的PreferencesFragment了,正如Tenacious所说的那样。它可以正常工作,用户也可以享受首选项。

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