Android Tech And Thoughts.

All About Fragment

Word count: 5.7kReading time: 24 min
2019/11/29 Share

Fragment,简称碎片,Android 3.0(11)提出,为了兼容低版本, support-v4 库中也开发了一套 fragment API,最低兼容 Android 1.6

Fragment 定义

A Fragment represents a behavior or a portion of user interface in an Activity. You can combine multiple fragments in a single activity to build a multi-pane UI and reuse a fragment in multiple activities. You can think of a fragment as a modular section of an activity, which has its own lifecycle, receives its own input events, and which you can add or remove while the activity is running.

总结下:

1). 模块化(Modularity) :不用所有代码堆在Activity里面

2). 可重用(reusebility)

3). 可适配(更好的适应UI而产生不同的组合方案)

Fragment核心类

  • Fragment : 基类
  • Fragmentmanager:管理和维护 Fragment,这是一个抽象类,具体的实现是 FragmentManagerImpl
  • FragmentTransaction : 对 fragment 的添加、删除 等操作都需要通过事务方式进行,它也是一个抽象类,具体的实现类是 BackStackRecord

基本使用

1
public class Fragment1 extends Fragment{
2
    private static final String ARG_FIRST = "ARG_FIRST";
3
    private String mParam;
4
    private Activity mActivity;
5
6
    @Nullable
7
    @Override
8
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, 		@Nullable Bundle savedInstanceState) {
9
        View v = inflater.inflate(R.layout.fragment1,container,false);
10
        return v;
11
    }
12
13
    @Override
14
    public void onAttach(@NonNull Context context) {
15
        super.onAttach(context);
16
        mParam = getArguments().getString(ARG_FIRST,"");
17
    }
18
19
    public static Fragment1 newInstance(String str){
20
        Fragment1 fragment1 = new Fragment1();
21
        Bundle bundle = new Bundle();
22
        bundle.putString(ARG_FIRST,str);
23
        fragment1.setArguments(bundle);
24
        return fragment1;
25
    }
26
}

Fragment有很多可以复写的method

其中最常用的就是 onCreateView(),该方法返回 Fragment 的 UI 布局;注意 attachToRoot 为 false,因为在 Fragment 的内部实现中,会把该布局添加到 container 中,如果设为 true ,就会重复两次添加 ,则会抛出 IllegalStateException 异常

如果在创建 Fragment 时需要传入参数,可以有两种方式,他们也都有相似的地方,那就是最终都是通过 setArguments 来传递和保存参数

1
Fragment fragment = new Fragment1();
2
fragment.setArguments(bundle);
1
Fragment fragment = Fragment1.newInstance(str);

我们可以在 Fragment 的 onAttach() 中通过 getArguments() 获得传进来的参数,并在之后使用这些参数。

非常不建议在构造函数中,通过 new Fragment1(str) 的方式传入参数,因为这样的数据是无法恢复的 ,如果程序因为内存原因被杀死,就会导致系统再次还原的时候,没有数据可以恢复。

如果需要获取 Activity 对象,不建议使用 getActivity() 方法,而是在 onAttach 中将 Context 对象强转为 Activity 对象(是不是这样就避免了出现多个引用,程序的效率也会提升)

Fragment的添加

在Activity中添加Fragment的方法有两种:

1.静态添加:在xml中添加(缺点是一旦运行则不能删除)

2.动态添加:运行时添加,这种比较灵活和常见

静态添加的方式其实就是通过 android:name 属性来指定 fragment控件关联的 fragment,我们主要来看看

动态添加

首先需要有一个容器,一般选择 FrameLayout

1
<FrameLayout
2
     .../>

然后在 onCreate() 中,通过事务的方式将 Fragment 添加进 Activity

1
if(bundle==null){
2
    getSupportFragmentManager().beginTransaction()
3
        .add(R.id.container,Fragment1.newInstance("hello,world"),"f1")
4
        .commit();                                   
5
}

