
- Fragment是什么?和Activity的联系?生命周期如何?⭐⭐⭐⭐⭐⭐
- Activity和Fragment之间如何通讯?Fragment和Fragment之间如何通讯?⭐⭐⭐⭐⭐
- Fragment的回退栈了解吗?⭐⭐⭐⭐
- Fragment的使用方式⭐⭐⭐
- 你有遇到过哪些关于Fragment的问题,如何处理的?⭐⭐⭐
什么是Fragment
刚开始学习Activity的时候,一个界面就是一个Activity。那么,如果想在一个Activity界面上镶嵌另一个界面如何做呢?Fragment翻译为“片段,破片”,可以理解为“显示在Activity中的Activity”,为解决Android碎片化而生。Fragment可以作为一个Activity界面中独立的子界面,拥有自己的生命周期,也可以接受用户的触摸事件。我们可以在一个Activity界面上添加多个Fragment子界面,并且每个Fragment都可以动态的添加、删除、替换,从而使得安卓界面开发具有更强大的灵活性。
比如微信首页有“微信”、“通讯录”、“发现”、“我”,这4个子界面就是4个Fragment。我们在设计Fragment的时候,需要考虑模块化和可复用化。Fragment的优势:
- 可以把一个Activity分为多个可复用的组件,使得UI开发更加有灵活性,之前一个Activity如果需要多个布局的话,就需要设置多个布局文件,又麻烦,性能也不高;
- 每个Fragment都是独立的个体,可以动态的添加、删除、替换等,同时也可以同一个Fragment供多个Activity使用;
- 与Activity切换相比,Fragment属于轻量切换,Fragment的出现,解决了Activity之间切换不流畅的问题;
- 与View相比,View也可以实现在一个Activity上部署几个子界面,但View不能通过startActivityForResult()方法(现在建议使用:registerForActivityResult()方法)接收到返回结果,而且View通常更关注视图的实现;
Fragment的生命周期
因为Fragment是依附于Activity存在的,所以它的生命周期也受到Activity的生命周期影响。
简化版
Activity一般有创建-开始-继续-暂停-停止-销毁共六大阶段,Fragment同样也经历了这六大阶段。从上面可以看到最大的差别在于创建-销毁这两个阶段,多出了以下5个方法(上面加粗的5个方法):
- onAttach(Activity):当Fragment与Activity发生关联时调用,即将Fragment绑定到Activity时,并且在这个方法可以通过getArguments()方法获取传入该Fragment的参数;
- onCreateView(LayoutInflater, ViewGroup, Bundle):创建该Fragment的视图时调用;
- onActivityCreated(Bundle):当Activity的onCreated()方法返回时调用;
- onDestroyView():对应onCreateView()方法,当该Fragment的视图被移除时调用;
- onDetach():对应onAttach()方法,当Fragment与Activity取消关联时调用;
注:
- 除了onCreateView(),如果重写了其他的方法,则必须调用父类对于该方法的实现;
- Activity每一个生命周期都会引发Fragment调用同样的回调,如Activity收到onStop()后,里面的每个Fragment都会收到onStop(),同理,Fragment的onResume()也是在Activity的onResume()之后调用。但是onAttach()->onCreate()->onCreateView()->onActivityCreated()->onStart()都是在Activity的onStart()中调用的;
Fragment的使用方式
Fragment的使用方式包括静态使用和动态使用。其中,静态是直接在xml布局文件中声明Fragment,动态则是使用代码来动态实现。
静态使用
步骤:
- 创建一个继承Fragment的自定义XrFragment类,重新onCreateView()方法,在该方法中设置对应的xml布局文件;
1 | public class XrFragment extends Fragment { |
- 在Activity的布局文件直接声明该自定义XrFragment类即可。
1 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
动态使用
一个Activity可以有多个Fragment,如果我们需要设置两个按键,按下按键可以打开对应的Fragment可以这么做:
1 | // 先创建两个自定义Fragment类 |
接着在Activity中将上述两个自定义Fragment绑定两个按键即可:
1 | public class XuruiActivity extends AppCompatActivity { |
R.id.myframelayout就是一个FrameLayout,作为Fragment的容器,上述自定义的Fragment可通过该myframelayout在Activity中显示。为了更好的管理多个Fragment,可以通过[注释1]获取Fragment管理对象,在[注释2]使用FragmentManager对象用来开启一个FragmentTransaction(事务),FragmentTransaction常用的方法有:
- add():向Activity中添加一个Fragment
- remove(): 从Activity中移除一个Fragment,如果被移除的Fragment没有添加到回退栈(回退栈见下文),这个Fragment实例将会被销毁
- replace():使用新的Fragment替换当前的的Fragment,其实就是remove()和add()的结合
- hide(): 隐藏当前的Fragment,但不会销毁
- show():显示之前隐藏的Fragment
- detach():会将view从UI中移除,和remove()不同,此时fragment的状态依然由FragmentManager维护
- attach():重建view视图,附加到UI上并显示
- ransatcion.commit():提交事务,上述add/replace/hide/show方法都需要执行commit()后才生效
通常情况下,建议使用show()和hide(),避免Fragment重复加载。
getFragmentManager,getSupportFragmentManager,getChildFragmentManager之间的区别?
为了管理Fragment,需要获取Fragment管理对象:FragmentManager,其中:
- getFragmentManager():Activity可以通过该方法获取Activity类里面的Fragment管理器,Fragment里不能用;
- getSupportFragmentManager():Activity可以通过该方法获取FragmentActivity类里面的Fragment管理器,用于管理这个Activity里面的所有一级Fragment。和getFragmentManager()作用确实是一样的作用,因为,Android3.0版本之前是没有Fragment这个概念,因此3.0版本以前的不可以直接调用 getFragmentManager(),因此3.0版本以前的可以调用getSupportFragmentManager()间接获取FragmentManager。而3.0以后的版本则两个方法任选一个即可,所以建议都调用getSupportFragmentManager()。同时,getSupportFragmentManager()还可以在Fragment中使用,但在Fragment中使用时,是获取的父类Fragment的FragmentManager,如果没有父类,则获取该Fragment所属Activity的FragmentManager;
- getChildFragmentManager():如果在Fragment里面还需要继续嵌套Fragment,则需要通过该方法在Fragment里面获取FragmentManager
1 | Fragment1 fragment1 = new Fragment1(); |
通讯
4.1 Fragment和Actvity的通讯
Fragment依附于Activity,两者的通讯可以有以下方式:
- 在Activity有对应的Fragment的引用,则直接通过该引用就可以访问Fragment里面的public方法,如果没有Fragment引用,则可以通过getFragmentManager.findFragmentByTag()或者findFragmentById()获得任何Fragment实例对其进行操作,因为每个Fragment都有唯一的ID(比如用android:id属性或者android:tag属性提供唯一标识);
- 在Fragment里可以通过getActivity()来获取当前绑定的Activity的实例,并对其进行操作;
- 接口方式:Activity里继承一个接口,并在Fragment里实现;
- 广播/文件:这是通用的Android跨进程通讯方式,用于Fragment和Activity通讯自然可以;
- Bundle:在Activity中建一个Bundle,将需要传的值存入Bundle,并通过Fragment的setArguments(bundle)传到Fragment,最后在Fragment中,用getArguments()接收。
- registerForActivityResult():可以在Fragment里调用registerForActivityResult()来启动另一个Activity,并返回一些数据回来Fragment。比如在myFragment启动编辑名字的Actvity,编辑完成后把编辑完的名字返回给myFragment:
1 | //EditNameActivity |
Fragment和Fragment的通讯
首先,不建议Fragment之间直接通讯,最好是借助Activity为中介。那么,如果一定要在Fragment1打开Fragment2后,从Fragment2返回一些数据回去Fragment1要怎么做呢? 只要在Fragment1打开Fragment2的时候,多执行一句:
fragment2.setTargetFragment(Fragment1.this, REQUEST_CODE);
然后在Fragment1类里面实现:
1 |
|
接着,进入Fragment2的代码,直接调用以下代码即可:
1 | Intent intent = new Intent(); |
Fragment的回退栈
Activity有任务栈,Fragment也有回退栈(Back Stack)。比如现在ActivityA先后启动了FragmentA、FragmentB,此时在FragmentB按后退键,会直接退回桌面。如果我们希望能退到FragmentA的话,需要执行
addToBackStack(String tag):标记本次的回滚操作
1 | Fragment newFragment = new FragmentA(); |
此时,在FragmentB按下后退键,就会跳回FragmentA,再按后退键才最后退回桌面。更进一步,如果回退栈里有4个Fragment:Fragment1到Fragment4,如果想在Fragment4的界面按后退键直接回到Fragment1,可以执行:
- popBackStack(int id, int flags):其中id表示提交变更时commit()的返回值。
- popBackStack(String name, int flags):其中name是addToBackStack(String tag)中的tag值
通过上面两个方法就可以指定回到某个特定的Fragment,并且根据第二个参数flags的不同,有两种情况:
- 0:表示除了指定的Fragment所在的这一层之上的所有层都退出栈;
- FragmentManager.POP_BACK_STACK_INCLUSIVE(inclusive):表示连同指定Fragment所在层以及之上的所有层都一起退出。
同样的,如果现在ActivityA先后启动了FragmentA、FragmentB,FragmentC,此时在FragmentC需要直接返回到Activity界面可以执行:
getActivity().getSupportFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
使用POP_BACK_STACK_INCLUSIVE参数是为了确保返回栈中的所有Fragment都被弹出,而不仅仅是单一地把FragmentC弹出。如果不使用该参数,则只会弹出FragmentC,而保留其他Fragment在返回栈中。
Fragment状态保存
Fragment状态保存入口:
1、Activity的状态保存, 在Activity的onSaveInstanceState()里, 调用了FragmentManger的saveAllState()方法, 其中会对mActive中各个Fragment的实例状态和View状态分别进行保存.
2、FragmentManager还提供了public方法: saveFragmentInstanceState(), 可以对单个Fragment进行状态保存, 这是提供给我们用的。
3、FragmentManager的moveToState()方法中, 当状态回退到ACTIVITY_CREATED, 会调用saveFragmentViewState()方法, 保存View的状态.
你有遇到过哪些关于Fragment的问题,如何处理的?
从微信接收分享返回APP,在APP内弹窗,如果只是在首页Fragment中设置弹窗,一旦用户切到别的Fragment,此时承载弹窗的Fragment被销毁,会导致崩溃
getActivity()空指针:
这种情况一般发生在在异步任务里调用getActivity(),此时宿主Activity可能已经销毁了,因此引发空指针问题。最简单的方法就是在引用getActivity()时做个判空判断,同时可以根据情况考虑用获取Context来代替Activity,建议在onAttach()方法中将Context强制转为Activity这种方式来代替直接调用getActivity()。反正不要把Fragment事务放在异步线程的回调中或者AsyncTask的onPostExecute()。
Fragment视图重叠
如果我们在onCreate()方法加载Fragment时,特别是当Activity重建的时候,没有判断saveInstanceState==null或if(findFragmentByTag(mFragmentTag) == null),就直接加载布局,可能会导致重复加载了同一个Fragment布局,因此建议加上判断。