4

我对 Fragments 和 BroadcastManager 有疑问。在我的应用程序中,我切换到一个主要活动并使用新的 NavigationDrawer。所有内容都包含在片段中。

一个片段(搜索用户)包含两个选项卡(按名称搜索、按条件搜索),使用来自 Android 设计网站的导航模式“横向导航”:它有一个带有 FragmentStatePagerAdapter 的 ViewPager。

|  Main Activity  |    |  Main Activity  |
-------------------    -------------------
| Search Fragment |    | Search Fragment |
| >Tab 1< | Tab 2 |    | Tab 1 | >Tab 2< |
|    View Pager   |    |    View Pager   |
|   Fragment 1    |    |   Fragment 2    |

我希望两个选项卡都使用相同的操作栏(选项)菜单:搜索操作。但只有活动片段才能对其做出反应。

我尝试了不同的方法,最烦人的是我无法直接从视图寻呼机轻松获取当前片段(不依赖非 API 实现细节)。

方法

我现在使用 LocalBroadcast 来通知片段已单击搜索。每个片段在 onResume 中注册一个小的 Wrapper-Receiver(并在 onPause 中将其删除),它将 onReceive 转发给片段本身(如下所示的方法)。我重写setMenuVisibilitywhich 是 FragmentStatePagerAdpater 在 Fragments 上调用的回调,以了解哪个是活动片段。只有菜单集可见的片段才会对广播做出反应。

ActionBar Tab -> ViewPager -> ViewPagerAdapter -> Fragment.setMenuVisibility
ActionBar Menu Action -> Broadcast ->  ... -> BroadcastReceiver -> Fragments

两个片段都会触发片段事务以显示搜索结果并将其添加到后台堆栈。

问题 片段事务通常工作,但是当我旋转设备然后按搜索时,我得到一个 IllegalStateException(在 onSaveInstanceState 之后无法提交)。

@Override
    public void onReceive(Context context, Intent intent) {
        if (m_menuVisible) {
            final String s = ((TextView) getView().findViewById(R.id.search_text)).getText().toString();

            SearchResultsFragment f = new UserSearchResultsFragment();
            Bundle b = new Bundle();
            b.putString(SearchResultsFragment.EXTRA_SEARCHNAME, s);
            f.setArguments(b);

            FragmentTransaction transaction = getSherlockActivity().getSupportFragmentManager().beginTransaction();
            transaction.replace(R.id.fragment_container_frame, f).addToBackStack(null).commit(); // <<< CRASH
        }
    }

我试图将提交更改为commitAllowingStateLoss,但随后我得到一个带有“活动已被破坏”的 IllegalStateException。

你知道这里出了什么问题吗?我很茫然怎么办...


附加代码:

MainActivity onCreate(基于 NavigationDrawer 示例)和 selectItem

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
    setContentView(R.layout.fragment_container_layout);

    setupDrawer(); // Sets up the drawer layout, adapter, etc.

    if (savedInstanceState == null) {
        selectItem(0); // Selects and adds the fragment(s) for the position
    }

    // Other setup stuff
}

private void selectItem(int position) {
        // update the main content by replacing fragments

        Fragment f = null;
        switch (position) {
            case 0:
                f = ...
                break;
            case 1:
                ...
            default:
                throw new IllegalArgumentException("Could not find right fragment");
        }
        f.setRetainInstance(true);
        m_drawerList.setItemChecked(position, true);
        setTitle(mDrawerTitles.get(position).titleRes);

        // Hide any progress bar that might be visible in the actionbar
        setProgressBarIndeterminateVisibility(false);

        // When we select something from the navigation drawer, the back stack is discarded
        final FragmentManager fm = getSupportFragmentManager();
        fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);

        fm.beginTransaction().replace(R.id.fragment_container_frame, f).commit();

        // update selected item and title, then close the drawer
        m_drawerLayout.closeDrawer(GravityCompat.START);
    }

故障选项卡式片段中的 FragmentStatePagerAdapter:

protected class TabPagerAdapter extends FragmentStatePagerAdapter {

        private List<String> m_fragmentTags = new ArrayList<String>();

        public TabPagerAdapter(FragmentManager fm) {
            super(fm);
        }

        public void addFragments(List<String> list) {
            ViewPager pager = (ViewPager) getView().findViewById(R.id.viewpager);

            startUpdate(pager);
            for (String tag : list) {
                m_fragmentTags.add(tag);
            }
            finishUpdate(pager);
            notifyDataSetChanged();
        }

        public void addFragment(String tag) {
            addFragments(Collections.singletonList(tag));
        }

        @Override
        public Fragment getItem(int pos) {
            // CreateFragmentForTag: Retrieves the classes, instantiates the fragment
            // Does not do retainInstance or any transaction there.
            return createFragmentForTag(m_fragmentTags.get(pos));
        }

        @Override
        public int getCount() {
            return m_fragmentTags.size();
        }

}

该项目的精简版仅包含必需品,发布在https://docs.google.com/file/d/0ByjUMh5UybW7cnB4a0NQeUlYM0E/edit?usp=sharing

如下重现它: 启动,您会看到两个选项卡。选择其中一个并单击 ActionBar 中的 Search -> Fragment transaction works。使用返回键,旋转设备并重复 -> 崩溃![在赏金结束或发现错误后,该文件可能无法访问。]

编辑:(1)阐明 onReceive 存在于片段的上下文中(2)为主要活动添加了代码

4

2 回答 2

3

最初使用时出现错误的原因commit()是必须在调用包含活动之前提交事务onSaveInstanceState(),这就是为什么在 using 时纠正错误的原因commitAllowingStateLoss (),这可能会在提交后丢失状态。

在此方法更改之后,您将Activity has been destroyed在方向更改期间遇到异常,因为片段提交持有对在方向更改期间被破坏的原始活动的引用。因此,您可以定义一个片段容器并将您的片段添加到onCreate()父活动的容器中,而不是在您的布局 xml 中定义片段,以确保在重新创建活动时,您也在创建一个新片段并提交

您可以在此处找到有关setRetainInstance的详细信息。

于 2013-06-30T11:32:26.223 回答
2

将我的评论移到此处的答案中,并对其进行扩展。

第一条线索是你只有在旋转后才会崩溃,而你有setRetainInstance(true). 首先,尝试将其设置为false. 即使这样可以防止崩溃,您仍然需要谨慎。它可能指向您现在通过不保留片段实例来掩盖的其他错误。

你一开始有什么特别的原因setRetainInstance(true)吗?

至于您为什么会看到崩溃开始,我仍然无法确定问题所在。我怀疑错误onReceive是在轮换之后被调用的——但这只是在黑暗中的一次尝试。

如果您可以创建应用程序的骨架并将代码发布到某处,我可以查看它。我知道您已经总结了如何“转发” onReceive()s ,但是如果做得不对,还有可能在此处引入错误。

如果这是不可能的,这就是我的建议 - 将日志记录语句放在 your Activity、 your Fragments 和 your的主要生命周期方法中onReceive()。这会告诉你发生的事情的确切顺序。

于 2013-06-26T05:18:10.457 回答