Fragment详解
一、定义
因为Android设备尺寸大小不一 同一应用在不同尺寸上显示会有很大差异,fragment就是为了解决这个问题推出的。fragment可以看做是Activity界面的一部分,它有属于自己的生命周期和事件处理机制而且它可以动态的添加、替换、移除。
此处补充一个官方定义:A Fragment represents a behavior or a portion of user interface in an Activity. You can combine multiple fragments in a single activity to build a multi-pane UI and reuse a fragment in multiple activities. You can think of a fragment as a modular section of an activity, which has its own lifecycle, receives its own input events, and which you can add or remove while the activity is running.
Fragment是依赖于Activity的,不能独立存在的。
一个Activity里可以有多个Fragment。
一个Fragment可以被多个Activity重用。
Fragment有自己的生命周期,并能接收输入事件。
我们能在Activity运行时动态地添加或删除Fragment。
二、生命周期
onAttach
当 fragment 实例依附于 Activity 时被回调,Activity 对象的引用回传到该方法中
onCreate
当创建 Fragment 实例时,系统回调的方法。在该方法中,需要对一些必要的组件进行初始化,以保证这个组件的实例在 Fragment 处于 pause或stop 状态时仍然存在
onCreateView
当第一次在 Fragment 上绘制UI时,系统回调的方法。该方法返回一个 View 对象,该对象表示 Fragment 的根视图;若 Fragment 不需要展示视图,则该方法可以返回 null
onActivityCreated
当宿主 Activity 的 onCreate() 方法返回后,该方法被回调
onStart
onResume
onPause
当用户离开 Fragment 时回调的方法(并不意味着该 Fragment 被销毁)。在该方法中,可以对 Fragment 的数据信息做一些持久化的保存工作,因为用户可能不再返回这个 Fragment
onStop
onDestoryView
当与 fragment 绑定的UI视图被移除时,该方法被回调
onDetach
当 fragment 不再依附于 Activity 时,该方法被回调会将view从UI中移除,和remove()不同,此时fragment的状态依然由FragmentManager维护
注意:1、Fragment 必须嵌入在 Activity 中才能生存,其生命周期也直接受宿主 Activity 的生命周期的影响,如Activity调用了onPause那么依赖该Activity的fragment也会调用onPause。只有当Activity处于onresume时 fragment才可以自由的控制自己的生命周期 2、当 Activity 的 onCreate() 被回调时,将导致 fragment 的 onAttach()、onCreate()、onCreateView()、onActivityCreate() 被连续回调 3、在 Fragment 中应至少重写onCreate()、onCreateView()、onPause()
三、使用
静态(资源文件中使用fragment标签)
示例代码:
Step 1:定义Fragment的布局,就是fragment显示内容的
Step 2:自定义一个Fragment类,需要继承Fragment或者他的子类,重写onCreateView()方法 在该方法中调用:inflater.inflate()方法加载Fragment的布局文件,接着返回加载的view对象
public class Fragmentone extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment1, container,false);
return view;
}
}
Step 3:在需要加载Fragment的Activity对应的布局文件中添加fragment的标签, 记住,name属性是全限定类名哦,就是要包含Fragment的包名
Step 4: Activity在onCreate( )方法中调用setContentView()加载布局文件即可!
动态(编写代码将 fragment 动态添加至现存的 ViewGroup)
Fragment以及布局代码就不贴出来了,直接贴MainActivity的关键代码:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Display dis = getWindowManager().getDefaultDisplay();
if(dis.getWidth() > dis.getHeight())
{
Fragment1 f1 = new Fragment1();
getFragmentManager().beginTransaction().replace(R.id.LinearLayout1, f1).commit();
}
else
{
Fragment2 f2 = new Fragment2();
getFragmentManager().beginTransaction().replace(R.id.LinearLayout1, f2).commit();
}
}
}
四、Fragment管理与Fragment事务
FragmentManager
获取
(1)每个Fragment以及宿主Activity(继承自FragmentActivity)都会在创建时,初始化一个FragmentManager对象,处理好Fragment嵌套问题的关键,就是理清这些不同阶级的栈视图。
(2)对于宿主Activity,getSupportFragmentManager()获取的FragmentActivity的FragmentManager对象;
对于Fragment,getFragmentManager()是获取的是父Fragment(如果没有,则是FragmentActivity)的FragmentManager对象,而getChildFragmentManager()是获取自己的FragmentManager对象。
相关方法
调用 findFragmentById() 方法获取由 Activity 管辖的绑定了UI的 Fragment 实例(for fragments that provide a UI in the activity layout);
调用 findFragmentByTag() 方法获取由 Activity 管辖的未绑定UI的 Fragment 实例(for fragments that do or don’t provide a UI);
调用 popBackStack() 方法将 Fragment 从后退栈中弹出;
调用 addOnBackStackChangedListener() 方法注册监听器,用于监听后退栈的变化。
相关源码
FragmentTransaction
- add
往Activity中添加一个Fragment - replace
使用另一个Fragment替换当前的,实际上就是remove()然后add()的合体 - remove
从Activity中移除一个Fragment,如果被移除的Fragment没有添加到回退栈(回退栈后面会详细说),这个Fragment实例将会被销毁。 - show
显示之前隐藏的Fragment - hide
藏当前的Fragment,仅仅是设为不可见,并不会销毁 - addToBackStack
FragmentManager拥有回退栈(BackStack),类似于Activity的任务栈,如果添加了该语句,就把该事务(事务链)加入回退栈,当用户点击返回按钮,会回退该事务(回退指的是如果事务是add(frag1),那么回退操作就是remove(frag1));如果没添加该语句,用户点击返回按钮会直接销毁Activity。 - detach
会将view从UI中移除,和remove()不同,此时fragment的状态依然由FragmentManager维护 - attach
重建view视图,附加到UI上并显示 - commit
不要把Fragment事务放在异步线程的回调中,比如不要把Fragment事务放在AsyncTask的onPostExecute(),因为onPostExecute()可能会在onSaveInstanceState()之后执行。
逼不得已时使用commitAllowingStateLoss()。
使用建议
如果你有一个很高的概率会再次使用当前的Fragment,建议使用show(),hide(),可以提高性能。
大部分情况下都是用show(),hide(),而不是replace()。
注意:如果你的app有大量图片,这时更好的方式可能是replace,配合你的图片框架在Fragment视图销毁时,回收其图片所占的内存
show(),hide()和replace()区别
show(),hide()最终是让Fragment的View setVisibility(true还是false),不会调用生命周期;
replace()的话会销毁视图(remove+add),即调用onDestoryView、onCreateView等一系列生命周期;
add()和 replace()不要在同一个阶级的FragmentManager里混搭使用。
add和replace 区别
add 不会重新初始化 fragment,replace 每次都会。所以
如果在 fragment 生命周期内获取获取数据,使用 replace
会重复获取;
添加相同的 fragment 时,replace 不会有任何变化,add
会报 IllegalStateException 异常;
replace 先 remove 掉相同 id 的所有 fragment,然后在
add 当前的这个 fragment,而 add 是覆盖前一个
fragment。所以如果使用 add 一般会伴随 hide()和
show(),避免布局重叠;
使用 add,如果应用放在后台,或以其他方式被系统销
毁,再打开时,hide()中引用的 fragment 会销毁,所以依
然会出现布局重叠 bug,可以使用 replace 或使用 add
时,添加一个 tag 参数
五、Fragment与Activity交互
组件获取
Fragment获得Activity中的组件: getActivity().findViewById();
Activity获得Fragment中的组件(根据id和tag都可以):getFragmentManager.findFragmentByid(R.id.fragment1);
数据交互
①Activit传递数据给Fragment:
在Activity中创建Bundle数据包,调用Fragment实例的setArguments(bundle) 从而将Bundle数据包传给Fragment,然后Fragment中调用getArguments获得 Bundle对象,然后进行解析就可以了
②Fragment传递数据给Activity
在Fragment中定义一个内部回调接口,再让包含该Fragment的Activity实现该回调接口, Fragment就可以通过回调接口传数据了,回调,相信很多人都知道是什么玩意,但是 写不出来啊,网上的一百度”fragment传数据给Activity”,全是李刚老师的那个代码,真心无语 算了,这里就写下局部代码吧,相信读者一看就懂的了:
Step 1:定义一个回调接口:(Fragment中)
/*接口*/
public interface CallBack{
/*定义一个获取信息的方法*/
public void getResult(String result);
}
Step 2:接口回调(Fragment中)
/*接口回调*/
public void getData(CallBack callBack){
/*获取文本框的信息,当然你也可以传其他类型的参数,看需求咯*/
String msg = editText.getText().toString();
callBack.getResult(msg);
}
Step 3:使用接口回调方法读数据(Activity中)
/* 使用接口回调的方法获取数据 */
leftFragment.getData(new CallBack() {
@Override
public void getResult(String result) { /*打印信息*/
Toast.makeText(MainActivity.this, “—>>” + result, 1).show();
}
});
总结下方法: 在Fragment定义一个接口,接口中定义抽象方法,你要传什么类型的数据参数就设置为什么类型;
接着还有写一个调用接口中的抽象方法,把要传递的数据传过去
再接着就是Activity了,调用Fragment提供的那个方法,然后重写抽象方法的时候进行数据 的读取就可以了!!!
③Fragment与Fragment之间的数据互传
其实这很简单,找到要接受数据的fragment对象,直接调用setArguments传数据进去就可以了 通常的话是replace时,即fragment跳转的时候传数据的,那么只需要在初始化要跳转的Fragment 后调用他的setArguments方法传入数据即可!
如果是两个Fragment需要即时传数据,而非跳转的话,就需要先在Activity获得f1传过来的数据, 再传到f2了,就是以Activity为媒介~
示例代码如下:
FragmentManager fManager = getSupportFragmentManager( );
FragmentTransaction fTransaction = fManager.beginTransaction();
Fragmentthree t1 = new Fragmentthree();
Fragmenttwo t2 = new Fragmenttwo();
Bundle bundle = new Bundle();
bundle.putString(“key”,id);
t2.setArguments(bundle);
fTransaction.add(R.id.fragmentRoot, t2, “~~~”);
fTransaction.addToBackStack(t1);
fTransaction.commit();
还可以通过setTaragetFragment方法进行数据传递(仅限于两个fragment依赖同一个activity)如:f1传递数据给f2,则f2.setTargetFragment 然后在f1中getTargetFragment().onActivityResult 这样就会把数据返回给f2的onActivityResult中.
六、Fragment使用注意事项
getActivity为空
原因
在fragmnet中调用getactivity时,fragment已经ondetach比如:你在pop了Fragment之后,
该Fragment的异步任务仍然在执行,并且在执行完成后调用了getActivity()方法,这样就会空指针。
解决方法
1、首先是避免在ondetach里调用getactivity,detach之后取消异步任务
2、在fragment基类里设置一个mActivity的全局变量,在onattach里赋值,代替getactivity,
但是这种方法有可能导致内存泄漏
异常:Can not perform this action after onSaveInstanceState
原因
离开当前Activity等情况下,系统会调用onSaveInstanceState()帮你保存当前Activity的状态、数据等,
直到再回到该Activity之前(onResume()之前),你执行Fragment事务,就会抛出该异常!
(一般是其他Activity的回调让当前页面执行事务的情况,会引发该问题)
解决方法
1、该事务使用commitAllowingStateLoss()方法提交,但是有可能导致该次提交无效!(宿主Activity被强杀时)
2、利用onActivityForResult()/onNewIntent(),可以做到事务的完整性,不会丢失事务
fragment重叠异常
原因
在类onCreate()的方法加载Fragment,并且没有判断saveInstanceState==null或if(findFragmentByTag(mFragmentTag) == null),导致重复加载了同一个Fragment导致重叠。(PS:replace情况下,如果没有加入回退栈,则不判断也不会造成重叠,但建议还是统一判断下)
解决方法
1、24.0.0版本之后官方修复了这个bug
2、在add()或者replace()时绑定一个tag,一般我们是用fragment的类名作为tag,
然后在发生“内存重启”时,通过findFragmentByTag找到对应的Fragment,并hide()需要隐藏的fragment
使用建议
1、对Fragment传递数据,建议使用setArguments(Bundle args),而后在onCreate中使用getArguments()取出,在 “内存重启”前,系统会帮你保存数据,不会造成数据的丢失。和Activity的Intent恢复机制类似。
2、使用newInstance(参数) 创建Fragment对象,优点是调用者只需要关系传递的哪些数据,而无需关心传递数据的Key是什么。
3、如果你需要在Fragment中用到宿主Activity对象,建议在你的基类Fragment定义一个Activity的全局变量,在onAttach中初始化,这不是最好的解决办法,但这可以有效避免一些意外Crash
A、比如:我在FragmentA中的EditText填了一些数据,当切换到FragmentB时,如果希望会到A还能看到数据,则适合你的就是hide和show;也就是说,希望保留用户操作的面板,你可以使用hide和show,当然了不要使劲在那new实例,进行下非null判断。
B、再比如:我不希望保留用户操作,你可以使用remove(),然后add();或者使用replace()这个和remove,add是相同的效果。
C、remove和detach有一点细微的区别,在不考虑回退栈的情况下,remove会销毁整个Fragment实例,而detach则只是销毁其视图结构,实例并不会被销毁。那么二者怎么取舍使用呢?如果你的当前Activity一直存在,那么在不希望保留用户操作的时候,你可以优先使用detach。
4、为了让 fragment 执行一些后台行为(background behavior),可以不为 fragment 绑定UI 此时可以通过设置tag来唯一标识一个fragment
onHiddenChanged的回调时机
当使用add()+show(),hide()跳转新的Fragment时,旧的Fragment回调onHiddenChanged(),不会回调onStop()等生命周期方法,而新的Fragment在创建时是不会回调onHiddenChanged(),这点要切记
Fragment与ActionBar和MenuItem集成
Fragment可以添加自己的MenuItem到Activity的ActionBar或者可选菜单中。
a、在Fragment的onCreate中调用 setHasOptionsMenu(true);
b、然后在Fragment子类中实现onCreateOptionsMenu
c、如果希望在Fragment中处理MenuItem的点击,也可以实现onOptionsItemSelected;当然了Activity也可以直接处理该MenuItem的点击事件。
startActivityForResult
Fragment中存在startActivityForResult()以及onActivityResult()方法,但是呢,没有setResult()方法,用于设置返回的intent,这样我们就需要通过调用getActivity().setResult(ListTitleFragment.REQUEST_DETAIL, intent)
setRetainInstance
调用了setRetainInstance(true)的Fragment在Activity重创建时候不会销毁Fragment的实例,只是会销毁视图并detach,不会执行onDestroy。
原理
Fragment调用setRetainInstance的原理_ZhangJian的博客-CSDN博客_setretaininstance
出栈方法remove
如果fragment加入了后退栈那么remove并不会把该fragment移除,此时如果想移除fragment 请使用popBackStack
popBackStack和popBackStackImmediate的区别
前者是加入到主线队列的末尾,等其它任务完成后才开始出栈,后者是队列内的任务立即执行,再将出栈任务放到队列尾(可以理解为立即出栈)。
如果你想出栈多个Fragment,你应尽量使用popBackStackImmediate(tag/id),而不是popBackStack(tag/id),如果你想在出栈后,立刻beginTransaction()开始一项事务,你应该把事务的代码post/postDelay到主线程的消息队列里
七、Fragment嵌套使用
嵌套使用时的问题大多是栈试图产生的混乱,主要是正确使用
getFragmentManager()还是getChildFragmentManager()
在support 23.2.0以下的支持库中,对于在嵌套子Fragment的startActivityForResult (),
会发现无论如何都不能在onActivityResult()中接收到返回值,只有最顶层的父Fragment
才能接收到,这是一个support v4库的一个BUG,已经修复了
fragment切换
https://mp.weixin.qq.com/s/cvvUb4xged0NpV8hnctyLg
八、fragment使用模式
单activity+多fragment
优缺点:单activity占用内存、资源少,但是该方式应用复杂
优点:性能高,速度最快。参考:新版知乎 、google系app
缺点:逻辑比较复杂,尤其当Fragment之间联动较多或者嵌套较深时,比较复杂。
分模块activity+多fragment
优缺点:分模块,每个模块用单activity+多fragment组成,这样既有利于减少内存占用又降低应用难度
1、登录注册流程:
LoginActivity + 登录Fragment + 注册Fragment + 填写信息Fragment + 忘记密码Fragment
2、或者常见的数据展示流程:
DataActivity + 数据列表Fragment + 数据详情Fragment + …
优点:速度快,相比较单Activity+多Fragment,更易维护。
九、fragment系统提供的子类
DialogFragment
可展示一个悬浮的对话框。使用该类创建的对话框可以很好地替换由 Activity 类中的方法创建的对话框,因为您可以像管理其他 Fragment 一样管理 DialogFragment——它们都被压入由宿主 Activity 管理的 Fragment 栈中,这可以很方便的找回已被压入栈中的 Fragment。
优点是:即使旋转屏幕,也能保留对话框状态
prefectionFragment
将Preference对象的层次结构显示为列表。用于app的设置页
十、ViewPager+Fragment相关
可以通过setOffscreenPageLimit(count)设置离线缓存的界面个数
FragmentPagerAdapter
使用FragmentPagerAdapter+ViewPager时,切换回上一个Fragment页面时(已经初始化完毕),不会回调任何生命周期方法以及onHiddenChanged(),只有setUserVisibleHint(boolean isVisibleToUser)会被回调,所以如果你想进行一些懒加载,需要在这里处理。
对于不再需要的fragment,选择调用detach方法,仅销毁视图,并不会销毁fragment实例。
在给ViewPager绑定FragmentPagerAdapter时,
new FragmentPagerAdapter(fragmentManager)的FragmentManager,一定要保证正确,如果ViewPager是Activity内的控件,则传递getSupportFragmentManager(),如果是Fragment的控件中,则应该传递getChildFragmentManager()。只要记住ViewPager内的Fragments是当前组件的子Fragment这个原则即可。
你不需要考虑在“内存重启”的情况下,去恢复的Fragments的问题,因为FragmentPagerAdapter已经帮我们处理啦。
FragmentStatePagerAdapter
会销毁不再需要的fragment,当前事务提交以后,会彻底的将fragmeng从当前Activity的FragmentManager中移除,state标明,销毁时,会将其onSaveInstanceState(Bundle outState)中的bundle信息保存下来,当用户切换回来,可以通过该bundle恢复生成新的fragment,也就是说,你可以在onSaveInstanceState(Bundle outState)方法中保存一些数据,在onCreate中进行恢复创建
懒加载
十一、其他
Fragment State Loss
http://toughcoder.net/blog/2016/11/28/fear-android-fragment-state-loss-no-more/
fragment应用
-
使用 Fragment 封装权限申请
-
使用 Fragment 优雅处理 onActivityResult
-
Activity reCreate 的时候用来存储数据