【问题标题】:isValidFragment Android API 19isValidFragment Android API 19
【发布时间】:2013-11-14 08:41:03
【问题描述】:

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

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

在文档中我找到以下解释

受保护的布尔 isValidFragment(字符串片段名称)

在 API 级别 19 中添加

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

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

【问题讨论】:

  • 您是否尝试过覆盖它?
  • 如果我用简单的“return true”覆盖该方法,它可以工作,但现在的问题是:“我必须在这个覆盖的方法中进行哪些检查”?
  • 我认为第一个问题应该是:“什么是有效片段?”
  • 我同意,什么是有效片段? :-)
  • 我所有的应用程序现在都在 4.4 上抛出该异常。什么?

标签: android android-fragments android-4.4-kitkat


【解决方案1】:

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

protected boolean isValidFragment(String fragmentName) {
  return StockPreferenceFragment.class.getName().equals(fragmentName);
}

【讨论】:

  • 是否必须对每个片段都执行此操作?还是 PREFERENCE 就足够了?!
  • @Wolkenjaeger 您将片段名称保存在数组中,然后从那里读取。 API 会比较名称,因此您必须找到一种方法让您的代码匹配字符串。使用标准的 String 实践来抽象它应该不难。毕竟,您是负责向系统返回简单布尔值的人。由您来创建自己的代码逻辑。
  • 所以在 PreferenceActivity 中我重写了这个方法并返回 true... 看起来很愚蠢的事情,嗯?
  • @Tobor 为什么这么笨?这是一个简单的安全检查,以确保当时调用的片段是授权片段(您识别的片段)。如果您只是 return true 没有任何逻辑,那是毫无意义的。这就像白宫授权任何人和每个人在街上的公共大门上行走一样,但如果他们return true 只对在那里工作的人是正确的。
  • 非常糟糕的是,当您通过 Android Studio 添加 PreferenceActivity 时,您获得的默认实现未解决此问题。
【解决方案2】:

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

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

;}

【讨论】:

    【解决方案3】:

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

    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 实际上并没有执行双窗格偏好活动。因此也需要静态列表成员。

    【讨论】:

    • 很好的解决方案!我有口味问题,因为我在开发、测试、现场加载了不同的片段。这就像做梦一样
    • 不错的解决方案!像魅力一样工作!
    • 更干净,更好的解决方案:)
    • 我没有看到 Ofek Ron 在重写这个最好的解决方案时提到的安全漏洞。
    • 这是个坏主意!!! - 特别是当开始偏好活动并且你将设置额外内容时 -> Intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMEN,Fragment.class.getName()); Intent.putExtra(PreferenceActivity.EXTRA_NO_HEADERS, true);然后你在黑洞里:)
    【解决方案4】:

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

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

    【讨论】:

    • 嗨@RoeeHay,你能详细说明链接的文章吗?将文章链接为答案通常被认为是错误的答案。你能指出漏洞中的重点吗?我看到这篇文章本身是你写的,并且解释了很多,关于为什么它被介绍了。我很想赞成这个答案,因为它到目前为止解释了原因而不是如何解决问题。
    【解决方案5】:

    用实际的 4.4 设备验证:

    (1) 如果您的 proguard.cfg 文件有这一行 (which many define anyway):

    -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);
    
       }
    }
    

    【讨论】:

      【解决方案6】:

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

       @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;
          }
      

      【讨论】:

      • 正如我对@lane 的回答所说的那样——使用 onBuildHeaders (List) 方法来设置片段列表是个坏主意——你的方法更安全——我投赞成票!但是您可以对其进行更多改进-创建私有目标并在为空时填充一次
      【解决方案7】:

      这是我的解决方案:

      • 如果您需要动态重建标头
      • 如果你使用 extras 来启动偏好活动 - 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;
              }
          }
      }
      

      【讨论】:

        【解决方案8】:

        这是我的 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>  
        

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

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

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

        【讨论】:

          【解决方案9】:

          我的解决方案(而不是创建类的 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;
          }
          

          【讨论】:

            【解决方案10】:
            @Override
            protected boolean isValidFragment (String fragmentName) {
                for (Class<?> cls : ImePreferences.class.getDeclaredClasses()) {
                    if (cls.getName().equals(fragmentName)){return true;}
                }
                return false;
            }
            

            【讨论】:

            • 您好,感谢您的首次贡献!您能否添加更多详细信息(为什么有人应该使用此代码 sn-p 而不是其他答案之一)?
            猜你喜欢
            • 1970-01-01
            • 2014-01-24
            • 2023-04-01
            • 2014-08-08
            • 1970-01-01
            • 1970-01-01
            • 2017-05-23
            • 2014-01-24
            • 1970-01-01
            相关资源
            最近更新 更多