需要注意几点:

  • 使用 support包的 Fragment, 必须使用 getSupportFragmentManager() 获取 FragmentManager

  • add()是对 Fragment 众多操作中的一种,还有 remove()、replace() 等,第一个参数是 根容器 的id,第二个参数是 Fragment 对象;第三个参数是 fragment 的 tag 名,指定 tag 的好处是后续我们可以通过 下面的方式从 FragmentManager 中查找 Fragment 对象

    1
    Fragment1 fragment1 = getSupportFragmentManager().findFragmentByTag("f1");
  • 在一次事务中,可以做多个操作,比如同时 add().remove().replace()

  • commit() 操作是异步的,内部通过 mManager.enqueueAction() 加入处理队列。对应的同步方法为 commitNow()

    尽量使用 commit

  • addToBackStack(“frame”)是可选的,FragmentManager拥有回退栈(BackStack),如果添加了该语句。就把该事务加入回退栈,当用户点击返回按钮时,会回退该事务(比如事务是add(frag1),那么回退操作就是 remove(frag1) );如果没有添加该语句,用户点击返回按钮会直接销毁 Activity

  • Fragment 重叠问题

    其实这个问题并不复杂,Activity在异常回收或者横竖屏的情况下,onSavedInsatnce 方法会保存当前的 fragment 实例,待到重新加载之后,它是独立于 FrgamentManager 的,即它不受 FragmentManager 管理了,它会直接显示在屏幕中,而当我们点击下方的 tab 按钮时,就相当于两个 Fragment 都显示了,自然会出现重影(如果点击的tab时之前的tab,表面上不重影了,其实只是他们一样,所以不会出现错位)

FragmentTransaction

事务(Transaction): 是并发控制的单位,是用户定义的一个操作序列,这些操作要么都做,要么都不做,是一个不可分割的工作单位。常见于数据库中,也有其他的例子。事务通常是以 begin transaction 开始,以 commit 或者 rollback结束。

1)commit表示提交,即提交事务的所有操作。

2)rollback 表示回滚,即在事务运行的过程中发生了某种故障,事务不能继续进行下去,系统将事务中所有的已进行的操作全部撤销,回滚到事务开始前的状态。

add、remove、hide、show等操作都是不起实质作用的,仅仅设置一些参数和标记位,只有当 commit 执行后,才会执行相应的操作,一次事务只能commit一次。下次还需要操作的时候,请新建新的事物 FragmentTransaction

关于 replace 方式和 add 方式的差别:

1
transaction.replace(viewId, fragmentA);

等效于

1
transaction.add(viewId, fragmentA);
2
transaction.show(fragmentA);
3
transaction.hide(preFragment);
4
transaction.remove(preFragment);

相信你已经理解了他们间的关系

采用replace的话,每次显示fragment都会重新创建(在ViewPager中会保留缓存);而采用 add…show 方式,会重用已有的实例化的 fragment

addToBackStack()

1
transaction.replace(viewId,fragmentA);
2
transaction.addToBackStace();
3
transaction.commit();

当我们调用了 addToBackStack 之后,按 back 键:

1.AFragment消失(销毁)

2.preFragment又出现了(重建)

addToBackStack() 记录的是一个 transaction 的操作记录,当你按 back 键时,相当于逆向操作了一波,对比之前的操作

1
transaction.add(viewId,preFragment);
2
transaction.show(preFragment);
3
transaction.hide(fragmentA);
4
transaction.remove(fragment);

假设 transaction 做出了 n 个操作 fragment 的行为,一旦按 back 键,会读取这 n 个记录,并按反方向调用, add 变成 remove, hide 变成 show(反之一样)

注意下面这种情况

新建了三个 fragment,并在每一个fragment内部设置一个按钮分别跳到下一个fragment(replace方式)

fragment1 —-> fragment2 —->fragment3

他们对应的事务分别为 tansaction_12,transaction_23

1) transcation_12.addToBackStack.commit() , tansaction_23不处理

2) 连续点击 back后,先返回fragment1(和fragment3产生了重影),然后退出了 Activity

其余的情况基本和预料的一样

对于 addToBackStack 的理解,接下来,我会更深入地了解它的工作原理

BackStackRecord

在源碼中,FragmentTransaction 是一個 abstract class ,代表著抽象的事物 ;BackStackRecord其实就是 FragmentTransaction 的一个具體的實現类。

