记得去年面试的时候, 面了一家小公司, 那个面试官问我, fragment的懒加载作过吗?我说没作过(确实没作过).后来面试快结束了, 又问我, 懒加载没作过是吗?后来可想而知也没收到offer, (ಥ_ಥ)
一直对这个问题耿耿于怀, 不就是fragment的懒加载吗, 有那么难?因而这几天看了一下网上的一些方案, 最终以为仍是本身亲手敲一遍好.面试
懒加载的原理其实挺简单的, 最主要的就是利用fragment中的setUserVisibleHint(boolean isVisibleToUser)
方法中传进来的那个isVisibleToUser
这个参数, 这个参数的字面意思是表示当前fragment是否对用户可见.注意fragment还有一个getUserVisibleHint()
的方法, 这个方法在我看来其实没什么用, 由于我试过打印这个方法的返回值, 返回为true
并不能保证用户切换到了当前fragment.ide
首先定义一个基类, BaseLazyFragment
重写setUserVisibleHint()
这个方法布局
@Override public void setUserVisibleHint(boolean isVisibleToUser) { Log.d(TAG, "setUserVisibleHint, isVisibleToUser = " + isVisibleToUser); super.setUserVisibleHint(isVisibleToUser); // 若是尚未加载过数据 && 用户切换到了这个fragment // 那就开始加载数据 if (!mHaveLoadData && isVisibleToUser) { loadDataStart(); mHaveLoadData = true; } }
用一个布尔变量mHaveLoadData
来表示该fragment是否加载过数据. 若是没有加载过数据, 而且isVisibleToUser
为true
(表示用户切换到了这个fragment), 那就开始加载数据, 而后标记该fragment已经加载过数据post
加载数据这个方法是基类里的一个抽象方法, 须要子类来重写, 由于具体的加载过程是须要子类本身来实现的.
写到这里我忽然想到了那个抽象类和接口有什么区别的面试题.
在这里的情景的话, 抽象类就是帮子类统一处理了一些逻辑, 好比判断何时须要进行加载数据, 这是由父类帮咱们作好的, 子类就不须要再写重复的代码了, 而具体的请求过程是由子类本身去实现的.抽象类在这里的做用就是将重复的逻辑统一处理.
而接口的做用, 就我本身来讲用的最多的就是使用一个接口类型的变量来引用一个对象, 这样就不用去关心这个对象具体是什么, 而咱们只要知道这个对象中必定有接口中的方法, 到时候咱们就能调用这个对象的方法, 虽然咱们并不知道方法中的具体逻辑.
扯远了.
如今咱们写一个子类继承这个基类fragmentthis
@Override public void loadDataStart() { Log.d(TAG, "loadDataStart"); // 模拟请求数据 mHandler.postDelayed(new Runnable() { @Override public void run() { mData = "这是加载下来的数据"; // 一旦获取到数据, 就应该马上标记数据加载完成 mLoadDataFinished = true; if (mViewInflateFinished) { mTextView.setVisibility(View.VISIBLE); mTextView.setText(mData); mTextView.setText("这是改变后的数据"); mPb.setVisibility(View.GONE); } } }, 3000); }
在具体的fragment中模拟请求数据, 在请求完成后将mLoadDataFinished
这个变量置为true, 这个字段是继承自基类fragment的. 而后再判断是否布局加载和找控件已经完成, 防止将数据设置到控件上的时候出现控件空指针的错误.
那么咱们是在那里将mViewInflateFinished置为true的呢?在去看基类spa
@Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { Log.d(TAG, "onCreateView"); if (mRootView != null) { return mRootView; } mRootView = initRootView(inflater, container, savedInstanceState); findViewById(mRootView); mViewInflateFinished = true; return mRootView; }
咱们回到基类看onCeateView()
方法, 在这里将布局用一个mRootView
的全局变量储存起来是由于当viewpager中的fragment比较多的时候, 切换到别的fragment会致使回调onDestroyView()
方法, 再切回来的时候致使onCreateView()
和onViewCreate()
方法又被调用, 为了防止fragment从新从layout文件中加载布局致使以前设置到控件上的变量和状态丢失, 在布局初次加载完成以后用mRootView
这个变量储存起来, 当这个变量不为null时就直接复用这个布局就行了.
initRootView()
是初次从layout文件加载布局的方法, 是一个抽象方法, 由子类具体去实现, 返回的View表示fragment的布局.
在初次加载布局完成以后就是找控件的findViewById()方法了, 这也是个抽象方法, 须要子类本身去实现.
在findViewById()
完成以后就将mViewInflateFinished
置为true
, 表示加载布局和找控件完成.指针
咱们再去看子类具体实现的findViewById()方法.code
@Override protected void findViewById(View view) { mTextView = view.findViewById(R.id.section_label); mPb = view.findViewById(R.id.pb); if (mLoadDataFinished) { // 通常状况下这时候数据请求都还没完成, 因此不会进这个if mTextView.setVisibility(View.VISIBLE); mTextView.setText(mData); mPb.setVisibility(View.GONE); } }
在这个方法里, 若是找控件完成以后, 咱们马上将数据设置到控件上.可是在这以前咱们仍是须要判断一下是否数据已经加载完成, 若是数据已经加载完成, 那就将数据设置到控件上.这个mLoadDataFinished
标志位在上面已经提过.对象
这个方法其实没什么好说的继承
@Override protected View initRootView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Log.d(TAG, "initRootView"); return inflater.inflate(R.layout.fragment_tab, container, false); }
再来贴一下基类和子类的完整代码
BaseLazyFragment
public abstract class BaseLazyFragment extends Fragment { public final String TAG = getClass().getSimpleName(); public boolean mHaveLoadData; // 表示是否已经请求过数据 public boolean mLoadDataFinished; // 表示数据是否已经请求完毕 private View mRootView; // 表示开始加载数据, 但不表示数据加载已经完成 public abstract void loadDataStart(); // 表示找控件完成, 给控件们设置数据不会报空指针了 public boolean mViewInflateFinished; @Override public void onAttach(Context context) { Log.d(TAG, "onAttach"); super.onAttach(context); } @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d(TAG, "onCreate"); } @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { Log.d(TAG, "onActivityCreated"); super.onActivityCreated(savedInstanceState); } @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { Log.d(TAG, "onCreateView"); if (mRootView != null) { return mRootView; } mRootView = initRootView(inflater, container, savedInstanceState); findViewById(mRootView); mViewInflateFinished = true; return mRootView; } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); Log.d(TAG, "onViewCreated"); } protected abstract void findViewById(View view); protected abstract View initRootView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState); @Override public void onDestroyView() { Log.d(TAG, "onDestroyView"); super.onDestroyView(); } @Override public void onDestroy() { Log.d(TAG, "onDestroy"); super.onDestroy(); } @Override public void onDetach() { Log.d(TAG, "onDetach"); super.onDetach(); } @Override public void setUserVisibleHint(boolean isVisibleToUser) { Log.d(TAG, "setUserVisibleHint, isVisibleToUser = " + isVisibleToUser); super.setUserVisibleHint(isVisibleToUser); // 若是尚未加载过数据 && 用户切换到了这个fragment // 那就开始加载数据 if (!mHaveLoadData && isVisibleToUser) { loadDataStart(); mHaveLoadData = true; } } }
子类PlaceholderFragment0
public class PlaceholderFragment0 extends BaseLazyFragment { private TextView mTextView; private ProgressBar mPb; private Handler mHandler = new Handler(); private String mData; public PlaceholderFragment0() { } /** * Returns a new instance of this fragment for the given section * number. */ public static PlaceholderFragment0 newInstance() { PlaceholderFragment0 fragment = new PlaceholderFragment0(); return fragment; } @Override public void loadDataStart() { Log.d(TAG, "loadDataStart"); // 模拟请求数据 mHandler.postDelayed(new Runnable() { @Override public void run() { mData = "这是加载下来的数据"; // 一旦获取到数据, 就应该马上标记数据加载完成 mLoadDataFinished = true; if (mViewInflateFinished) { // mViewInflateFinished通常都是true mTextView.setVisibility(View.VISIBLE); mTextView.setText(mData); mTextView.setText("这是改变后的数据"); mPb.setVisibility(View.GONE); } } }, 3000); } @Override protected View initRootView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Log.d(TAG, "initRootView"); return inflater.inflate(R.layout.fragment_tab, container, false); } @Override protected void findViewById(View view) { mTextView = view.findViewById(R.id.section_label); mPb = view.findViewById(R.id.pb); if (mLoadDataFinished) { // 通常状况下这时候数据请求都还没完成, 因此不会进这个if mTextView.setVisibility(View.VISIBLE); mTextView.setText(mData); mPb.setVisibility(View.GONE); } } }