Android 浅谈Fragment懒加载

在Android开发中一旦涉及到和ViewPager使用,有时候ViewPager的预加载功能很好用,可是在某些情况下我们需要这种交互,只有跳转到当前页面时才进行数据加载。在以前版本中有些实现就是将ViewPager源码copy一份出来,把预加载的常量设置为0,这样就可以屏蔽预加载功能了,随着Android版本SDK的升级,现在这种实现方式在新SDK版本已经不再起作用了。那么我们只有再找其它方式了,本文讨论的这种方式也是各大论坛博客中常见的一种实现方式:ViewPager+Fragment。

一般我们切换Fragment的时候使用是FragmentTransaction,但是这种方式不论是使用add或者replace的时候都会执行Fragment的onDestoryView()方法,也就是说会执行Fragment完整的生命周期方法,但是这和我们需要的交互有很大的区别,懒加载不仅仅是用到时才加载页面数据,而且要求一旦加载成功后即使返回默认不需要再次重新加载,除非用户需要自己手动加载。在介绍懒加载之前,先介绍一下Fragment中两个不常用的方法:

public void setMenuVisibility(boolean menuVisible)
public void setUserVisibleHint(boolean isVisibleToUser)

这两个方法都是public修饰符修饰的方法,也就是说我们可以在外部调用。实际上这两个方法在刚开始Fragment和ViewPager结合的时候就已经被使用了,只是Android本身内部实现时调用的,我们可以在FragmentPagerAdapter中一睹真容。

public void setPrimaryItem(ViewGroup container, int position, Object object) {
	Fragment fragment = (Fragment)object;
	if (fragment != mCurrentPrimaryItem) {
		if (mCurrentPrimaryItem != null) {
			mCurrentPrimaryItem.setMenuVisibility(false);
			mCurrentPrimaryItem.setUserVisibleHint(false);
		}
		if (fragment != null) {
			fragment.setMenuVisibility(true);
			fragment.setUserVisibleHint(true);
		}
		mCurrentPrimaryItem = fragment;
	}
}

public void setMenuVisibility(boolean menuVisible)

该方法用于设置Fragment对象的菜单是否应该显示。如果该Fragment对象已经被放到了View的层次树中,而用户当前还看不到它,所以该Fragment对象的任何菜单也是不可见,这时调用这个方法就可以帮助显示和隐藏菜单。获取Fragment中菜单是否可见的方法是isMenuVisible()。

public void setUserVisibleHint(boolean isVisibleToUser)

该方法用于设置Fragment的UI是否对用户可见,默认是true,获取当前Fragment的UI是否对用户可见的方法是getUserVisibleHint()。

上面setUserVisibleHint()和setMenuVisibility()两个方法都可以用于标记Fragment的UI是否可见了,实现懒加载的目标是只有在UI可见时才执行数据加载,所以在用于标记Fragment的UI是否对可见使用任何一种方式都可以,本文示例中使用的是setUserVisibleHint()方法。接下来看一下setUserVisibleHint()方法处于生命周期的哪个阶段。

Fragment01: setUserVisibleHint:false
Fragment01: setUserVisibleHint:true
Fragment01: onAttach
Fragment01: onCreate
Fragment01: onCreateView
Fragment01: onViewCreated
Fragment01: onActivityCreated
Fragment01: onStart
Fragment01: onResume
Fragment01: setUserVisibleHint:false
Fragment01: onPause
Fragment01: onStop
Fragment01: onDestroyView
Fragment01: onDestroy
Fragment01: onDetach

当将ViewPager中的所有Fragment中View都预加载时,再次切换ViewPager不同页面,我们发下这时候Fragment只是执行了setUserVisibleHint()方法,当不可见时isVisibleToUser是false而且getUserVisibleHint()返回值也是false,一旦Fragment处于当前页isVisibleToUser是true而且getUserVisibleHint()返回值也是true。

