知乎的单Activity+多Fragment客户端在使用的时候真的是如丝袜版顺滑,给知乎团队笔芯,可是Fragment在使用过程当中会遇到各类各样的问题,平时使用都费劲,要写这么一个客户端不得吐血?java
本篇文章介绍一个关于Fragment
的管理框架FragmentRigger。
这个框架的目标只有两个:一、让Fragment的使用更简单。二、使用成本最低的Fragment框架。
本篇先对该框架产生的背景进行说明,接着介绍该框架解决的问题并给出部分解决方案,最后,介绍该框架的用法(水star三部曲)。git
疑问一: 你可能会问了,网上关于Fragment的框架不是一抓一大把,为何还要重复造轮子呢?github
是的,关于Fragment
的框架在网上是比较多的,如比较出名的YoKeyword大神的Fragmentation,解决了各个场景下的Fragment问题,并添加了左滑退出等额外的支持,不可谓不强大,请收下个人膝盖。数组
疑问二: 请正面回答疑问一,这个框架有什么不同的地方吗?难道是老农民吗?重复造轮子闲的蛋疼?安全
网上大多数的Fragment
框架都是写了一个Fragment
和Activity
父类,并添加了相应的方法支持,因此在使用那些框架的时候须要你的Activity
和Fragment
继承他们框架提供的父类(不知怎么的,笔者对继承别人的父类总是有点排斥)。
是啊,Fragment
的不少操做都是生命周期相关的,因此不继承父类按理说是没法进行Fragment
的管理的,可是FragmentRigger
就是可让你在集成的时候不须要继承任何类就能够对Fragment进行操做!!!(固然,你本身的父类仍是要继承的= =)框架
疑问三: 啥?不继承??那怎么使用???会不会更复杂?ide
复杂??本框架的目的就是让Fragment的使用更加简单,好了,废话不BB,仍是来一行代码最省事。svg
//在Activity中add并show BFragment.
@Puppet(containerViewId = R.id.container)
public class AActivity extend AppcompatActivity{
//触发显示操做
Rigger.getRigger(this).startFragment(BFragment.newInstance());
}
复制代码
没骗你吧,上述的代码有没继承,调用一行代码,成本只有一行注解就可使用!!!源码分析
疑问四: 代码这么少,也不继承,靠不靠谱啊?动画
重温一下本框架的目的:让Fragment的使用更加简单,不继承是由于确实有不少人排斥使用第三方的父类,笔者也不例外,就算知道里面没什么要紧的事,但仍是极度没有安全感,框架的原理是:使用AOP把Activity/Fragment
的生命周期等方法定义为切点,插入到代理类中,一切操做都经过代理类来进行!!!
上节扯的本身框架多牛逼多牛逼,但都是纸上谈兵,还不如来点实际的,这节将列举平时遇到的问题并给出其中一些问题的解决方案,这样你总该放心了吧!!!我不仅是来骗star的!!!
由于在使用Fragment的时候常常遇到错误,并且有些场景在实现的时候无从下手,那么在Fragment使用过程当中让咱们头痛的问题有哪些呢?下面咱们一一列举一下。
难点一: 在使用过程当中遇到让人抓狂的异常抛出!!
难点二: 使用Fragment自己遇到的错误
难点三: 其余问题
上述只是在使用Fragment中遇到的部分问题,种种恶行,罄竹难书!!! 可是这些问题都在FragmentRigger中被解决了!!!
那么这些问题是如何解决的呢?因为篇章限制,下面列举几个特别常见的问题的解决方法。
已解决:Can not perform this action after onSaveInstanceState
咱们先来看看这个异常的抛出的出处,这是在FragmentManager
中被抛出的,源码以下:
private void checkStateLoss() {
if (mStateSaved) {
throw new IllegalStateException(
"Can not perform this action after onSaveInstanceState");
}
}
复制代码
而这个方法是在方法enqueueAction(Runnable,boolean)
中被调用的,调用代源码以下:
public void enqueueAction(Runnable action, boolean allowStateLoss) {
if (!allowStateLoss) {
checkStateLoss();
}
}
复制代码
这个方法会在提交事物的时候调用,而且参数也是在那时候传递的,因此,使用commitAllowingStateLoss
方法确实能够避免该异常的抛出,可是此次提交可能丢失,因此这并非最好的解决方案。 使用该方法只能说是避免异常,并非解决异常!!!
因此要解决该异常,咱们须要知道mStateSaved
方法是何时被置为true
的 ,经过源码分析(请自行分析,此处对分析过程不进行阐述),发现mStateSaved
会在Activity#onStop
调用时被置为true
。而onSaveInstanceState
是在onStop
以前被调用的,那么这个错误的意思也是没毛病的。
那么咱们如何解决这个问题呢,Activity
生命周期中onSaveInstanceState
方法以前执行的是onPause
方法,因此咱们只须要判断onPause
是否被执行,并在已经被执行的时候不进行事物提交便可!!! 贴心的是在Fragment
中提供了方法isResumed()
能够判断该状态,咱们能够手动在Activity
中实现该方法。
那么最终解决方案就是:在Activity/Fragment非onResume的状态下不要提交事物,保存下来,在onResum的状况下从新提交,就能够确保事物必定提交成功,而且不会丢失!!!
已解决:Fragment重叠显示
Fragment重叠显示的缘由就很明显了,多个Fragment被add在同一个container中,而且都是show的状态,因此会致使重叠!!! 这个的解决方案 在YoKeyword的文章《9行代码让你App内的Fragment对重叠说再见》中已经解决,就不在此进行重复了。
已解决:没法监听onBackPressed
这个问题相比是大多数人都有的需求,可是奈何Fragment
中并无该方法的支持,因此咱们只能手动去实现该功能。
解决方案:在Fragment中定义方法onBackPressed
,并在Activity中遍历所持有的Fragment并对该方法进行调用。
一切看似很简单,可是时候存在一系列新的问题,如:在Fragment入栈以后多级嵌套后的传递顺序问题、在栈内该方法的拦截问题等。 实现起来成本仍是很大的。不过,在FragmenTRigger 中这个问题获得了合理的解决。
已解决:在ViewPager中使用懒加载
ViewPager
为咱们提供了预加载的机制,但这种机制在使用的时候有时候反而不是好事,若是咱们经过setOffscreenPageLimit
设置的条目少了会让在切换的时候从新生成Fragment
实例,但要是添加的多了则会让好多Fragment
同时被初始化,因此此时,使用懒加载能够有效处理该场景,只有在显示的时候进行数据加载等行为,而且在正常状况下只加载一次。
那么咱们如何在ViewPager
中加入懒加载呢?经过源码分析,ViewPager
是经过setUserVisibleHint(boolean)
来控制Fragment
是否显示的,因此咱们能够在Fragment
中重写该方法,并根据传入的boolean
值判断Fragment
是否显示的状态,可是须要注意的是,咱们须要进行Fragment是否手机加载的判断进行是否懒加载的调用,不然,ViewPager
每次切换都会调用setUserVisibleHint
。
解决方案:在Fragment中重写setUserVisibleHint()方法,而且定义一个懒加载的方法如:onLazyLoad(),根据setUserVisibleHint()传入的值判断Fragment是否显示,并调用懒加载方法。
样例代码以下:
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
if (!mHasInitView||!isVisibleToUser) return;
//make sure the method onLazyViewCreated will be called only once.
if (mHasInvokeLazyLoad) return;
onLazyLoad();
}
复制代码
固然,上述只是伪代码,不过进行懒加载的原理就是这样。
上面列举了部分Fragment
在使用过程当中遇到的问题给给出部分解决方案,看上去好像是这么解决的啊,因此,我不是骗子啦~接下来正式对框架FragmentRigger进行介绍。
一个强大的Fragment框架,目标:让Fragment的使用更简单!!
这多是使用成本最低的Fragment框架了。
无需继承!!!无需继承!!!无需继承!!! 重要的话说三遍!!
在使用FragmentRigger
的时候,使用成本只有一行注解!!!
原理是把Fragment
/Activity
生命周期相关方法定义为切点,经过ASpectJ绑定并使用代理类进行操做。
onBackPressed
等常见的方法支持内存重启
时的一系列异常栈管理 | 懒加载 | 同级显示 |
---|---|---|
![]() |
![]() |
![]() |
支持Fragment同级\多层嵌套,并提供返回自动显示栈顶成员等一系列场景支持 | 支持ViewPager 等场景下的懒加载机制,使用简单,一行注解就能够支持 |
经过show 方法显示Fragment ,支持预加载,懒加载等场景 |
为了保持篇章的简洁和美观性,其余的场景具体请在项目中查看!!!
本框架在开始的时候就声明强大的Api支持,那么本节举例几个场景。
场景一: Fragment懒加载
在前面也对懒加载提出了相应的解方案,那么在本框架中是怎样使用的呢?请看下面代码:
@LazyLoad
@Puppet
public class ContainerFragment extends Fragment{
public void onLazyLoadViewCreated(Bundle savedInstanceState) {
//do something in here
}
}
复制代码
使用成本: 两行注解,一个方法,不须要继承父类!!!
场景二: 转场动画
Fragment为咱们提供了转场动画机制,可是在使用的时候须要和事物提交一块儿使用,而且在remove的时候不支持转场动画。
@Animator(enter=R.anim.enter,exit=R.anim.exit,popEnter=R.anim.popEnter,popExit=R.anim.popExit)
@Puppet
public class AnimatorFragment extends Fragment{
}
复制代码
使用成本: 两行注解,一个方法,不须要继承父类!!!
那么问题来了,如何在library
使用该注解呢,由于在library
中R中的资源id都是变量,没法直接在注解中使用,本框架对此也进行了相应的解决方案。
@Puppet
public class AnimatorFragment extends Fragment{
public int[] getPuppetAnimations(){
return new int[]{
R.anim.enter, R.anim.exit, 0, 0
};
}
}
复制代码
不须要支持某场景的转场动画就置为0,可是返回参数必须为长度为4的int数组。无需继承,直接添加该方法便可!!!
场景三: 栈管理
本框架彻底摒弃了原生的栈,内部本身维护了栈进行管理!!!那么如何打开一个Fragment进行入栈操做呢?请看下面代码:
@Puppet(containerViewId = R.id.container)
public class AActivity extend AppcompatActivity{
//触发显示操做
Rigger.getRigger(this).startFragment(BFragment.newInstance());
}
复制代码
使用成本: 一行注解,一行代码,不须要继承父类!!!
本框架甚至提供了栈的树状图的打印,能够实时查看内部栈的成员!!出栈的时候默认会显示栈顶的成员,无需再进行额外的显示操做,还有onBackPress在栈成员中的调用并支持任意层级的拦截!!!
场景四: onBackPressed及其拦截
本框架为栈内的Fragment提供onBackPressed方法的支持!!并支持任意层级的拦截,传递顺序由外至内!!!
@Puppet
public class StackFragment extends Fragment{
public void onRiggerBackPressed(){
//Rigger.getRigger(this).onBackPressed();
//不拦截不须要写该方法,如有该方法则可在此方法中进行拦截,上行代码为调用默认的返回代码。
}
}
复制代码
使用成本: 一行注解,一个方法,不须要继承父类!!!
若是须要调用默认的返回方法,使用Rigger.getRigger(this).onBackPressed()
便可。
上述场景支持该框架的一部分使用方式,具体使用请看Wiki
本项目遵循MIT开源协议. 浏览LICENSE查看更多信息.