Android基础知识-Fragment相关

https://www.jianshu.com/p/d9143a92ad94
https://www.jianshu.com/p/fd71d65f0ec6

Fragment生命周期

Fragment#onStart,在所在的Activity的onStart 之前执行

1、定义

Fragment 表示 FragmentActivity 中的行为或界面的一部分

2、Fragment优点

与Activity相比,Fragment内存占用低,响应速度快

3、Fragment生命周期
  • onAttach
    onAttach()在 fragment与Activity关联之后调用
    初始化fragment参数可以从getArguments()获得,
    onAttach 之后,就无法再调用setArguments()

  • onCreate
    fragment初次创建时调用
    此时只是创建Fragment,此时的 Activity 还没有创建完成,getActivity()可能为null

  • onCreateView
    在这个fragment 构造布局时调用

  • onActivityCreated
    Activity.OnCreate()结束后,调用此方法。
    此时,Activity已经创建完成,在这里才可以使用Activity的所有资源

  • onStart
    当到OnStart()时,Fragment对用户就是可见,但还无法与Fragment交互

  • onResume
    当这个fragment 对用户可见、且可以交互。这是Fragment与用户交互之前的最后一个回调。
    与Activity.onResume是相互绑定的
    依赖于包含它的 activity 的 Activity.onResume

  • onPause
    与 Activity.onPause() 绑定

  • onStop
    与 Activity.onStop() 绑定
    已停止的Fragment 可以直接返回到OnStart()回调,然后调用OnResume()

  • onDestroyView
    如果Fragment 即将被结束或保存,将回调onDestoryView(),将onCreateView创建的 View与这个fragment分离
    下次这个fragment若要显示,那么将会创建新视图。
    在onStop之后、onDestroy之前调用
    这个方法的调用同onCreateView是否返回非null视图无关。
    在View状态被保存之后、被父View 回收之前调用

  • onDestroy
    当这个fragment不再使用时调用。
    fragment 在 onDestroy()阶段,能从Activity中找到,因为它还没有Detach

  • onDetach
    Fragment 生命周期中最后一个回调是onDetach()
    调用它以后,Fragment 与 Activity 解绑,不再拥有视图层次结构,它的所有资源都将被释放

二、Fragment的懒加载

1、原理

利用 fragment 的
setUserVisibleHint(boolean isVisibleToUser) //与ViewPager一起使用时,判断加载时机
onHiddenChanged(boolean hidden) //FragmentTransaction 的 show()、hide() 的方法来控制显示

public abstract class LazyFragment extends Fragment {
	
	public boolean isVisible;//是否可见状态

	/**
	 * 标志位,View已经初始化完成,且Activity已经创建。
	 * onActivityCreated
	 */
	public boolean isPrepared;

	public boolean isFirstLoad = true;//是否第一次加载

	/**
	 * 要实现延迟加载Fragment内容,需要在 onActivityCreated
	 * isPrepared = true;
	 */
	public boolean isReadyLazyLoad() {
		if (!isPrepared || !isVisible || !isFirstLoad) {
			return false;
		}
		isFirstLoad = false;
		return true;
	}

	@Override
	public void onActivityCreated(@Nullable Bundle savedInstanceState) {
		super.onActivityCreated(savedInstanceState);
		if (!isLazy()) {
			initData();
		} else {
			isPrepared = true;
			onVisible();
		}
	}

	/**
	 * 如果是与ViewPager一起使用,调用的是setUserVisibleHint
	 * @param isVisibleToUser 是否显示出来了
	 */
	@Override
	public void setUserVisibleHint(boolean isVisibleToUser) {
		super.setUserVisibleHint(isVisibleToUser);
		if (getUserVisibleHint()) {
			isVisible = true;
			onVisible();
		} else {
			isVisible = false;
			onInvisible();
		}
	}

	/**
	 * 如果是通过 FragmentTransaction 的 show 和 hide 的方法来控制显示,调用的是 onHiddenChanged.
	 * 若是初始就show的Fragment 为了触发该事件 需要先hide再show
	 * @param hidden hidden True if the fragment is now hidden, false if it is not visible.
	 */
	@Override
	public void onHiddenChanged(boolean hidden) {
		super.onHiddenChanged(hidden);
		if (!hidden) {
			isVisible = true;
			onVisible();
		} else {
			isVisible = false;
			onInvisible();
		}
	}

	/**
	 * 是否为懒加载模式
	 * 注意:仅仅适用于和viewpager搭配使用
	 * 否则无法触发回调方法
	 *
	 * @return true 懒加载模式
	 */
	protected boolean isLazy() {
		return false;
	}

	// 可见时回调
	protected void onVisible() {
		if (isReadyLazyLoad()) {
			initData();
		}
	}