1
class BackStackRecord extends FragmentTransaction implementions FragmentManager.BackStackEntry,Runnable
2
{}

从它的定义我们就可以看出,它有三重含义:

1)它是事务:继承自 FragmentTransaction,保存了整个事务的全部操作轨迹

2)实现了 BackStackEntry,作为回退栈的元素,也正是因为该类拥有事务全部的操作轨迹,因此在 popBackStack() 时能回退整个事务

3)它是一个可执行的 Runnable,被放入 FragmentManager 的执行队列,等待被执行

先来看第一层

1
public FragmentTransaction beginTransaction(){
2
     return new BackStackRecord(this);
3
}

BackStackRecord 类包含了一次事务的整个操作轨迹,其以链表的形式存在,链表的元素是 Op 类,表示 operation,即操作

1
static final class Op{
2
    Op next; // 双向链表的前置和后置节点
3
    Op prev;                
4
    int cmd;	// 操作类型,add / remove / hide / show /replace
5
    Fragment fragment; // 对哪个fragment对象做操作
6
}

我们来看下具体的操作,加深对其的理解:

FragmentTransactionRecord.java

1
// 鏈表是存在于事務中的,下面兩個屬性就是 record 類裏面的
2
Op mHead;
3
Op mTail;
4
final FragmentManagerImpl mManager;
5
...
6
public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) {
7
   doAddOp(containerViewId, fragment, tag, OP_ADD);
8
   return this;
9
}
10
11
---------
12
private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
13
    fragment.mTag = tag;  //设置fragment的tag
14
    fragment.mContainerId = fragment.mFragmentId = containerViewId;  //设置fragment的容器id
15
    ==Op op = new Op();==
16
    op.cmd = opcmd; // 这里的 opcmd就是传过来的 OP_ADD
17
    op.fragment = fragment;
18
    addOp(op);  // 将其加入链表
19
}
20
21
-------- 插入链表
22
void addOp(Op op) {
23
    if (mHead == null) {
24
        mHead = mTail = op;
25
    } else {
26
        op.prev = mTail;
27
        mTail.next = op;
28
        mTail = op;
29
    }
30
    mNumOp++;
31
}

==addToBackStack==做了什么

addToBackStack(“”)是将 mAddToBackStack 变量标记为 true,在 commit() 中会用到该变量;commit是异步的

1
BackStackRecord.java
2
-------------------------------------------------------------------------------------
3
public int commit() {
4
    return commitInternal(false);
5
}
6
....;
7
8
/**
9
* @allowStateLoss  這個 allowStateLoss 參數咱們暫且不看
10
* 主要關注屬性 mAddToBackStack .
11
*/
12
int commitInternal(boolean allowStateLoss) { 
13
    // 重複提交抛出異常
14
    if (mCommitted) throw new IllegalStateException("commit already called");
15
    .....
16
        
17
    mCommitted = true;
18
    if (mAddToBackStack) {  // mAddToBackStack 为 true
19
        mIndex = mManager.allocBackStackIndex(this); //將事務添加進迴退棧
20
    } else {
21
        mIndex = -1;
22
    }
23
    // 加入事件隊列 ,this就是runnable ,mManager維護著事務隊列
24
    mManager.enqueueAction(this, allowStateLoss);
25
    return mIndex;
26
}
27
28
29
FragmentManager.java
30
------------------------------------------------------------------------------------
31
    
