Android Handle Fragment如何在ViewPager中实现生命周期?

时间:2014-01-20 11:22:16

标签: android android-fragments

这不是一个简单的问题。我会尽我所能地解释自己,所以这可能会有点长。

解释

最近,我和Fragments以及ViewPager一直在打架。

我发现的是,ViewPager可以保存尽可能多的片段。当它第一次被实例化时,取决于类型(FragmentStatePagerAdapter vs FragmentPagerAdapter Difference between FragmentPagerAdapter and FragmentStatePagerAdapter),它将创建所有片段或仅创建“所需片段”。

在这些片段中,您可以自由使用getActivity()这样的方法,它们基本上是用于创建视图,查找视图,显示对话框的上下文......

但是,一旦离开片段,getActivity()就开始返回null,因为片段是从Activity中释放出来的。听起来很不错。

但是,对我来说听起来不合逻辑,因此我在这里要求的是,当你回到片段页面(或足够接近)时,它会再次触发基本方法,如onAttach

只要你onAttach,就可以保留Activity提供的上下文,但如果你尝试做类似下面的事情(伪代码):

class MyFragment extends Fragment
{
    private Context context;

    private doThingsAfterAttach()
    {
        getActivity(); //this is null.

        null != context; //TRUE
    }

    @Override
    public onAttach( Activity activity )
    {
        context = activity;
        doThingsAfterAttach();
    }
}

您可以看到,即使您在触发getActivity()后调用此方法,onAttach也会返回null。

如果您决定将保留的上下文强制转换为Activity,要执行findViewById任务,您将看到您尝试查找的视图为null,这意味着无法找到/不能不存在。

问题

想象一下,你有一个带有5个标签(片段)的ViewPager。 当在这5个选项卡中的任何一个上执行任务时,您希望通知“持有者”活动,以便通知所有应更新其内容的片段,因为某些内容已更改。 但是当你通知他们时,如果他们必须改变布局,他们就不能这样做,因为只要你尝试findViewById,就会发生两件事:getActivity()返回null,因此你可以获取视图,如果将上下文转换为活动,则在搜索任何视图时不会返回任何视图。

让我最害怕的是,当你旋转设备时,就像离开片段页面然后回去一样; “失去”活动。

真实问题

所以我正在寻找的是一个答案,它解释了我内部发生了什么,所以我可以找到适当的代码来处理这些情况。

我可以提供的代码不多,因为它没用。使用ViewPager和Fragments的人,可能会处理这些事情,所以你会理解我。

谢谢你,我愿意回答你的问题。

感谢。

3 个答案:

答案 0 :(得分:10)

注意:这是一篇冗长且可能很无聊的帖子

ViewPager的问题不是新问题。不要心疼。我们都经历过似乎是 new 范例的神奇解决方案(我将在Italics中添加新内容,因为它当时并不是新的)。随着对Fragments的增加支持(通过你提到的State和Stateless适配器),事情变得更加有趣。

我不打算详细说明适配器是好的还是烦人的(你可以得出你自己的结论)或为什么适配器(和小部件本身)缺少非常基本的东西,没人知道什么在哪里Android开发人员在考虑将这些公开API公开时。

相反,我会帮助你管理活动/片段沟通的方式 - 我认为 - 每个人都这样做。

ViewPager中的碎片发生了什么?

活动/片段概念 - 在我的拙见中 - 可怕而且它是一个黑客。但是一个有效的,因为它很快证明它有效,直到Android团队决定开始为越来越多的东西添加Fragment支持。甚至嵌套片段,片段内的碎片!而且,由于Android开发人员(越来越少,但仍然经常)必须处理旧版本的平台,因此所有这些支持最初都被添加到支持库中,并且它从未移出它。

但让我们回到主题。 Fragment生命周期有些令人困惑(部分原因是由于某些生命周期方法的命名不正确以及它们无法保证发生的顺序)。有些东西隐藏在不友好的回调背后(TreeLayout有人吗?)。

所以...... ViewPager需要一个适配器。适配器负责向ViewPager提供其视图。因此,虽然ViewPager是一个了解触摸,拖动,拖动,绘制,测量等的视图,但它确实需要一个适配器来提供要显示的数据。 (这是一个巨大的简化)。

