面试总结(5):Fragment的懒加载
2017-05-08 12:03
357 查看
前言
在我们的项目里经常会用到ViewPager+Fragment实现选项卡滑动切换的效果,ViewPager会预加载下一个Framgment的内容,这样的机制有优点也有缺点:预加载让用户可以更快的看到接下来的内容,浏览起来连贯性更好,但是app在展示内容的同时还增加了额外的任务,这样可能影响界面的流畅度,并且可能造成流量的浪费。
目前大部分的app都使用Fragment懒加载机制,例如哔哩哔哩,360手机助手等等。
正文
实现Fragment懒加载也可以有很多种办法,可能有些朋友会第一时间想到通过监听滚动的位置,通过判断Fragment的加载状态实现懒加载,这种方法的缺点就是把实现暴露在Framgment之外,从封装的角度来说这不是一个好的方案。最好的方案在网上已经随处可见了,那就是重写Fragment的setUserVisibleHint()方法,实现Fragment内部的懒加载机制。
首先我们看看这个方法的注释:
/**
* Set a hint to the system about whether this fragment’s UI is currently visible
* to the user. This hint defaults to true and is persistent across fragment instance
* state save and restore.
*
*
An app may set this to false to indicate that the fragment’s UI is
* scrolled out of visibility or is otherwise not directly visible to the user.
* This may be used by the system to prioritize operations such as fragment lifecycle updates
* or loader ordering behavior.
*
* Note: This method may be called outside of the fragment lifecycle.
* and thus has no ordering guarantees with regard to fragment lifecycle method calls.
*
* @param isVisibleToUser true if this fragment’s UI is currently visible to the user (default),
* false if it is not.
*/
英文有点多,简单的翻译总结一下就是,Framgent没有直接显示给用户(例如滚动出了屏幕)会返回false,值得注意的是这个方法可能在Fragment的生命周期以外调用。
官方已经提示这个方法可能在生命周期之外调用,先看看这个方法到底是在什么时候会被调用:
我在Fragment中打印了Fragment生命周期的几个比较重要的方法,从log上看setUserVisibleHint()的调用早于onCreateView,所以如果在setUserVisibleHint()要实现懒加载的话,就必须要确保View以及其他变量都已经初始化结束,避免空指针。
然后滑动ViewPager,看一下运行情况:
滑动的时候,两个Fragment的setUserVisibleHint()方法被调用,显示状态也发生了变化。
ok,那就可以直接写代码了,先定义我们的MyFragment:
/** * Created by li.zhipeng on 2017/5/7. * <p> * Fragment */ public class MyFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener{ private SwipeRefreshLayout swipeRefreshLayout; private ListView listView; private Handler handler = new Handler(); /** * 是否已经初始化完成 * */ private boolean isPrepare; @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { Log.e("lzp", "onCreateView" + hashCode()); return inflater.inflate(R.layout.fragment_my, container, false); } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { Log.e("lzp", "onViewCreate" + hashCode()); swipeRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.swipeRefreshLayout); listView = (ListView) view.findViewById(R.id.listView); swipeRefreshLayout.setOnRefreshListener(this); // 初始化完成 isPrepare = true; } @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); Log.e("lzp", "onActivityCreated" + hashCode()); // 创建时要判断是否已经显示给用户,加载数据 onVisibleToUser(); } @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); Log.e("lzp", "setUserVisibleHint" + hashCode() + ":" + isVisibleToUser); // 显示状态发生变化 onVisibleToUser(); } /** * 显示给用户的操作 * */ private void onVisibleToUser(){ if (isPrepare && getUserVisibleHint()){ onRefresh(); } } /** * 加载数据 * */ private void loadData() { listView.setAdapter(new ArrayAdapter<>(getActivity(), android.R.layout.simple_list_item_1, new String[]{ "111", "111", "111", "111", "111", "111", "111", "111", "111", "111", "111", "111", "111", "111", "111", "111", "111", "111", "111", "111", "111", "111" })); } @Override public void onRefresh() { // 只加载一次数据,避免界面切换的时候,加载数据多次 if (listView.getAdapter() == null){ swipeRefreshLayout.setRefreshing(true); new Thread(){ @Override public void run() { // 延迟1秒 try { sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } handler.post(new Runnable() { @Override public void run() { loadData(); swipeRefreshLayout.setRefreshing(false); } }); } }.start(); } } }
效果图我就不贴出来了,最后会有源码链接,大家可以下载运行看看效果。
这样就结束了吗?那肯定不行,虽然了解了用法,但是不封装一下,以后就要写很多次,简直不要太low。
封装之后的BaseFragment的代码:
/** * Created by li.zhipeng on 2017/5/8. * <p> * Fragment 的基类 */ public abstract class BaseFragment extends Fragment { /** * 布局id */ private View contentView; /** * 是否启用懒加载,此属性仅对BaseLazyLoadFragment有效 * */ private boolean isLazyLoad; @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { contentView = inflater.inflate(setLayoutId(), container, false); return contentView; } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); // 初始化 init(); } @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); // 如果不是懒加载模式,创建就加载数据 if (!isLazyLoad){ loadData(); } } /** * 设置加载的布局Id */ protected abstract int setLayoutId(); /** * 初始化操作 */ protected abstract void init(); /** * findViewById */ protected View findViewById(int id) { return contentView.findViewById(id); } /** * 懒加载数据 */ protected abstract void loadData(); /** * 是否启用懒加载,此方法仅对BaseLazyLoadFragment有效 * */ public void setLazyLoad(boolean lazyLoad) { isLazyLoad = lazyLoad; } }
接下来是BaseLazyLoadFragment:
/** * Created by li.zhipeng on 2017/5/8. * * 懒加载的Fragment */ public abstract class BaseLazyLoadFragment extends BaseFragment { /** * 是否已经初始化结束 */ private boolean isPrepare; @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); setLazyLoad(true); isPrepare = true; } @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); // 创建时要判断是否已经显示给用户,加载数据 onVisibleToUser(); } @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); // 显示状态发生变化 onVisibleToUser(); } /** * 判断是否需要加载数据 */ private void onVisibleToUser() { // 如果已经初始化完成,并且显示给用户 if (isPrepare && getUserVisibleHint()) { loadData(); } } }
最后看看MyFragment的代码:
/** * Created by li.zhipeng on 2017/5/7. * <p> * Fragment */ public class MyFragment extends BaseLazyLoadFragment implements SwipeRefreshLayout.OnRefreshListener { private SwipeRefreshLayout swipeRefreshLayout; private ListView listView; private Handler handler = new Handler(); @Override protected int setLayoutId() { return R.layout.fragment_my; } @Override protected void init() { swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipeRefreshLayout); listView = (ListView) findViewById(R.id.listView); swipeRefreshLayout.setOnRefreshListener(this); } @Override protected void loadData() { onRefresh(); } @Override public void onRefresh() { // 只加载一次数据,避免界面切换的时候,加载数据多次 if (listView.getAdapter() == null) { swipeRefreshLayout.setRefreshing(true); new Thread() { @Override public void run() { // 延迟1秒 try { sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } handler.post(new Runnable() { @Override public void run() { listView.setAdapter(new ArrayAdapter<>(getActivity(), android.R.layout.simple_list_item_1, new String[]{ "111", "111", "111", "111", "111", "111", "111", "111", "111", "111", "111", "111", "111", "111", "111", "111", "111", "111", "111", "111", "111", "111" })); swipeRefreshLayout.setRefreshing(false); } }); } }.start(); } } }
ok,这样封装就结束了,以后不需要懒加载Framgent可以直接继承BaseFragment,需要懒加载的直接继承BaseLazyLoadFragment。
扩展
如果你也自己敲着代码去研究懒加载,并且Framgent的数量大于2个,你会发现等你翻到第三个,再重新返回第一个的时候,第一个又重新加载了,并且重新走了创建周期,我在这里给不明白的原因的朋友解释一下:ViewPager只默认预加载下一页的Fragment,其他的Fragment会被移除并销毁。
下一次再添加的时候就需要重新创建Fragment。
那如果解决这个问题呢?
viewPager.setOffscreenPageLimit(2);
这个方法可以设置ViewPager预加载的页数,我们看一下方法的注释:
/**
* Set the number of pages that should be retained to either side of the
* current page in the view hierarchy in an idle state. Pages beyond this
* limit will be recreated from the adapter when needed.
*
*
This is offered as an optimization. If you know in advance the number
* of pages you will need to support or have lazy-loading mechanisms in place
* on your pages, tweaking this setting can have benefits in perceived smoothness
* of paging animations and interaction. If you have a small number of pages (3-4)
* that you can keep active all at once, less time will be spent in layout for
* newly created view subtrees as the user pages back and forth.
*
* You should keep this limit low, especially if your pages have complex layouts.
* This setting defaults to 1.
*
* @param limit How many pages will be kept offscreen in an idle state.
*/
英文注释有点长,简单翻译总结一下几点:
1、设置当前页的左右两边的预加载数量。
2、通过设置预加载的数量,有利于动画的显示和交互。(上面的英文提到了懒加载,个人感觉跟懒加载没什么关系,预加载数量少了,必然流畅度相对会越高)
3、如果页数比较少(例如3到4个),可以加载所有的页数,这样可以介绍页数创建的时间,滑动更流畅
4、保持预加载个数偏小,除非你的页数有多种复杂的布局,默认为1。(不可能小于1,方法里判断)
了解了这个方法的特性,我们只要viewPager.setOffscreenPageLimit(2)就可以了,这样就解决了刚才的问题。
另外要说的是setUserVisibleHint()只有在ViewPager+Fragment的时候才有效,单用Fragment的时候可以考虑使用onHiddenChanged(boolean hidden)方法。
总结
ok,懒加载就到此结束,这个问题一家国内大型的互联网上市公司问的问题,我们平时可能觉得懒加载什么的无所谓,产品就是矫情,然后开始抱怨这个那个的,现在看来这就是差别的所在,大公司对于性能的要求真是非常的严格,这一点我们都需要耐心的学习,才能真正的有所提高。Demo下载地址
相关文章推荐
- Fragment面试总结
- 总结面试中问到的一个问题构造函数、静态代码块、和调用的方法的加载顺序
- fragment懒加载小总结
- 面试经验总结
- 面试经验总结
- 面试总结
- 总结Asp.net中Page加载PostData的具体过程 进而解决"获取动态创建的控件的PostData数据"问题
- 这几年的面试与被面试总结
- "分析器错误信息: 未能加载类型“WebApplication1.Global”。"类似问题总结。
- ATC电话面试总结
- 第一次面试总结
- ASP.net中动态加载控件时一些问题的总结
- 总结了一些常见的排序算法,面试必备啊!
- java面试大总结(1)
- 关于Hibernate一对一不能延迟加载的总结
- 总结Asp.net中Page加载PostData的具体过程 进而解决"获取动态创建的控件的PostData数据"问题
- 总结Asp.net中Page加载PostData的具体过程 进而解决"获取动态创建的控件的PostData数据"问题
- 这几年的面试与被面试总结
- 总结面试时没有回答上的设置内存对齐方式问题
- 微软搜狐面试总结