49

当我使用 Android KitKat 尝试我的应用程序时,我在 PreferenceActivity 中出现错误。

PreferenceActivity 的子类必须重写 isValidFragment(String) 以验证 Fragment 类是否有效!com.crbin1.labeltodo.ActivityPreference 尚未检查片段 com.crbin1.labeltodo.StockPreferenceFragment 是否有效

在文档中,我找到以下解释

protected boolean isValidFragment(字符串片段名称)

在 API 级别 19 中添加

子类应覆盖此方法并验证给定片段是否是要附加到此活动的有效类型。对于为 android:targetSdkVersion 早于 KITKAT 构建的应用程序,默认实现返回 true。对于以后的版本,它会抛出异常。

我没有找到任何例子来解决这个问题。

4

10 回答 10

65

试试这个......这就是我们检查片段有效性的方式。

protected boolean isValidFragment(String fragmentName) {
  return StockPreferenceFragment.class.getName().equals(fragmentName);
}
于 2013-11-22T07:57:50.663 回答
24

出于纯粹的好奇,您也可以这样做:

@Override
protected boolean isValidFragment(String fragmentName) {
    return MyPreferenceFragmentA.class.getName().equals(fragmentName)
            || MyPreferenceFragmentB.class.getName().equals(fragmentName)
            || // ... Finish with your last fragment.

;}
于 2013-12-10T12:37:12.010 回答
20

我发现我可以在加载时从我的标头资源中获取我的片段名称的副本:

public class MyActivity extends PreferenceActivity
{
    private static List<String> fragments = new ArrayList<String>();

    @Override
    public void onBuildHeaders(List<Header> target)
    {
        loadHeadersFromResource(R.xml.headers,target);
        fragments.clear();
        for (Header header : target) {
            fragments.add(header.fragment);
        }
    }
...
    @Override
    protected boolean isValidFragment(String fragmentName)
    {
        return fragments.contains(fragmentName);
    }
}

这样,如果我想更新它们,我就不需要记住更新埋在代码中的片段列表。

我曾希望直接使用getHeaders()现有的标头列表,但似乎活动在被调用之后被破坏onBuildHeaders()并在被调用之前重新创建isValidFragment()

这可能是因为我正在测试的 Nexus 7 实际上并没有进行两窗格偏好活动。因此也需要静态列表成员。

于 2015-01-21T13:43:57.953 回答
18

由于新发现的漏洞而添加了此 API。请参阅http://ibm.co/1bAA8kFhttp://ibm.co/IDm2Es

2013 年 12 月 10 日 “我们最近向 Android 安全团队披露了一个新漏洞。[...] 更准确地说,任何使用导出活动扩展 PreferenceActivity 类的应用程序都会自动受到攻击。Android 中提供了一个补丁KitKat。如果您想知道为什么您的代码现在被破坏,那是由于 Android KitKat 补丁要求应用程序覆盖新方法 PreferenceActivity.isValidFragment,该方法已添加到 Android 框架中。-- 来自上面的第一个链接

于 2013-12-10T20:19:51.850 回答
3

用实际的 4.4 设备验证:

(1)如果你的 proguard.cfg 文件有这一行(很多人都定义了):

-keep public class com.fullpackage.MyPreferenceFragment

(2) 最有效的实施方式是:

@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public class EditPreferencesHC extends PreferenceActivity {
...
   protected boolean isValidFragment (String fragmentName) {

     return "com.fullpackage.MyPreferenceFragment".equals(fragmentName);

   }
}
于 2013-11-22T23:07:45.540 回答
3

我不确定lane的实现是否没有此处讨论的漏洞,但如果是,那么我认为更好的解决方案是避免使用该静态列表并简单地执行以下操作:

 @Override
    protected boolean isValidFragment(String fragmentName)
    {
        ArrayList<Header> target = new ArrayList<>();
        loadHeadersFromResource(R.xml.pref_headers, target);
        for (Header h : target) {
            if (fragmentName.equals(h.fragment)) return true;
        }
        return false;
    }
于 2015-09-25T14:56:41.160 回答
0

