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 | |
7 | |
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 | |
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 | |
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的生命周期图
掌握Actiuvity和Fragment的几个状态更容易帮助理解生命周期函数(状态间的变换)。在Activity中,常态只有三种,分别是
1)Resumed //可以交互
2)Paused // 可见但不能交互
3)stoped // 不可见
其他状态是瞬态(created 和 started)
Fragment生命周期
Fragment生命周期与 Activity 生命周期的关系
再来分析它的各种生命周期函数会简单很多
- 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
的生命周期方法并不会被执行。
仅仅是Fragment
的View
被显示或者隐藏。而且,尽管Fragment
的View
被隐藏,但它在父布局中并未被detach,仍然是作为containerView
的childView
存在着。
3)attach和detach: 不同于Fragment的 onAttach和onDetach,attach和detach会使得当前Fragment的生命周期彻底地执行一遍
- 一旦一个
Fragment
被detach()
,它的onPause()-onDestroyView()
周期都会被执行。 - 调用
attach()
后,onCreateView()-onResume()
周期也会被再次执行。
4)remove(): 对比add很容易得出结论,remove之后执行 onPause-onDetach
5)replace(): 使用replace()
来添加Fragment
的时候,第二次添加会导致第一个Fragment
被销毁,即执行第二个Fragment
的onAttach()
方法之前会先执行第一个Fragment
的onPause()-onDetach()
方法,同时containerView
会detach第一个Fragment
的View
。
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 变得不可见,不仅视图层次销毁,实例也被销毁,即调用了 onDestroyView
和 onDestroy
方法,仅仅保存 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 |
|
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 | |
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 来实现懒加载。探究懒加载