32
// 将事物添加进入回退栈
33
public int allocBackStackIndex(BackStackRecord bse){
34
    // mBackStackIndices是FragmentManager类的变量 
35
    // ArrayList mBackStackIndices; 就是回退栈
36
    if(mBackStackIndices == null){ // 合理的设计,在需要的地方初始化
37
        mBackStackIndices = new ArrayList<BackStackRecord>();
38
    }
39
    int index = mBackStackIndices.size();
40
    mBackStackIndices.add(bse);  // 加入回退栈
41
    
42
    return index;
43
}
44
45
//将事物(以Runnable的形式)加入待执行队列
46
public void enqueueAction(Runnable action, boolean allowStateLoss) {
47
    if (mPendingActions == null) {  // 在需要时初始化
48
        mPendingActions = new ArrayList<Runnable>(); 
49
    }
50
    //加入待执行队列
51
    mPendingActions.add(action); 
52
    if (mPendingActions.size() == 1) {
53
        // mHost.getHandler获取到主线程的Handler,因此事物(runnable)是在主线程执行的 注①
54
        // 这一步的目的我还没搞明白
55
        mHost.getHandler().removeCallbacks(mExecCommit);
56
        // mExecCommit就是调用execPendingActions()执行待执行队列的事务
57
        // 这里也可以看出通过 post 异步处理这个任务,不过任务仍然是执行在主线程
58
        mHost.getHandler().post(mExecCommit);  
59
    }
60
}
61
62
// 这里的 mExecCommit 实际上就是一个任务啦,这个任务就是
63
Runnable mExecCommit = new Runnable() {
64
    @Override
65
    public void run() {
66
        // 执行ACTIONS
67
        execPendingActions();  
68
    }
69
};
70
71
72
// only call from main thread!
73
// 在主线程执行事务,涉及到生命周期和UI等
74
public boolean execPendingActions() {
75
	....
76
	boolean didSomething = false;
77
78
	while (true) {
79
		int numActions;
80
		
81
        // 核心代码
82
		synchronized (this) {
83
			if (mPendingActions == null || mPendingActions.size() == 0) {
84
				break;
85
			}
86
			
87
			numActions = mPendingActions.size();
88
			if (mTmpActions == null || mTmpActions.length < numActions) {
89
				mTmpActions = new Runnable[numActions];
90
			}
91
			mPendingActions.toArray(mTmpActions);
92
			mPendingActions.clear();
93
			mActivity.mHandler.removeCallbacks(mExecCommit);
94
		}
95
		
96
		mExecutingActions = true;
97
		for (int i=0; i<numActions; i++) {
98
			mTmpActions[i].run();
99
			mTmpActions[i] = null;
100
		}
101
		mExecutingActions = false;
102
		didSomething = true;
103
	}
104
	...
105
        
106
	return didSomething;
107
}

讓我們來理清一下幾個概念之間的關係

backStackrecord 里面的 mManager就是FragmentManager,它維護者一個事务隊列 mPendingActions (注意,不是操作列表),隊列中的每一個節點就是一個事務,事務即是 BackStackRecord(Transaction的實現類),每個事務内部維護著一個操作列表(即链表,每个节点都是一个 Op 对象,包含着前驱和后继指针,以及 cmd和 fragment引用)

与 addToBackStack() 对应的是 ==popBackStack()== ,有以下几种变种:

1
popBackStack()

将回退栈的栈顶弹出,并回退该事务

1
popBackStack(String name,int flg)

name 为 addToBackStack(String name) 的参数,通过 name可以找到回退栈的特定元素

flag 表示弹出方式(只弹出该元素以上的元素 || 弹出以上包括该元素在内的所有元素,弹出的意思包括回退这些事务)

popBackStack() 是异步执行的,是丢到主线程的 MessageQueue 执行

popBackStackImmediate() 是同步版本

LifeCyclers

先回顾一下 Activity的生命周期图

Activity生命周期

掌握Actiuvity和Fragment的几个状态更容易帮助理解生命周期函数(状态间的变换)。在Activity中,常态只有三种,分别是

1)Resumed //可以交互

2)Paused // 可见但不能交互

3)stoped // 不可见

其他状态是瞬态(created 和 started)

Fragment生命周期

Fragment生命周期与 Activity 生命周期的关系

fraglife.png

再来分析它的各种生命周期函数会简单很多

  • onAttach():Fragment和Activity相关联时调用。可以通过该方法获取Activity引用,还可以通过getArguments()获取参数。
  • onCreate():Fragment被创建时调用。它可不是在 new Fragment() 时调用,但是得思考下 new 和 create 的区别
  • onCreateView():创建Fragment的布局。
  • onActivityCreated():当Activity完成onCreate()时调用。
  • onStart():当Fragment可见时调用。
  • onResume():当Fragment可见且可交互时调用。
  • onPause():当Fragment不可交互但可见时调用。
  • onStop():当Fragment不可见时调用。
  • onDestroyView():当Fragment的UI从视图结构中移除时调用。
  • onDestroy():销毁Fragment时调用。
  • onDetach():当Fragment和Activity解除关联时调用。

