本系列是介绍Fragment相关的一些内容,由浅入深。
1. 深入理解Fragment进阶系列(一):生命周期详解
Fragment最初出现在Android3.0,用来解决大小屏设备适配问题。Fragment不能单独存在,必须依附于Activity(Android4.2起Fragment也可以嵌套Fragment)。Fragment有一套自己的生命周期,相应的也是依赖Activity的生命周期( 例如,当 Activity 暂停时,其中的所有Fragment也会暂停;当 Activity 被销毁时,所有Fragment也会被销毁)。一个Activity可以使用多个Fragment,一个Fragment也可以附属在多个Activity上,十分灵活。
对于低版本可以使用V4库,需要注意的是使用V4的Fragment,Activity要继承FragmentActivity,获取FragmentManger要使用getSupportFragmentManger()。
既然Fragment拥有自己的生命周期,本文就来详细的讲一讲相关的内容。主要包括以下几个方面。
说起Fragment的生命周期,就不得不放一张十分经典的官方图。
emmmm… 乍一看是不是有一张似曾相识的感觉,Fragment的生命周期大体上和Activity类似,我们先对每个回调方法都具体的介绍一下。
Fragment的生命周期是和其关联的Activity有关,在这里为了方便叙述之后说的Activity都特指Fragment相关联的Activity
onAttach(Context context):在Fragment和Activity关联上的时候调用,且仅调用一次。在该回调中我们可以将context转化为Activity保存下来,从而避免后期频繁调用getAtivity()获取Activity的局面,避免了在某些情况下getAtivity()为空的异常(Activity和Fragment分离的情况下)。同时也可以在该回调中将传入的Arguments提取并解析,在这里强烈推荐通过setArguments给Fragment传参数,因为在应用被系统回收时Fragment不会保存相关属性,具体之后会讲解。
onCreate:在最初创建Fragment的时候会调用,和Activity的onCreate类似。
View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState):在准备绘制Fragment界面时调用,返回值为Fragment要绘制布局的根视图,当然也可以返回null。注意使用inflater构建View时一定要将attachToRoot指明false,因为Fragment会自动将视图添加到container中,attachToRoot为true会重复添加报错。onCreateView并不是一定会被调用,当添加的是没有界面的Fragment就不会调用,比如调用FragmentTransaction的 add(Fragment fragment, String tag)方法。
onActivityCreated :在Activity的onCreated执行完时会调用。
onStart() :Fragment对用户可见的时候调用,前提是Activity已经started。
onResume():Fragment和用户之前可交互时会调用,前提是Activity已经resumed。
onPause():Fragment和用户之前不可交互时会调用。
onStop():Fragment不可见时会调用。
onDestroyView():在移除Fragment相关视图层级时调用。
onDestroy():最终清楚Fragment状态时会调用。
onDetach():Fragment和Activity解除关联时调用。
图中有一条从onDestroyView指向onCreatedView的线,意思是Fragment从back栈(存放Fragment的栈)回到前台时经历的生命周期。
将Fragment生命周期中每个回调单独讲完之后,下面就来看看Fragment生命周期和Activity生命周期之前的联系,这里也有一张十分经典的官方图。
可以十分明了的看到,当Activity处于不同生命周期时Fragment生命周期的流程。需要关注一下两者生命周期顺序问题,其中onCreate、onStart、onResume都是Activity先调用之后才是Fragment,onPause、onStop、onDestroy(在Fragment中是onDetach),是先Fragment调用之后才是Activity。其他的就不展开说了,图十分明了。
添加Fragment可以分为静态添加和动态添加两大类。静态添加是在XML中直接添加Fragment,简单方便,缺点是添加之后不能在删除。动态添加是在代码中FragmentManger使用一系列FragmentTransaction事务操作动态控制,灵活多变。一般都是使用动态添加,下面就讲讲动态添加有关的生命周期。
add:onAttach->onCreate->onCreateView->onActivityCreated->onStart->onResume
remove:onPause->onStop->onDestroyView->onDestroy->onDetach
show:onHiddenChanged(boolean hidden) hidden为false
hide:onHiddenChanged(boolean hidden) hidden为true
replace:旧Fragment的remove生命周期->新Fragment的add生命周期
replace+addToBackStack:onPause->onStop->onDestroyView->新Fragment的add生命周期
之后点击back:新Fragment的remove->onCreateView->onViewCreated->onActivityCreated->onStart->onResume 就是第一张图的线
detach:onPause->onStop->onDestroyView 可以看到只是视图被移除,Fragment关联状态还是不变,还是处于FragmentManger的管理下
FragmentTransaction.attach(Fragment var1):onStart->onResume->onCreateView
注意:Fragment的show和hide仅仅是将Fragment视图设置为是否可见,不会调用任何生命周期。该Fragment的生命周期还是会随着Activity的生命周期变化而变化,例如FragmentA hide、FragmentB show,点击Home A和B都会onPause->onStop
应用被回收一般都是后台应用,所以生命周期是从onDestroyView开始
单独一个Fragment
onDestroyView->onDestroy->onDetach->add生命周期
Fragment A hide,Fragment B show
A.onDestroyView->A.onDestroy->A.onDetach->B.onDestroyView->B.onDestroy->B.onDetach->A.onAttach->A.onCreate->B.onAttach->B.onCreate->A.onCreateView->A.onActivityCreated->B.onCreateView->B.onActivityCreated->A.onStart->B.onStart->A.onResume->B.onResume
为了防止在系统回收应用情况下,再次进不出错,强烈建议大家
1. 使用setArguments(Bundle bundle)方法传递参数。对于常规变量想必大家都已经十分熟练了,就不细说了。这里主要强调View变量和接口变量,View变量可以通过传入View的id,之后再通过id获取view的方法来实现。接口可以通过Activity实现,Fragment强转Activity实现。
2. addFragment之前先通过findFragmentById判断是否添加过避免重复添加,如使用FragmentAdapter可以在onSaveInstanceState存储相应Fragment.getTag
ViewPager+Fragment已经是比较常见的组合,一般搭配ViewPager的FragmentPagerAdapter或FragmentStatePagerAdapter使用。不过ViewPager为了防止滑动出现卡顿,有一个缓存机制,默认情况下ViewPager会创建并缓存当前页面左右两边的页面(如Fragment)。此时左右两个Fragment都会执行从onAttach->….->onResume的生命周期,明明Fragment没有显示却已经到onResume了,在某些情况下会出现问题。比如数据的加载时机、判断Fragment是否可见等。
可以通过ViewPager的setOffscreenPageLimit(int limit)来设置ViewPager缓存个数,最小是1个即便我们设置为0,源码如下。
public void setOffscreenPageLimit(int limit) {
if (limit < DEFAULT_OFFSCREEN_PAGES) {
// private static final int DEFAULT_OFFSCREEN_PAGES = 1;
Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to "
+ DEFAULT_OFFSCREEN_PAGES);
limit = DEFAULT_OFFSCREEN_PAGES;
}
if (limit != mOffscreenPageLimit) {
mOffscreenPageLimit = limit;
populate();
}
}
如果不对数据加载时机做处理,使用ViewPager默认会将左右两个Fragment的数据也加载,当数据加载比较消耗资源会影响性能。我们可以对加载时机做调整使用懒加载,具体就是在Fragment真正可见时才加载数据。
在Fragment切换时候会调用setUserVisibleHint(boolean isVisibleToUser),isVisibleToUser表示是否对用户可见。setUserVisibleHint不单单在Fragment切换时调用,在onAttach之前都会调用一次此时isVisibleToUser为false,在onCreateView之前会调用一次此时isVisibleToUser的值为当前Fragment是否可见,之后就是在Fragment切换的时候会调用。
因为setUserVisibleHint会在onCreateView之前调用,如果数据加载涉及到view相关的操作别忘了设置一个变量来判断一下视图是否已经创建好。
判断Fragment是否可见最常见就是用在埋点上了,不同情况下判断方法并不相同,主要分为两大类。
先上一段在Fragment可见的埋点代码
@Override
public void onResume() {
super.onResume();
// 注意这个判断 isHidden()是当前Fragment是否隐藏
if (!isHidden()) {
reportPageShow();
}
}
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
if (!hidden) {
reportPageShow();
}
}
在添加Fragment或者从Fragment和其他Activity相互跳转的时候,都会调用onResume和onStop。如果是show或在hide Fragment的时候会调用onHiddenChanged。
不知道大家有没有发现在onResume的时候判断了一下当前Fragment是否被隐藏了,这个判断是有说法的。之前说过show和hide仅仅是将Fragment视图设置为是否可见,不会影响其生命周期。还原场景,如果Fragment A hide,Fragment B show,然后Activity从后台到前台是A和B的onResume都会被调用!所以要在onResume判断一下当前Fragment是否被隐藏,当然如果没有用到show和hide,只使用简单的onResume和onStop也没问题。
之前说过ViewPager为了防止滑动出现卡顿,有一个缓存机制,默认情况下ViewPager会创建并缓存当前页面左右两边的页面(如Fragment)。左右两个Fragment都会执行从onAttach->….->onResume的生命周期,此时Fragment的生命周期已经不可靠。不过在Fragment切换的时候会调用setUserVisibleHint(boolean isVisibleToUser),isVisibleToUser表示是否对用户可见。因此可以在setUserVisibleHint进行Fragment是否可见的判断。
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) {
reportPageShow();
}
}
看完上面的内容想必对Fragment的生命周期了解有所加深,下面一篇将会从源码来分析一下Fragment相关的内容