SanfenR的博客

Android-Fragment懒加载

ViewPager的预加载

在项目中,经常会遇到ViewPager+TabLayout实现对多个fragment的管理。但是由于ViewPager的预加载(默认的预加载为1),viewpager会调用fragment的onCreateView()进行fragmemt的初始化:

fragment

而在Android中当fragment的onCreateView()会将整个xml文件中的UI控件实例到内存,而且数据的请求初始化基本也是在onCreateView()中进行。导致App启动的时候加载的数据过多。

解决方案

解决这个问题有两种方式,一种是禁止ViewPager的预加载,重写ViewPager,但是该方法会出现左右滑动时会出现卡顿现象,带来不好的用户体验。而另外一种就是我们接下来要讲的通过Fragment的懒加载来实现。当用户切换到某个fragment时再加载。

setUserVisibleHint()方法

懒加载主要使用到fragment中的setUserVisibleHint(),这个方法是当fragment的UI显示状态变化时会被调用。源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 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>
*
* <p><strong>Note:</strong> This method may be called outside of the fragment lifecycle.
* and thus has no ordering guarantees with regard to fragment lifecycle method calls.</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;
}

实现

  1. 因为fragment的onCreateView()方法会将xml中的控件加载到内存中,所以我们定义一个空的FragmentLayout。

创建一个空的容器作为预加载界面

1
2
3
4
5
6
7
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/other_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.mz.sanfen.lazyfragment.OtherFragment">
</FrameLayout>

并在onCreateView()中初始化这个布局

1
2
3
4
5
6
7
8
9
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
// Inflate the layout for this fragment
View inflate = inflater.inflate(R.layout.fragment_other, container, false);
frameLayout = (FrameLayout) inflate.findViewById(R.id.one_frame);
Log.e(TAG, "onCreateView: " + frameLayout );
return inflate;
}
  1. 将具体的内容的UI定义到一个子xml中,并在setUserVisibleHint()方法中进行初始化控件和数据加载,并将这个xml放入外部的容器中。

重新创建一个子布局

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/other_text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/hello_blank_fragment" />
</LinearLayout>

并在setUserVisibleHint()中进行初始化

1
2
3
4
5
6
7
8
9
10
11
12
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
// Log.e(TAG, "setUserVisibleHint: " + isVisibleToUser );
if (isVisibleToUser) {
if (contentView == null ) {
contentView = LayoutInflater.from(getActivity()).inflate(R.layout.content_other, frameLayout, true);
textView = (TextView) contentView.findViewById(R.id.other_text);
textView.setText("load other fragment");
}
}
}

注意

ViewPager在预加载fragment的时候是先调用setUserVisibleHint(), 然后再是调用onCreateView()方法。

image

源码传送门