一.需求背景
由于本猿在一个视频app开发组,有一个页面曝光埋点需求,用来统计界面曝光率与点击率,曝光埋点目的是运营为了统计节目曝光与被点击播放的比例,所以为了实现精准的统计数据,需要每个页面展示出来时才进行曝光数据上传,同时用户切换页面之后需要重新曝光,那么就需要监听到页面的显示与隐藏状态,而该视频app架构首页采用ViewPager与Fragment的多级嵌套,外层ViewPager采用FragmentPagerAdapter内层ViewPager采用FragmentStatePagerAdapter作为适配器(FragmentPagerAdapter与FragmentStatePagerAdapter区别,请自行百度),外层ViewPager会默认缓存相邻两个Fragment,而内层Viewpager也会默认缓存相邻两个Fragment,所以每次切换外层ViewPager将会创建多个内层Fragment,这个只是两级,如果三级嵌套呢?是不是很恐怖?如果每次埋点都放在Fragment的某一个生命周期里,是不是会有好很多的错误统计?那么问题来了:
1).哪个生命周期会只有Fragment页面真正可见与隐藏时才会执行?
a).看知道这里,肯定有人会说Fragment懒加载机制setUserVisibleHint()
利用Fragment的setUserVisibleHint()方法来进行监听Fragment可见性,没错,但是需要解决几个问题
- setUserVisibleHint一定会在onViewCreated()之前执行么?
- 最外层Fragment可以用setUserVisibleHint()进行监听,那么内层的Fragmen会乖乖的听话么,会单单只执行到某一个Fragment的setUserVisibleHint()么
答案自然都是否定的,下面感受一下被Fragments们支配的恐惧,看图中日志说话(图1是常规操作,图二为啥?)
3
图中每次点击打印的日志显示在底部,只打印了最底层的Fragment的setUserVisibleHint()与onViewCreated(),请仔细观察Fragment标题与底部日志变化,可以发现,
- 图一是初次创建,生命周期会按着setUserVisibleHint()->onCreate()->onCreateView()->onViewCreated()->on....执行
- 图二由于采用FragmentPagerAdapter保存了状态导致setUserVisibleHint()最后执行
- 每次点击并不会只执行单独某一个Fragment的setUserVisibleHint()
- onCreate()甚至不执行,其他生命周期为了显示,并没有打印出来,可以自己写个Demo测试下
b)还有人肯定会说,用Fragment的onHiddenChanged()
很遗憾,这个真不行,onHiddenChanged()方法调用前提是采用了Fragment的hide与show,来代替replace会保存每个Fragment实例
- hide与show这么用的在普通页面较少的app中是可行的
- 本猿参与的项目是视频app,页面较多,都保存下来,app内存占用是不是要炸啊
2).setUserVisibleHint真的一定会执行么
这个答案也是否定的,setUserVisibleHint只会因为与ViewPager一起使用,有预加载机制才会执行,Activity下单个Fragment只会走标准的生命周期
3).那么看着这么乱七八糟的一大堆生命周期,如何抽取出正在显示与隐藏的页面每次执行一个方法呢
很高兴真的可以抽取出来,看图说话,
无论点击下面或者上面的tab,都会只有一个Fragmnet执行消失方法一个执行显示方法,效果怎么样,你要是觉得很骚的话,不妨内心默默的说一声,“哎哟,不错哦”,哈哈哈
二.实现流程
1).定制基类BaseFragment
定制基类是常规操作,封装其他相关操作,每个人可以有不同封装,这里简单封装下BaseFragment.java
public abstract class BaseFragment extends Fragment {
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
if (getLayoutId() > 0) {
View view = inflater.inflate(getLayoutId(), container, false);
initView(view);
return view;
} else {
return super.onCreateView(inflater, container, savedInstanceState);
}
}
protected abstract void initView(View view);