Fragment学习笔记备忘

Posted by 程序亦非猿 on 2015-11-02

前言

// todo

生命周期

http://www.codeceo.com/article/android-fragment-circle.html

##

懒加载

场景

场景前提:Fragment里有个ViewPager,vp里有三个Fragment的情况.
我需要的情况是,第一次加载的时候就第一页去请求数据,而其他两页不去请求直到滑动到该界面.

预备知识

  1. public void setUserVisibleHint(boolean isVisibleToUser) isVisibleToUser代表fragment是否可见.

实践

动手,先研究一下它在什么时候调用的

第一次加载

即app刚启动,刚到这个界面
PS:LiveFragment在第一个位置.

1
2
3
4
5
6
7
8
9
10
11
12
13
D/ChildFragment: LiveFragment setUserVisibleHint() called with: isVisibleToUser = [false]
D/ChildFragment: FollowFragment setUserVisibleHint() called with: isVisibleToUser = [false]
D/ChildFragment: FansFragment setUserVisibleHint() called with: isVisibleToUser = [false]
D/ChildFragment: LiveFragment onAttach:
D/ChildFragment: LiveFragment setUserVisibleHint() called with: isVisibleToUser = [true]
D/ChildFragment: LiveFragment onCreateView:
D/ChildFragment: LiveFragment onResume:
D/ChildFragment: FollowFragment onAttach:
D/ChildFragment: FollowFragment onCreateView:
D/ChildFragment: FansFragment onAttach:
D/ChildFragment: FansFragment onCreateView:
D/ChildFragment: FollowFragment onResume:
D/ChildFragment: FansFragment onResume:

可以看到,方法的调用顺序:
setUserVisibleHint->onAttach->(setUserVisibleHint如果可见)/onCreateView(不可见)->onResume

需要注意,第一次调用的时候连onCreateView都没执行,即view都没有初始化.

另外,每个Fragment在一开始,都先调用了setUserVisibleHint()并且传入了false,即还是不可见状态.

第二次and more

而后,滑动viewpager或者点击tab则会导致相关的fragment调用setUserVisibleHint(这里的相关指的是,即将不可见的fragment与即将可见的fragment,也即可见性会发生变化的fragment)

通过日志也可以看到true,false,成对出现,每次也只有两个相关fragment出现在日志里:

1
2
3
4
5
6
7
8
9
10
11
D/ChildFragment: FansFragment setUserVisibleHint() called with: isVisibleToUser = [false]
D/ChildFragment: FollowFragment setUserVisibleHint() called with: isVisibleToUser = [true]
D/ChildFragment: FollowFragment setUserVisibleHint() called with: isVisibleToUser = [false]
D/ChildFragment: LiveFragment setUserVisibleHint() called with: isVisibleToUser = [true]
D/ChildFragment: LiveFragment onResume:
D/ChildFragment: FollowFragment onResume:
D/ChildFragment: FansFragment onResume:
D/ChildFragment: LiveFragment setUserVisibleHint() called with: isVisibleToUser = [false]
D/ChildFragment: FansFragment setUserVisibleHint() called with: isVisibleToUser = [true]
D/ChildFragment: FansFragment setUserVisibleHint() called with: isVisibleToUser = [false]
D/ChildFragment: FollowFragment setUserVisibleHint() called with: isVisibleToUser = [true]

封装使用

大致的流程在这里,可以根据自己调用Fragment的生命周期需求可以适当修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public abstract class LazyFragment extends Fragment {
/**
* 是否可见
*/
protected boolean isVisiable;
/**
* 是否已经调用onCreatView
*/
protected boolean isViewCreated;
/**
* 先于onCreateView
* @param isVisibleToUser
*/
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
isVisiable = isVisibleToUser;
//可以根据需要修改
if (isVisiable) {
onVisible();
} else {
onInvisible();
}
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
isViewCreated = true;
}
/**
* 懒加载 在可见的状态下会被调用
*/
protected abstract void lazyload();
protected void onVisible() {
if (isViewCreated && isVisiable) {
lazyload();
}
}
protected void onInvisible() {
}
}