这里我们有两种类型的适配器,它们知道如何处理碎片。一个维持一个状态而另一个没有。意思是一个人并没有真正发布任何东西(FragmentPagerAdapter),而且确实发布了它的片段(FragmentStatePagerAdapter)......但是等等......什么是"发布"在这种情况下?

事实证明片段并不真正存在于自由世界中,它们由FragmentManager控制,它确保它们不会迟到并决定何时释放它们。这是规则,即使是适配器也无法覆盖!所以无论如何他们必须向FragmentManager报告。您可以告诉适配器必须与此FragmentManager通信,因为它必须在构造函数中收到一个!

public static class MyAdapter extends FragmentStatePagerAdapter {
       public MyAdapter(FragmentManager fm) {
           super(fm);
       }

无论如何,这个FragmentManager是谁?

嗯,文档不是很友好,只是说:

  

用于与活动内的Fragment对象进行交互的接口

好的,谢谢你先生!

实际上,FragmentManager是一个静态类,您不能创建它,只需从活动中获取

现在想想一个控制器的FragmentManager,它会保留对你的碎片的引用,甚至在你离开它们之后很久。它知道如何将它们带回来,它可以帮助它们在配置更改时保存它们的状态(例如旋转),它可以保持堆叠/堆叠,知道它们可以恢复生命的顺序,并完成所有这些,必须提交的运动FragmentTransactions,就像关系数据库事务一样。

这种复杂性的大部分(我确信他们有他们的理由)都发生在幕后,所以你真的不必过于担心它。

我们现在可以回到原来的问题吗?

是的,这是一个很长的主题,你可以看到,但让我们明白这一点,因为我必须回去工作......

当片段离开视野时,适配器秘密地告诉FragmentManager:"哟dawg,此处不再需要此片段 - 现在 - "。 (它可能不会使用那个短语,而是类似的东西)。有关更详细的回复,请you can actually look at the more or less recent code of the FragmentStatePagerAdapter并从中学到很多。

看看它是如何有一个Fragments和SavedStates列表:

private ArrayList<Fragment.SavedState> mSavedState = new ArrayList<Fragment.SavedState>();
private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();

以及对FragmentManager的引用:

private final FragmentManager mFragmentManager;

毫不奇怪,FragmentPagerAdapter没有片段列表,也没有已保存状态列表,它只是让FragmentManager完成它的工作。

所以,让我们首先看一下&#34; State&#34;寻呼机适配器,看看什么时候去毁坏碎片...

获得Google的许可,让我们查看destroyItem()的源代码:

1 @Override
2 public void destroyItem(ViewGroup container, int position, Object object) {
3      Fragment fragment = (Fragment)object;
4      if (mCurTransaction == null) {
5          mCurTransaction = mFragmentManager.beginTransaction();
6      }
7      if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
8              + " v=" + ((Fragment)object).getView());
9      while (mSavedState.size() <= position) {
10         mSavedState.add(null);
11     }
12     mSavedState.set(position, mFragmentManager.saveFragmentInstanceState(fragment));
13     mFragments.set(position, null);
14     mCurTransaction.remove(fragment);
15 }

如果尚未启动FragmentTransaction,则第4行启动。

第9-11行用空条目填充mSavedState数组,直到它至少是我们要删除的片段索引的大小。

第12行保存要删除的片段的状态(如果需要,可以在以后恢复)。

第13行有效地删除了片段引用...

第14行将此添加到Fragment的Manager事务中,以便FragmentManager知道该怎么做。

(注意:当您动态添加/更改/删除片段时,ViewPager中存在一个模糊的错误,我会在最后链接到问题/解决方案。)

