Fragment是什么?和Activity的联系?生命周期如何?⭐⭐⭐⭐⭐⭐
Activity和Fragment之间如何通讯?Fragment和Fragment之间如何通讯?⭐⭐⭐⭐⭐
Fragment的回退栈了解吗?⭐⭐⭐⭐
Fragment的使用方式⭐⭐⭐
你有遇到过哪些关于Fragment的问题,如何处理的?⭐⭐⭐
1、什么是Fragment
2、Fragment的生命周期
3、Fragment的使用方式
3.1 静态使用
3.2 动态使用
3.3 getFragmentManager(),getSupportFragmentManager(),getChildFragmentManager()之间的区别
4、通讯
4.1 Fragment和Actvity的通讯
4.2 Fragment和Fragment的通讯
5、Fragment的回退栈
6、Fragment状态保存
7、你有遇到过哪些关于Fragment的问题,如何处理的?
7.1 getActivity()空指针:
7.2 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生命周期的比较,同时学习Fragment的生命周期最好也是结合Activity的生命周期来学习,因为Fragment是依附于Activity存在的,所以它的生命周期也受到Activity的生命周期影响。下图是网上找到的一张非常全面的图。
结合我们熟悉的Activity生命周期来学习:
Activity::onCreate()
Fragment::onAttach()
*Fragment::onCreate()
*Fragment::onCreateView()
*Fragment::onActivityCreated()
Activity::onStart()
*Fragment::onStart()
Activity::onResume()
*Fragment::onResume()
Activity::onPause()
*Fragment::onPause()
Activity::onStop()
*Fragment::onStop()
Activity::onDestroy()
*Fragment::onDestroyView()
*Fragment::onDestroy()
*Fragment::onDetach()
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的使用方式包括静态使用和动态使用。其中,静态是直接在xml布局文件中声明Fragment,动态则是使用代码来动态实现。
步骤:
创建一个继承Fragment的自定义XrFragment类,重新onCreateView()方法,在该方法中设置对应的xml布局文件;
public class XrFragment extends Fragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
/*
* 参数1:布局xml文件的ID
* 参数2:容器,被inflate的布局的父ViewGroup
* 参数3:是否将这个生成的View添加到这个容器中去
* 作用是将布局文件封装在一个View对象中,并填充到此Fragment中
* */
View v = inflater.inflate(R.layout.xr_fragment, container, false);
return v;
}
}
在Activity的布局文件直接声明该自定义XrFragment类即可。
一个Activity可以有多个Fragment,如果我们需要设置两个按键,按下按键可以打开对应的Fragment可以这么做:
// 先创建两个自定义Fragment类
public class XrFragment1 extends Fragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.xr_fragment_1, container, false);
return v;
}
}
public class XrFragment2 extends Fragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.xr_fragment_2, container, false);
return v;
}
}
接着在Activity中将上述两个自定义Fragment绑定两个按键即可:
public class XuruiActivity extends AppCompatActivity {
private Button bt_1;
private Button bt_2;
private FragmentManager manager;
private XrFragment1 fragment1;
private XrFragment2 fragment2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bt_1 = (Button) findViewById(R.id.bt_1);
bt_2 = (Button) findViewById(R.id.bt_2);
fragment1 = new XrFragment1();
fragment2 = new XrFragment2();
//1:初始化FragmentManager对象
manager = getSupportFragmentManager();
//2:使用FragmentManager对象用来开启一个Fragment事务
FragmentTransaction transaction = manager.beginTransaction();
//3:默认显示fragment1
transaction.add(R.id.myframelayout, fragment1).commit();
//对bt_1设置监听
bt_1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
FragmentTransaction transaction = manager.beginTransaction();
transaction.replace(R.id.myframelayout, fragment1).commit(); // 4
}
});
//对bt_2设置监听
bt_2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
FragmentTransaction transaction = manager.beginTransaction();
transaction.replace(R.id.myframelayout, fragment2).commit();
}
});
}
}
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重复加载。
为了管理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
Fragment1 fragment1 = new Fragment1();
FragmentManager fragmentManager = fragment1.getChildFragmentManager(); val Fragment2 fragment2 = new Fragment2();
fragmentManager.beginTransaction().add(R.id.my_framelayout, fragment2, "newFragment2").commit()
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:
//EditNameActivity
val intent = Intent()
intent.putExtra(myData.EDIT_NAME, name.text.toString()) //传入修改后的名字
setResult(Activity.RESULT_OK, intent)
finish()
//myFragment
private val startEditNameActivityLauncher: ActivityResultLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == Activity.RESULT_OK) {
val editName = it.data?.getStringExtra(myData.EDIT_NAME) //获取EditNameActivity返回的姓名
if (!editName.isNullOrEmpty()) {
myViewModel.setUserName(editName)
}
}
}
startEditNameActivityLauncher.launch(Intent(requireContext(),EditNameActivity::class.java))
首先,不建议Fragment之间直接通讯,最好是借助Activity为中介。那么,如果一定要在Fragment1打开Fragment2后,从Fragment2返回一些数据回去Fragment1要怎么做呢? 只要在Fragment1打开Fragment2的时候,多执行一句:
fragment2.setTargetFragment(Fragment1.this, REQUEST_CODE);
然后在Fragment1类里面实现:
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(resultCode != Activity.RESULT_OK){
return;
}else{
int rlt = data.getIntExtra("xr",0); //获取数据
//处理数据...
}
}
接着,进入Fragment2的代码,直接调用以下代码即可:
Intent intent = new Intent();
intent.putExtra("xr", 100);
getTargetFragment().onActivityResult(Fragment1.REQUEST_CODE,resultOk,intent);
Activity有任务栈,Fragment也有回退栈(Back Stack)。比如现在ActivityA先后启动了FragmentA、FragmentB,此时在FragmentB按后退键,会直接退回桌面。如果我们希望能退到FragmentA的话,需要执行
addToBackStack(String tag):标记本次的回滚操作
Fragment newFragment = new FragmentA();
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(R.id.Fragment_container, newFragment);
transaction.addToBackStack(null);
transaction.commit();
此时,在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所在层以及之上的所有层都一起退出。
Fragment状态保存入口:
1、Activity的状态保存, 在Activity的onSaveInstanceState()里, 调用了FragmentManger的saveAllState()方法, 其中会对mActive中各个Fragment的实例状态和View状态分别进行保存.
2、FragmentManager还提供了public方法: saveFragmentInstanceState(), 可以对单个Fragment进行状态保存, 这是提供给我们用的。
3、FragmentManager的moveToState()方法中, 当状态回退到ACTIVITY_CREATED, 会调用saveFragmentViewState()方法, 保存View的状态.
这种情况一般发生在在异步任务里调用getActivity(),此时宿主Activity可能已经销毁了,因此引发空指针问题。最简单的方法就是在引用getActivity()时做个判空判断,同时可以根据情况考虑用获取Context来代替Activity,建议在onAttach()方法中将Context强制转为Activity这种方式来代替直接调用getActivity()。反正不要把Fragment事务放在异步线程的回调中或者AsyncTask的onPostExecute()。
如果我们在onCreate()方法加载Fragment时,特别是当Activity重建的时候,没有判断saveInstanceState==null或if(findFragmentByTag(mFragmentTag) == null),就直接加载布局,可能会导致重复加载了同一个Fragment布局,因此建议加上判断。