FragmentTransaction与Fragment生命周期的关系:

从Fragment的结果来看,FragmentTransaction对Fragment的操作大抵分为两大类

1.显示: add() 、 show()、 attach()、replace()

2.隐藏:remove()、hide()、detach()

下面的操作都是基于commit了情况下哈,不commit的话,只是设置了一些标记和状态,并未真正地执行生命周期方法

1)add() : 会执行完整的生命周期方法, onAttach-onResume 。

会创建新的Fragment(onAttach),并且创建其视图(onCreateView),绑定到相应的 containerView上;如果连续地add了多个Fragment,那么他们都会被创建,并绑定在相应的 container 上,这时候可能会出现重影的现象哦,一般我们采用hide和show来避免

2)show() 和 hide(): 调用show() & hide()方法时,Fragment的生命周期方法并不会被执行

仅仅是FragmentView被显示或者隐藏。而且,尽管FragmentView被隐藏,但它在父布局中并未被detach,仍然是作为containerViewchildView存在着。

3)attach和detach: 不同于Fragment的 onAttach和onDetach,attach和detach会使得当前Fragment的生命周期彻底地执行一遍

  • 一旦一个Fragmentdetach(),它的onPause()-onDestroyView()周期都会被执行。
  • 调用attach()后,onCreateView()-onResume()周期也会被再次执行。

4)remove(): 对比add很容易得出结论,remove之后执行 onPause-onDetach

5)replace(): 使用replace()来添加Fragment的时候,第二次添加会导致第一个Fragment被销毁,即执行第二个FragmentonAttach()方法之前会先执行第一个FragmentonPause()-onDetach()方法,同时containerView会detach第一个FragmentView

6)replace() 和 addToBackStack() 连用的时候,生命周期会有所变化

1
private void replaceTofrag2() {
2
        getActivity().getSupportFragmentManager()
3
                .beginTransaction()
4
                .replace(R.id.fragment_container, new Fragment2())
5
                .addToBackStack("frag2")
6
                .commit();
7
    }
8
-----------------------------------------
9
 D/DAN: onAttach: Fragment_1
10
 D/DAN: onViewCreated: Fragment_1
11
 D/DAN: onAttach: Fragment_2
12
 D/DAN: onDestroyView: Fragment_1
13
 D/DAN: onViewCreated: Fragment_2

并不销毁 fragment1. 我猜想这可能是设计上的原因,设置了 addToBackStack 的事务,被回退的几率是很大的,为了避免过多的创建的消耗,故而不 destroy preFragment,而仅仅是 destroyView。

Pay Attention

当 replace 和 addToBackStack 连用的时候,情况和预想的不同:

1
private void replaceTofrag2() {
2
        getActivity().getSupportFragmentManager()
3
                .beginTransaction()
4
                .replace(R.id.fragment_container, Fragment2.newInstance("frag2"))
5
                .addToBackStack("frag2")
6
                .commit();
7
}
8
--------------------log----------------------------------
9
2019-11-23 01:50:50.192 15249-15249/com.example.fragmentapp D/DAN: onAttach: Fragment_1
10
2019-11-23 01:50:50.202 15249-15249/com.example.fragmentapp D/DAN: onViewCreated: Fragment_1
11
2019-11-23 01:50:54.743 15249-15249/com.example.fragmentapp D/DAN: onAttach: Fragment_2
12
2019-11-23 01:50:54.745 15249-15249/com.example.fragmentapp D/DAN: onDestroyView: Fragment_1
13
2019-11-23 01:50:54.755 15249-15249/com.example.fragmentapp D/DAN: onViewCreated: Fragment_2

当我们注释掉 addToBackStack 的时候