这是我的解决方案:

  • 如果您需要动态重建标头
  • 如果您使用附加功能来启动偏好活动 - onBuildHeaders() 方法将失败!(具有以下启动意图附加功能 - 为什么??? - 很简单,因为从未调用过 onBuildHeaders()):

    Intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMEN,Fragment.class.getName()); Intent.putExtra(PreferenceActivity.EXTRA_NO_HEADERS, true);

这是示例类:

/**
 * Preference Header for showing settings and add view as two panels for tablets
 * for ActionBar we need override onCreate and setContentView
 */
public class SettingsPreferenceActivity extends PreferenceActivity {

    /** valid fragment list declaration */
    private List<String> validFragmentList;

    /** some example irrelevant class for holding user session  */
    SessionManager _sessionManager;

    @Override
    public void onBuildHeaders(List<Header> target) {
        /** load header from res */
        loadHeadersFromResource(getValidResId(), target);
    }

    /**
     * this API method was added due to a newly discovered vulnerability.
     */
    @Override
    protected boolean isValidFragment(String fragmentName) {
        List<Header> headers = new ArrayList<>();
        /** fill fragments list */
        tryObtainValidFragmentList(getValidResId(), headers);
        /** check  id valid */
        return validFragmentList.contains(fragmentName);
    }

    /** try fill list of valid fragments */
    private void tryObtainValidFragmentList(int resourceId, List<Header> target) {  
        /** check for null */
        if(validFragmentList==null) {
            /** init */
            validFragmentList = new ArrayList();
        } else {
            /** clear */
            validFragmentList.clear();
        }
        /** load headers to list */
        loadHeadersFromResource(resourceId, target);
        /** set headers class names to list */
        for (Header header : target) {
            /** fill */
            validFragmentList.add(header.fragment);
        }
    }

    /** obtain valid res id to build headers */
    private int getValidResId() {
        /** get session manager */
        _sessionManager = SessionManager.getInstance();
        /** check if user is authorized */
        if (_sessionManager.getCurrentUser().getWebPart().isAuthorized()) {
            /** if is return full preferences header */
            return R.xml.settings_preferences_header_logged_in;
        } else {
            /** else return short header */
            return R.xml.settings_preferences_header_logged_out;
        }
    }
}
于 2016-01-14T18:27:03.783 回答
0

这是我的 headers_preferences.xml 文件:

<?xml version="1.0" encoding="utf-8"?>  
<preference-headers  
xmlns:android="http://schemas.android.com/apk/res/android">  

    <header  

        android:fragment="com.gammazero.signalrocket.AppPreferencesFragment$Prefs1Fragment"  
        android:title="Change Your Name" />  

    <header  
        android:fragment="com.gammazero.signalrocket.AppPreferencesFragment$Prefs2Fragment"  
        android:title="Change Your Group''s Name" />  

    <header  
        android:fragment="com.gammazero.signalrocket.AppPreferencesFragment$Prefs3Fragment"  
        android:title="Change Map View" />  

</preference-headers>  

在出现 isValidFragment 代码的 PreferencesActivity 中,我只是把它转过来:

@Override
protected boolean isValidFragment(String fragmentName)
{
  //  return AppPreferencesFragment.class.getName().contains(fragmentName);
    return fragmentName.contains (AppPreferencesFragment.class.getName());
}

只要我在所有片段名称的开头使用 AppPreferencesFragment 字符串,它们都可以正常验证。

于 2016-09-21T01:09:44.883 回答
0

我的解决方案(而不是创建类的 ArrayList),因为加载的片段假设是 PreferenceFragment.class 的子类,在 @OverRide 方法中运行此检查

@Override
protected boolean isValidFragment(String fragmentName) {
    try {
        Class cls = Class.forName(fragmentName);
        return (cls.getSuperclass().equals(PreferenceFragment.class));
                                  // true if superclass is PreferenceFragmnet
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
    return false;
}
于 2018-04-15T20:56:01.343 回答
0
@Override
protected boolean isValidFragment (String fragmentName) {
    for (Class<?> cls : ImePreferences.class.getDeclaredClasses()) {
        if (cls.getName().equals(fragmentName)){return true;}
    }
    return false;
}
于 2021-02-22T09:07:39.567 回答