了解懒加载和普通加载的区别
文章开头,先让我们了解一下什么是viewpager的懒加载。下面让两个对比动图来告诉我们普通的fragment+viewpager和使用了懒加载fragment+viewpager的区别所在:
普通viewpager:
懒加载viewpager:
经过上边两个动图我们可以看出使用了懒加载的viewpager只有当被划到下一个页面的时候那个页面才会加载。而普通的viewpager如果你不使用setoffscreenpagelimit(int limit)这个方法去设置默认加载数的话是会默认加载页面的左右两页的(不了解这个方法的同学请先去百度一下这个方法了解一下用法和效果),也就是说当你进入viewpager第一页的时候第二页和第一页是会被一起加载的,这样同时加载就会造成一些问题,试想我们如果设置了setoffscreenpagelimit为3的话,那么进入viewpager以后就会同时加载4个fragment,像我们平时的项目中在这些fragment中一般都是会发送网络请求的,也就是说我们有4个fragment同时发送网络请求去获取数据,这样的结果显而易见给用户的体验是不好的(如:浪费用户流量,造成卡顿等等)。实现懒加载分析
这样我们就需要使用viewpager的懒加载机制去让被浏览页面只有被用户可见的时候再会完成加载操作。接着来介绍一下fragment的setUserVisibleHint方法,这也是此实现最重要的一个方法,来让我们看下关于这个方法的介绍:
* 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.
*
* <p>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.</p>
*
* @param isVisibleToUser true if this fragment's UI is currently visible to the user (default),
* false if it is not.
*/
public void setUserVisibleHint(boolean isVisibleToUser) {
if (!mUserVisibleHint && isVisibleToUser && mState < STARTED
&& mFragmentManager != null && isAdded()) {
mFragmentManager.performPendingDeferredStart(this);
}
mUserVisibleHint = isVisibleToUser;
mDeferStart = mState < STARTED && !isVisibleToUser;
}
可以看到该方法用于告诉系统,这个Fragment的UI是否是可见的。也就是说当Fragment被显示在屏幕时,setUserVisibleHint的isVisibleToUser参数为true,不显示时为fasle。所以我们只需要围绕这个方法即可完成fragment的懒加载。那就让我们先写一个看看。
public abstract class BasePagerFragment extends Fragment {
protected AppCompatActivity mActivity;
protected boolean isVisibleToUser; //页面是否可见
@Override
public void setUserVisibleHint(boolean isVisibleToUser) { //page 1等会此读数据 生命周期
super.setUserVisibleHint(isVisibleToUser);
this.isVisibleToUser = isVisibleToUser;
prepareFetchData();
}
public abstract void loadData();
protected void prepareFetchData() {
if (isVisibleToUser) {
loadData();
}
}
}
这么写乍一看好像是没有问题,请接着往下看。
好的,让我们再来看下viewpager加载fragment时fragment的生命周期,直接上代码:
public class MainActivity extends AppCompatActivity {
ViewPager viewPager;
List<Fragment> fragmentList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
viewPager = (ViewPager) findViewById(R.id.viewpager_id);
Fragment payOrderFragment = DemoFragment.newInstance("0", "");
Fragment unPayOrderFragment = DemoFragment.newInstance("1", "");
Fragment payOrderHistoryFragment = DemoFragment.newInstance("2", "");
fragmentList.add(payOrderFragment);
fragmentList.add(unPayOrderFragment);
fragmentList.add(payOrderHistoryFragment);
viewPager.setOffscreenPageLimit(2); //fragmentList.size()-1
MyFragmentPagerAdapter adapter = new MyFragmentPagerAdapter(getSupportFragmentManager(), (ArrayList<Fragment>) fragmentList);
viewPager.setAdapter(adapter);
}
public class MyFragmentPagerAdapter extends FragmentPagerAdapter {
ArrayList<Fragment> list;
public MyFragmentPagerAdapter(FragmentManager fm, ArrayList<Fragment> list) {
super(fm);
this.list = list;
}
@Override
public int getCount() {
return list.size();
}
@Override
public Fragment getItem(int arg0) {
return list.get(arg0);
}
@Override
public int getItemPosition(Object object) {
return super.getItemPosition(object);
}
}
}
public abstract class BasePagerFragment extends Fragment {
protected AppCompatActivity mActivity;
}
public class DemoFragment extends BasePagerFragment {
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2";
private String TAG="";
// TODO: Rename and change types of parameters
private String mParam1;
private String mParam2;
ImageView imageview;
public DemoFragment() {
// Required empty public constructor
}
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param param1 Parameter 1.
* @param param2 Parameter 2.
* @return A new instance of fragment DemoFragment.
*/
// TODO: Rename and change types and number of parameters
public static DemoFragment newInstance(String param1, String param2) {
DemoFragment fragment = new DemoFragment();
Bundle args = new Bundle();
args.putString(ARG_PARAM1, param1);
args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.e(TAG,"onCreate");
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
}
if(mParam1.equals("0")){
TAG="0";
}else if(mParam1.equals("1")){
TAG="1";
}else {
TAG="2";
}
Log.e(TAG,"isVisibleToUser"+isVisibleToUser);
super.setUserVisibleHint(isVisibleToUser);
}
// @Override
// protected void prepareFetchData(boolean forceUpdate) {
// super.prepareFetchData(true);
// }
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Log.e(TAG,"onCreateView");
View view = inflater.inflate(R.layout.fragment_demo, container, false);
imageview = (ImageView) view.findViewById(R.id.imageview);
imageview.setImageResource(R.mipmap.ic_launcher);
return view;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Log.e(TAG,"onActivityCreated");
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
}
if(mParam1.equals("0")){
TAG="0";
}else if(mParam1.equals("1")){
TAG="1";
}else {
TAG="2";
}
mActivity = (AppCompatActivity) context;
Log.e(TAG,"onAttach");
}
@Override
public void onStart() {
super.onStart();
Log.e(TAG,"onStart");
}
@Override
public void onStop() {
super.onStop();
Log.e(TAG,"onStop");
}
@Override
public void onDestroyView() {
super.onDestroyView();
Log.e(TAG,"onDestroyView");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.e(TAG,"onDestroy");
}
@Override
public void onDetach() {
super.onDetach();
Log.e(TAG,"onDetach");
}
@Override
public void onResume() {
super.onResume();
Log.e(TAG,"onResume");
}
@Override
public void onPause() {
super.onPause();
Log.e(TAG,"onPause");
}
}
如上图所示代码很简单,就是3个fragment加载到viewpager中,fragment通过tag区分不用的fragment对象方便我们观察生命周期log,当我们运行代码后的生命周期为:
可以看到首先第一个首先运行的是第一个fragment的setUserVisibleHint方法,因为第一个fragment的值可见,所以参数为true,但是我们会发现这个方法是在第一个fragment被加载前被调用的,如果你只在按照我们初识那么写的话,当它走到loaddata()中的时候控件view并没有初始化,这样肯定会造成空指针问题。既然如此我们让它控件加载后再读取数据即可,控件初识化一般在oncreateview方法中,所以我们在fragment的onActivityCreated方法中再去加载即可,改造后如下:
public abstract class BasePagerFragment extends Fragment {
protected AppCompatActivity mActivity;
protected boolean isViewInitiated; //控件是否初始化完成
protected boolean isVisibleToUser; //页面是否可见
@Override
public void setUserVisibleHint(boolean isVisibleToUser) { //page 1等会此读数据 生命周期
super.setUserVisibleHint(isVisibleToUser);
this.isVisibleToUser = isVisibleToUser;
prepareFetchData();
}
@Override
public void onActivityCreated(Bundle savedInstanceState) { //page 0进入再此读数据
super.onActivityCreated(savedInstanceState);
isViewInitiated = true;
prepareFetchData();
}
public abstract void loadData();
protected void prepareFetchData() {
if (isVisibleToUser && isViewInitiated ) {
loadData();
}
}
}
好的,这样我们遍完成了viewpager的懒加载,这时又会出现一个问题,加入当我们翻到第二页的时候,setUserVisibleHint便又会被置为true,如图最下方:
这也就会造成已经加载过的页面又被加载一次的局面,这时我们就可以声明一个标志位来告诉改界面是否已经加载过数据,如下:
public abstract class BasePagerFragment extends Fragment {
protected AppCompatActivity mActivity;
protected boolean isViewInitiated; //控件是否初始化完成
protected boolean isVisibleToUser; //页面是否可见
protected boolean isDataInitiated; //数据是否加载
@Override
public void setUserVisibleHint(boolean isVisibleToUser) { //page 1等会此读数据 生命周期
super.setUserVisibleHint(isVisibleToUser);
this.isVisibleToUser = isVisibleToUser;
prepareFetchData(false);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) { //page 0进入再此读数据
super.onActivityCreated(savedInstanceState);
isViewInitiated = true;
prepareFetchData(false);
}
public abstract void loadData();
protected void prepareFetchData(boolean forceUpdate) {
if (isVisibleToUser && isViewInitiated && (!isDataInitiated || forceUpdate)) {
loadData();
isDataInitiated = true;
}
}
}
可以看到我们加标志位的同时加入了一个forceUpdate参数,这个参数是由我们控制的,默认为false,我们可以复写这个方法将它改为true,当我们改为true之后每次重新划到该页面的时候便会重新加载一遍数据以便满足实时更新等需求。
如下:
@Override
protected void prepareFetchData(boolean forceUpdate) {
super.prepareFetchData(true);
}
最后如有不足请小伙伴们提出,感谢大家的观看,完整代码我已经放到github上了,如有需要请下载,谢谢大家!!
https://github.com/lvzishen/ViewPagerLazyLoad/tree/master