	// 不可见时回调
	protected void onInvisible() {
	}

	protected abstract void initData();
}

三、Fragment之间的通信

1、Fragment 与 Activity通信,通过接口回调
  • 1)定义一个接口
  • 2)Activity实现接口
  • 3)Fragment的 onAttach 方法,传入的 Activity 参数转为接口对象引用
    通过该引用把数据传给Activity
  • 4)Activity传入Fragment,在Fragment创建时可以通过Bundle传入数据
    在Fragment已经 attach 到Activity后,只能通过Fragment的引用来传入数据
2、Fragment之间通信

Fragment 获取和它相关联的 Activity,通过Activity 去获取另一个Fragment的实例

四、add(), show(), hide(), replace()

1、区别

show(),hide() 是让Fragment 的 View setVisibility(true / false)不会调用生命周期

replace()会销毁视图,即调用onDestoryView、onCreateView等一系列生命周期

add()、replace() 不要在同一个阶级的 FragmentManager里混搭使用

2、使用场景

高频使用的 Fragment,使用show(),hide(),可以提高性能。
如果Fragment 有大量图片,使用 replace(),配合图片框架、在Fragment 视图销毁时,回收其图片

3、onHiddenChanged的回调时机

当使用 add() + show()、hide()跳转新的 Fragment 时,旧的 Fragment 回调onHiddenChanged()
不会回调 onStop()等生命周期方法,新的 Fragment 创建时不会回调onHiddenChanged()

4、Fragment重叠问题

使用 support-v4 24.0.0+

5、同时pop多个Fragment的问题

使用 support-v4 25.4.0+

五、FragmentManager 管理Fragment栈

1、Fragment 及 宿主Activity在创建时,会初始化一个 FragmentManager对象
处理好Fragment嵌套问题的关键,就是理清这些不同阶级的栈视图。

FragmentActivity 的 FragmentManager 栈,管理嵌套在该Activity中的Fragment

Fragment 的 ChildFragmentManager栈,管理嵌套在该Fragment中的子Fragment

2、
1)宿主 Activity 中,getSupportFragmentManager() 获取的是 FragmentActivity的 FragmentManager 对象

2)Fragment中,getFragmentManager() 获取的是父Fragment(如果没有,则是FragmentActivity)的FragmentManager对象
getChildFragmentManager()是获取自己的FragmentManager对象

六、FragmentPagerAdapter 与 FragmentStatePagerAdapter 的区别

1、 区别
  • 1)FragmentPagerAdapter每一个Fragment 都长存在内存中,适用于比较固定的少量的Fragment
    FragmentPagerAdapter 在我们切换Fragment过程中不会销毁Fragment,只是调用事务中的detach方法
    而在detach方法中只会销毁Fragment中的View,而不会销毁Fragment对象

  • 2)FragmentStatePagerAdapter中实现将 只保留当前页面
    页面切换时,调用add/remove,当页面不可见,就会被移除,释放其资源
    页面需要显示时,生成新的页面

    移除Fragment 前会保存 Fragment状态,重新展示时,会根据状态重新创建页面

    较多 Fragment 时候,为了减少内存可适用
    FragmentStatePagerAdapter 在我们切换 Fragment,会把前面的Fragment直接销毁掉

2、 使用 FragmentPagerAdapter+ViewPager 的注意事项
  • 1)切换 Fragment时(已经初始化完毕),不回调任何生命周期方法、以及onHiddenChanged()
    只有setUserVisibleHint(boolean isVisibleToUser)会被回调,所以如果你想进行一些懒加载,需要在这里处理

  • 2)在 ViewPager 绑定 FragmentPagerAdapter 时,
    new FragmentPagerAdapter(fragmentManager) 的 FragmentManager,一定要保证正确
    如果 ViewPager 是 Activity 的控件 -> getSupportFragmentManager()
    如果 ViewPager 是 Fragment的控件 -> getChildFragmentManager()

    只要记住【ViewPager内的 Fragments 是当前组件的子Fragment这个原则】

  • 3)不需要考虑在“内存重启”的情况下,去恢复的Fragments的问题,因为 FragmentPagerAdapter 已经帮我们处理了

七、为什么不 new Fragment 的方式传入数据

因为“内存重启”,内存不够时,有可能会回收后台页面,导致数据丢失

使用 setArguments(Bundle args),在 “内存重启”前,系统会保存数据,不会造成数据的丢失

和Activity的Intent恢复机制类似。

八、Fragment坑点、解决方案

1、 getActivity()空指针