当您将片段添加到ViewPager时,该过程相对类似(请参阅instantiateItem()方法......

首先检查对象是否已经实例化,然后立即返回。

如果片段不存在,则创建一个......

if (mCurTransaction == null) {
    mCurTransaction = mFragmentManager.beginTransaction();
}

Fragment fragment = getItem(position);

请记住,扩展此适配器并创建自己的getItem()方法,这是调用它的地方。您正在为适配器提供一个新创建的片段。

接下来,适配器检查片段的savedState以查看它是否可以找到一个(这里有一个错误)(见最后的链接)......

最后,它继续添加新收到的片段:

while (mFragments.size() <= position) {
    mFragments.add(null);
}
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
mFragments.set(position, fragment);
mCurTransaction.add(container.getId(), fragment);

它必须为数组添加空填充以确切大小,当然也使用FragmentTransaction。

到目前为止,故事的寓意是适配器保留了自己的东西,但它让老板(a.k.a:FragmentManager)高兴,让他知道他掌握了控制权。

作为参考,the support v13 versions几乎相同,但引用了Fragment,FragmentManager,FragmentTransaction等非支持版本。

因此,如果适配器保留一个列表并询问FragmentManager(通过FragmentTransaction)或只是使用FragmentManager,那么FragmentManger会做什么?!

这是更多&#34;复杂&#34;但FragmentManager实现有一个Added和Active片段列表(以及无数其他集合和数据结构)。

 ArrayList<Fragment> mActive;
 ArrayList<Fragment> mAdded;

^来自FragmentManagerImpl类!

所以我不会详细了解FragmentManager(你可以找到它的源代码here),因为它是一个非常大的类,并且它使用事务处理所有事情并且它是&#39;超级凌乱。它为每个片段(创建,初始化等)保留状态机。有趣的方法可能是moveToState(),这就是Fragment生命周期回调的位置,所以看一下源代码,看看发生了什么。

另请查看那里的removeFragment()方法,最后调用moveToState()方法。

足够了......我什么时候可以调用getActivity()而不是在片段中获取null?

好的,所以你举了一个你想做的例子。

你有ViewPager有5个片段(例如)你想要通知他们发生了什么事情,所以他们可以做些什么。很多事情。

我说这是典型的观察者模式+ ViewPager.OnPageChangeListener()。

观察者模式很简单,您可以创建一个接口,如:

public interface FragmentInterface {
    void onBecameVisible();
}

你的片段实现了这个界面......

public Fragment YourFragment implements FragmentInterface {

然后你有一个:

@Override
public void onBecameVisible() {
    if (getActivity() != null && !getActivity().isFinishing()) {
          // do something with your Activity -which should also implement an interface!-
    }
}

谁打电话给BecameVisible?

您的活动中的ViewPager.OnPageChangeListener():

public void onPageSelected(final int i) {
    FragmentInterface fragment = (FragmentInterface) mPagerAdapter.instantiateItem(mViewPager, i);
    if (fragment != null) {
        fragment.onBecameVisible();
    } 
}

现在,Activity可以可靠地告诉Fragment它已变得可见。

现在......如果你想告诉其他片段,那么你必须有一个&#34;听众列表&#34; (观察者模式)但你必须明白,根据我们所看到的关于适配器和片段管理器的内容,碎片可能是分离的(即使它们可能不一定被破坏)。它们不可见的事实可能意味着它们倾向于破坏它们的视图层次结构(因此在那时改变它是不可能的)。

您最好的办法是确保您的onCreateView能够根据您的业务逻辑询问应显示哪个视图。

了解生命周期的最佳方法是向Fragment生命周期的每个日志添加一个Log(onStart,Stop,Pause,Resume,Detach,Destroy,ActivityCreated等等),看看当你在ViewPager中移动时他们会如何反应。

我希望这篇冗长的帖子让您了解ViewPager对您的片段做了什么。如果您有更具体的问题或我是否真的帮助过您,请告诉我们:)

关于我之前提到的BUG,请查看this post,其中讨论FragmentStatePagerAdapter处理片段恢复的方式中的错误(以及修复)。

答案 1 :(得分:0)

我认为getActivity()会返回null onAttach(),因为你没有把它称为超级,换句话说它没有机会将它设置为活动。此外,您无需致电getActivity()查找观看次数,您可以在onCreateView()上保留其参考资料,并在onStart()更新资料,如果其值有任何变化。

将活动上下文保持为全局将阻止片段在活动存在时被垃圾收集。因此,请在onDetach()上发布参考。

答案 2 :(得分:-1)

如果您的问题是要通知Activity,只需将Activity作为“listener”传递给片段即可。请记住将其作为WeakReg传递,以避免泄漏问题。