1
private void replaceTofrag2() {
2
        getActivity().getSupportFragmentManager()
3
                .beginTransaction()
4
                .replace(R.id.fragment_container, Fragment2.newInstance("frag2"))
5
                //.addToBackStack("frag2")
6
                .commit();
7
}
8
---------------------log--------------------------------
9
2019-11-23 01:52:03.182 15364-15364/com.example.fragmentapp D/DAN: onAttach: Fragment_1
10
2019-11-23 01:52:03.194 15364-15364/com.example.fragmentapp D/DAN: onViewCreated: Fragment_1
11
2019-11-23 01:52:05.162 15364-15364/com.example.fragmentapp D/DAN: onAttach: Fragment_2
12
2019-11-23 01:52:05.164 15364-15364/com.example.fragmentapp D/DAN: onDestroyView: Fragment_1
13
2019-11-23 01:52:05.167 15364-15364/com.example.fragmentapp D/DAN: onDestroy: Fragment_1
14
2019-11-23 01:52:05.167 15364-15364/com.example.fragmentapp D/DAN: onDetach: Fragment_1
15
2019-11-23 01:52:05.173 15364-15364/com.example.fragmentapp D/DAN: onViewCreated: Fragment_2

看的出来,是否使用 addTobackStack 会影响到 fragment 的生命周期

FragmentPagerAdapter 与 FragmentStatePagerAdapter

1)使用 FragmentPagerAdapter 时,ViewPager 中的所有 Fragment 实例常驻内存,当 Fragment 变得不可见时仅仅是视图结构的销毁(也就是从Resumed状态到stopped状态),即调用了 onDestroyView 方法。由于 FragmentPagerAdapter 内存消耗较大,所以适合少量静态页面的场景

2)使用 FragmentStatePagerAdapter 时,当 Fragment 变得不可见,不仅视图层次销毁,实例也被销毁,即调用了 onDestroyViewonDestroy 方法,仅仅保存 Fragment 状态。相比而言, FragmentStatePagerAdapter 内存占用较小,所以适合大量动态页面,比如我们常见的新闻列表类应用。

*ViewPager -> setPageOfferScreenLimit() *

为什么提到这个方法,是因为在结合Adapter和ViewPager的时候,两种 Adapter 都遵循预加载特性,这与 Adapter 无关,是ViewPager的特性。

1
public void setOffscreenPageLimit(int limit) {
2
    // 当设置 limit < Default(1) 的时候,设置无效,返回 default limit
3
    if (limit < DEFAULT_OFFSCREEN_PAGES) {
4
         Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to "
5
                 + DEFAULT_OFFSCREEN_PAGES);
6
         limit = DEFAULT_OFFSCREEN_PAGES;
7
     }
8
     // 只有当设置 limit > default 时,才会起到效果
9
     if (limit != mOffscreenPageLimit) {
10
         mOffscreenPageLimit = limit;
11
         populate();
12
     }
13
}

limit有两层含义:一是ViewPager会预加载 2n+1 页,另一层含义是 ViuewPager 会缓存 2n+1 页

setPageOffScreen(0) 是无效的,也就是说,ViewPager至少预加载左右的两个 View

接下来,我们分别来看两种 FragmentAdapter。用例子来详细说明一下:

Example:

1.分别创建四个Fragment : fragment1,fragment2,fragment3,fragment4

2.复写生命周期函数,并在super方法后面调用log打印日志

3.进行 frag1->frag2->frag3->frag4 三步切换,四种状态

观察生命周期函数的调用情况:

情况1:FragmentPagerAdapter (OffScreenlimit为默认的1)

1
D/DAN: onAttach: Fragment_1  // frag1和frag2都走完整的声明周期 onAttach->onResume
2
D/DAN: onAttach: Fragment_2
3
D/DAN: onCreateView: Fragment_1
4
D/DAN: onStart Fragment_1
5
D/DAN: onResume: Fragment_1
6
D/DAN: CreateView: Fragment_2
7
D/DAN: onStart Fragment_2
8
D/DAN: onResume: Fragment_2
9
10
--------------------------------[ frag1-->frag2 ]--------------------------
11
D/DAN: onAttach: Fragment_3				// frag3 走完整的生命周期 onAttach->onResume
12
D/DAN: onCreateView: Fragment_3
13
D/DAN: onStart Fragment_3
14
D/DAN: onResume: Fragment_3
15
16
--------------------------------[ frag2->frag3 ]-------------------------
17
D/DAN: onAttach: Fragment_4       // frag4走完整周期,frag1销毁视图结构 onPause->onDestroyView
18
D/DAN: onPause: Fragment_1
19
D/DAN: onStop Fragment_1
20
D/DAN: onDestroyView: Fragment_1
21
D/DAN: onCreateView: Fragment_4
22
D/DAN: onStart Fragment_4
23
D/DAN: onResume: Fragment_4
24
25
--------------------------------[ frag3->frag4 ]-------------------------
26
D/DAN: onPause: Fragment_2			//frag2 销毁视图结构 onPause->onDestroyView
27
D/DAN: onStop Fragment_2
28
D/DAN: onDestroyView: Fragment_2