原因

  • 后台Activity “内存重启”,回到前台时系统恢复Activity,此时 Fragment已经onDetach()
  • 在pop了Fragment 后,Fragment 的异步任务仍然在执行,执行完成后调用了getActivity()

解决

  • 在Fragment的 onDestroyView()中,结束不必要的异步任务、延时任务
  • 如果你需要在Fragment中用到宿主Activity对象,在基类Fragment定义一个Activity的全局变量,
    在onAttach中,持有Activity引用,可以有效避免一些意外Crash。
2、 Can not perform this action after onSaveInstanceState

原因
后台Activity “内存重启”,系统调用 onSaveInstanceState() 保存Activity状态和数据
在 Activity onResume()之前,执行Fragment事务,抛出异常。

(一般是其他 Activity 的回调,让当前页面执行事务的情况,会引发该问题)

解决

  • 使用 commitAllowingStateLoss()方法提交,但是有可能导致该次提交无效!(宿主Activity被强杀时)

  • 对于 popBackStack() 没有对应的 popBackStackAllowingStateLoss()方法
    利用onActivityForResult()/onNewIntent(),可以做到事务的完整性,不会丢失事务

    //ReceiverActivity 或 其子Fragment:
    
    void start(){
         startActivityForResult(new Intent(this, SenderActivity.class), 100);
    }
    
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
      	 super.onActivityResult(requestCode, resultCode, data);
      	 if (requestCode == 100 && resultCode == 100) {
      		 // 执行Fragment事务
         }
    }
    
    // SenderActivity 或 其子Fragment:
    void do() { // 操作ReceiverActivity(或其子Fragment)执行事务
      	setResult(100);
      	finish();
    }
    
3、Fragment重叠异常—正确使用hide、show的姿势【v4-24.0.0+ 开始,官方修复了上述 没有保存mHidden的问题】
  • 1)触发条件
    i. 发生了页面重启(旋转屏幕、内存不足等情况被强杀重启)。
    在类onCreate()的方法加载Fragment,
    没有判断 saveInstanceState==nullif (findFragmentByTag(mFragmentTag) == null)
    导致重复加载了同一个Fragment导致重叠

    (PS:replace情况下,如果没有加入回退栈,则不判断也不会造成重叠,但建议还是统一判断下)

    ii.重复 replace|add Fragment 或者 使用 show , hide控制Fragment;

    如果 add() 几个Fragment,使用show()、hide()方法控制,比如微信、QQ的底部tab等情景,
    如果你什么都不做的话,在“内存重启”后回到前台,app的这几个Fragment界面会重叠。

    iii.重叠原因
    FragmentManager 帮我们管理Fragment,当发生“内存重启”,他会从栈底向栈顶的顺序一次性恢复Fragment
    但是因为官方没有保存Fragment 的 mHidden属性,默认为false,即show状态
    所以,所有Fragment都是以show的形式恢复,我们看到了界面重叠。

    (如果是replace,恢复形式和Activity一致,只有当你 pop之后上一个Fragment才开始重新恢复,
    所有使用replace不会造成重叠现象)

  • 2)解决
    i. findFragmentByTag()
    add()、replace()时绑定一个tag,一般我们是用fragment的类名作为tag,

    发生“内存重启”时(savedInstanceState != null)),
    通过 findFragmentByTag 找到对应的 Fragment,hide()需要隐藏的fragment

    @Override
    protected void onCreate(Bundle savedInstanceState) {
      	super.onCreate(savedInstanceState);
      	setContentView(R.layout.activity);
      	TargetFragment targetFragment;
      	HideFragment hideFragment;				  
      	if (savedInstanceState != null) {  // “内存重启”时调用
      		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();
      	}
    }
    

    ii.手动维护一个 mSupportHidden

    public class BaseFragment extends Fragment {
        private static final String STATE_SAVE_IS_HIDDEN = "STATE_SAVE_IS_HIDDEN";
    
      	@Override
      	public void onCreate(@Nullable Bundle savedInstanceState) {
      		if (savedInstanceState != null) {
      			boolean isSupportHidden = savedInstanceState.getBoolean(STATE_SAVE_IS_HIDDEN);
      			FragmentTransaction ft = getFragmentManager().beginTransaction();
      			if (isSupportHidden) {
      				ft.hide(this);
      			} else {
      				ft.show(this);
      			}
      			ft.commit();
      		}
      	}
    
      	@Override
      	public void onSaveInstanceState(Bundle outState) {
      		outState.putBoolean(STATE_SAVE_IS_HIDDEN, isHidden());
      	}
    }
    

    iii. support升级到 v4-24.0.0+

4、Fragment的嵌套问题

5、不靠谱的出栈方法remove()

addToBackStack(name) 将Fragment加入回退栈,remove()不能正常将Fragment从栈内移除
会返回到被remove()的Fragment,而且是空白页面

