若是你问我Android系统框架里哪三个问题最复杂,那么Fragment的生命周期确定会成为其中的一个。android
Android Framwork开发人员中的传奇人物Dianne Hackborn在2010年将Fragment引入了Android,他在提交信息中写道:安全
Author: Dianne Hackborn
Date: Thu Apr 15 14:45:25 2010 -0700
Introducing Fragment.
Basic implementation of an API for organizing a single activity into separate, discrete pieces. Currently supports adding and removing fragments, and performing basic lifecycle callbacks on them.bash
“将单一的Activity拆分红多个独立的部件”的想法很是好。 然而,从今天Fragment的的实际使用效果来看,这一API的实现和演变并不理想。app
截至目前,Fragment是Android系统框架里最具备争议的Android API之一。 许多专业的Android开发人员甚至都不会在他们的项目中使用Fragments。 不是由于他们不了解Fragment的好处,而是由于他们清楚地看到了Fragment的缺点和局限性。框架
若是你认真的看过下面这个流程图的话,你就知道我并无夸大Fragment生命周期的复杂性。这很恐怖。ide
幸运的是,您在应用中使用Fragment的时候无需了解整个生命周期的转换过程。函数
在这篇文章中,我将介绍一些处理Fragment生命周期的方法,它隐藏了大部分的复杂细节。 我使用这种方法已经有好几年了,它确实有效。post
正如你即将看到的,当我在使用Fragments的时候,我会尽量地将它们从Activity的生命周期中解耦出来。可是,这并不能改变它们之间有许多类似之处的事实。学习
我已经写了一篇关于我是如何处理Activity的生命周期的文章:android应用开发者,大家真的了解Activity的生命周期吗?。 该文章收到了很是积极的反馈,并在不到一个月的时间内成为了我博客上最受欢迎的文章之一。优化
正如我所说的,Activity和Fragment的生命周期在不少方面都是类似的。 因此,在这篇文章中,我会在他们之间进行不少类比。可是,我不想作重复的工做。 所以,我假设你已经阅读过有关Activity生命周期的文章。
我处理Fragment生命周期的方法旨在实现两个目标:
使用相似于处理Activity的生命周期的方法来处理Fragment的生命周期,能大幅下降应用程序的总体复杂性。 开发人员只须要学习一种方法,而不是两种不一样的方法。 这意味着开发过程当中的工做量会减小,维护会更容易,新团队成员熟悉项目的速度会更快。 我也彻底肯定这样作能够下降产生bug的风险,尽管这只是我我的的一种主观想法。
经过实现上述两个目标,我大大下降了与Fragment相关的问题的复杂性,从而使它更具吸引力。
请记住,您无权访问Activity的构造函数,所以您没法经过它来将依赖注入到Activity中。好消息是:Fragment有一个公开的构造函数,咱们甚至能够定义更多的构造函数。 坏消息是:这样作会致使严重的bug,因此咱们不能这样作。
当Activity被强制销毁,以后又被自动恢复的时候,Android系统会在这一过程当中销毁并从新建立Fragment。 从新建立的机制是经过使用反射的方法来调用Fragment的无参构造函数来实现的。 所以,若是您是使用带参数的构造函数来实例化Fragment,并在其中将依赖的对象传递给它,那么在保存和恢复后,全部这些依赖的对象都将被设置为null。
所以,就像Activity同样,您须要使用onCreate(Bundle)方法做为构造函数的替代者。 Fragment中依赖对象的注入和初始化就发生在这里。
可是,与Activity的onCreate(Bundle)方法不一样的是, 您不得在Fragment的onCreate(Bundle)方法中执行任何与Android View相关的操做。 这个很是重要,其缘由将在下一节中详细阐述。
总而言之,Fragment的onCreate(Bundle)
方法的基本的处理逻辑以下所示:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getInjector().inject(this); // inject dependencies
if (savedInstanceState != null) {
mWelcomeDialogHasAlreadyBeenShown = savedInstanceState.getBoolean(SAVED_STATE_WELCOME_DIALOG_SHOWN);
}
mActivityAsListener = (ActivityAsListener) requireActivity();
}
复制代码
还有一点忘了说 - 将Activity转换为listener也发生在onCreate(Bundle)中。 若是你喜欢这种方式的话,这将比在onAttach(Context)中抛出一个通用的ClassCastException要有意义的多。
这个方法是Fragment独有的,这是它与Activity生命周期最显着的区别。
但它也是Fragment相关问题的“万恶之源”。 稍后我会讨论它的“邪恶”,但请记住,若是您使用Fragment,最好不要低估View onCreateView(LayoutInflater,ViewGroup,Bundle)的复杂性。
那么,故事是什么呢?
Activity在生命周期的转换过程当中都只有同一个View hierarchy。 你在Activity的onCreate(Bundle)中初始化这个View hierarchy,而后它就会一直存在于Activity的整个生命周期,直到Activity被垃圾收集器回收为止。 您能够手动更改Activity的View hierarchy的组成,Android系统是不会为您作任何事情的。
然而,Fragment在其生命周期中能够存在有多个View hierarchy,由Android系统决定什么时候进行替换。
换句话说,你能够在程序运行的时候动态改变Fragment的View hierarchy,如今你应该清楚为何不能在Fragment的onCreate(Bundle)中操做View了吧。 onCreate(Bundle)方法在Fragment被Attach到Activity后仅被调用一次,它没法支持Fragment的View hierarchy的动态化。
每次须要建立新的View hierarchy的时候,Android系统都会调用onCreateView(LayoutInflater, ViewGroup, Bundle)方法。 您的工做是建立View hierarchy并将其初始化为正确的状态,而后将它做为该方法的返回值,以后它就会被Android系统接管。
重写这个方法的主要原则是:Fragment中全部持有与View hierarchy相关的对象的引用的成员变量,必须在View onCreateView(LayoutInflater,ViewGroup,Bundle)中进行初始化。 换句话说,若是Fragment的成员变量持有View或者相关对象的引用,请确保在此方法中初始化这些成员变量,这很是重要。
总而言之,Fragment的View onCreateView(LayoutInflater, ViewGroup, Bundle)
方法的基本的处理逻辑以下所示:
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.some_layout, container, false);
mSomeView = rootView.findViewById(R.id.some_view); // must assign all Fragment's fields which are Views mLstSomeList = rootView.findViewById(R.id.lst_some_list); // must assign all Fragment's fields which are Views
mAdapter = new SomeAdapter(getContext()); // must assign all Fragment's fields related to View hierarchy mLstSomeList.setAdapter(mAdapter); return rootView; } 复制代码
这个方法的另外一个有趣的地方是它接收了一个参数:保存状态的Bundle。 老实说,我以为这样很麻烦。Android系统框架开发的人员彷佛本身也不肯定应该在哪里恢复这些状态,因此他们将这个Bundle参数注入了到这个方法里,让咱们本身去弄清楚。
不要在这个方法里恢复状态。在后面介绍onSaveInstanceState(Bundle)方法时我会解释不要这样作的缘由。
用户Boza_s6在Reddit上提交了他(她)对这篇文章的反馈,咱们进行了一次很是有趣的讨论。 问题的焦点在于当在Fragment里使用列表和适配器的时候,个人方法是否会致使内存泄漏。根据上面的讨论,我想这个问题的答案是清楚的。
若是您遵循我在本文中分享的原则,那么就不存在内存泄漏的风险。事实上,我使用这种方法的部分缘由就是为了减轻Fragment内存泄漏的内在风险。
个人原则是Fragment里每一个与View hierarchy相关的成员变量都必须在此方法中初始化, 这包括列表适配器,用户交互事件的监听器等。保持Fragment里的代码可维护性的惟一方法是确保此方法在从新建立整个View hierarchy的时候会从新初始化Fragment里与之相关的成员变量。
Fragment的这个方法与Activity的onStart()方法具备彻底相同的职责和指导原则。 你能够阅读我以前关于Activity的生命周期的文章,android应用开发者,大家真的了解Activity的生命周期吗?。
总而言之,Fragment的onStart()
方法的基本的处理逻辑以下所示:
@Override
public void onStart() {
super.onStart();
mSomeView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
handleOnSomeViewClick();
}
});
mFirstDependency.registerListener(this);
switch (mSecondDependency.getState()) {
case SecondDependency.State.STATE_1:
updateUiAccordingToState1();
break;
case SecondDependency.State.STATE_2:
updateUiAccordingToState2();
break;
case SecondDependency.State.STATE_3:
updateUiAccordingToState3();
break;
}
if (mWelcomeDialogHasAlreadyBeenShown) {
mFirstDependency.intiateAsyncFunctionalFlowAndNotify();
} else {
showWelcomeDialog();
mWelcomeDialogHasAlreadyBeenShown = true;
}
}
复制代码
如您所见,这个方法里包含了Fragment的大部分功能逻辑。 保持onStart()方法在Activity和Fragment里的一致性具备不少好处。
这个方法的处理逻辑与Activity的onResume()方法相同。
这个方法的处理逻辑与Activity的onPause()方法相同。
这个方法的处理逻辑一样与Activity的onStop()方法相同。其基本的处理逻辑以下所示:
@Override
public void onStop() {
super.onStop();
mSomeView.setOnClickListener(null);
mFirstDependency.unregisterListener(this);
}
复制代码
这里有一行有趣而又使人惊讶的代码:mSomeView.setOnClickListener(null)。 我以前已经解释过了为何你可能要在Activity的onStop()注销点击事件的监听器,因此这里我不会再重复。
不过,我想借此机会回答另外一个问题:注销点击事件的监听器是否是必需的?
据我所知,绝大多数的Android应用程序都不会这样作,而且仍然运行良好。 因此,我认为这不是强制性的。可是若是你不这样作,你的app可能会遇到必定数量的bug和崩溃。
绝大多数状况下,您都不该该重写此方法。 我想有些读者会对此感到惊讶,但我真的是这么想的。
正如我前面所说的,你必须在onCreateView(LayoutInflater,ViewGroup,Bundle)里初始化Fragment里全部持有View或者相关对象的引用的成员变量。这个要求源自于这样一个事实:Fragment的View hierarchy能够被从新建立,因此全部未在该方法中被初始化的持有View的引用对象都将被置为null。这样作很是重要,不然你的app可能会遇到一些很是使人讨厌的bug和崩溃。
若是你这样作了,Fragment将一直持有对这些View的强引用,直到下一次调用View onCreateView(LayoutInflater,ViewGroup,Bundle)方法或者整个Fragment被销毁。
如今有一种普遍的建议是,您应该在onDestroyView()方法里将全部前面提到的成员变量设置为Null。 目的是尽快释放这些引用以容许垃圾收集器在onDestroyView()返回后当即回收它们,这样就能更快地释放与这些View相关的内存空间。
上面的解释虽然听起来很合理,但这是过早优化的经典案例。 在绝大多数状况下,你并不须要这种优化。所以,你没有必要去重写onDestroyView()的方法,这只会使得原本已经很复杂的处理Fragment生命周期的逻辑更复杂。
因此,你不须要重写onDestroyView()方法。
像Activity同样,您不须要在Fragment中重写此方法。
这个方法的基本的处理逻辑以下所示:
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(SAVED_STATE_WELCOME_DIALOG_SHOWN, mWelcomeDialogHasAlreadyBeenShown);
}
复制代码
我但愿你不要被这个方法的表面的简单性所误导。 保存和恢复流程的错误处理是Android应用程序中bug和崩溃的主要缘由之一。 在以前关于Activity生命周期的文章中我花了很大的篇幅来讨论这个方法,这不是巧合。
所以,在Activity中处理保存和恢复状态的过程很麻烦。你可能会认为Fragment不会比这更糟糕。然而,使人惊讶的是,Fragment更糟糕。
2011年2月Dianne Hackborn在Javadoc里介绍了这个方法,其中包含一些使人可怕的描述:
这个方法对应于Activity的onSaveInstanceState(Bundle)方法,因此对Activity的onSaveInstanceState(Bundle)的大多数讨论也适用于此。但请注意:此方法可能在onDestroy()以前的任什么时候候被调用。在许多状况下,Fragment可能会被销毁(例如,当Fragment被放置在回退栈上而没有UI显示时),可是它的状态不会被保存,除非其所属的Activity确实须要保存其状态。
若是这个“注释”不能令你感到惊讶的话,那么说明你尚未深刻的理解Activity和Fragment生命周期。
根据官方文件:这个方法可能在onDestroy()以前的任什么时候候调用。 这里有两个主要问题:
正如Dianne说的那样,与Fragment相关联的View hierarchy能够被销毁而不实际保存其状态。 所以,若是您想在View onCreateView(LayoutInflater,ViewGroup,Bundle)中恢复Fragment的状态,那么您将冒着覆盖最新状态的风险。这是一个很是严重的错误, 这正是我告诉你只能在onCreate(Bundle)中恢复状态的缘由。
若是在onDestroy()以前的任什么时候候均可以调用onSaveInstanceState(Bundle),那么您没法保证什么时候才能安全地更改Fragment的状态(例如替换嵌套的Fragments)。
我不认为Dianne Hackborn对这个方法的描述是准确的。事实上,我认为Dianne Hackborn在2011年写这篇文章的时候就已经犯了一个错误。“任什么时候候”的概念意味着某种不肯定性或随机性,我认为它历来就不存在。最有可能的是,只有几个因素影响了这种行为,Dianne决定不列出它们,由于她认为它们不够重要。
若是真是这样的话,那么她显然是错的。
若是这个描述在当时是正确的,那么就说明设计这个框架的Google开发人员并不知道这样的行为会致使上面列出的两个问题。特别是,它意味着包含已保存状态的Bundle从未被传入View onCreateView(LayoutInflater,ViewGroup,Bundle)方法。
切勿使用retained的Fragment。你不须要它们。
若是你这样作了的话,请记住它改变了Fragment的生命周期。 那么本文中所描述的一切内容都是无效的。
正如你所看到的,Fragment确实很复杂,我甚至会说很是复杂。
Fragment最大的问题是它的View hierarchy能够独立于Fragment对象自己被销毁和从新建立,若是不是这样的话,Fragment的生命周期几乎与Activity的生命周期如出一辙。
形成这种复杂性的缘由是什么?显然我不知道答案,我只能根据个人理解力去进行推测。
我认为引入这种机制是为了优化内存的消耗。当Fragment不可见的时候,销毁Fragment的View hierarchy容许释放一些内存。 但另外一个问题是:Fragment实现了对状态保存和恢复流程的支持,Google为何要这样作?
我不知道。
然而,我所知道的是,FragmentStatePagerAdapter并非采用这种机制去保存和恢复Fragments的状态。
就我而言,这整个机制是一种过早的优化。它没有任何用处,反而会使得Fragment使用更复杂。
比较讽刺的一点在于,Google开发人员彷佛本身都不了解Fragment的生命周期。
Google发布了LiveData Architecture Component,可是存在一个严重漏洞。 若是有一位开发人员仔细的研究过Fragment,那么他们就会真正理解Fragment的生命周期,在设计阶段他们就会发现这个问题。
Google花了几个月的时间来修复这个bug。最后,在Google IO 18期间,他们宣布bug已修复。 解决方法是为Fragment的View hierarchy引入了另外一种生命周期。
所以,若是您使用的是LiveData组件,如今您须要记住Fragment对象有两个不一样的生命周期。这让我很是难过。
好的,让咱们结束这篇文章。
Fragment真是一团糟。惟一比Fragment更糟糕的是他们的官方文档。 Google开发人员并无彻底理解Fragment的生命周期,而且会继续增长它的复杂性。
说了这么多,其实我一直在使用Fragment,并在未来继续使用它们。 与“一个界面一个Activity”的方法相比,Fragment能够提供更好的用户体验。
为了在使用Fragment的同时并保持个人理智,我正在使用本文所描述的处理Fragment生命周期的方法。它可能不包含全部的情形,但在过去的几年里它对我颇有帮助。
这种方法有两个好处:它使得处理Fragment的生命周期很是相似于Activity的生命周期而且独立于它。您可能已经注意到,我在这篇文章中并无提到Activity的状态。 只要我坚持使用这种方法,我并不在意Activity会发生什么,我甚至不用关心这个Fragment是不是嵌套的。
所以,我重写了Fragment里最少数量的方法,并且互相之间没有依赖关系。 这使得Fragment的复杂性对我来讲是可管理的。
我相信本身在写这篇文章时错过了一些重要的观点。 所以,若是您有任何补充或者想要指出本文中的任何缺陷,请随时在下面的评论中提出来。