废话不多说,fragment延迟加载,网上有好几种方法,想要求同存异有些耗费时间,如果你不想花时间研究写demo,就往下看吧。如果你想要花时间研究,也可以看一下前面几段就不用往下看了。
1、所谓的fragment延迟加载,目前为止按类型分有两种:
- 1)一种是常见的APP主页,activity+fragments ,下面简称af
- 2)一种是viewpager + fragments,下面简称vf
2、如果vf按类别分的话,还可以分:
- 1) 在fragment内部调用延迟加载方法
- 2)在fragment外部viewpager那层手动调用延迟加载方法
目前我就查到了这几种,如果还有请留言,感谢。
先上af代码:
**activity里面代码**
//hide 和 show才能触发onHidden add不能触发
//所以要想做延迟加载 在onHidden里面处理的话 需要初始化的时候就把所有的fragment都add
//但是 add了还不行 必须 要commit 一次,否则 如果放在同一个commit执行
//那么 onCreate 之后就会立马执行onHidden而此时还咩有onCreateView所以会报空指针
private void initFragments() {
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
if (fragmentList.size() == 0) {
for (int i = 0; i < 3; i++) {
MainItemFragment fragment = new MainItemFragment();
Bundle bundle = new Bundle();
bundle.putInt("type", i + 1);
fragment.setArguments(bundle);
fragmentList.add(fragment);
fragmentTransaction.add(R.id.container, fragment, "fragment" + i);
Log.d("debug", ":add :" + i);
}
}
fragmentTransaction.commitAllowingStateLoss();
}
private void changeFragment(int index) {
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
// 如果不等于空就初始化
// hide 和 show才能触发onHidden add不能触发 所以要想做延迟加载
// 在onHidden里面处理的话 需要初始化的时候就把所有的fragment都add
hideAll(fragmentTransaction);
fragmentTransaction.show(fragmentList.get(index));
fragmentTransaction.commitAllowingStateLoss();
}
private void hideAll(FragmentTransaction fragmentTransaction) {
for (int i = 0; i < fragmentList.size(); i++) {
if (fragmentList.get(i) != null) {
fragmentTransaction.hide(fragmentList.get(i));
}
}
}
**fragment里面代码**
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
if (!hidden) {
delayInit();
}
Log.d("debug", type + ":onHiddenChanged" + hidden);
}
private boolean canLoad = true;
public void delayInit() {
if (!canLoad) {
return;
}
//todo
}
代码解释:上面的代码大家可以直接复制粘贴过去用,很方便,只需按需改动一下即可。
如果是求便捷的同学可以拿走了,没你事儿了,下面是测试过程,就是在写Demo的过程中的收获和需要注意的地方。
注意:
- 1、我们的fragment里面 有onCreate,onCreateView,onActivityCreated 这几个主要方法,调用顺序也如此。而我们的加载view必须要放在 onCreateView之后,因为你看在onCreateView之后才有根布局,如果根布局都没有,那就会报空指针之类的错误。
-
2、hide 和 show 才能触发onHidden,并且是在状态改变的时候。而 add不能触发 。
-
3、需要两次commit ,一次是在init初始化的时候,需要把所有fragment add进去,然后commit一次,然后另一次是在changeFragment的时候commit.,并且,在show之前最好是把所有的fragment都hide掉(图方便,其实代码里面循环里做了判断的)
**log代码 **
这是第一次commit,执行add 1\2\3 验证一个道理:
**add 是不会让fragment执行 onCreate onCreatView 等一系列生命周期方法的 **
第二次commit, 执行了生命周期方法,验证一个道理:
只有当fragment show出来的时候才会执行其生命周期方法
回到上面那个问题,如果只有一次commit,我当初就是为了图方便,直接把initFragment写在changeFragment里面同一个transaction,结果:
private void changeFragment(int index) {
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
// 如果不等于空就初始化
// hide 和 show才能触发onHidden add不能触发 所以要想做延迟加载
// 在onHidden里面处理的话 需要初始化的时候就把所有的fragment都add
if (fragmentList.size() == 0) {
for (int i = 0; i < 3; i++) {
MainItemFragment fragment = new MainItemFragment();
Bundle bundle = new Bundle();
bundle.putInt("type", i + 1);
fragment.setArguments(bundle);
fragmentList.add(fragment);
fragmentTransaction.add(R.id.container, fragment, "fragment" + i);
Log.d("debug", ":add :" + i);
}
}
hideAll(fragmentTransaction);
fragmentTransaction.show(fragmentList.get(index));
fragmentTransaction.commitAllowingStateLoss();
}
**如图,onHiddenChanged方法在onCreate之后 在onCreateView之前了**
这样的话,第一次是不会执行延迟加载方法的,进入是一片空白。只有所有的都add一遍了,然后再来切换,也就是commit add 所有fragment了一回,再commit change 才会执行延迟加载方法,呈现出布局。
OK以上的就是对于af这种类型的测试精简结果。
对于vf:
鄙人拙见,所谓的延迟加载,如果在Viewpager里面的话,如果在不修改viewpager源码设置 defaultlimit = 0 的情况下,无论如何都会预加载1页,也就是每次都会最多加载2页的数据,也就是说 延迟加载 如果只有3个fragment 最多 可以节约1个fragment的数据内存, 如果是4个话可以节约2个,也就是n-2 个数据内存。(如果有不同看法的,快点留言!我渴望你的答案)
第一种:内部调用
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (getUserVisibleHint()) {
isVisible = true;
onFragmentVisible();
} else {
isVisible = false;
onFragmentInvisible();
}
Log.d("debug", type + ":setUserVisibleHint" + isVisibleToUser);
}
//这里通过判断view是否隐藏或者显示状态来执行延迟加载方法
private void onFragmentInvisible() {
Log.d("debug", type + ":onFragmentInvisible" + isVisible);
}
private void onFragmentVisible() {
Log.d("debug", type + ":onFragmentVisible" + isVisible);
if (canLoad && isFirstLoad) {
delayInit();
}
}
public void delayInit() {
isFirstLoad = false;
textView = new TextView(mContext);
textView.setText(type + "");
ToggleButton button = new ToggleButton(mContext);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
handler.sendEmptyMessageDelayed(0, 2000);
button.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (!isChecked) {
setUserVisibleHint(isChecked);
} else {
setUserVisibleHint(isChecked);
}
}
});
mRoot.addView(textView);
mRoot.addView(button);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
Log.d("debug", type + ":onCreateView");
if (mRoot == null) {
mRoot = new LinearLayout(container.getContext());
}
if (isFirstLoad) {
delayInit();
}
return mRoot;
}
解释一下:上面有一些条件变量isFirstLoad、canLoad
- isFirstLoad是判断是否是第一次加载,如果是第二次的话就不会再加载了。因为都已经加载过了。但是问题来了,viewpager如果不设置setOffscreenLimit(list.size() )的话,默认至少会预加载1页,那么,如果所有的3页都已经加载了,切换的时候会发生什么呢?
所以,fragment除了onCreate只执行一次之外,onCreateView如果预加载的话也是再执行的。再具体一点:因为现在在第3页,会缓存第2页,但是第一页没有缓存已经destroyVIew了(因为limit == 1,如果是2那么第1页也缓存),而点击切换到第2页,有要默认预加载1页,第3页已经在缓存里面,而第1页没有,所以要执行onCreateView方法。
我们的isFirstLod就是为了避免多次加载。
- canLoad 这个就是比较重点的地方了。看上面的代码,canLoad默认是false,只有在onCreate里面才设置为true.为什么呢?(将引出一个问题!)
我们来看看如果没有canLoad的话log:
注意: setUserVisibleHint 居然在onCreate前面执行了!!
这是我测试时,万万没有想到的,找了半天的源码看看哪里调用,终于找到:它在FragmentPageAdapter里面被调用
所以一定要小心了。canLoad就是为了解决这个问题,只有在onCreate(因为紧接着就是跟的onCreateView)或者onCreateView(最好这这里)之后才能去执行延迟加载方法
还有一点要注意:
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
Log.d("debug", type + ":onCreateView");
if (mRoot == null) {
mRoot = new LinearLayout(container.getContext());
}
if (isFirstLoad) {
delayInit();
}
return mRoot;
}
onCreateView里面,有一个对mRoot的判空,这里是可以判空的,因为已经执行过一次之后,就别再多次执行了,直接返回
第二种:外部调用
这种是群里朋友给我说的思路:给viewpager设置onPageChanged监听,onSelected里面 去调用延迟加载方法,并且把延迟加载方法public出来。
多的代码我就不上了,fragment里面差不多,其余代码也相差不大。有一点就是:默认的第一页你要在fragment里面去调用加载方法,其实也需要设置 isFirstload 、canload这些变量,实现也跟上面的方法差不多,就只是 调用方法一个在onselected里, 一个在setUserVisibleHint里。据我demo来看,没有啥区别和优势(如果,我说错了,有更好的方法,快点留言!我渴望你的答案,也希望学习到)
总结
研究了一下写了个demo,如果想要demo的话,那分享一下 github
吧。testdelayloadfragment这个module下的。demo里面很乱很杂,我不建议你pull下来。。。
最后,希望和热爱研究的小伙伴一起交流讨论!
网友评论