AndroidFragment 懒加载的实现及封装

关于懒加载

我们常用的多页面布局是利用TabLayout+Fragment+ViewPager组合完成的。

ViewPager是预先加载机制,ViewPager会预先加载当前界面左侧和右侧的View以便切换时快速访问

ViewPager加载机制

在界面需要的数据量少的时候,预加载能实现无缝切换的效果。但数据量一多,反而会拖慢速度、耗费资源,极大影响用户体验。

所以在这种情况下,懒加载登场了。

懒加载是APP中常用的一种加载方式,像知乎就使用了懒加载。

相比于普通的加载方式,懒加载只有在用户看到界面时才会加载数据,这样能最大程度减小资源消耗。特别是对于手机来说,本来就少的可怜的内存和流量是十分珍贵的。

接下来,我们同样利用TabLayout+Fragment+ViewPager来实现多页面切换的懒加载。


具体实现

Fragment中有一个方法setUserVisibleHint(),在Fragment的可见状态变更时该方法会被调用。

这个方法就是懒加载的实现关键。

我们定义变量isVisibleToUser来存储Fragment的可见状态:

public void setUserVisibleHint(boolean isVisibleToUser)
{
    super.setUserVisibleHint(isVisibleToUser);
    this.isVisibleToUser = isVisibleToUser;
    lazyInitData();
}

由于setUserVisibleHint()的回调时机与Fragment的生命周期并没有100%关联。因此 引入另一个标志位isPrepareView判断View是否加载完成 以避免空指针

public void onViewCreated(View view, @Nullable Bundle savedInstanceState)
{
    super.onViewCreated(view, savedInstanceState);
    isPrepareView = true;
}

接着,进行数据初始化,引入第二个标志位isInitData。在数据加载完成之后,将其赋值为true

至此,我们的懒加载方法考虑了所有条件。也就是当isVisibleToUsertrueisInitDatafalseisPrepareViewtrue时。

接着完成数据加载方法,并且加载后为了防止重复调用,将isInitData赋值为true

private void lazyInitData()
{
    //全部符合条件才进行加载
    if(!isInitData && isPrepareView && isVisibleToUser)
    {
        isInitData = true;
        initData();
    }
}

lazyInitData()的调用时机,首先是在setUserVisibleHint()

public void setUserVisibleHint(boolean isVisibleToUser)
{
    super.setUserVisibleHint(isVisibleToUser);
    this.isVisibleToUser = isVisibleToUser;
    lazyInitData();
}

然后,为了防止在某些情况下setUserVisibleHint()onCreateView()之前被调用,导致不主动加载数据。

我们需要在View创建完成后再执行一次该方法:

public void onActivityCreated(@Nullable Bundle savedInstanceState)
{
    super.onActivityCreated(savedInstanceState);
    lazyInitData();
}

Fragment被回收后,如果没有做缓存处理,就需要重置状态,不然再次划回去就不会自动加载数据。
onDestroy()onDestroyView()方法中将isInitDataisPrepareView重新赋值为false

@Override
public void onDestroy()
{
    isInitData = false;
    isPrepareView = false;
    super.onDestroy();
}

@Override
public void onDestroyView()
{
    isInitData = false;
    isPrepareView = false;
    super.onDestroyView();
}

至此,Fragment的懒加载已经实现完毕。


封装

为了更好的复用性以及遵守面向对象思想,我们对懒加载的实现进行封装。

创建一个继承Fragment的抽象类,实现懒加载主要方法;

任何需要懒加载功能的Fragment只需要继承该类并重写相关方法即可实现。

该类代码如下:

/**
 * 抽象封装的懒加载Fragment
 */
public abstract class AbsLazyFragment extends Fragment
{
    private boolean isPrepareView = false;  //用来确定View是否创建完成
    private boolean isInitData = false;     //数据是否加载完成
    private boolean isVisibleToUser = false;//Fragment是否可见

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState)
    {
        return super.onCreateView(inflater, container, savedInstanceState);
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState)
    {
        super.onViewCreated(view, savedInstanceState);
        isPrepareView = true;
    }

    /**
     * 由子类实现具体的加载数据方法
     */
    abstract void initData();

    /**
     * 懒加载
     */
    private void lazyInitData()
    {
        //全部符合条件才进行加载
        if(!isInitData && isPrepareView && isVisibleToUser)
        {
            isInitData = true;
            initData();
        }
    }

    /**
     * Fragment可见状态变化时该方法被调用
     * @param isVisibleToUser
     */
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser)
    {
        super.setUserVisibleHint(isVisibleToUser);
        this.isVisibleToUser = isVisibleToUser;
        lazyInitData();
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState)
    {
        super.onActivityCreated(savedInstanceState);
        lazyInitData();
    }
    
    //Fragment给回收后需重新加载
    @Override
    public void onDestroy()
    {
        isInitData = false;
        isPrepareView = false;
        super.onDestroy();
    }

    @Override
    public void onDestroyView()
    {
        isInitData = false;
        isPrepareView = false;
        super.onDestroyView();
    }
}

子类通过继承抽象类实现懒加载:

public class CustomFragment extends AbsLazyFragment implements SwipeRefreshLayout.OnRefreshListener
{
    private SwipeRefreshLayout sl;
    private TextView tv;

    private int uid;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState)
    {
        View view = inflater.inflate(R.layout.fragment_custom, container, false);
        sl = (SwipeRefreshLayout)view.findViewById(R.id.fragment_sl);
        tv = (TextView)view.findViewById(R.id.fragment_tv);

        sl.setOnRefreshListener(this);
        sl.setRefreshing(false);

        return view;
    }

    public int getUid()
    {
        return uid;
    }

    public void setUid(int uid)
    {
        this.uid = uid;
    }

    @Override
    public void onRefresh()
    {
        initData();
    }

    /**
     * 重写加载数据方法
     */
    @Override
    void initData()
    {
        tv.setText("");
        sl.setRefreshing(true);
        sl.postDelayed(new Runnable()   //模拟耗时操作
        {
            @Override
            public void run()
            {
                tv.setText("fragment" + getUid());
                sl.setRefreshing(false);
            }
        }, 2000);
    }

}

Demo地址

GitHub