文章目录
1. 前言2. ViewPager+Fragment优化2.1 预加载2.2 懒加载3. 后记1. 前言
ViewPager
+Fragment
的组合比较适合用来做页面的导航,这里因为在Android
插件化开发指南——实践之仿酷狗音乐首页一文的实践中需要用来这块的知识。为了app
加载更加流畅,这里考虑使用预加载和懒加载两种机制。当然,这里对于ViewPager
+Fragment
的简单实现,这里记录下:
首先定义好ViewPager
控件:
<androidx.viewpager.widget.ViewPagerandroid:id="@+id/fx_viewpager"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"/>
然后定义好需要显示的三个子项的布局文件:
然后定义一个ViewPager
控件的适配器:
public class PageViewPagerAdapter<T extends View> extends PagerAdapter {// 外部传入的ViewPager对应的Item对象private List<T> mList;public PageViewPagerAdapter(List<T> mList) {this.mList = mList;}@Overridepublic int getCount() {return this.mList.size();}@Overridepublic boolean isViewFromObject(@NonNull View view, @NonNull Object object) {return object == view;}@NonNull@Overridepublic Object instantiateItem(@NonNull ViewGroup container, int position) {container.addView(mList.get(position));return mList.get(position);}@Overridepublic void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {container.removeView(mList.get(position));}}
然后只需要在Fragment
中完成初始化item
到View
实例,并设置adapter
,最后设置OnPageChangeListener
监听即可。这里我的效果为:
2. ViewPager+Fragment优化
预加载和懒加载。ViewPager
控件有个特有的预加载机制,即默认情况下当前页面左右两侧的1
个页面会被加载,以方便用户滑动切换到相邻的界面时,可以更加顺畅的显示出来。这样就会导致本来加载一个页面,其实在背后会预先加载三个页面,也就是会导致内存消耗比较严重。如果页面的数据也很大的时候,可能存在极端的情况,即将内存撑爆,也就是OOM
问题。
所以在内存消耗比较低的场景中,可以使用预加载技术来提高响应时间,进而带来比较丝滑的滑动效果。在内存消耗比较高的场景中,对应的需要使用懒加载技术,来延迟资源的加载。懒加载对服务器端和客户端内存有一定的缓解压力作用,预加载则会增加服务器和和客户端压力。
2.1 预加载
在ViewPager
中,可以通过setOffscreenPageLimit(int limit)
来设置预加载页面数量,当前页面相邻的limit
个页面会被预加载进内存。不妨来看看源码:
// ViewPager.javaprivate static final int DEFAULT_OFFSCREEN_PAGES = 1;private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES;public void setOffscreenPageLimit(int limit) {if (limit < DEFAULT_OFFSCREEN_PAGES) {// 如果小于1limit = DEFAULT_OFFSCREEN_PAGES; // 直接设置为1}if (limit != mOffscreenPageLimit) {// 如果大于1mOffscreenPageLimit = limit; // 设置预加载页面数量为设置的limipopulate(); // 填充}}
从上面的代码中可以看出,预加载无法按照预想的,将limit
设置为0
来取消预加载。所以我们需要考虑其余的方式来实现取消ViewPager
+Fragment
的预加载。在博客 ViewPager+Fragment取消预加载(延迟加载)一文中给出了一个解决的思路。即:
通过判断Fragment
对用于的可见性来实现,也就是在这个Fragment
对用户可见了再进行数据的加载。而再Fragment
中提供了两个方法,分别是:
boolean getUserVisibleHint() // 获得Fragment可见状态void setUserVisibleHint(boolean isVisibleToUser) // 设置Fragment可见状态
我们只需要复写这两个方法即可,就可以判断当前的Fragment
是否可见,进而判断是否进行数据的加载。其实根据上面的分析,这里我们知道根本上这个Fragment
还是会加载,只是我们将那些实际请求数据的操作放置在了之后,其实也就是懒加载。
也即是说,取消Fragment
预加载的解决为使用懒加载。因为预加载从前面代码中我们知道,解决不了,且默认设置为1
,也就是会预加载左右两个页面。
2.2 懒加载
比如定义如下一个懒加载的Fragment
父类:
public abstract class LazyFragment extends Fragment {private View rootView; // 表示当前View实例对象private boolean isViewCreated = false; // rootView是否创建private boolean isDatasLoaded = false; // 是否加载过private boolean isCurrentVisible = false; // 是否可见@Nullable@Overridepublic View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {if(rootView == null){rootView = inflater.inflate(getLayoutResources(), container, false);}isViewCreated = true; // rootView创建完毕if(getUserVisibleHint()) setUserVisibleHint(true);return rootView;}protected View getRootView(){return rootView;}@Overridepublic void setUserVisibleHint(boolean isVisibleToUser) {super.setUserVisibleHint(isVisibleToUser);if(isViewCreated){Log.e("TAG", "isCurrentVisible: " + isCurrentVisible + " | isVisibleToUser: " + isVisibleToUser );if(!isCurrentVisible && isVisibleToUser){// 加载数据if(!isDatasLoaded) lazyLoadData();isDatasLoaded = true;isCurrentVisible = isVisibleToUser;}else if(isCurrentVisible && !isVisibleToUser){// 停止加载数据stopLoadData();isCurrentVisible = isVisibleToUser;}}}/*** 对子类提供一个查找元素的方法*/protected <T extends View> T findViewById(int id) {if(isViewCreated) return (T) rootView.findViewById(id);return null;}/*** 由具体子类来实现这个方法,以实现返回当前页面的布局文件ID*/protected abstract int getLayoutResources();/*** 提供两个方法,用来进行加载数据,或者停止加载数据*/protected abstract void lazyLoadData();protected void stopLoadData(){}}
然后将原本传入ViewPager
的直接通过LayoutInflater
实例化的View
对象换成Fragment
对象。同时,将适配器修改为继承自FragmentPagerAdapter
的类:
public class PageViewPagerAdapter<T extends Fragment> extends FragmentPagerAdapter {// 外部传入的ViewPager对应的Item对象private List<T> mList;public PageViewPagerAdapter(@NonNull FragmentManager fm, List<T> mList) {super(fm);this.mList = mList;}@NonNull@Overridepublic Fragment getItem(int position) {return mList.get(position);}@Overridepublic int getCount() {return this.mList.size();}}
然后再创建ViewPager
关联的三个页面的Fragment
的时候,就需要继承自前面所定义的LazyFragment
,比如在下面的示例中,我使用Handler
发送一个延迟消息,来模拟数据的耗时加载:
public class FxPageMusicFragment extends LazyFragment {private View rootView;@Nullable@Overridepublic View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {super.onCreateView(inflater, container, savedInstanceState);rootView = getRootView();initViews();return rootView;}private void initViews() {// todo 初始化一些数据}@Overrideprotected int getLayoutResources() {// 设置布局文件-音乐return R.layout.fx_viewpager_item_yy;}@Overrideprotected void lazyLoadData() {Log.e("TAG", "lazyLoadData: 音乐加载数据");// todo 加载数据模拟Handler handler = new Handler(Looper.getMainLooper()){@Overridepublic void handleMessage(@NonNull Message msg) {super.handleMessage(msg);if (msg.what == 0){Button button = findViewById(R.id.yy_page_loading_button);button.setText("数据加载完毕。");}}};Message msg = new Message();msg.what = 0;handler.sendMessageDelayed(msg, 1000);}@Overrideprotected void stopLoadData() {super.stopLoadData();Log.e("TAG", "lazyLoadData: 音乐停止加载数据");}}
最后的效果为:
当然,对于ViewPager
+Fragment
优化的懒加载处理这块,我看的bilibili
的视频:懒加载方案源码解析之一。
3. 后记
对于ViewPager
+Fragment
优化的懒加载处理,主要参考了上面的那个视频。然后简单修改了一部分。从观看视频到依葫芦画瓢的这个过程,确实也说明了自己知识储备确实不够。还需要多加练习。
References
Android懒加载vs预加载------Viewpager+FragmentViewPager+Fragment组合的预加载和懒加载ViewPager+Fragment取消预加载(延迟加载)【视频】懒加载方案源码解析之一