Android(安卓)Fragment详解
转载请注明转载自:http://blog.csdn.net/feather_wch/article/details/79462351
Fragment有这一篇就够了!汲取各路大神精华,亲自从源码剖析Fragment工作原理和底层机制。
包含:
- Fragment基础-讲解Fragment基本用法和很多注意要点
- Fragment相关的FragmentManager和FragmentTransaction讲解
- Fragment通信
- 从源码角度分析Fragment底层机制和原理
Android Fragment详解
版本: 2019/3/30-1(20:23)
文章目录
- Android Fragment详解
- Fragment基础
- 生命周期
- 添加
- 静态
- 动态
- 重复添加
- Fragment基础要点
- FragmentTransaction
- FragmentManager
- DialogFragment
- Fragment通信
- Fragment和Activity
- Fragment之间
- Fragment与ViewPager
- 懒加载
- FragmentPagerAdapter
- FragmentStatePagerAdapter
- Fragment进阶
- Fragment源码分析
- 实战场景
- 预加载首页的所有页面
- 点击底部Button,加载不同的Fragment
- 补充题
- 额外收获
- 参考资料
Fragment基础
1、Fragment特点
- Activity界面的一部分,必须依赖Activity
Fragment
拥有自己的生命周期
,可以在Activity
内部被添加或者删除Fragment
的生命周期只接受所在的Activity
影响(Activity
暂停,其Fragment
也会暂停)Fragment
在Android 3.0
引入,低版本中需要采用android-support-v4.jar
兼容包Fragment
的可重用:多个Activity
可以重用一个Fragment
生命周期
2、官方生命周期图
3、Fragment生命周期方法调用场景
方法 | 解释 |
---|---|
onAttach() | Fragment和Activity建立关联时调用(获取Activity 传递的值) |
onCreateView() | 为Fragment 创建View(加载布局)时调用 |
onActivityCreated() | Activity 的onCreate() 方法执行完毕后调用 |
onDestoryView() | Fragment 的布局被移除时调用 |
onDetach() | Fragment 和Activity 解除关联的时候调用 |
4、Fragment生命周期场景
场景 | 生命周期调用顺序 |
---|---|
Fragment被创建 | onAttach()->onCreate()->onCreateView()->onActivityCreated() |
Fragment可见 | onStart()->onResume() |
Fragment进入后台 | onPause()->onStop() |
Fragment被销毁/退出应用 | onPause()->onStop()->onDestoryView()->onDestory()->onDetach() |
屏幕灭掉/回到桌面 | onPause()->onSaveInstanceState()->onStop() |
屏幕解锁/回到应用 | onStart()->onResume() |
切换到其他Fragment | onPause()->onStop()->onDestoryView() |
切换回本身Fragment | onCreateView()->onActivityCreated()->onStart()->onResume() |
5、Fragment和Activity生命周期对比图
- 创建流程中:先执行Activity生命周期回调,再执行Fragment方法。
- 销毁流程中:先执行Fragment生命周期回调,再执行Activity方法。
添加
静态
6、Fragment的静态添加-在Activity的布局文件中添加
- Fragment
//BlankFragment.javapublic class BlankFragment extends Fragment { public BlankFragment() { } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // 1. 加载布局,第三个参数必须为`false`,否则会加载两次布局并且抛出异常!! return inflater.inflate(R.layout.fragment_blank, container, false); }}
//fragment的布局<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.a6005001819.androiddeveloper.Fragment.BlankFragment"> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:text="@string/hello_blank_fragment" />FrameLayout>
- Activity
//如一般Activity代码-直接省略如果`Fragment`使用的是`support-v4`中的, Activity需要继承自`FragmentActivity`
- Activity的布局
//用fragment标签加载fragment<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.example.a6005001819.androiddeveloper.MainActivity"> <fragment android:id="@+id/example_fragment" android:name="com.example.a6005001819.androiddeveloper.Fragment.BlankFragment" android:layout_width="match_parent" android:layout_height="match_parent"/>LinearLayout>
7、Fragment静态添加注意点
- 如果
Fragment
属于android.support.v4.app.Fragment
,所用的Activity
必须继承自FragmentActivity
- 如果
Fragment
属于android.app.Fragment
,直接使用Activity即可
动态
8、Fragment的动态添加
- Fragment代码不变
- Activity中进行动态添加
public class MainJavaActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main_java); //1. 获取FragmentManager FragmentManager fragmentManager = getFragmentManager(); //2. 获取FragmentTransaction FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); //3. 创建Fragment BlankFragment blankFragment = new BlankFragment(); //4. 将Fragment替换到Activity的布局中(Framelayout) fragmentTransaction.add(R.id.main_java_activity_container, blankFragment); fragmentTransaction.commit(); }}
<?xml version="1.0" encoding="utf-8"?><android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.a6005001819.androiddeveloper.MainJavaActivity"> <FrameLayout android:id="@+id/main_java_activity_container" android:layout_width="match_parent" android:layout_height="match_parent">FrameLayout>android.support.constraint.ConstraintLayout>
重复添加
9、Fragment的重复添加
问题:
Fragment
的动态添加是在onCreate()
中,当手机横竖屏切换,会导致多次调用onCreate()
最终导致创建多个Fragment
解决办法:在onCreate()
检查参数savedInstanceState
来确定是第一次运行还是恢复后运行
//java版本protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main_java); if(savedInstanceState == null){ //1. 获取FragmentManager(support-v4中用`getSupportFragmentManager`) FragmentManager fragmentManager = getFragmentManager(); //2. 获取FragmentTransaction FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); //3. 创建Fragment BlankFragment blankFragment = new BlankFragment(); //4. 将Fragment替换到Activity的布局中(Framelayout) fragmentTransaction.add(R.id.main_java_activity_container, blankFragment); fragmentTransaction.commit(); Log.i("feather", "创建新的Fragment"); }}
//kotlin版本override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main_kotlin) if(savedInstanceState == null){ fragmentManager.beginTransaction() .add(R.id.main_kotlin_activity_container, BlankFragment()) .commit() Log.i("feather", "创建新的Fragment") }}
Fragment基础要点
10、Fragment中onCreateView()
- 内部加载布局的
inflate()
的第三个参数必须是false
,否则会导致重复添加,抛出异常
11、Fragment需要传入参数该怎么做?
- 应该通过
setArguments(Bundle bundle)
方法添加- 之后通过
onAttach()
中的getArguments()
获得传进来的参数。- 不建议通过给
Fragment
添加带参数的构造函数
来传参数,这样内存紧张
时被系统杀掉会导致无法恢复数据
12、Fragment中获取Activity
对象
- 不应该!使用
getActivity()
获取- 应该在
onAttach()
中将Context对象
强转为Activity对象
13、Fragment不是一个View,而是和Activity一个层级
14、不能在onSaveInstanceState()
后调用commit()
- onSaveInstanceState()在onPause()和onStop()之间调用。
- onRestoreInstanceState()在onStart()和onResume()之间调用。
避免异常方法:- 不要把Fragment事务放在异步线程的回调中,比如不要把Fragment事务放在AsyncTask的onPostExecute()—onPostExecute()可能会在onSaveInstanceState()之后执行。
- 无可奈何时使用commitAllowingStateLoss()-一般不推荐。
15、生命周期场景:Fragment1在Activity初始化时就添加
- Fragment的onAttach()->onCreate()->onCreateView()->onActivityCreated()->onStart()都是在Activity的onStart()中调用的。
- Fragment的onResume()在Activity的onResume()之后调用。
16、生命周期场景:15的情况下,点击一个Button执行replace将F1替换为F2(不加addToBackStack())
- F2.onAttach()->Activity.onAttachFragment()->F2.onCreate()->F1.onPause()onDetach()->F2.onCreateView()onResume()
- F1进行销毁并添加了F2
17、生命周期场景:15的情况下,点击一个Button执行replace将F1替换为F2(加addToBackStack())
- F1被替换只会调用
onDestoryView()
而不会调用后续生命周期。
FragmentTransaction
18、FragmentTransaction的replace()
方法
- 作用: 等于先
Remove
移除容器所有的Fragment
,然后再add一个Fragment
- 在
replace()
前调用多次add()
, 最终只有replace()
的Fragment
留存
getSupportFragmentManager().beginTransaction() .add(R.id.fl_main, new ContentFragment(), null) .add(R.id.fl_main, new ContentFragment(), null) .add(R.id.fl_main, new ContentFragment(), null) .replace(R.id.fl_main, new ContentFragment(), null) .commit();
19、FragmentTransaction的addToBackStack()
方法
- 将此次
事物
添加到后台堆栈,按下“回退键”
不会退出该Activity
而是回到上一个操作时的状态。按下“回退键”
=执行了add()
对应的remove()
- 加入
回退栈
,则用户点击返回按钮,会回滚Fragment事务
getSupportFragmentManager().beginTransaction() .add(R.id.fl_main, new ContentFragment(), null) .addToBackStack(null) //1. 记录之前的add操作,回退会出现“白屏”,也就是回到`add`前的状态 .commit();
20、FragmentTransaction的show()、hide()
方法
hide(fragment)
-隐藏一个存在的fragmentshow(fragment)
-显示一个被隐藏过的fragmenthide()和show()
不会调用任何生命周期方法
Fragment
中重写onHiddenChanged()
可以对Fragment
的hide、show
状态进行监听
21、FragmentTransaction的attach()、detach()
方法
detach(fragment)
-分离fragment的UI视图attach(fragment)
-重新关联一个Fragment(必须在detach
后才能执行)- 当Fragment被detach后,Fragment的生命周期执行完onDestroyView就终止—Fragment的实例并没有被销毁,只是UI界面被移除(和remove有区别)。
- 当Fragment被detach后,执行attach操作,会让Fragment从onCreateView开始执行,一直执行到onResume。
22、FragmentTransaction的setCustomAnimations()
- 给
Fragment
的进入/退出
设置指定的动画资源getSupportFragmentManager
不支持属性动画,仅支持补间动画
。getFragmentManager
支持属性动画
。setCustomAnimations
一定要放在add、remove…等操作之前。
//1. 载入Fragment动画getFragmentManager().beginTransaction() //1. 设置动画 .setCustomAnimations(R.animator.enter_animator, R.animator.exit_animator) .add(R.id.main_java_activity_container, new BlankFragment()) .commit();//2. 销毁Fragment时动画(support-v4中只能用)getSupportFragmentManager().beginTransaction() .setCustomAnimations(R.anim.enter_anim, R.anim.exit_anim) .remove(getSupportFragmentManager().findFragmentById(R.id.fl_main)) .commit();
//动画:// res/animator/enter_animator<?xml version="1.0" encoding="utf-8"?><objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:interpolator/accelerate_decelerate" android:valueFrom="-1280" android:valueTo="0" android:valueType="floatType" android:propertyName="X" android:duration="2000" />// res/animator/exit_animator<?xml version="1.0" encoding="utf-8"?><objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:interpolator/accelerate_decelerate" android:valueFrom="0" android:valueTo="1280" android:valueType="floatType" android:propertyName="X" android:duration="2000" />
23、FragmentTransaction的addSharedElement(View sharedElement, String name)
- 用于将
View
从一个被删除的或隐藏
的Fragment
映射到另一个显示或添加的Fragment
上。- 设置共享View,第二个参数name是这个共享元素的标识。
- 博客推荐: 使用SharedElement切换Fragment页面:https://www.jianshu.com/p/e9f63ead8bf5
24、FragmentTransaction的commit()、commitAllowingStateLoss()、commitNow()、commitNowAllowingStateLoss()
commit()
:安排一个事务的提交。commitAllowingStateLoss()
:和commit一样,但是允许Activity的状态保存之后提交。commitNow()
:同步的提交这个事务。(API_24添加)commitNowAllowingStateLoss()
:和commitNow()一样,但是允许Activity的状态保存之后提交。(API_24添加)
FragmentManager
25、FragmentManagerImpl是什么?
- 是
FragmentManager
的实现类mAdded
是已经添加到Activity的Fragment的集合mActive
不仅包含mAdded
,还包含虽然不在Activity中,但还在回退栈
中的Fragment。
final class FragmentManagerImpl extends FragmentManager implements LayoutInflater.Factory2 { //1. 待加入的Fragment列表 final ArrayList<Fragment> mAdded = new ArrayList<>(); //2. 活跃的Fragment列表 SparseArray<Fragment> mActive; //3. 回退栈 ArrayList<BackStackRecord> mBackStack; //4. 通过ID查找Fragment public Fragment findFragmentById(int id) {...} //5. 通过tag查找Fragment public Fragment findFragmentByTag(String tag) {...} //6. 获得所有"待添加列表"中的Fragment public List<Fragment> getFragments() {...}}
26、findFragmentById()
- 根据ID来找到对应的Fragment实例,主要用在静态添加fragment的布局中,因为静态添加的fragment才会有ID
- 会先从
mAdded(待添加列表)
中查找,没有找到就会从mActive(活跃列表)
中查找
27、findFragmentByTag()
- 根据TAG找到对应的Fragment实例,主要用于在动态添加的fragment中,根据TAG来找到fragment实例
- 会先从
mAdded(待添加列表)
中查找,没有找到就会从mActive(活跃列表)
中查找
28、getFragments()
- 返回
mAdded(待添加列表)
29、popBackStack()的作用
- 进行
回退
功能,对应于addToBackStack()
void popBackStack(int id, int flags);
—根据Fragment的ID进行回退,ID可以通过Commit返回值获取void popBackStack(String name, int flags);
—根据Tag进行回退flags = 0
时,参数指定的Fragment
以上的所有内容全部出栈
flags = POP_BACK_STACK_INCLUSIVE
时,参数指定的Fragment
(包括该Fragment)以上的所有内容全部出栈
popBackStackImmediate()
相关所有函数,都是立即执行。而不是将事务
插入队列后等待执行。
DialogFragment
30、DialogFragment的作用和优缺点
DialogFragment
在Android 3.0
提出,用于替代Dialog
优点:
旋转屏幕也能保留对话框状态自定义对话框样式
: 继承DialogFragment
并重写onCreateView()
,该方法返回对话框UI
//自定义DialogFragmentpublic class CustomDialogFragment extends DialogFragment{ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { //1. 消除标题 getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE); //2. 自定义样式 View root = inflater.inflate(R.layout.fragment_blank, container); return root; }}//使用DialogFragmentCustomDialogFragment dialogFragment = new CustomDialogFragment();dialogFragment.show(getFragmentManager(), "dialog");
Fragment通信
31、EventBus是解决Fragment通信的终极解决方案
1.
Fragment
通信涉及:Fragment向Activity传递数据、Activity向Fragment传递数据、Fragment之间床底数据
2.EventBus:
一个Android事件发布/订阅轻量级框架—能够便捷、高效解决Fragment所有通信问题
Fragment和Activity
32、Fragment调用Activity中的方法
- getActivity()或者onAttach()中Context转为Activity
- 用该Activity对象,调用Activity的对象方法。
33、Activity调用Fragment中的方法
- Activity中直接用该Fragment对象去调用方法。
- 用过接口回调
34、Fragment向Activity传递数据的方法
- 接口回调: 在
Fragment
中定义接口
,并让Activity实现该接口
- FABridge: 以
注解
的形式免去了接口回调
的各种步骤,github:(https://github.com/hongyangAndroid/FABridge)
//接口回调实例//1、Fragment中定义接口public interface onFragmentInteractionListerner{ void onItemClick(String msg);}//2、Activity实现接口public class MainActivity extends Activity implements BlankFragment.onFragmentInteractionListerner{ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main_java); //1. Activity作为参数传入 Fragment f = new Fragment(MainActivity.this); } //2. 操作从Fragment传递进来的数据 public void onItemClick(String msg) { Log.i("feather", "Activity:" + msg); }}//3、Fragment中转换Context为接口onFragmentInteractionListerner mInterface;public BlankFragment(Context context) { mInterface = (onFragmentInteractionListerner) context;}//4、Fragment中调用方法进行数据传递mInterface.onItemClick("data from fragment");
35、Activity向Fragment传递数据
- 通过
构造函数
传递数据(不推荐)- 通过
参数
传递数据,在Fragment
中onAttach()
中getArguments()
- 在
Activity
中获取Fragment
对象,直接调用Fragment的方法
,通过参数传递数据
Fragment之间
36、Fragment之间的通信
- 需要借助
Activity
进行数据通信- Activity中通过FragmentManager的
findFragmentById
去获取Fragment
Fragment与ViewPager
ViewPager的基本使用:请参考该链接
37、ViewPager介绍
ViewPager
常用于实现Fragment的左右滑动切换
的效果ViewPager
会缓存当前页相邻的界面,比如当滑动到第2页时,会初始化第1页和第3页的Fragment对象,且生命周期函数运行到onResume()。- 通过
ViewPager的setOffscreenPageLimit(count)
设置离线缓存的界面个数。
38、Fragment的FragmentPagerAdapter的实现方法
public class MyFragmentStatePagerAdapter extends FragmentStatePagerAdapter{ //1. Fragment链表 List<Fragment> fragmentList; //2. 构造 public MyFragmentStatePagerAdapter(android.support.v4.app.FragmentManager fm, List<Fragment> list) { super(fm); fragmentList = list; } //3. 返回当前Position对应的Fragment @Override public Fragment getItem(int position) { return fragmentList.get(position); } //4. Fragment的总数量 @Override public int getCount() { return fragmentList.size(); }}
懒加载
39、懒加载是什么
ViewPager
默认会预加载
左右相邻的Fragment
,但是在一些有耗时操作的情况下,需要懒加载
-在打开相应Fragment
时才加载数据懒加载的实现思路
:用户不可见的界面,只初始化UI,但是不会做任何数据加载。等滑到该页,才会异步做数据加载并更新UI。
40、懒加载之Fragment的setUserVisibleHint()方法
- 懒加载需要依赖Fragment的setUserVisibleHint()方法
- 当Fragment变为
可见
时,需要调用setUserVisibleHint(true)
- 当Fragment变为
不可见
时,会调用setUserVisibleHint(false)
31、setUserVisibleHint()的调用时机
onAttach()
之前,调用setUserVisibleHint(false)
。onCreateView()
之前,如果该界面为当前页,则调用setUserVisibleHint(true)
,否则调用setUserVisibleHint(false)
。- 界面变为可见时,调用setUserVisibleHint(true)。界面变为不可见时,调用setUserVisibleHint(false)。
42、懒加载Fragment的实现
1、网络请求不涉及UI更新时直接重写
Fragment
的setUserVisibleHint()
方法
public void setUserVisibleHint(boolean isVisibleToUser) { Log.d("TAG", mTagName + " setUserVisibleHint() --> isVisibleToUser = " + isVisibleToUser); if (isVisibleToUser) { pullData(); } super.setUserVisibleHint(isVisibleToUser);}
2、如果耗时请求涉及UI更新(请求好数据后结果UI还没有绑定好),这种情况适合在
onStart()
通过getUserVisibleHint()
判断.
@Overridepublic void onStart() { super.onStart(); Log.d("TAG", mTagName + " onStart()"); ... if(getUserVisibleHint()) { pullData(); }}
FragmentPagerAdapter
43、FragmentPagerAdapter的特点
- Item销毁时,只是将其detach()和界面分离
- 适合Fragment较少的情况,更占内存,但是反应速度会更快
- destroyItem()源码
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) { //... this.mCurTransaction.detach((Fragment)object);}
FragmentStatePagerAdapter
44、FragmentStatePagerAdapter的特点
- Item销毁时,会直接remove。
- 不占内存,适合大量Fragment的情况。
- destroyItem()源码
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) { //... this.mCurTransaction.remove(fragment);}
Fragment进阶
Fragment源码分析
45、getFragmentManager()源码和逻辑分析
//Activty.javaFragmentController mFragments = FragmentController.createController(new HostCallbacks());public FragmentManager getFragmentManager() { //1. 调用FragmentController的方法 return mFragments.getFragmentManager();}//FragmentController.javapublic static final FragmentController createController(FragmentHostCallback<?> callbacks) { //2. 用callback(也就是HostCallbacks)创建对象 return new FragmentController(callbacks);}//FragmentController.javapublic FragmentManager getFragmentManager() { //3. 调用callback的方法 return mHost.getFragmentManagerImpl();}//FragmentHostCallback.java-HostCallBacks的父类final FragmentManagerImpl mFragmentManager = new FragmentManagerImpl();FragmentManagerImpl getFragmentManagerImpl() { //4. 返回FragmentManagerImpl() return mFragmentManager;}
FragmentManager
是一个抽象类
,最终实现类是FragmentManagerImpl
46、beginTransaction()源码解析
//FragmentManager.java的内部类FragmentManagerImpl的方法:public FragmentTransaction beginTransaction() { //1. 返回BackStackRecord---保存了全部操作轨迹 return new BackStackRecord(this);}
47、BackStackRecord
类是什么?
final class BackStackRecord extends FragmentTransaction implements FragmentManager.BackStackEntry, FragmentManagerImpl.OpGenerator { //1. Op用于表示`某个操作` static final class Op { int cmd; //记录操作:add()或者remove()或者replace()等等 Fragment fragment; //操作的Fragment对象 int enterAnim; int exitAnim; int popEnterAnim; int popExitAnim; Op() { } Op(int cmd, Fragment fragment) { this.cmd = cmd; this.fragment = fragment; } } //2. BackStackRecord内部拥有Op类的链表 ArrayList<Op> mOps = new ArrayList<>();}
继承FragmentTransaction(事务)
—保存了整个事务的全部操作轨迹
实现BackStackEntry
—Fragment回退栈中的实体,因此在popBackStack()
时能回退整个事务。实现OpGenerator
—用于UI线程
调度一个add或pop
事务。- 内部拥有表示
add
等操作的Op
类的链表。
48、add()源码解析
//BackStackRecord.javapublic FragmentTransaction add(int containerViewId, Fragment fragment) { //1. OP_ADD作为参数传入 doAddOp(containerViewId, fragment, null, OP_ADD); return this;}private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) { ... //2. 保存`容器ViewId`等信息到Fragment中 fragment.mFragmentManager = mManager; fragment.mTag = tag; fragment.mContainerId = fragment.mFragmentId = containerViewId; ... //3. 创建Op并保存到链表中 addOp(new Op(opcmd, fragment));}void addOp(Op op) { //3. 添加到链表中 mOps.add(op); //4. Fragment的相关动画 op.enterAnim = mEnterAnim; op.exitAnim = mExitAnim; op.popEnterAnim = mPopEnterAnim; op.popExitAnim = mPopExitAnim;}
49、addToBackStack(“”)源码
//BackStackRecord.javapublic FragmentTransaction addToBackStack(String name) { ... //1. 设置为true,该变量在cmmoit中被使用 mAddToBackStack = true; mName = name; return this;}
50、commit()源码解析
/**==============================* * 思路流程: * 1. 将“事务”添加到“回退栈”中 * 2. 将“事务”添加到队列中 * 3. 发起异步调度commit操作 * //BackStackRecord.java *==============================*/public int commit() { return commitInternal(false);}//BackStackRecord.javaint commitInternal(boolean allowStateLoss) { //1. 提交过会导致异常 if (mCommitted) { throw new IllegalStateException("commit already called"); } ... mCommitted = true; //2. `addToBackStack`设置的变量, 为true时将当前`事务`加入到`回退栈`中 if (mAddToBackStack) { mIndex = mManager.allocBackStackIndex(this); } else { mIndex = -1; } //3. 将该`事务`加入到队列中 mManager.enqueueAction(this, allowStateLoss); return mIndex;}//BackStackRecord.javapublic int allocBackStackIndex(BackStackRecord bse) { synchronized (this) { ... //1. 将"事务"加入到"回退栈中" int index = mBackStackIndices.size(); mBackStackIndices.add(bse); return index; ... }}//FragmentManager.java: 将`action`加入到 待定操作 的队列中public void enqueueAction(OpGenerator action, boolean allowStateLoss) { synchronized (this) { ... if (mPendingActions == null) { mPendingActions = new ArrayList<>(); } //1. 加入到待定操作的链表中 mPendingActions.add(action); //2. 异步调度commit操作 scheduleCommit(); }}//FragmentManager.javaprivate void scheduleCommit() { synchronized (this) { //1. 准备"延期缓办"---延期事务不为null 且 延期事务不为空 boolean postponeReady = mPostponedTransactions != null && !mPostponedTransactions.isEmpty(); //2. 准备"即将发生"---待定操作不为null 且 待定操作数量为1 boolean pendingReady = mPendingActions != null && mPendingActions.size() == 1; //3. 两者满足其中之一, 主线程中执行“待定操作” if (postponeReady || pendingReady) { mHost.getHandler().removeCallbacks(mExecCommit); mHost.getHandler().post(mExecCommit); } }}/**=============================== * 异步调度commit流程: * 1. 遍历所有需要执行的事务 * 2. 最终执行BackStackRecord的executeOps()执行所有操作 * //FragmentManager.java *===============================*/Runnable mExecCommit = new Runnable() { @Override public void run() { //1. 执行“待定操作” execPendingActions(); }};//FragmentManager.java: 只能在“主线程”中调用public boolean execPendingActions() { ... while (generateOpsForPendingActions(mTmpRecords, mTmpIsPop)) { //1. 将pendingActions中所有积压的"事务"全部执行 removeRedundantOperationsAndExecute(mTmpRecords, mTmpIsPop); } ...}//FragmentManager.javaprivate void removeRedundantOperationsAndExecute(ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop) { ... executePostponedTransaction(records, isRecordPop); ... //1. 执行"事务列表"中范围(startIndex~endIndex)的所有事务 executeOpsTogether(records, isRecordPop, startIndex, endIndex); ...}//FragmentManager.javaprivate void executeOpsTogether(ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop, int startIndex, int endIndex) { //1. 执行"事务列表"中所有事务(范围为startIndex~endIndex) executeOps(records, isRecordPop, startIndex, endIndex); ...}//FragmentManager.javaprivate static void executeOps(ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop, int startIndex, int endIndex) { //1. 遍历"事务" for (int i = startIndex; i < endIndex; i++) { final BackStackRecord record = records.get(i); final boolean isPop = isRecordPop.get(i); if (isPop) { record.bumpBackStackNesting(-1); //2. 执行Pop操作: 仅仅在所有事务最后执行add操作 boolean moveToState = i == (endIndex - 1); record.executePopOps(moveToState); } else { //3. 执行操作 record.bumpBackStackNesting(1); record.executeOps(); } }}//BackStackRecord.javavoid executeOps() { /**============================================== * 遍历事务的全部操作: * 1.add 2.remove 3.hide 4.show 5.detach 6.attach *===============================================*/ final int numOps = mOps.size(); for (int opNum = 0; opNum < numOps; opNum++) { final Op op = mOps.get(opNum); final Fragment f = op.fragment; if (f != null) { f.setNextTransition(mTransition, mTransitionStyle); } switch (op.cmd) { case OP_ADD: f.setNextAnim(op.enterAnim); mManager.addFragment(f, false); break; case OP_REMOVE: f.setNextAnim(op.exitAnim); mManager.removeFragment(f); break; case OP_HIDE: f.setNextAnim(op.exitAnim); mManager.hideFragment(f); break; case OP_SHOW: f.setNextAnim(op.enterAnim); mManager.showFragment(f); break; case OP_DETACH: f.setNextAnim(op.exitAnim); mManager.detachFragment(f); break; case OP_ATTACH: f.setNextAnim(op.enterAnim); mManager.attachFragment(f); break; case OP_SET_PRIMARY_NAV: mManager.setPrimaryNavigationFragment(f); break; case OP_UNSET_PRIMARY_NAV: mManager.setPrimaryNavigationFragment(null); break; default: throw new IllegalArgumentException("Unknown cmd: " + op.cmd); } if (!mReorderingAllowed && op.cmd != OP_ADD && f != null) { mManager.moveFragmentToExpectedState(f); } } if (!mReorderingAllowed) { // Added fragments are added at the end to comply with prior behavior. mManager.moveToState(mManager.mCurState, true); }}
51、Fragment的7种状态
static final int INVALID_STATE = -1; // 作为null值的非法状态static final int INITIALIZING = 0; // 没有被createstatic final int CREATED = 1; // 已经createstatic final int ACTIVITY_CREATED = 2; // Activity已经完成了createstatic final int STOPPED = 3; // 完全创建,还没startstatic final int STARTED = 4; // 已经create和start,还没有resumestatic final int RESUMED = 5; // 已经完成create,start和resume
Fragment的生命周期
就是Fragment状态切换
的过程- 若Fragment的
当前状态
小于新状态
,就会进行创建
、唤醒
等过程- 若Fragment的
当前状态
大于新状态
,就会进行暂停
、彻底销毁
等过程
52、FragmentManager的addFragment()源码解析
//BackStackRecord.javavoid executeOps() { //1. 通过"FragmentManager"的addFragment()将Fragment添加到"待添加的列表"中 switch (op.cmd) { case OP_ADD: f.setNextAnim(op.enterAnim); mManager.addFragment(f, false); break; } ... //2. 执行FragmentManager的方法,对Fragment进行状态切换 mManager.moveToState(mManager.mCurState, true);}//FragmentManager.javapublic void addFragment(Fragment fragment, boolean moveToStateNow) { //1. Fragment放置到"活跃的"Fragment列表中 makeActive(fragment); //2. 将Fragment添加到待添加的Fragment列表(mAdded)中 synchronized (mAdded) { mAdded.add(fragment); } ...}//FragmentManager.javavoid moveToState(int newState, boolean always) { //1. FragmentManager的当前状态 mCurState = newState; if (mActive != null) { //2. 遍历取出"待添加的Fragment"--执行onAttach()~onResume()的全部生命周期 if (mAdded != null) { final int numAdded = mAdded.size(); for (int i = 0; i < numAdded; i++) { Fragment f = mAdded.get(i); moveFragmentToExpectedState(f); //将Fragment跳转到预期状态 ... } } //3. 遍历取出"活跃列表"中的Fragment -根据Fragment的状态决定是唤醒、终止还是彻底销毁 final int numActive = mActive.size(); for (int i = 0; i < numActive; i++) { Fragment f = mActive.valueAt(i); moveFragmentToExpectedState(f); //将Fragment跳转到预期状态 ... } ... }}//FragmentManager.javavoid moveFragmentToExpectedState(final Fragment f) { //1. 根据Fragment的旧状态和新状态, 进行操作, 最终Fragment达到预期状态 moveToState(f, nextState, f.getNextTransition(), f.getNextTransitionStyle(), false); //2. 此前Fragment已经完成onCreate()~onResume()所有周期, mView(Fragment视图)一定不为空 if (f.mView != null) { ... //3. Fragment的动画 Animator anim = loadAnimator(f, f.getNextTransition(), true, f.getNextTransitionStyle()); anim.setTarget(f.mView); anim.start(); } //3. 根据mHiddenChanged,完成Fragment的show和hide if (f.mHiddenChanged) { completeShowHideFragment(f); }}//FragmentManager.javavoid moveToState(Fragment f, int newState, int transit, int transitionStyle, boolean keepActive) { /**==============================================* * 1. Fragment状态 < 新状态 : 表明Fragment需要去创建或者唤醒 *================================================*/ if (f.mState <= newState) { ... /**=============================== * switch中没有break,直接顺序执行 *================================*/ switch (f.mState) { /**=======================================* * 2. Fragment初始化: onAttach、onCreate() *========================================*/ case Fragment.INITIALIZING: if (newState > Fragment.INITIALIZING) { //1. Fragment执行onAttach()方法 f.onAttach(mHost.getContext()); //2. 没有父Fragment: 包含Fragment的Activity执行onAttachFragment if (f.mParentFragment == null) { mHost.onAttachFragment(f); } else { //3. 有父Fragment: 父Fragment执行onAttachFragment f.mParentFragment.onAttachFragment(f); } ... //4. 执行Fragment的onCreate()方法 f.performCreate(f.mSavedFragmentState); } /**=======================================* * 3. Fragment创建: onCreateView()、onViewCreated() *========================================*/ case Fragment.CREATED: if (newState > Fragment.CREATED) { //1. Fragment的onCreateView()-【创建了mView】 f.mView = f.performCreateView(f.performGetLayoutInflater(f.mSavedFragmentState), container, f.mSavedFragmentState); //2. 将mView添加到父容器内, 根据mHidden隐藏 if (f.mView != null) { f.mView.setSaveFromParentEnabled(false); if (container != null) { container.addView(f.mView); } if (f.mHidden) { f.mView.setVisibility(View.GONE); } //3. Fragment的onViewCreated f.onViewCreated(f.mView, f.mSavedFragmentState); ... } } //4. Fragment的onActivityCreated() f.performActivityCreated(f.mSavedFragmentState); ... case Fragment.ACTIVITY_CREATED: ... case Fragment.STOPPED: //1. onStart()和分发 f.performStart(); case Fragment.STARTED: //2. onResume()和分发 f.performResume(); } } /**==============================================* * 4. Fragment状态 > 新状态 : 表明Fragment需要停止或者彻底销毁 *================================================*/ else if (f.mState > newState) { switch (f.mState) { case Fragment.RESUMED: //1. onPause()和分发 f.performPause(); dispatchOnFragmentPaused(f, false); case Fragment.STARTED: //2. onStop()和分发 f.performStop(); dispatchOnFragmentStopped(f, false); case Fragment.STOPPED: case Fragment.ACTIVITY_CREATED: if (newState < Fragment.ACTIVITY_CREATED) { //1. onDestoryView()和分发 f.performDestroyView(); dispatchOnFragmentViewDestroyed(f, false); //3. Fragment销毁动画 anim = loadAnimator(f, transit, false, transitionStyle); anim.setTarget(f.mView); anim.start(); //4. 移除Fragment视图 f.mContainer.removeView(f.mView); } case Fragment.CREATED: if (newState < Fragment.CREATED) { //1. 执行Fragment的onDestory(), 并分发 f.performDestroy(); dispatchOnFragmentDestroyed(f, false); //2. 执行Fragment的onDetach(), 并分发 f.performDetach(); dispatchOnFragmentDetached(f, false); //3. Fragment从"活跃列表"中移除 makeInactive(f); } } } ...}
实战场景
预加载首页的所有页面
1、 预加载首页的所有页面
// 预加载所有fragment,且只展示第3个Store的FragmentloadMultipleFragment(R.id.fl_container, 2, tabsFragment);// 加载多个Fragmentprivate void loadMultipleFragment(int containerId, int showPosition, ArrayList<SupportFragment> targetFragments){ // 1. 获取到Fragment的事物 FragmentTransaction fragmentTransaction = getChildFragmentManager().beginTransaction(); // 2. add所有需要加载的fragment,并将除了需要展示的fragment都进行hide for (int i = 0; i < targetFragments.size(); i++) { SupportFragment fragment = targetFragments.get(i); fragmentTransaction.add(containerId, fragment); if(i != showPosition){ fragmentTransaction.hide(fragment); } } fragmentTransaction.commit();}
点击底部Button,加载不同的Fragment
2、点击底部Button,加载不同的Fragment
//如果已加载就用show hide方式切换,如果没加载就直接加载新Fragmentif (findStackFragment(tabsFragment.get(id).getClass(), getChildFragmentManager(), true) != null){ // 如果show和hide的Fragment不是同一个 getChildFragmentManager().beginTransaction().show(tabsFragment.get(id)).hide(tabsFragment.get(lastPosition)).commit();}else{ getChildFragmentManager().beginTransaction().add(tabsFragment.get(id))..hide(tabsFragment.get(lastPosition)).commit();}
判断目标Fragment是否已经加载
// 正常方式: 利用Tag找到该Fragment,但是如果是fragment存在于FragmentPagerAdapter中,这种方式就不准了,需要遍历子childFragment列表<T extends SupportFragment> T findStackFragment(Class<T> fragmentClass, FragmentManager fragmentManager, boolean isChild){ Fragment fragment = null; if (isChild) { // 如果是 查找子Fragment,则有可能是在FragmentPagerAdapter/FragmentStatePagerAdapter中,这种情况下, // 它们的Tag是以android:switcher开头,所以这里我们使用下面的方式 List<Fragment> childFragmentList = fragmentManager.getFragments(); if (childFragmentList == null) return null; for (int i = childFragmentList.size() - 1; i >= 0; i--) { Fragment childFragment = childFragmentList.get(i); if (childFragment instanceof SupportFragment && childFragment.getClass().getName().equals(fragmentClass.getName())) { fragment = childFragment; break; } } } else { fragment = fragmentManager.findFragmentByTag(fragmentClass.getName()); } if (fragment == null) { return null; } return (T) fragment;}
补充题
1、Fragment的常见问题,以及如何处理?
getActivity()空指针
:常见于进行异步操作的时候,此时如果Fragment已经onDetach()
,就会遇到。解决办法:在Fragment
里面使用一个全局变量mActivity
,可能会导致内存泄露。但是比崩溃
更好。视图重叠
:主要是因为Fragment
的onCreate()
中没有判断saveInstanceSate == null
,导致重复加载了同一个Fragment
2、Fragment的切换方式有多少种?
- add()、replace()、remove(): 会将Fragment进行移除(就是回收了实例),每次都会新建一个实例。
- hide()、show()
- detach()、attach()
3、add和replace的区别
- replace会将栈中的fragment全部remove,再add进行添加。
- add是在原有基础上进行添加。
4、hide和show的作用
隐藏和显示:将Fragment的显示和隐藏,占一点内存,
5、detach和attach的作用
- 不会回收Fragment,detach()会将Fragment中的View销毁掉。
- attach()会重新构建View。
- 极少使用
6、detach()和attach()为什么没啥用?
- 并不能节约多少内存
- 导致每次都会去重建这个View。
7、FragmentTransaction报错:commit already called
- beginTransaction()和commit()要配套使用
- 多次commit会出现该问题。
8、Fragment show之前需要已经被add到container中了
要注意重叠显示的问题
额外收获
- SparseArray:
当使用HashMap(K, V),如果K为整数类型时,使用SparseArray的效率更高
- 进度条动画库-Lottie:
进度条动画库:Lottie(https://github.com/airbnb/lottie-android)实现
Lottie动画:(https://www.lottiefiles.com/)。
优点: 使用非常方便,只需要下载JSON动画文件,然后在XML中写入。
参考资料
- Frgament基本使用方法
- FragmentTransaction详解
- Fragment详解
- Android基础:Fragment看这篇就够了
- ViewPager的使用
- Android优化方案之–Fragment的懒加载实现
更多相关文章
- [Android]使用全局变量传递数据
- android的native_bridge
- Android手机端调用webservice来获得手机归属地号码
- Android中home键和back键区别实例分析
- Android(安卓)UI开发第三十篇――使用Fragment构建灵活的桌面
- Android:如何给 ScrollView 添加滑块滚动条
- Android界面跳转时候onDestroy和onResume的调用顺序
- Unity与Android对比学习之生命周期方法
- [Android] ProcessBuilder与Runtime.getRuntime().exec分别创建