没有将Fragment加入回退栈,remove方法可以正常出栈。

加入了回退栈,popBackStack()系列方法才能真正出栈

6、多个Fragment同时出栈的深坑BUG 【support-25.4.0 版本修复】

在Fragment库中如下4个方法是可能产生BUG的:

popBackStack(String tag,int flags)
popBackStack(int id,int flags)
popBackStackImmediate(String tag,int flags)
popBackStackImmediate(int id,int flags)

上面4个方法作用是,出栈到 tag/id 的fragment,(一次多个Fragment被出栈)

  • 1)FragmentManager 栈中 管理 fragment下标位置的数组 ArrayList mAvailIndeices 的BUG

    FragmentManagerImpl.makeActive(Fragment f)

    用 ArrayList 模拟的栈,多个Fragment 同时出栈时,数组中对应位置变成了null
    新的Fragment再入栈时,栈内还存在null

    比如
    栈中原始数据: [A,B,C,D]
    清除C以及其以上的所有Fragment:popBackStack(C, FragmentManager.POP_BACK_STACK_INCLUSIVE)
    清除后栈结构:[A,B,null,null]
    添加新的 Fragment: addToBackStack©
    添加后的栈结构:[A,B,null,C]

    导致“内存重启”后,异常和Crash

  • 2)popBackStack的坑

    popBackStack 是加入到主线程队列的末尾,【等其它任务完成后才开始出栈】
    popBackStackImmediate 是队列内的任务立即执行,再将出栈任务放到队列尾(可以理解为立即出栈)。

    问题:
    如果 popBackStack 多个Fragment后,紧接着 beginTransaction() add新的一个Fragment,
    接着发生了“内存重启”后,你再执行 popBackStack(),app就会Crash

    解决方案
    postDelay出栈动画时间再执行其它事务,但是根据我的观察不是很稳定。

    如果你想【出栈多个Fragment,你应尽量使用 popBackStackImmediate(tag/id)】,而不是 popBackStack(tag/id),
    如果你想在【出栈后,立刻 beginTransaction()开始一项事务,应该把事务的代码 post/postDelay 到主线程的消息队列

7、深坑 Fragment转场动画(仅分析v4包下的Fragment)
getFragmentManager().beginTransaction()
					.setCustomAnimations(enter, exit)
  • 如果有 通过 tag/id 同时出栈多个Fragment,需要搭配 Fragment.onCreateAnimation() 临时取消出栈动画
    请谨慎使用.setCustomAnimations(enter, exit, popEnter, popExit)
    在support-25.4.0之前出栈多Fragment时,伴随出栈动画,会在某些情况下发生异常

  • 如果想给下一个Fragment 设置进栈动画、出栈动画
    setCustomAnimations(enter, exit) 只能设置进栈动画,第二个参数并不是设置出栈动画

    setCustomAnimations(enter, exit, popEnter, popExit),第1个参数对应进栈动画,第4个参数对应出栈动画
    setCustomAnimations(进栈动画, exit, popEnter, 出栈动画)

  • 让出栈动画运作正常的话,使用 Fragment 的 onCreateAnimation 控制动画

    1)pop多个Fragment时转场动画 带来的问题 【在support-25.4.0 版本修复

    在使用 pop(tag/id) 同时出栈多个Fragment的这种情况下,将转场动画临时取消、延迟一个动画的时间再去执行其他事务;

    原因在于这种情景下【可能会导致栈内顺序错乱】(上文有提到),
    如果发生“内存重启”后,因为 Fragment 转场动画没结束时再执行其他方法,
    导致Fragment状态不会被FragmentManager正常保存下来

    2)进入新的Fragment、并立刻关闭当前Fragment 时的一些问题
    如果从当前Fragment进入一个新的Fragment,并且同时要关闭当前Fragment。
    由于数据结构是栈,所以正确做法是先pop,再add
    但是转场动画会有覆盖的不正常现象,需要特殊处理,不然会闪屏

  • Tip:
    如果遇到 Fragment 的 mNextAnim 空指针的异常(通常是在你的Fragment被重启的情况下),
    1)检查 操作的 Fragment是否为null
    2)在 Fragment转场动画还没结束时,是否执行了其他事务等方法

    解决思路
    延迟一个动画时间再执行事务,或者 临时将该Fragment设为无动画

推荐阅读:

Fragment全解析系列(一):那些年踩过的坑 https://www.jianshu.com/p/d9143a92ad94
Google-Fragment概览 https://developer.android.google.cn/guide/components/fragments
Google-与其他Fragment通信 https://developer.android.google.cn/training/basics/fragments/communicating

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值