- fragment基本使用
- fragment使用需要注意的点
2.1 fragment重叠问题
2.2 fragment的onActivityResult分发的问题 - fragment应用
3.1 保存大数据
3.2 处理屏幕旋转的问题 - framentManager的作用
- fragment的事务的真正实现原理以及回退栈原理
- fragment的生命周期
Fragment的产生与介绍
Android运行在各种各样的设备中,有小屏幕的手机,超大屏的平板甚至电视。针对屏幕尺寸的差距,很多情况下,都是先针对手机开发一套App,然后拷贝一份,修改布局以适应平板神马超级大屏的。难道无法做到一个App可以同时适应手机和平板么,当然了,必须有啊。Fragment的出现就是为了解决这样的问题。你可以把Fragment当成Activity的一个界面的一个组成部分,甚至Activity的界面可以完全由不同的Fragment组成,更帅气的是Fragment拥有自己的生命周期和接收、处理用户的事件,这样就不必在Activity写一堆控件的事件处理的代码了。更为重要的是,你可以动态的添加、替换和移除某个Fragment。
Fragment常见使用场景
1.单Activity+多Fragment:
- 一个app仅有一个Activity,界面皆是Frament,Activity作为app容器使用。
- 优点:性能高,速度最快。参考:新版知乎 、google系app
2.多模块Activity+多Fragment
- 优点:速度快,相比较单Activity+多Fragment,更易维护
3.无视图的fragment ,保存数据用
- activity的onSaveInstanceState(Bundle outState)能保存的数据量有限,当有大量数据要保存的时候用无视图的fragment
- Activity异常销毁时,onSaveInstanceState能保存的数据有限,数据过大容易oom。所以我们可以在onSaveInstanceState时attach一个Fragment,并将要存储的大数据保存到这个Fragment。当Activity重建时找到这个Fragment,再从中取出那些数据
Fragment基本使用
1. 静态加载Fragment
定义Fragment的xml布局文件
自定义Fragment类,继承Fragment类或其子类,同时实现onCreate()方法,在方法中,通过inflater.inflate加载布局文件,接着返回其View
在需要加载Fragment的Activity对应布局文件中<fragment>
的name属性设为全限定类名,即包名.fragment
最后在Activity调用setContentView()加载布局文件即可
<fragment
android:id="@+id/right_fragment"
android:name="com.xxx.xxxFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
/>
使用注意点:
1. 静态加载的Fragment能不能嵌套使用fragment? 可以
//作业: 尝试下 fragment嵌套 静态使用fragment
2. 静态加载Fragment一旦添加就不能在运行时删除
3. 静态fragment 在哪儿初始化的?
4. fragment一定要有一个没有参数的构造函数。
fragment 为什么需要一个没有参数的构造函数? 看源码
2. 动态加载Fragment
动态加载Fragment:
- 获得FragmentManager对象,通过getSupportFragmentManager()
- 获得FragmentTransaction对象,通过fm.beginTransaction()
- 调用add()方法或者repalce()方法加载Fragment;
- 最后调用commit()方法提交事务
public class MainActivity extends FragmentActivity {
private String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i(TAG, "onCreate()");
setContentView(R.layout.activity_main);
if(savedInstanceState == null){
//1.获取fragmentManager
FragmentManager fragmentManager = getSupportFragmentManager();
//2.开启一个fragment事务
FragmentTransaction transaction = fragmentManager.beginTransaction();
//3.向FrameLayout容器添加MainFragment,现在并未真正执行
transaction.add(R.id.frameLayout, MainFragment.newIntance(), MainFragment.class.getName());
// 4.提交事务,真正去执行添加动作
transaction.commit();
}
}
}
Fragment使用注意点与常见异常
Fragment使用注意点
- Fragment的onCreateView()方法返回Fragment的UI布局,需要注意的是inflate()的第三个参数是false
- 如果在创建Fragment时要传入参数,必须要通过setArguments(Bundle bundle)方式添加参数,通过Bundle getArguments()获取参数,而不建议通过为Fragment添加带参数的构造函数, 因为内存重启时Fragment会自动恢复,这时只会调用无参的构造函数。
- 可以在Fragment的onAttach()中通过getArguments()获得传进来的参数。如果要获取Activity对象,不建议调用getActivity(),而是在onAttach()中将Context对象强转为Activity对象
- commit方法一定要在Activity.onSaveInstance()之前调用
- FragmentManager拥有回退栈(BackStack)
Fragment常见异常:
1. getActivity == null
出现的场景:请求网络接口后退出Activity(网络延迟比较高的情况容易出现)
解决方案: 不建议fragment要使用getActivity()方法获取宿主activity,
而是在onAttach(Activity activity)里赋值,使用mActivity代替getActivity(),
保证Fragment即使在onDetach后,仍持有Activity的引用(有引起内存泄露的风险,但是异步任务没停止的情况下,本身就可能已内存泄漏,相比Crash,这种做法“安全”些)
@Override
public void onAttach(Context context) {
super.onAttach(context);
mActivity = (Activity)context;
Log.i("Zero", "onAttach : " + getActivity());
}
2. Can not perform this action after onSaveInstanceState
出现的场景:在子线程commit fragment
解决方案:
1)不能在onSaveInstanceState()之后的生命周期里面commit fragment
2)不要在子线程commit fragment,因为这时Activity很有可能已经走完onSaveInstanceState()
3. fragment 重叠
出现的场景1: 屏幕旋转
原因:横竖屏切换时activity 会重新调用生命周期方法,onCreate 、onStart等
( 如果配置了android:configChanges=“orientation” 会调用onConfigurationChanged()方法)
在onCreate中 add了 fragment, 屏幕切换到横屏再切换到竖屏时 onCreate 又会执行, 所以又会重新add fragment,但是fragment 自动有一个恢复机制 ,所以系统自动恢复了一个fragment ,就有了两个fragment。
解决方案:
1). 重写onSaveInstanceState方法,使activity失去自动存储的功能。
2). onCreate中判断savedInstanceState == null才进行add fragment,
if(savedInstanceState == null){add fragment }
出现的场景2:异常销毁,即不是由于用户主动退出Activity(比如按BACK键后Activity被主动销毁)导致其被销毁,而是由于其他非正常情况导致的销毁,Activity的异常销毁会导致Fragment的异常销毁。
内存重启,比如:接了个电话,进入电话页面,此时系统内存不足,杀掉了你的activity,重新返回原来页面时activity也会重新调用生命周期方法,onCreate 、onStart等
(打开手机上的“开发者选项”中的“不保留活动”选项,模拟内存重启)
4. fragment 嵌套使用引起的异常
fragment 常见操作
add
add(@NonNull Fragment fragment, @Nullable String tag)
add(@IdRes int containerViewId, @NonNull Fragment fragment,
@Nullable String tag)
containerViewId: 指定fragment添加到那个viewgroup, findFragmentById
tag : findFragmentByTag会根据tag进行寻找
添加到回退栈
addToBackStack(@Nullable String name)
popBackStack()
popBackStack(@Nullable final String name, final int flags)
addToBackStack和popBackStack这两个方法就是分别执行入栈和出栈的操作
popBackStack() 按照栈弹出的顺序回退,比如栈中有 abcd,则出栈顺序是: d c b a
popBackStack(@Nullable final String name, final int flags) name表示这个Fragment上面的Fragment都出栈,flags为1表示name指定的Fragment也出栈。
比如栈中有 abcd,
如果调用popBackStack(b,0),则栈中有 ab
如果调用popBackStack(b,1),则栈中有 a
fragment嵌套时的回退栈问题 (单activity + fragment 架构的app比较常见)
解决方案:
方案1.自定义OnBackPressed 接口,所有的Fragment都实现该接口:
interface OnBackPressed{
fun onBackPressed()
}
open class LifeCycleFragment : Fragment(), OnBackPressed
然后在activity 的onBackPressed()中管理回退栈
override fun onBackPressed() {
super.onBackPressed()
//自己管理回退栈
}
该方案比较麻烦,推荐使用方案2.
方案2.使用jetpack navigation管理fragment
多次add fragment 到同一个位置,重叠问题
调用hide隐藏上一个添加的
add 与 replace的区别
add 是往containerViewId的容器里面添加fragment view
replace将containerViewId的容器里面之前添加的view全部清空
add和replace千万不要混合使用,要么全部用add,要么全部用replace
remove 操作
remove前要判断fragment是否已经添加:fragment.isAdded
if (fragment != null && fragment.isAdded) {
}
setRetainInstance(true)的使用
setRetainInstance的应用
- Activity如何保存大数据
- Android屏幕旋转如何处理AsysncTask和ProgressDialog
FragmentPagerAdapter与FragmentStatePagerAdapter
FragmentPagerAdapter:对于不再需要的fragment,选择调用detach方法,仅销毁视图, 并不会销毁fragment实例。
FragmentStatePagerAdapter:会销毁不再需要的fragment,当当前事务提交以后,会彻底 的将Fragment从当前Activity的FragmentManager中移除,state标明,销毁时,会将其 onSaveInstanceState(Bundle outState) 中的bundle信息保存下来,当用户切换回来,可以通过该bundle恢复生成新的 fragment,也就是说,你可以在 onSaveInstanceState(Bundle outState)方法中保存一些数据,在onCreate中进行 恢复创建。
FragmentStatePagerAdapter FragmentPagerAdapter
省内存 不省
创建需要时间 不要重新创建
tab页面多 tab <= 4
销毁 detach
Fragment的生命周期
Fragment必须是依存与Activity而存在的,因此Activity的生命周期会直接影响到Fragment的生命周期。官网这张图很好的说明了两者生命周期的关系:
onAttach(Activity)
当Fragment与Activity发生关联时调用,即Fragment attached到Activity时调用。
此时Fragment是ATTACHED状态,在此之前Fragment是INITIALIZING状态。
//Fragment.java
void performAttach() {
...
mState = ATTACHED;
mCalled = false;
onAttach(mHost.getContext());
...
}
/**
* Called when a fragment is first attached to its context.
* {@link #onCreate(Bundle)} will be called after this.
*/
@CallSuper
public void onAttach(@NonNull Context context) {
mCalled = true;
final Activity hostActivity = mHost == null ? null : mHost.getActivity();
if (hostActivity != null) {
mCalled = false;
onAttach(hostActivity);
}
}
onCreate(Bundle savedInstanceState)
该方法调用时可能Fragment所在的Activity正在Create过程中,因此不能在该方法中进行与Activity的ui视图结构的交互的相关操作。
此时Fragment是CREATED状态。
//Fragment.java
void performCreate(Bundle savedInstanceState) {
mChildFragmentManager.noteStateNotSaved();
mState = CREATED;
mCalled = false;
mSavedStateRegistryController.performRestore(savedInstanceState);
onCreate(savedInstanceState);
mIsCreated = true;
...
}
onCreateView(LayoutInflater, ViewGroup,Bundle)
创建该Fragment的视图,google官方建议只在该方法中创建布局视图,操作Fragment的布局视图的相关操作在onViewCreated()中进行。
此时Fragment是CREATED状态。
onActivityCreated(Bundle)
当Activity的onCreate方法返回时调用,此时Fragment所在的Activity已经完成Create过程。
当执行onActivityCreated()的时候 activity的 onCreate才刚完成,所以在onActivityCreated()调用之前 activity的onCreate可能还没有完成,所以不能再onCreateView()中进行与activity有交互的UI操作,UI交互操作可以在onActivityCreated()里面进行。
此时Fragment是ACTIVITY_CREATED状态。
onDestroyView()
与onCreateView相对应,当该Fragment的视图被移除时调用
onDestroy()
与onCreate相对应。
//Fragment.java
/**
* Called when the fragment is no longer in use. This is called
* after {@link #onStop()} and before {@link #onDetach()}.
*/
@CallSuper
public void onDestroy() {
mCalled = true;
}
onDetach()
与onAttach相对应,当Fragment与Activity关联被取消时调用
//Fragment.java
/**
* Called when the fragment is no longer attached to its activity. This
* is called after {@link #onDestroy()}.
*/
@CallSuper
public void onDetach() {
mCalled = true;
}
还有一些其他回调方法你也应当用来去处理fragment生命周期的各种阶段。
首先第一次创建Fragment的时候,其实执行了这么多方法:
onAttach -> onCreate -> onCreateView ->onViewCreated -> onActivityCreated -> onViewStateRestored -> onStart -> onResume
举几个例子:
1.按HOME键或者打开别的应用时:onPause -> onStop
2.从桌面回来或者从别的应用回来:onStart -> onResume
3.离开Activity时Fragment被彻底销毁执行的是:onPause -> onStop -> onDestoryView -> onDestory -> onDetach
Fragment中定义的各个状态:
//Fragment.java
static final int INITIALIZING = 0; // Not yet created.
static final int CREATED = 1; // Created.
static final int ACTIVITY_CREATED = 2; // Fully created, not started.
static final int STARTED = 3; // Created and started, not resumed.
static final int RESUMED = 4; // Created started and resumed.
int mState = INITIALIZING;
不同版本的Fragment定义的状态有不同,新版本是这几个状态:
//Fragment.java
static final int INITIALIZING = -1; // Not yet attached.
static final int ATTACHED = 0; // Attached to the host.
static final int CREATED = 1; // Created.
static final int ACTIVITY_CREATED = 2; // Fully created, not started.
static final int STARTED = 3; // Created and started, not resumed.
static final int RESUMED = 4; // Created started and resumed.
int mState = INITIALIZING;
...
Fragment的状态转换图:
Activity的状态与Fragment的生命周期方法回调图:
fragment的状态转换与生命周期方法回调图:
fragment的state 与Activity 的state不是等同。
与activity的生命周期详细对比
Activity的生命周期官方文档图:
Fragment的生命周期官方文档图:
生命周期探究
onHiddenChanged 的回调时机
- 当使用add()+show(),hide()跳转新的Fragment时,旧的Fragment回调onHiddenChanged(),不会回调onStop()等生命周期方法
- 而新的Fragment在创建时是不会回调onHiddenChanged()
获取FragmentManage的方式
- getFragmentManager()
- v4中,getSupportFragmentManager
主要的操作都是FragmentTransaction的方法
- FragmentTransaction transaction = fm.benginTransatcion();//开启一个事务
- transaction.add()
往Activity中添加一个Fragment - transaction.remove()
从Activity中移除一个Fragment,如果被移除的Fragment没有添加到回退栈(回退栈后面会详细
说),这个Fragment实例将会被销毁。 - transaction.replace()
使用另一个Fragment替换当前的,实际上就是remove()然后add()的合体~ 5. transaction.hide()
隐藏当前的Fragment,仅仅是设为不可见,并不会销毁 - transaction.show()
显示之前隐藏的Fragment - transaction.detach()
会将view从UI中移除,和remove()不同,此时fragment的状态依然由FragmentManager维护。 - transaction.attach()
重建view视图,附加到UI上并显示。 - transatcion.commit()//提交一个事务
注意:常用Fragment的哥们,可能会经常遇到Activity状态不一致:State loss这样的错误。
主要是因为:commit方法一定要在Activity.onSaveInstance()之前调用
上述,基本是操作Fragment的所有的方式了,在一个事务开启到提交可以进行多个的添加、移除、替换等操作。
值得注意的是:如果你喜欢使用Fragment,一定要清楚这些方法,哪个会销毁视图,哪个会销毁实例,哪个仅仅只是隐藏,这样才能更好的使用它们。
-
比如:我在FragmentA中的EditText填了一些数据,当切换到FragmentB时,如果希望回到FragmentA还能看到数据,则适合你的就是hide和show;也就是说,希望保留用户操作的面板,你可以使用hide和show,当然了不要使劲在那new实例,进行下非null判断。
-
再比如:我不希望保留用户操作,你可以使用remove(),然后add();或者使用replace()这个和remove,add是相同的效果。
-
remove 和 detach有一点细微的区别,在不考虑回退栈的情况下,remove会销毁整个Fragment实例,而detach则只是销毁其视图结构,实例并不会被销毁。那么二者怎么取舍使用呢?如果你的当前Activity一直存在,那么在不希望保留用户操作的时候,你可以优先使用detach。
Fragment Arguments
setArguments方法必须在fragment创建以后,添加给Activity前完成
Fragment与Activity通信
fragment交互后数据应该通过接口返回给activity
- 如果你Activity中包含自己管理的Fragment的引用,可以通过引用直接访问所有的Fragment的public方法
- 如果Activity中未保存任何Fragment的引用,那么没关系,每个Fragment都有一个唯一的TAG或者ID,可以通过getFragmentManager.findFragmentByTag()或者findFragmentById()获得任何Fragment实例,然后进行操作。
- 在Fragment中可以通过getActivity得到当前绑定的Activity的实例,然后进行操作。
Fragment与ActionBar和MenuItem集成
Fragment可以添加自己的MenuItem到Activity的ActionBar或者可选菜单中。
- 在Fragment的onCreate中调用 setHasOptionsMenu(true);
- 然后在Fragment子类中实现onCreateOptionsMenu
- 如果希望在Fragment中处理MenuItem的点击,也可以实现onOptionsItemSelected;当然了
Activity也可以直接处理该MenuItem的点击事件。
Fragment原理
Fragment本质上是 view 的容器和控制器,Fragment 是 activity 的碎片。
activity 是四大组件之一,是 android 系统的组成部件,fragment 除了不是系统组件外,拥有Activity 的所有的功能。fragment 的存在就是对 activity 的功能进行拆分,降低 activity 的负担,减少 activity 中的代码量。
Fragment 可以使你能够将 activity 分离成多个可重用的组件,每个都有它自己的生命周期和UI。
Fragment 是一个独立的模块,紧紧地与 activity 绑定在一起。可以运行中动态地移除、加入、交换等。
Fragment源码分析
FragmentTransaction的add() 、replace()、 hide() 、show() -> commit ()
简单API后面是什么原理?
FragmentActivity: AMS控制Activity的生命周期,FragmentActivity在一系列的dispatchXXX方法里面调用FragmentController来控制Fragment的生命周期
FragmentController:是一个代理,所有操作都交给FragmentHostCallback实现
FragmentHostCallback: 被FragmentController持有,FragmentHostCallback有一个FragmentManagerImpl成员属性,FragmentManagerImpl真正的进行fragment操作
FragmentContainer: 定义了所在容器的Callbacks
FragmentTransaction: Fragment操作的事务,每个FragmentTransaction对象,代表着一个包含一系列Fragment操作的事务,保证一些列Fragment操作的原子性。
操作Fragment的添加,删除,隐藏,显示时,都要使用FragmentTransaction。
FragmentTransaction其实是个抽象类,真正的实现类为BackStackRecord
BackStackRecord: BackStackRecord实现了FragmentTransaction,每一个BackStackRecord对象,都代表了一个包含一系列Fragment操作的事务。
Op: Op对象,主要保存了FragmentTransaction操作事务中的单次操作,是相对于Fragment而言的。每个Op对象,保存了操作的命令、操作的Fragment对象,切换动画等对象。
比如:add()操作会new一个Op对象,把操作的命令 OP_ADD和fragment进行映射。
创建FragmentManagerImpl对象
在FragmentActivity 创建的时候就会创建FragmentManagerImpl对象。
public class FragmentActivity extends ComponentActivity implements
ActivityCompat.OnRequestPermissionsResultCallback,
ActivityCompat.RequestPermissionsRequestCodeValidator {
private static final String TAG = "FragmentActivity";
...
final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
...
//FragmentActivity.HostCallbacks继承FragmentHostCallback,通过重写FragmentHostCallback中的函数,
//将Fragment操作与Fragment宿主(例如FragmentActivity)捆绑起来。有些方法需要通过宿主来实现,单单依靠Fragment,无法实现
class HostCallbacks extends FragmentHostCallback<FragmentActivity> implements
ViewModelStoreOwner,
OnBackPressedDispatcherOwner {
public HostCallbacks() {
super(FragmentActivity.this /*fragmentActivity*/);
}
...
}
}
public abstract class FragmentHostCallback<E> extends FragmentContainer {
@Nullable private final Activity mActivity;
@NonNull private final Context mContext;
@NonNull private final Handler mHandler;
private final int mWindowAnimations;
final FragmentManagerImpl mFragmentManager = new FragmentManagerImpl();
...
}
add、remove、replace、show、commit等过程分析
事务操作
操作 command 意义
add OP_ADD 添加fragment
remove OP_REMOVE 删除fragment
replace OP_REPLACE 替换fragment
show OP_SHOW 显示fragment
hide OP_HIDE 隐藏fragment
attach OP_ATTACH 依附activity
detach OP_DETACH 从activity分离
cmd firstOutFragments lastInFragements
OP_ADD - 被添加的fragment
OP_REPLACE 被替换的fragment 新的fragment
OP_REMOVE 被删除的fragment -
OP_HIDE 被隐藏的fragment -
OP_SHOW - 被显示的fragment
OP_DETACH 被detach的fragment -
OP_ATTACH - 被attach的fragment
BackStackRecord的executeOps()方法
cmd 调用FragmentManager方法
OP_ADD addFragment
OP_REPLACE removeFragment->addFragment
OP_REMOVE removeFragment
OP_HIDE hideFragment
OP_SHOW showFragment
OP_DETACH detachFragment
OP_ATTACH attachFragment
add过程分析
//BackStackRecord.java
@Override
public FragmentTransaction add(int containerViewId, Fragment fragment) {
doAddOp(containerViewId, fragment, null, OP_ADD);
return this;
}
private void doAddOp(int containerViewId, Fragment fragment, @Nullable String tag, int opcmd) {
...
addOp(new Op(opcmd, fragment));
}
void addOp(Op op) {
mOps.add(op);// ArrayList<Op> mOps = new ArrayList<>();
op.enterAnim = mEnterAnim;
op.exitAnim = mExitAnim;
op.popEnterAnim = mPopEnterAnim;
op.popExitAnim = mPopExitAnim;
}
添加 Fragment 就是给 fragment 事务对象中的Op对象链表中添加一个Op对象。
commit过程分析
commit与commitNow()与什么区别:
//BackStackRecord.java
@Override
public int commit() {
return commitInternal(false);
}
@Override
public int commitAllowingStateLoss() {
return commitInternal(true);
}
@Override
public void commitNow() {
disallowAddToBackStack();
mManager.execSingleAction(this, false);
}
@Override
public void commitNowAllowingStateLoss() {
disallowAddToBackStack();
mManager.execSingleAction(this, true);
}
int commitInternal(boolean allowStateLoss) {
if (mCommitted) throw new IllegalStateException("commit already called");
if (FragmentManagerImpl.DEBUG) {
Log.v(TAG, "Commit: " + this);
LogWriter logw = new LogWriter(TAG);
PrintWriter pw = new PrintWriter(logw);
dump(" ", null, pw, null);
pw.close();
}
mCommitted = true;
if (mAddToBackStack) {
mIndex = mManager.allocBackStackIndex(this);//更新 index 信息
} else {
mIndex = -1;
}
mManager.enqueueAction(this, allowStateLoss);//异步任务入队
return mIndex;
}
//FragmentManager.java
/**
* Adds an action to the queue of pending actions.
*
* @param action the action to add
* @param allowStateLoss whether to allow loss of state information
* @throws IllegalStateException if the activity has been destroyed
*/
public void enqueueAction(OpGenerator action, boolean allowStateLoss) {
if (!allowStateLoss) {
checkStateLoss();
}
synchronized (this) {
if (mDestroyed || mHost == null) {
if (allowStateLoss) {
// This FragmentManager isn't attached, so drop the entire transaction.
return;
}
throw new IllegalStateException("Activity has been destroyed");
}
if (mPendingActions == null) {
mPendingActions = new ArrayList<>();
}
// 将commit处理操作放置到mPendingActions列表中
mPendingActions.add(action);
scheduleCommit();//发送任务
}
}
private void scheduleCommit() {
synchronized (this) {
boolean postponeReady =
mPostponedTransactions != null && !mPostponedTransactions.isEmpty();
boolean pendingReady = mPendingActions != null && mPendingActions.size() == 1;
if (postponeReady || pendingReady) {
mHost.getHandler().removeCallbacks(mExecCommit);
mHost.getHandler().post(mExecCommit);
}
}
}
Runnable mExecCommit = new Runnable() {
@Override
public void run() {
execPendingActions();
}
};
/**
* Only call from main thread!
*/
public boolean execPendingActions() {
ensureExecReady(true);
boolean didSomething = false;
//将mPendingActions列表的action拷贝到mTempActions数组里
//mTmpRecords, mTmpIsPop 前者用来临时存储所有待执行的动作(mPendingActions)生成的 BackStackRecord,
//后者用来存储 BackStackRecord 是否为出栈
while (generateOpsForPendingActions(mTmpRecords, mTmpIsPop)) {
mExecutingActions = true;
try {
removeRedundantOperationsAndExecute(mTmpRecords, mTmpIsPop);//这里是入口执行 BackStackRecord 列表
} finally {
cleanupExec();
}
didSomething = true;
}
doPendingDeferredStart();
burpActive();
return didSomething;
}
public void execSingleAction(OpGenerator action, boolean allowStateLoss) {
if (allowStateLoss && (mHost == null || mDestroyed)) {
// This FragmentManager isn't attached, so drop the entire transaction.
return;
}
ensureExecReady(allowStateLoss);
if (action.generateOps(mTmpRecords, mTmpIsPop)) {
mExecutingActions = true;
try {
removeRedundantOperationsAndExecute(mTmpRecords, mTmpIsPop);
} finally {
cleanupExec();
}
}
doPendingDeferredStart();
burpActive();
}
上述源码分析可以知道,commitNow()是直接同步进行Fragment的相关操作的,而commit()是把操作加入到ArrayList<OpGenerator> mPendingActions;
列表中,然后调用mHost.getHandler().post(mExecCommit);
异步进行操作的(消息队列里面可能还有很多其他待处理的任务,所以commit()操作不一定马上执行)。
commit与commitAllowingStateLoss有什么区别?
//FragmentManager.java
public void enqueueAction(OpGenerator action, boolean allowStateLoss) {
if (!allowStateLoss) {
checkStateLoss();
}
...
}
private void checkStateLoss() {
if (isStateSaved()) {
throw new IllegalStateException(
"Can not perform this action after onSaveInstanceState");
}
if (mNoTransactionsBecause != null) {
throw new IllegalStateException(
"Can not perform this action inside of " + mNoTransactionsBecause);
}
}
@Override
public boolean isStateSaved() {
// See saveAllState() for the explanation of this. We do this for
// all platform versions, to keep our behavior more consistent between
// them.
return mStateSaved || mStopped;
}
commit()会调用checkStateLoss()判断 Activity 的状态是否已经保存,如果已经保存则报错。
即commit()需要在宿主 Activity 保存状态之前((Activity#onSaveInstanceState()被调用之前))调用,否则会报错。
而commitAllowingStateLoss可以在宿主 Activity 保存状态之后调用。
总结:
commit() 是异步执行,且需要在宿主 Activity 保存状态之前调用,否则会报错。
这是因为如果 Activity 出现异常需要恢复状态,在保存状态之后的 commit() 将会丢失,这和调用的初衷不符,所以会报错。
commitAllowingStateLoss() 也是异步执行,但它的不同之处在于,允许在 Activity 保存状态之后调用,也就是说它遇到状态丢失不会报错。
因此我们一般在界面状态出错是可以接受的情况下使用它。
commitNow() 是同步执行的,立即提交任务。和 commit() 一样, commitNow() 也必须在 Activity 保存状态前调用,否则会抛异常。
commitNowAllowingStateLoss() 同步执行的 commitAllowingStateLoss() 。
commitNow()和commitNowAllowingStateLoss() 都是同步执行的,所以都不支持添加到回退栈的操作,即:调用commitNow()或者commitNowAllowingStateLoss()之前不能调用addToBackStack()。
不能对要加在back stack中的transaction使用commitNow(), 即addToBackStack()和commitNow()不能同时使用。为什么呢?
如果你有一个提交使用了commit(), 紧接着又有另一个提交使用了commitNow(), 两个都想加入back stack, 那back stack会变成什么样呢? 到底是哪个transaction在上, 哪个在下? 答案将是一种不确定的状态, 因为系统并没有提供任何保证来确保顺序, 所以系统决定干脆不支持这个操作。
Fragment生命周期的调用
FragmentActivity的各个生命周期方法里面调用mFragments.dispatchXXXXX方法进行调用Fragment的生命周期方法。
//FragmentActivity.java
/**
* Perform initialization of all fragments.
*/
@SuppressWarnings("deprecation")
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
mFragments.attachHost(null /*parent*/);
...
mFragments.dispatchCreate();
}
/**
* Dispatch onResume() to fragments. Note that for better inter-operation
* with older versions of the platform, at the point of this call the
* fragments attached to the activity are <em>not</em> resumed.
*/
@Override
protected void onResume() {
super.onResume();
mResumed = true;
mFragments.noteStateNotSaved();
mFragments.execPendingActions();
}
/**
* Dispatch onStart() to all fragments.
*/
@Override
protected void onStart() {
super.onStart();
...
mFragments.dispatchStart();
}
//FragmentManagerImpl.java
public void dispatchCreate() {
mStateSaved = false;
mStopped = false;
dispatchStateChange(Fragment.CREATED);
}
public void dispatchActivityCreated() {
mStateSaved = false;
mStopped = false;
dispatchStateChange(Fragment.ACTIVITY_CREATED);
}
public void dispatchStart() {
mStateSaved = false;
mStopped = false;
dispatchStateChange(Fragment.STARTED);
}
public void dispatchResume() {
mStateSaved = false;
mStopped = false;
dispatchStateChange(Fragment.RESUMED);
}
public void dispatchPause() {
dispatchStateChange(Fragment.STARTED);
}
public void dispatchStop() {
mStopped = true;
dispatchStateChange(Fragment.ACTIVITY_CREATED);
}
public void dispatchDestroyView() {
dispatchStateChange(Fragment.CREATED);
}
private void dispatchStateChange(int nextState) {
try {
mExecutingActions = true;
moveToState(nextState, false);
} finally {
mExecutingActions = false;
}
execPendingActions();
}
最终调用moveToState进行Fragment状态的切换,Fragment生命周期的相关方法都在moveToState方法里面被调用。
FragmentManager的moveToState()方法
状态表格
值 状态 意义 入口方法
0 INITIALIZING Fragment尚未初始化或者已经被销毁 dispatchDestroy
1 CREATED Fragment已被创建 dispatchDestoryView
2 ACTIVITY_CREATED Fragment所在Activity已经创建成功 dispatchActivityCreated
3 STARTED Fragment已经可见,但是还不可以接收点击事件 dispatchPause
4 RESUMED Fragment已经可见,并且可以接收点击事件 dispatchResume
已经废弃
3 STOPPED Fragment已经被创建,但是尚未不可见 dispatchStop
STARTED 与 RESUMED区别:
STARTED(可见):Fragment已经可见,但是还不可以接收点击事件
RESUMED(可见且可交互):Fragment已经可见,并且可以接收点击事件
注意:可见与可交互的区别
当activity的onCreate()调用dispatchCreate()的时候,不管fragments 之前处于什么状态都会切换到CREATED状态。
Fragment如何显示到页面上
Fragment如何显示到页面上?最终是把Fragment的view添加到Fragment所在的ViewGroup中:
//FragmentManager.java
void moveToState(Fragment f, int newState, int transit, int transitionStyle,
boolean keepActive) {
...
f.mContainer = container;
f.performCreateView(f.performGetLayoutInflater(
f.mSavedFragmentState), container, f.mSavedFragmentState);
if (f.mView != null) {
f.mInnerView = f.mView;
f.mView.setSaveFromParentEnabled(false);
if (container != null) {
container.addView(f.mView);//把Fragment的view添加到Fragment所在的ViewGroup中
}
if (f.mHidden) {
f.mView.setVisibility(View.GONE);
}
f.onViewCreated(f.mView, f.mSavedFragmentState);
...
}
Fragment的BackStack管理
//FragmentManager.java
// Must be accessed while locked.
ArrayList<BackStackRecord> mBackStackIndices; //mBackStackIndices 返回栈索引列表,用来给 BackStackRecord 分配 mIndex 需要
ArrayList<Integer> mAvailBackStackIndices;//返回栈可用索引列表,存储着 mBackStackIndices 列表中值为 null 的下标
// Temporary vars for removing redundant operations in BackStackRecords:
ArrayList<BackStackRecord> mTmpRecords;//用于优化执行的临时变量,用来临时存储所有待执行的动作(mPendingActions)生成的 BackStackRecord,
ArrayList<Boolean> mTmpIsPop;//用于优化执行的临时变量,用来存储 BackStackRecord 是否为出栈
ArrayList<Fragment> mTmpAddedFragments;
//用于给加入到返回栈的 BackStackRecord 分配 mIndex 下标,并加入到返回栈
public int allocBackStackIndex(BackStackRecord bse) {
synchronized (this) {
if (mAvailBackStackIndices == null || mAvailBackStackIndices.size() <= 0) {
if (mBackStackIndices == null) {
mBackStackIndices = new ArrayList<BackStackRecord>();
}
int index = mBackStackIndices.size();
if (DEBUG) Log.v(TAG, "Setting back stack index " + index + " to " + bse);
mBackStackIndices.add(bse);
return index;
} else {
int index = mAvailBackStackIndices.remove(mAvailBackStackIndices.size()-1);
if (DEBUG) Log.v(TAG, "Adding back stack index " + index + " with " + bse);
mBackStackIndices.set(index, bse);
return index;
}
}
}
//该方法是出栈的时候调用的,当 BackStackRecord 出栈后更新返回栈可用索引列表mAvailBackStackIndices的
public void freeBackStackIndex(int index) {
synchronized (this) {
mBackStackIndices.set(index, null);//把相应位置设置成 null,然后把该下标加入可用返回栈列表的列尾
if (mAvailBackStackIndices == null) {
mAvailBackStackIndices = new ArrayList<Integer>();
}
if (DEBUG) Log.v(TAG, "Freeing back stack index " + index);
mAvailBackStackIndices.add(index);
}
}
FragmentManager用mAvailBackStackIndices和mBackStackIndices两个数组来为BackStackRecord分配Index以及存储。mAvailBackStackIndices用来存储在mBackStackIndices中能够分配的Index,mBackStackIndices则用来保存BackStackRecord。
这利用两个数组可以减少对mBackStackIndices的动态扩容以及数组复制的次数,是一个以空间换时间的策略。上面的代码首先判断是否有可用的Index分配给BackStackRecord,若无则直接将BackStackRecord插入到mBackStackIndices;若存在的话则从mAvailBackStackIndices的队尾取出一个index,然后在mBackStackIndices中的该index位置设置BackStackRecord值。
当从mBackStackIndices中pop出一个BackStackRecord时,并不会进行数组复制移动来回收这块内存,而是调用freeBackStackIndex()把BackStackRecord所在的mBackStackIndices的index这块内存置为null,然后把index加入mAvailBackStackIndices,这样当新的BackStackRecord入栈时,先从mAvailBackStackIndices中remove出一个index,然后把新的BackStackRecord插入到mBackStackIndices的index处。我们都知道ArrayList在插入和删除元素时会进行数组复制和移动,这样性能不高,这里采用额外加个mAvailBackStackIndices用来保存可用下标的方法,进行复用内存,避免了数组复制和移动,避免了内存的频繁分配与释放。
如下图(图只是画个大概,并不是精确画的):
下次新的BackStackRecord入栈时,会先从mAvailBackStackIndices中remove出一个index(这里即下标为2),然后把新的BackStackRecord插入到mBackStackIndices的index(即下标为2)处。
参考:
https://developer.android.google.cn/guide/components/fragments
https://developer.android.com/guide/topics/fundamentals/fragments.html