由初始化时于setUserVisibleHint()方法在Fragment生命周期方法之前会别调用两次,当不可见时此时还会再次被调用,所以如果直接在setUserVisibleHint()方法中设置数据加载会被多次调用,特别是初次调用时,即使isVisibleToUser值为true时也不可以,因为Fragment中View还没有创建成功,这时候Fragment还没有被attach到Activity中。如果想要执行懒加载必须在View创建完成之后才可以,因此我们需要在View创建完成之后设置一个标记位isPrepared,这个标记位用于标记Fragment中View是否创建完成,初次之外还需要设置一个标记位用于标记是否数据加载成功了的标记位hasDone,如果本次加载成功,则再次访问时默认不需要重新加载数据。

public abstract class LazyFragment extends Fragment {

    private static final String TAG = "Lazy" + Fragment.class.getSimpleName();

	//标记View是否创建成功
    protected boolean isPrepared;
	
	//标记是否加载成功
    protected boolean hasDone;

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (isVisibleToUser) {
            onVisible(isVisibleToUser);
        } else {
            onInVisible();
        }
    }

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

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        isPrepared = true;
        initView(view);
    }

	//如果页面没有创建完成或者不可见或者已经加载成功,则直接返回不需要继续执行
    private void onVisible(boolean isVisible) {
        if (!isPrepared || !isVisible || hasDone) {
            return;
        }
        initData();
    }

    public void onInVisible() {
    }

    public abstract void initView(View rootView);

    public abstract void initData();

    public abstract int getLayoutResId();

}

上面封装的LazyFragment是否满足我们开发需要了呢?执行之后会发现第一个Fragment始终不会显示View,原因很简单,上述类中封装的方法实际上相当于setUserVisibleHint()在用户可见时执行数据加载,而且只执行一次,但是第一个Fragment执行的时候回进行判断,由于setUserVisibleHint()方法位于Fragment生命周期方法执行之前发生,所以在执行到onVisible()方法时,isPrepared标记位为false,这时候initData()方法就不会再执行了,如果想要在首次执行时也执行initData()方法,开发者可以自己在第一个Fragment的onActivityCreated()方法中手动调用一下即可。但是如果遇到ViewPager在执行初始化时为了防止内存溢出,比如ViewPager中Fragment数目是根据服务端下发得到的,这时候手动调用一下就满足不了需要了,但是我们可以间onActivityCreated()方法中直接调用一下setUserVisibleHint(getUserVisibleHint())方法,这样在可见时就会执行initData()方法了,同样在Fragment中View销毁的时候需要重置一下标记位。

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
	super.onActivityCreated(savedInstanceState);
	setUserVisibleHint(getUserVisibleHint());
}
@Override
public void onDestroyView() {
	super.onDestroyView();
	Log.d(TAG,"onDestroyView");
	hasDone=false;
	isPrepared=false;
}

上述方式的懒加载并不是完全意义上的懒加载,实际上它会预加载一些View,等数据请求回来后再对当前页进行数据填充,但是讲过上面简单的封装,已经可以有效减少用户初次使用时流量,同样也可以提高APP的流畅性。虽然只是对逻辑进行简单的抽象剥离,但是确实是体现了这样一个思想-将架构和业务区分开来,从业务中独立出来。所有开发人员在使用到懒加载的时候只需要继承LazyFragment,这样就会让各个开发人员强制重写如下方法,每个方法到底要做什么一目了然。

@Override
public void initView(View rootView) {}

@Override
public void initData() {}

@Override
public int getLayoutResId() {return 0;}

本文对于Fragment+ViewPager的懒加载探讨就先到这里,当然了实现懒加载的方式有很多种,由于Android对Fragment提供了非常丰富的方法,可以使用FragmentTransaction的show()和hide()实现懒加载,或者结合ViewPager和FragmentPagerAdapter也可以实现懒加载,针对其它方式的懒加载后续有机会再继续探讨。

评论

您确定要删除吗?删除之后不可恢复