使用,以LiveFragment为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//为了不重复加载数据
private boolean isDataLoadedOnce;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
TLLogger.trace("ChildFragment", "LiveFragment onCreateView: ");
mRootView = inflater.inflate(R.layout.fragment_life, container, false);
EventBus.getDefault().register(this);
initData();
findViews();
return mRootView;
}
@Override
public void onResume() {
super.onResume();
TLLogger.trace("ChildFragment", "LiveFragment onResume: ");
lazyload();
}
@Override
protected void lazyload() {
//如果加载过数据 或者不可见,则不再请求
//防止不可见的情况去加载数据,也避免加载多次数据
if (!isDataLoadedOnce&&isVisiable) {
isDataLoadedOnce = true;
//dosth
}
}

知识点总结

  1. 第一次调用setUserVisibleHint的时候它比onCreateView之前,这时候控件都没有初始化,需要小心.
  2. onResume的时候,三个fragment都会调用onResume,但是setUserVisibleHint只涉及到可见性改变的fragment
  3. 滑动vp或者点击tab都会触发setUserVisibleHint
  4. 需要根据需要处理onResumelazyload

Fragment重叠问题

使用add hide show来控制显示fragment,但是一旦activity被回收,那些被hide的Fragment,也全部处于可见状态,并且无法操控,即出现fragment重叠问题.

原因:fragment被回收,然后 变透明了

解决办法
重写activity的onSaveInstanceState并且不调用super

1
2
3
@Override
protected void onSaveInstanceState(Bundle outState) {
}

参考

关于Activity被回收,Fragment还在的问题
让多个Fragment 切换时不重新实例化

Exceptions

记录我遇到的坑

remove(null).commit()

当我试图在解决fragment被回收后出现重叠的状况的时候,我试着去移除原先老的fragment,但是发现如果被回收两次,olds虽然有size,但是它所包含的数据都是null!,导致remove(null).commit()报错!

报错日志:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java.lang.NullPointerException
at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:707)
at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManag
at android.support.v4.app.FragmentController.execPendingActions(FragmentContro
at android.support.v4.app.FragmentActivity.onStart(FragmentActivity.java:511)
at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1174)
at android.app.Activity.performStart(Activity.java:5203)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2198)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2286)
at android.app.ActivityThread.access$600(ActivityThread.java:144)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1259)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:5166)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:525)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:584)
at dalvik.system.NativeStart.main(Native Method)

场景:activity里有两个fragment,一个fragment里有viewpager+3个fragment

Activity的onCreate 部分源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if (savedInstanceState == null) {
//.....dosth
}else {
//移除原来的fragment避免重叠
FragmentManager manager = getSupportFragmentManager();
List<Fragment> olds = manager.getFragments();
if (null != olds) {
int size = olds.size();
for (int i = 0; i < size; i++) {
Fragment old = olds.get(i);
//但是这里的old可能为null
manager.beginTransaction().remove(old).commit();
}
}
//获取回收之前的存储的当前显示页数,再把它显示出来
int index = savedInstanceState.getInt(KEY_CURR_INDEX);
showOrHideFragment(index);
}

所以需要再加一层判断:

1
2
3
if (null != old) {
manager.beginTransaction().remove(old).commit();
}

完美!~

资料

android-fragmentmanager-backstackrecord-run-throwing-nullpointerexception

activity被回收,fragment的onActivityCreated调用两次

场景:
Activity里有两个Fragment,其中一个里有viewpager+3个fagment.

当activity被回收,fragment的onActivityCreated被调用了两次(另外一个fragment也不例外):

  • 第一次 savedInstanceState 不为null 保存的数据还在
  • 第二次 savedInstanceState 为null 丢失了

这导致我在该fragment的onSaveInstanceState(Bundle outState)里保存的数据失效了

日志如:

1
2
onActivityCreated: TLToplistFragment{43ceb940 #1 id=0x7f0f0077 top}
onActivityCreated: TLToplistFragment{43cf7048 #1 id=0x7f0f0077 top}

找了很久,但是并没有找到原因以及解决办法,原本是用来保存数据的,现在没办法了,
最后通过在activity的onSaveInstanceState里获取fragment的数据,然后再在重新创建的时候通过setArguments传入的方式替代.

这个坑的原因还不知道,暂时用其他方法给解决了需求.

参考资料

http://stackoverflow.com/questions/12453710/lazy-load-data-for-viewpager-items
http://blog.csdn.net/myatlantis/article/details/42643733