情况2: FragmentStatePagerAdapter

1
D/DAN: onAttach: Fragment_1				  // frag1和frag2加载,执行完整的生命周期
2
D/DAN: onAttach: Fragment_2
3
D/DAN: onCreateView: Fragment_1
4
D/DAN: onStart Fragment_1
5
D/DAN: onResume: Fragment_1
6
D/DAN: CreateView: Fragment_2
7
D/DAN: onStart Fragment_2
8
D/DAN: onResume: Fragment_2
9
--------------------------------------[ fra1 -> fra2 ]----------------------------
10
D/DAN: onAttach: Fragment_3			     // frag3加载,执行完整的生命周期
11
D/DAN: onCreateView: Fragment_3
12
D/DAN: onStart Fragment_3
13
D/DAN: onResume: Fragment_3
14
--------------------------------------[ frag2-> frag3 ]---------------------------
15
D/DAN: onAttach: Fragment_4						// frag4加载,执行完整生命周期
16
D/DAN: onPause: Fragment_1						// frag1销毁,onPause->onDetach
17
D/DAN: onStop Fragment_1
18
D/DAN: onDestroyView: Fragment_1
19
D/DAN: onDestroy: Fragment_1
20
D/DAN: onDetach: Fragment_1
21
D/DAN: onCreateView: Fragment_4
22
D/DAN: onStart Fragment_4
23
D/DAN: onResume: Fragment_4
24
--------------------------------------[ frag3-> frag4] ----------------------------
25
D/DAN: onPause: Fragment_2						 // frag2销毁,onPause->onDetach
26
D/DAN: onStop Fragment_2
27
D/DAN: onDestroyView: Fragment_2
28
D/DAN: onDestroy: Fragment_2
29
D/DAN: onDetach: Fragment_2

总结:

我们看到,两个Adapter对于Fragment唯一的不同之处在于,当某个Fragment离开了 offScreenLimit 的范围后,采取何种策略,对于 fragmentPagerAdapter,仅仅是销毁它的视图结构,将他从 Resumed 状态转变为 Stopped 状态 ,执行 onPause->onStop->onDestroyView() 生命周期函数(注意,destroyView是紧接着onStop执行的),并未执行 onDestroy和onDeatch 函数,代表它并未被销毁。而对于FragmentStatePagerAdapter而言,当一个 Fragment离开了 limit 之后,会将他的状态从 Resumed 转变为 detached 状态,即完全地销毁它。即执行 onPause -> onDeatch 完整的销毁生命周期。

Fragment 懒加载

懒加载,即在展示相应 Fragment 页面时再动态加载页面数据。这种做法的合理性在于用户不一定会滑动页面,同时还可以帮助减轻当前页面数据请求的带宽压力,如果用户使用流量的话,还能避免流量的消耗。

从之前的分析我们可以看出,ViewPager 在展示当前页面的时候,会同时预加载左右的页面,而且由于 setOffScreenPageLimit 的数量不能设为0(无效),所以通过这个方法实现懒加载不可行。除非自定义一个ViewPager类来实现(可不可以重写setlimit 方法呢,待会试试),这种做法比较繁琐。

其实可以从 Fragment 下手(当然,这样做只能够改变数据加载的行为,而无法处理Fragment实例的缓存问题),我们分析懒加载的实质无非就是在 Fragment 对用户可见的情况下加载。那么我们自然会联想,如何知道fragment对于用户是可见的么?onResume?No,onResume对于 Activity 来说,确实不仅可见还可以交互,但是 Fragment 不同,万幸的是:

