基于AOP设计的Fragment框架

知乎的单Activity+多Fragment客户端在使用的时候真的是如丝袜版顺滑,给知乎团队笔芯,但是Fragment在使用过程中会遇到各种各样的问题,平时使用都费劲,要写这么一个客户端不得吐血?

本篇文章介绍一个关于Fragment的管理框架FragmentRigger,本篇先对该框架产生的背景进行说明,接着介绍该框架解决的问题并给出部分解决方案,最后,介绍该框架的用法(水star三部曲)。

一、抛出诱饵

疑问一: 你可能会问了,网上关于Fragment的框架不是一抓一大把,为什么还要重复造轮子呢?

是的,关于Fragment的框架在网上是比较多的,如比较出名的YoKeyword大神的Fragmentation,解决了各个场景下的Fragment问题,并添加了左滑退出等额外的支持,不可谓不强大,请收下我的膝盖。

疑问二: 请正面回答疑问一,这个框架有什么不一样的地方吗?难道是老农民吗?重复造轮子闲的蛋疼?

网上大多数的Fragment框架都是写了一个FragmentActivity父类,并添加了相应的方法支持,所以在使用那些框架的时候需要你的ActivityFragment继承他们框架提供的父类(不知怎么的,笔者对继承别人的父类老是有点排斥)。
是啊,Fragment的很多操作都是生命周期相关的,所以不继承父类按理说是无法进行Fragment的管理的,但是FragmentRigger就是可以让你在集成的时候不需要继承任何类就可以对Fragment进行操作!!!(当然,你自己的父类还是要继承的= =)

疑问三: 啥?不继承??那怎么使用???会不会更复杂?

复杂??本框架的目的就是让Fragment的使用更加简单,好了,废话不BB,还是来一行代码最省事。

//在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的!!!

1、常见难点

因为在使用Fragment的时候经常遇到错误,而且有些场景在实现的时候无从下手,那么在Fragment使用过程中让我们头痛的问题有哪些呢?下面我们一一列举一下。

难点一: 在使用过程中遇到让人抓狂的异常抛出!!

  • Can not perform this action after onSaveInstanceState
  • Can not perform this action inside of
  • Activity has been destroyed
  • FragmentManager is already executing transactions

难点二: 使用Fragment本身遇到的错误

  • getActivity()返回null
  • remove一个Fragment之后转场动画不执行问题
  • Fragment栈的各种问题
  • Fragment多层嵌套的问题
  • Fragment重叠显示
  • 屏幕翻转时(内存重启)后Fragment遇见的问题
  • 在一个ContainerView中添加两个Fragment,第一个Fragment还可以被点击问题
  • 提交事物后无法立即执行导致的各种问题

难点三: 其他问题

  • 无法监听onBackPressed
  • 在ViewPager中使用懒加载
  • Fragment多层嵌套时入栈出栈问题
  • Fragment事物提交失败
  • 多个Fragment同时入栈/出栈问题

上述只是在使用Fragment中遇到的部分问题,种种恶行,罄竹难书!!! 但是这些问题都在FragmentRigger中被解决了!!!

2、解决方案

那么这些问题是如何解决的呢?由于篇章限制,下面列举几个特别常见的问题的解决方法。

已解决: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绑定并使用代理类进行操作。

1、Wiki

2、特性

  • 超强大Api支持
  • 足够多的英文注释
  • 严格的异常抛出
  • 解决Fragment中常见的异常及Bug
  • 事务提交永不丢失
  • 扩展原生方法,添加onBackPressed等常见的方法支持
  • 当前栈成员树状图打印
  • Fragment懒加载
  • Fragment转场动画
  • Fragment间共享元素转场动画(TODO)
  • Kotlin支持(TODO)

3、解决的问题

  • Fragment界面重叠
  • Fragment多级嵌套
  • Fragment栈的管理问题
  • Fragment事务提交失败
  • Activity在非onResume状态下提交事务
  • Fragment事务提交不能立即执行导致两次提交事件冲突
  • 内存重启时的一系列异常
  • 屏幕翻转时的数据保存及恢复
  • Can not perform this action after onSaveInstanceState
  • 在ViewPager中的懒加载及其他场景下的懒加载
  • 不同场景下转场动画不执行问题

4、Demo演示

栈管理

懒加载

同级显示

为了保持篇章的简洁和美观性,其他的场景具体请在项目中查看!!!

5、超强大Api支持演示

本框架在开始的时候就声明强大的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数组。无需继承,直接添加该方法即可!!!

场景三: 栈管理

<img src="https://user-gold-cdn.xitu.io/2017/12/19/1606d79e3a968dae?w=441&h=456&f=png&s=25385" width = "250px" align="right"/>

本框架完全摒弃了原生的栈,内部自己维护了栈进行管理!!!那么如何打开一个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

6、开源协议

MIT

本项目遵循MIT开源协议. 浏览LICENSE查看更多信息.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,044评论 25 707
  • 上一篇总结了Activity的那些事,有兴趣的可以前往传送门:Activity你真的熟悉吗?看了才知道,这一篇将给...
    世锋日上阅读 23,765评论 16 119
  • 余年未知数十载,光华文章绝世才。 中秋三五乡愁月,思恋故土也成灾。
    杨书璞阅读 268评论 0 2
  • 今天同学跟我说,我很像她以前玩的好的闺蜜,给人的感觉,以及说话谈吐举止都很像,就是感觉和她一样的性格,一样的说...
    iiial阅读 335评论 0 0
  • 1.但凡未得到,但凡是过去,总是最登对。《似是故人来》 ——所以和你生活的那一个,永远是second best 。...
    秘诀阅读 1,046评论 2 1