您的位置:首页 > 运维架构

基于AOP设计的Fragment框架

2017-12-25 00:00 281 查看
摘要: 这可能是使用成本最低的Fragment框架了,提供超级强大的Api支持,无需继承!!!

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

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

一、抛出诱饵

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

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

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

网上大多数的
Fragment
框架都是写了一个
Fragment
Activity
父类,并添加了相应的方法支持,所以在使用那些框架的时候需要你的
Activity
Fragment
继承他们框架提供的父类(不知怎么的,笔者对继承别人的父类老是有点排斥)。

是啊,
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

安装

开始使用

Fragment的操纵

懒加载

转场动画

onBackPressed拦截

startFragmentForResult方法

如何在library module中使用

版本日志

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()
即可。

上述场景支持该框架的一部分使用方式,具体使用请看[b]Wiki[/b]

6、开源协议



本项目遵循MIT开源协议. 浏览LICENSE查看更多信息.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息