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

Rei*_*erd 5 android android-fragments

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

说明

最近,我和Fragments以及ViewPager一直在争吵.

我发现的是,ViewPager可以保存尽可能多的片段.当它第一次被实例化时,取决于类型(FragmentStatePagerAdapterFragmentPagerAdapter FragmentPagerAdapter和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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

问题

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

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

真正的问题

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

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

谢谢,在这里我准备回答你的问题.

谢谢.

Mar*_*ini 10

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

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

我不打算详细介绍适配器是好还是讨厌(你可以得出自己的结论),或者为什么适配器(和小部件本身)缺少非常基本的东西,没人知道Android开发人员在什么时候会想到什么暴露了这些公共API.

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

ViewPager中的Fragments发生了什么?

活动/片段概念 - 在我的拙见中 - 可怕而且是一个黑客.但是一个有效的,因为它很快证明它有效,直到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);
       }
Run Code Online (Sandbox Code Playgroud)

无论如何,这个FragmentManager是谁?

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

用于与Activity内部的Fragment对象交互的接口

好的,谢谢你先生!

实际上,FragmentManager是一个静态类,你不创建它,只需从一个Activity中获取它.

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

大多数这种复杂性(我确信他们有他们的理由)发生在幕后,所以你真的不必太担心它.

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

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

当片段离开视野时,适配器秘密地告诉FragmentManager:"哟dawg,这里不再需要这个片段 - 现在 - ".(它可能不会使用那个短语,而是类似的东西).对于更详细的响应,您实际上可以查看FragmentStatePagerAdapter的或多或少的最新代码,并从中学到很多东西.

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

private ArrayList<Fragment.SavedState> mSavedState = new ArrayList<Fragment.SavedState>();
private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();
Run Code Online (Sandbox Code Playgroud)

和对FragmentManager的引用:

private final FragmentManager mFragmentManager;
Run Code Online (Sandbox Code Playgroud)

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

那么让我们首先看一下"状态"寻呼机适配器,看看什么时候去毁坏一个片段......

在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 }
Run Code Online (Sandbox Code Playgroud)

第4行启动FragmentTransaction(如果还没有启动).

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

第12行保存了被删除的片段的状态(因此如果需要可以在将来恢复).

第13行有效地删除了片段参考...

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

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

当你添加一个片段到ViewPager,这个过程比较相似(见instantiateItem()方法...

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

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

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

Fragment fragment = getItem(position);
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

到目前为止,故事的寓意是,适配器保留了自己的东西,但它让老板(又名:FragmentManager)高兴,让他知道他在掌控之中.

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

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

这更加"复杂",但FragmentManager实现有一个Added和Active片段列表(以及无数其他集合和数据结构).

 ArrayList<Fragment> mActive;
 ArrayList<Fragment> mAdded;
Run Code Online (Sandbox Code Playgroud)

^那是来自FragmentManagerImpl类!

所以我不会详细介绍FragmentManager(你可以在这里找到它的源代码),因为它是一个非常大的类,它使用事务处理一切,而且它非常混乱.它为每个片段(创建,初始化等)保留状态机.也许有趣的方法是moveToState(),这就是Fragment生命周期回调的地方,所以看一下源代码,看看发生了什么.

另外看一下removeFragment()那里的方法,最后调用moveToState()方法.

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

好的,所以你提供了一个你想做的例子.

您有ViewPager 5片段(例如),并且您想要通知他们发生了某些事情,以便他们可以对此做些什么.很多事情.

我会说这是典型的Observer模式+ ViewPager.OnPageChangeListener().

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

public interface FragmentInterface {
    void onBecameVisible();
}
Run Code Online (Sandbox Code Playgroud)

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

public Fragment YourFragment implements FragmentInterface {
Run Code Online (Sandbox Code Playgroud)

然后你有一个:

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

谁打电话给BecameVisible?

您的Activity中的ViewPager.OnPageChangeListener():

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

所以现在Activity可以可靠地告诉Fragment它已变得可见.

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

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

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

我希望这篇冗长的文章能让您了解ViewPager对您的片段所做的工作.如果您有更具体的问题,或者我没有真正帮助过您,请告诉我们:)

关于我之前提到的BUG,请看一下这篇文章,它讲述了FragmentStatePagerAdapter处理片段恢复的方式中的错误(以及修复).


Pas*_*llo -1

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