1. 基础使用
1.1 静态使用
<fragment> </fragment> 标签,,name属性:指定package.name:表示加载对应的fragment
1.2 动态使用
1.3 v4包下fragment的使用
v4包向下兼容,兼容android3.0以前的fragment
1.4 生命周期
总结
- 创建阶段:从下往上,即:activity声明周期在前
- 切换阶段:被动先行,即:fragment间切换时候,要隐藏的先行,要显示的后行。。也可以形象的理解:坑位就一个,只有先离开,其他才能进来
- 前后台/销毁:从上往下,即:activity在下,fragment在上,activity相当于一个容易,fragment在视图层上层,先从上层销毁。也可以理解:容器先死了,孩子谁处理
2. fragment的通信
参考ios的思想:
正向传值:用属性传值,也就是用target的暴露方法
反向传值:用代理或block或通知
2.1 activity 向 fragment传值
- fragment未创建时候
ac: fg.setArguments(bundle)
fg: getArguments()
- fragment已经存在
方法一:
fragment暴露public方法给ac调用
方法二:被动方案,即fragment主动从ac拿数据
1:接口方案。fg持有接口,调用接口方法,ac实现接口方法,return 数据。
2:ac暴露方法
方法三:
无敌方案:EventBus
2.2 fragment 向 activity传值
方法一:
ac暴露方法给fg调用
方法二:
接口方案:同ac向fg传值。。反向传值,更适合
方法三:
无敌方案:EventBus。。反向传值,更适合
2.3 fragment间通信
方法一:暴露方法
方法二:接口
方法三:EventBus
3. 进阶
3.1 fg 页面跳转
fg中startActivityForResult,onActivityResult能接收回调
3.2 fg 嵌套
正确选择是使用getFragmentManager()还是getChildFragmentManager()
对于宿主Activity,getSupportFragmentManager()获取的FragmentActivity的FragmentManager对象;
对于Fragment,getFragmentManager()是获取的是父Fragment(如果没有,则是FragmentActivity)的FragmentManager对象,而getChildFragmentManager()是获取自己的FragmentManager对象
3.3 fg 恢复及重叠问题
善用findFragmentByTag获取到恢复的fg,然后结合hide和show控制状态
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity);
TargetFragment targetFragment;
HideFragment hideFragment;
if (savedInstanceState != null) { // “内存重启”时调用
//注意:其实ac销毁并恢复时,fg是被保存到savedInstanceState的,所以,如果不为空,就不用重新建fg,onSaveInstanceState已经保存了fg
targetFragment = getSupportFragmentManager().findFragmentByTag(TargetFragment.class.getName);
hideFragment = getSupportFragmentManager().findFragmentByTag(HideFragment.class.getName);
// 解决重叠问题
getFragmentManager().beginTransaction()
.show(targetFragment)
.hide(hideFragment)
.commit();
}else{ // 正常时
targetFragment = TargetFragment.newInstance();
hideFragment = HideFragment.newInstance();
getFragmentManager().beginTransaction()
.add(R.id.container, targetFragment, targetFragment.getClass().getName())
.add(R.id,container,hideFragment,hideFragment.getClass().getName())
.hide(hideFragment)
.commit();
}
}
3.4 Fragment 回收和恢复问题
场景或原因:内存吃紧被回收,或屏幕旋转导致的重建
fg是有宿主的,回收和恢复跟ac也是有关联的。回收时候,onSaveInstanceState保存了fg及一些其他状态。恢复时候,需要判断savedInstanceState是否为空,如果不为空,说明保存的有之前的fg,不用重新创建,fg内,系统会默认使用fragment的无参构造方法创建fragment,所以要恢复fg的参数,需要重新走解析mArguments参数的流程
onSaveInstanceState方法里会将我当前Activity内的Fragment存入HashMap中再存到Bundle对象中,然后当我Activity被销毁后返回回来时,系统会从onCreate方法和onRestoreInstanceState方法中将我们之前存入的Fragment取出来进行显示
方案:newInstance方案创建fragment
解析:创建fg时候setArguments传参,回收时候ac保存fg及mArguments等,恢复时候才能重新获取mArguments
public static OneFragment newInstance(Class param){
OneFragment oneFragment = new OneFragment();
Bundle bundle = new Bundle();
bundle.putInt("someArgs", param);
oneFragment.setArguments(bundle);
return oneFragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState==null) {
oneFragment = OneFragment.newInstance();
ft.add(R.id.fl_content, oneFragment, "OneFragment");
}else {
oneFragment = (OneFragment) getSupportFragmentManager().findFragmentByTag("OneFragment");
}
//重建的时候,可以从保存的Arguments中重新获取初始化参数
Bundle bundle = getArguments();
}
3.5 ViewPager + Fragment 方案
使用FragmentPagerAdapter+ViewPager时,切换回上一个Fragment页面时(已经初始化完毕),不会回调任何生命周期方法以及onHiddenChanged(),只有setUserVisibleHint(boolean isVisibleToUser)会被回调。。另外,setUserVisibleHint(boolean isVisibleToUser) 方法总是会优先于 Fragment 生命周期函数的调用,所以如果你想进行一些懒加载,需要在这里处理。
在给ViewPager绑定FragmentPagerAdapter时,
new FragmentPagerAdapter(fragmentManager)的FragmentManager,一定要保证正确,如果ViewPager是Activity内的控件,则传递getSupportFragmentManager(),如果是Fragment的控件中,则应该传递getChildFragmentManager()。只要记住ViewPager内的Fragments是当前组件的子Fragment这个原则即可。
3.6 ~PagerAdapter 注意点
FragmentPagerAdapter与FragmentStatePagerAdapter有什么区别
主要区别就在与对于fragment是否销毁,下面细说:
FragmentPagerAdapter:对于不再需要的fragment,选择调用detach方法,仅销毁视图,并不会销毁fragment实例。
FragmentStatePagerAdapter:会销毁不再需要的fragment,当当前事务提交以后,会彻底的将fragment从当前Activity的FragmentManager中移除,state标明,销毁时,会将其onSaveInstanceState(Bundle outState)中的bundle信息保存下来,当用户切换回来,可以通过该bundle恢复生成新的fragment,也就是说,你可以在onSaveInstanceState(Bundle outState)方法中保存一些数据,在onCreate中进行恢复创建。
如上所说,使用FragmentStatePagerAdapter当然更省内存,但是销毁新建也是需要时间的。一般情况下,如果你是制作主页面,就3、4个Tab,那么可以选择使用FragmentPagerAdapter,如果你是用于ViewPager展示数量特别多的条目时,那么建议使用FragmentStatePagerAdapter
另外,viewPager.setOffscreenPageLimit 这个离屏缓存,默认是1,所以,即使用的FragmentPagerAdapter,也要设置这个参数。否则,也只会保存两个fg存活
你不需要考虑在“内存重启”的情况下,去恢复的Fragments的问题,因为FragmentPagerAdapter已经帮我们处理啦。
3.7 Fragment 懒加载方案
学习文章:Androidx 下 Fragment 懒加载的新实现
原文:https://www.jianshu.com/p/2201a107d5b5
- add-show-hide 方案
- viewpager + fragment 方案
通过上述两种方案fg的生命周期,可以形成下面的懒加载方案
abstract class LazyFragment extends Fragment {
//是否执行了懒加载
private boolean isLoaded = false;
/**
* 当前Fragment是否对用户可见
*/
private boolean isVisibleToUser = false;
/**
* 当使用ViewPager+Fragment形式会调用该方法时,setUserVisibleHint会优先Fragment生命周期函数调用,
* 所以这个时候就,会导致在setUserVisibleHint方法执行时就执行了懒加载,
* 而不是在onResume方法实际调用的时候执行懒加载。所以需要这个变量
*/
private boolean isCalledResume = false;
/**
* 是否调用了setUserVisibleHint方法。处理show+add+hide模式下,默认可见 Fragment 不调用
* onHiddenChanged 方法,进而不执行懒加载方法的问题。
*/
private boolean isCallUserVisibleHint = false;
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
this.isVisibleToUser = isVisibleToUser;
this.isCallUserVisibleHint = true;
judgeLazyInit();
}
@Override
public void onResume() {
super.onResume();
this.isCalledResume = true;
if (!isCallUserVisibleHint) {
//处理这个,是因为add-show-hide模式管理fg方案下,不走setUserVisibleHint,并且第一个默认显示的fg不回调onHiddenChanged
// 所以表示:不走setUserVisibleHint的就是走onHiddenChanged的add-show-hide方案
// 但是默认第一个显示的fg不走onHiddenChanged方案,所以才有了这个onresume中的判断逻辑
this.isVisibleToUser = !isHidden();
}
judgeLazyInit();
}
@Override
public void onDestroyView() {
super.onDestroyView();
this.isLoaded = false;
this.isCalledResume = false;
this.isVisibleToUser = false;
this.isCallUserVisibleHint = false;
}
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
// add-show-hide 方案才会回调这个方法
// 把字段归并给回调setUserVisibleHint的情况。
this.isVisibleToUser = !hidden;
judgeLazyInit();
}
private void judgeLazyInit() {
//isVisibleToUser 是表示肉眼可见,是包括了两种情况,add-show-hide方案的情况,和vp+fg的方案
if (!this.isLoaded && this.isVisibleToUser && this.isCalledResume) {
lazyInit();
this.isLoaded = true;
}
}
abstract void lazyInit();
}
分析理解:
- viewpager + fg的方案,所有fg的setUserVisibleHint是先于其他生命周期运行的。
- 但是只有当fg显示或在预缓存限制内的fg才会走fg的生命周期。
- 只有当isVisibleToUser为true(表示当期窗口显示的fg,肉眼可见的) 并且 已经onresume的(比如第二个第三个等:isVisibleToUser为true是在onresume之后的)触发懒加载。
- 所以综合来看,两个回调都有可能要触发加载,所以,才有了lazyload的判断条件。
- 另外,兼容add-show-hide方案,其实onHiddenChanged和setUserVisibleHint对应是两种方案的两种回调,上面把标记字段统一为了isVisibleToUser。
- add-show-hide方案中,默认显示的第一个fg onHiddenChanged不回调
3.8 androidx中viewpager的setMaxLifecycle 懒加载方案
学习文章:Androidx 下 Fragment 懒加载的新实现
原文:https://www.jianshu.com/p/2201a107d5b5
androidx中,FragmentPagerAdapter构造方法多个@Behavior int behavior
参数:
- BEHAVIOR_SET_USER_VISIBLE_HINT:跟原Support下的ViewPager一致
- BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT:表示滑动切换fragment时候,只有显示可见的fragment才回调onresume,而不再像上一节方案中fragment的生命周期那么不友好
既然只有当显示的fragment才回调onresume,那么懒加载方案就简单了:
abstract class LazyFragment : Fragment() {
private var isLoaded = false
override fun onResume() {
super.onResume()
if (!isLoaded) {
lazyInit()
Log.d(TAG, "lazyInit:!!!!!!!”)
isLoaded = true
}
}
override fun onDestroyView() {
super.onDestroyView()
isLoaded = false
}
abstract fun lazyInit()
}
但是针对fragment嵌套的情况,上述方案并不完美,因为fragment嵌套情况下,并不是只有显示的fragment才调用onresume。。
所以,针对嵌套的子fragment,增加isHidden的判断
abstract class LazyFragment : Fragment() {
private var isLoaded = false
override fun onResume() {
super.onResume()
//增加了Fragment是否可见的判断
if (!isLoaded && !isHidden) {
lazyInit()
Log.d(TAG, "lazyInit:!!!!!!!”)
isLoaded = true
}
}
override fun onDestroyView() {
super.onDestroyView()
isLoaded = false
}
abstract fun lazyInit()
}
3.9 ViewPager2 懒加载方案
上面两种懒加载方案都涉及到Fragment生命周期的棘手问题。特别是onResume周期函数,用惯了activity的我们,Fragment的onResume使用就很不舒服,
ViewPager2 本身就支持对实际可见的 Fragment 才调用 onResume 方法
3.10 回退栈相关
原文:https://blog.csdn.net/guxiao1201/article/details/40476267
首先需要明确的是,FragmentActivity的FragmentManager是处理Fragment Transaction的而不是处理Fragment。BackStack内部的一个Transaction可以包含一个或多个和Fragment相关的操作
FragmentTransaction默认并不会主动被加入到BackStack中,除非开发者调用了addToBackStack(String tag)方法
和addToBackStack相对应的接口方法是popBackStack(),调用该方法后会将事物操作插入到FragmentManager的操作队列,只有当轮询到该事物时才能执行。所以Google还提供了可以立刻执行的接口popBackStackImmediate()
addToBackStack源码里是将事务加入到BackStackRecord,表示加入维护回退栈记录而已,并不是将事务操作里的fragment加入到回退栈之类的
理解分析: FragmentManager操作单元是Transaction,可以理解为FragmentManager每次操作是从beginTransaction到commit的所有动作,addToBackStack的时候添加上tag以标记这次操作,比如addToBackStack(TagA), addToBackStack(TagB)。表示事务栈内现在操作了两波,TagA和TagB标记的。然后点击返回键,拦截到回退栈里不为空,就popBackStack,弹出去一个事务操作,剩下TagA事务操作结果。效果就等于说现在页面效果是TagA操作后的结果。。例如:add(FragmentA),addToBackstack(TagA),add(FragmentB),addToBackstack(TagB)。这时候fragment先显示的FragmentA,然后显示的FragmentB。popBackStack(),就是把TagB的操作反向处理,TagB事务标记的是添加FragmentB,反向操作就是移除FragmentB,剩下TagA的操作。界面显示的是FragmentA
注意API:
popBackStack() 是从事务栈弹出顶层事务
popBackStack(String tag) 是从事务栈弹出tag及以上的所有事务
-
popBackStack(String tag, int flag) 是从事务栈弹出tag以上的所有事务,是否弹出tag事务,取决于flag
- flag:POP_BACK_STACK_INCLUSIVE 就是表示tag事务一块弹出
- 一般传0,表示不包括
public static final int POP_BACK_STACK_INCLUSIVE = 1<<0; (flags & POP_BACK_STACK_INCLUSIVE) == 0
popBackStack(int id, int flag) 同上原理,事务commit的时候会返回个id
以上方法是将事务弹出放到事件loop中等着处理,还有立即处理的方法,同上,~Immediate()
深入分析:
FragmentManager, BackStackRecord 源码还没有理清楚。。大致就是addBackStack是回退栈一条BackStackRecord记录。。该记录维护的有一个操作OP列表,大致包含操作命令cmd,fragment,一些进出动画。。重点是执行命令和fragment,有这俩,结合当前FragmentManager维护的现有fragments,就能反向操作了。比如addBackStack(“replaceOper”),replace操作就是把老的移除,把新的添加,popBackStack的时候,找到FragmentManager中栈顶的curruntFragment,在回退栈记录里的op里拿到oldFragment,cmd。先把curruntFragment移除,再把oldFragment添加,也就是反向replace
3.11 宿主引用问题
比如fg内网络请求的场景,异步任务,执行完成后回调,这时候宿主可能自己活宿主已经销毁了。所以,异步回调处的处理,要考虑处理自己和宿主是否可用的情况
方案:onAttach中保存ac对象。。Activity ac = (Activity)context
3.12 commit和commitAllowingStateLoss ~ commitNow和commitNowAllowingStateLoss
原文:https://blog.csdn.net/zxq614/article/details/85785297
这两种提交事务的区别就在于是否安全的问题:
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
...
看源码:
final class BackStackRecord extends FragmentTransaction implements
FragmentManager.BackStackEntry, Runnable {
……
public int commit() {
return commitInternal(false);
}
public int commitAllowingStateLoss() {
return commitInternal(true);
}
int commitInternal(boolean allowStateLoss) {
……
mManager.enqueueAction(this, allowStateLoss);
……
}
……
}
public abstract class FragmentManager {
……
boolean mStateSaved;
……
public void enqueueAction(Runnable action, boolean allowStateLoss) {
// 1: 不允许页面关闭后还执行action的,进行状态检测
if (!allowStateLoss) {
checkStateLoss();
}
}
private void checkStateLoss() {
if (mStateSaved) {
//3:页面已关,再提交,就抛异常
throw new IllegalStateException(
"Can not perform this action after onSaveInstanceState");
}
}
……
Parcelable saveAllState() {
……
if (HONEYCOMB) {
mStateSaved = true;
}
……
}
……
public void noteStateNotSaved() {
mStateSaved = false;
}
public void dispatchCreate() {
mStateSaved = false;
……
}
public void dispatchActivityCreated() {
mStateSaved = false;
……
}
public void dispatchStart() {
mStateSaved = false;
……
}
public void dispatchResume() {
mStateSaved = false;
……
}
……
public void dispatchStop() {
//2:这里标记页面关闭,保存状态了
mStateSaved = true;
……
}
……
}
注意:
- 使用commit(),异步业务中,可能抛上面的异常
- 使用commitAllowingStateLoss(),虽然避免了异常问题,但是activity恢复时,提交的事务是丢失的。所以,提交的事务对应的操作是恢复不了的
- 谷歌推荐使用:commitNow以及commitNowAllowingstateLoss()
3.13 replace的Fragment如何保持状态
调用addFragment添加的fragment的View会保存到视图树(ViewTree)中,其中各个控件的状态都会被保存。但如果调用replace()来添加fragment,我们前面讲到过,replace()的实现是将同一个container中的所有fragment视图从ViewTree中全部清空!然后再添加指定的fragment。由于repalce操作会把以前的所有视图全部清空,所以当使用Transaction回退时,也就只有重建每一个fragment视图,所以就导致从replace操作回退回来,所有的控件都被重建,以前的用户输入全部没了。
到这里,大家首先要明白一个问题,repalce()操作,会清空同一个container中的所有fragment视图!注意用词:请空的是fragment的VIEW!fragment的实例并不会被销毁!因为fragment的实例是通过FragmentManager来管理的。当fragment的VIEW被销毁时,fragment实例并不会被销毁。他们两个不是同时的,即在fragment中定义的变量,所上次运行中被赋予的值是一直存在的。那fragment实例什么时候会被销毁呢,当然是在不会被用到的时候才会被销毁。那什么时候不会被用到呢,即不可能再回退到这个操作的时候,就会被销毁。
在上面的例子中,fragment1虽然被fragment2的repalce操作把它的视图给销毁了,但在执行replace操作时,将操作加入到了回退栈,这时候,FragmentManager就知道,用户还可能通过回退再次用到fragment1,所以就会保留fragment1的实例。相反,如果,在执行repalce操作时,没有加入到回退栈,那FragmentManager就肯定也知道,用户不可能再回到上次那个Fragment1界面了,所以它的fragment实例就会在清除fragment1视图的同时也被清除了
解决方案:
方案都是基于replace操作加入到回退栈的
- 方案一
保存与恢复需要保存的状态:既然fragment实例没有销毁,销毁的知识视图,那么可以将状态保存到属性中啊,重新创建视图的时候再恢复
- 方案二
给需要保持的状态对应的控件加上id。。
android:id="@+id/id_name"
- 方案三
保存FragmentView视图
跟方案一差不多,就是将整个fragment的rootview保存到实例中
总结:总之理解了replace的原理,结合回退栈的原理。。怎么保持数据状态,要结合自己的业务需求,能实现的就是实现,不能实现的也可以考虑其他的方案,比如不用replace,show-hide方案也可行啊。
4. app框架方案
项目中用的该方案,效果不错