Fragment 提供了两个函数:

1
/**
2
* true if this fragment's UI is currently visible to the user(default)
3
* false if it is not
4
*/
5
public void setUserVisibleHint(boolean isVisibleToUser) {
6
     if (!mUserVisibleHint && isVisibleToUser && mState < STARTED
7
             && mFragmentManager != null && isAdded() && mIsCreated) {
8
      	mFragmentManager.performPendingDeferredStart(this);
9
     }
10
     mUserVisibleHint = isVisibleToUser;
11
     mDeferStart = mState < STARTED && !isVisibleToUser;
12
     if (mSavedFragmentState != null) {
13
         // Ensure that if the user visible hint is set before the Fragment has
14
         // restored its state that we don't lose the new value
15
         mSavedUserVisibleHint = isVisibleToUser;
16
     }
17
}

setUserVisibleHint 方法会在 Fragment 的可视状态发生变化时调用(第一次创建的时候也会调用,这时候是默认的 false),传入它的可视状态。

为了更好地说明,我们添加日志,记录从 frag1切换到frag4的变化

1
D/DAN: setUserVisibleHint: fragment1  false
2
D/DAN: setUserVisibleHint: fragment2  false
3
D/DAN: setUserVisibleHint: fragment1  true
4
5
----------------[ frag1->frag2]------------
6
D/DAN: setUserVisibleHint: fragment3  false
7
D/DAN: setUserVisibleHint: fragment1  false
8
D/DAN: setUserVisibleHint: fragment2  true
9
  
10
----------------[ frag2->frag3]------------
11
D/DAN: setUserVisibleHint: fragment4  false
12
D/DAN: setUserVisibleHint: fragment2  false
13
D/DAN: setUserVisibleHint: fragment3  true
14
15
----------------[frag3->frag4]--------------
16
D/DAN: setUserVisibleHint: fragment3  false
17
D/DAN: setUserVisibleHint: fragment4  true

注意,这个方法不是设置 Fragment 是否可见,而是 Fragment 自身的一个回调函数,参数将告知客户端程序员,当前Fragment 是否对于用户可见。那我们就可以在里面做些羞羞的事了。

1
@Override
2
public void setUserVisibleHint(boolean isVisibleToUser) {
3
     super.setUserVisibleHint(isVisibleToUser);
4
     if(isVisibleToUser){
5
     		onVisible();
6
     }else{
7
     		onInvisible();
8
     }          
9
}

onVisible和inVisible是我们定义的回调函数。

为了更好地封装,我们不必在每个 Fragment里都这么写,可以定义一个抽象父类

1
public abstract class LazyLoadFragment extends Fragment{
2
     ....
3
                                                    
4
     @Override
5
		 public void setUserVisibleHint(boolean isVisibleToUser) {
6
     			super.setUserVisibleHint(isVisibleToUser);
7
     			if(isVisibleToUser){
8
     					onVisible();
9
     			}else{
10
     					onInvisible();
11
     		  }          
12
		 }    
13
      
14
     public void onVisible(){
15
     	   requestData();
16
     }
17
                    
18
     public void onVisible(){
19
     	   //
20
     }
21
                    
22
     protected abstract void requestData();
23
}

然后,继承 LazyFragment 并实现 requestData() 抽象函数就可以了。另外,当我们使用 hide 和 show 来控制 Fragment 时,也可以实现 onHinddenChanged 来实现懒加载。探究懒加载

References

01.FragmentTransaction与Fragment生命周期的关系

02.Android基础:Fragment,看这篇就够了

03.addToBackStack对生命周期的影响

04.关于Fragment你需要注意的事项

05.Android Training中文

CATALOG
  1. 1. Fragment 定义
  2. 2. Fragment核心类
  3. 3. 基本使用
  4. 4. Fragment的添加
    1. 4.0.0.1. 动态添加
  • 5. FragmentTransaction
  • 6. LifeCyclers
  • 7. FragmentPagerAdapter 与 FragmentStatePagerAdapter
    1. 7.0.1. Fragment 懒加载
  • 7.1. References