你真的懂 Fragment 吗?Fragment 的过去、现在和未来~

Fragment 是一个历史悠久的组件,从 API 11 引入至今,已经成为 Android 开发中最常用的组件之一。

在这个专题里,我们将从「使用 & 核心原理 & 面试」三个层面来讨论 Fragment。如果能帮上忙,请务必点赞加关注,这真的对我非常重要。

Fragment 的过去、现在和未来

1.1 Fragment 解决了什么问题?(过去)

Fragment 可以将 Activity 视图拆分为多个区块进行模块化地管理 ,避免了 Activity 视图代码过度臃肿混乱。

虽然自定义 View 或 Window 也可以在一定程度拆分 Activity 界面,但事实上它们的职责不同:View / Window 的职责是封装某种视图功能,而 Fragment 是在更高的层次使用控制自定义 View。

此外,Fragment 还可以更方便地管理生命周期和事务(虽然我们会通过 MVP 或 MVVM 模式分离业务逻辑,但是对于复杂页面,我们还是无法避免 Activity 视图代码演化的非常臃肿混乱)。

需要注意的是,Fragment 不能脱离 Activity 独立存在,必须由 Activity 或另一个 Fragment 托管,Fragment#onCreateView 实例化的视图最终会被嵌入到宿主的视图树中。

角色MVVM 分层生命周期感知
Activity视图控制器View 层感知
Fragment视图控制器View 层感知
View视图View 层不感知
Window视图View 层不感知

1.2 Fragment 存在什么问题?(现在)

Fragment 的最初设计理念是 “一个微型 Activity” 的角色,正所谓 “欲戴王冠,必受其重”,很多专门为 Activity 设计 的 API 也需要添加到 Fragment 中,比如运行时权限,多窗口模式切换等新 API。这无疑是在无限制地扩充 Fragment 的职责边界,也在增大 Fragment 设计的复杂度,要知道 Fragment 的本质思想是界面模块化而已。

1.3 Fragment 2.0(未来)

Google 正在重新构思 Fragment 的定位,随着 AndroidX Fragment 版本陆续更新,新版 Fragment 正在渐渐走进我们的视野,我们称新版 Fragment 为 Fragment 2.0,已知的新特性包括:

  • FragmentScenario :Fragment 的测试框架;
  • FragmentFactory :统一的 Fragment 实例化组件;
  • FragmentContainerView :Fragment 专属视图容器;
  • OnBackPressedDispatcher :在 Fragment 或其他组件中处理返回按钮事件。

具体分析见:Android | 上车!AndroidX Fragment 新姿势!

https://developer.android.google.cn/jetpack/androidx/releases/fragment

https://www.jianshu.com/p/4c8ebe671f13

2

说一下 Fragment 的整体结构

现在,我们正式开始讨论 Fragment 的核心工作原理,分析的过程中我会结合读源码,帮助你更清晰地、全面地理解 Fragment 的工作原理。不过,在开始之前我们有必要先梳理Fragment 源码的整体结构设计,为下文深入阅读源码打下基础。

2.1 代码框架

FragmentActivity.java

finalFragmentController mFragments = FragmentController.createController( newHostCallbacks);

protectedvoidonCreate(@Nullable Bundle savedInstanceState){

mFragments.attachHost( null/*parent*/);

super.onCreate(savedInstanceState);

...

// FragmentController 中定义了很多 dispatchXX 方法

mFragments.dispatchCreate;

}

如下 UML 类图描述了 Framgent 整体的代码框架:

要点如下:

  • FragmentActivity 是 Activity 支持 Fragment 的基础,其中持有一个 FragmentController 中间类,它是 FragmentActivity 和 FragmentManager 的中间桥接者,对 Fragment 的操作最终是分发到 FragmentManager 来处理;
  • FragmentManager 承载了 Fragment 的核心逻辑,负责对 Fragment 执行添加、移除或替换等操作,以及添加到返回堆栈。它的实现类 FragmentManagerImpl 是我们主要的分析对象;
  • FragmentHostCallback 是 FragmentManager 向 Fragment 宿主的回调接口,Activity 和 Fragment 中都有内部类实现该接口,所以 Activity 和 Fragment 都可以作为另一个 Fragment 的宿主(Fragment 宿主和 FragmentManager 是 1 : 1 的关系);
  • FragmentTransaction 是 Fragment 事务抽象类,它的实现类 BackStackRecord 是事务管理的主要分析对象。

下图描述了每个宿主与关联的 FragmentManager 的关系:

提示(必看):在后面的讨论中,我们不会再感知 FragmentActivity 与 Fragment 中间的 FragmentController,因为这属于软件设计模式的实现细节,而不是 Fragment 的核心源码。阅读源码我们一定要拨开表面看本质,抓流程,不要拘泥细枝末节。

2.2 说一下 Fragment 生命周期?

生命周期感知是 Fragment 的最基础的功能,也是面试的重灾区,我认为在 “背诵” 生命周期之前,如果你先向面试官阐述你对以下问题的理解,或许是更棒的回答。

问题 1:什么是生命周期,生命周期回调方法(比如onCreateView)是生命周期的本质吗?

答:不然。状态转移才是生命周期的本质(Activity 同理)。生命周期方法的本质是Fragment 状态转移,当生命周期方法被调用,说明 Fragment 从一个状态转移到另一个状态,而所谓的 “生命周期回调” 只是 Framework 提供给开发者使用的 Hook 点,用于在状态转移时执行自定义逻辑。

问题 2:你提到状态转移,那你说下 Fragment 有哪几种状态?

答:从源码看,AndroidX Fragment 定义了以下五种状态,相对于早期的 Support Fragment 版本少了STOPPED 等状态,这是因为 Google 认为这些状态是可以对称使用的,例如STOPPED 状态和STARTED 状态其实没有本质区别。

Fragment.java

staticfinalintINITIALIZING = 0; 初始状态,Fragment 未创建

staticfinalintCREATED = 1; 已创建状态,Fragment 视图未创建

staticfinalintACTIVITY_CREATED = 2; 已视图创建状态,Fragment 不可见

staticfinalintSTARTED = 3; 可见状态,Fragment 不处于前台

staticfinalintRESUMED = 4; 前台状态,可接受用户交互

问题 3:Fragment 生命周期与宿主同步的吗,如果不是,是独立的吗?

答:不然。Fragment 的生命周期主要受「宿主」、「事务」、「setRetainInstanceAPI」三个因素影响:当宿主生命周期发生变化时,会触发 Fragment 状态转移到 宿主的最新状态。不过,使用事务和setRetainInstance API 也可以使 Fragment 在一定程度上与宿主状态不同步(需要注意:宿主依然在一定程度上形成约束)。

  • 宿主:【见第 3 节】
  • 事务:【见第 4 节】
  • setRetainInstance API : 【见第 6 节】

下面这张图完整描绘了 Fragment 生命周期:

内容很多,别怕。跟着我的节奏一步步分析下去,也就那样了:“就这?

3

宿主如何改变 Fragment 状态

前面提到,Fragment 的生命周期受到三个因素影响,我们暂且讨论宿主的因素。

3.1 Activity 与 Fragment 生命周期的同步关系

当宿主生命周期发生变化时,Fragment 的状态会同步到宿主的状态。从源码看,体现在宿主生命周期回调中会调用 FragmentManager 中一系列 dispatchXXX方法来触发 Fragment 状态转移。

FragmentActivity.java

@Override

protectedvoidonCreate(@Nullable Bundle savedInstanceState){

mFragments.attachHost( null/*parent*/);

...

mFragments.dispatchCreate; // 最终调用 FragmentManager#dispatchCreate

}

下表总结了 Activity 生命周期与 Fragment 生命周期的关系:

Activity 生命周期FragmentManagerFragment 状态转移Fragment 生命周期回调
onCreatedispatchCreateINITIALIZING

-> CREATE

-> onAttach

-> onCreate

onStart(首次)dispatchActivityCreated

dispatchStart

CREATE

-> ACTIVITY_CREATED

-> STARTED

-> onCreateView

-> onViewCreated

-> onActivityCreated

-> onStart

onStart(非首次)dispatchStartACTIVITY_CREATED

-> STARTED

-> onStart
onResumedispatchResumeSTARTED

-> RESUMED(Fragment 可交互)

-> onResume
onPausedispatchPauseRESUMED

-> STARTED

-> onPause
onStopdispatchStopSTARTED

-> ACTIVITY_CREATED

-> onStop
onDestroydispatchDestroyACTIVITY_CREATED

-> CREATED

-> INITIALIZING

-> onDestroyView

-> onDestroy

-> onDetach

3.2 状态转移核心源码分析

FragmentManager 中一系列 dispatchXXX方法会触发 Fragment 状态转移,我们点进去看看:

提示: 源码方法跳转太多,不利于理解核心流程。我直接帮你梳理出核心流程,跟你直接看源码会不同,但逻辑是相同的。

FragmentManager.java

(源码方法跳转太多,我直接帮你梳理出核心流程,跟你直接看源码会不同,但逻辑是相同的)

publicvoiddispatchCreate{

mStateSaved = false;

mStopped = false;

moveToState(Fragment.CREATED, false);

4、处理未执行的事务(见第 4节)

execPendingActions;

}

voidmoveToState( intnewState, boolean always ) {

1、状态判断

if(nextState == mCurState) {

return;

}

mCurState = nextState;

2、执行添加的 Fragment

// Must add them in the proper order. mActive fragments may be out of order

for( inti = 0; i < mAdded.size; i++) {

Fragment f = mAdded. get(i);

// 更新 Fragment 到当前状态

moveFragmentToExpectedState(f);

}

3、执行未添加,但是准备移除的 Fragment

// Now iterate through all active fragments. These will include those that are removed and detached.

for( inti = 0; i < mActive.size; i++) {

Fragment f = mActive.valueAt(i);

if(f != null&& (f.mRemoving || f.mDetached) && !f.mIsNewlyAdded) {

// 更新 Fragment 到当前状态

moveFragmentToExpectedState(f);

}

}

}

其中,moveFragmentToExpectedState最终调用到 moveToState(Fragment, int):

FragmentManager.java

-> moveFragmentToExpectedState 最终调用到

-> 更新 Fragment 到当前状态

voidmoveToState(Fragment f, intnewState) {

1、准备 Detatch Fragment 的情况,不再与宿主同步,进入 CREATED 状态

if((!f.mAdded || f.mDetached) && newState > Fragment.CREATED) {

newState = Fragment.CREATED;

}

2、移除 Fragment 的情况,Fragment 不再与宿主同步

if(f.mRemoving && newState > f.mState) {

if(f.isInBackStack) {

2.1移除动作添加到返回栈,则进入 CREATED 状态

newState = Math.min(nextState, Fragment.CREATED);

} else{

2.1移除动作添加到返回栈,则进入 DESTROY 状态

newState = Math.min(nextState, Fragment.INITIALIZING);

}

}

3、真正执行状态转移

if(f.mState <= newState ) {

switch(f.mState) {

caseFragment.INITIALIZING:

if(nextState> Fragment.INITIALIZING) {

...

}

// fall through

caseFragment.CREATED:

...

// fall through

caseFragment.ACTIVITY_CREATED:

...

// fall through

caseFragment.STARTED:

...

}

} else{

switch(f.mState) {

caseFragment.RESUMED:

if(newState < Fragment.RESUMED) {

...

}

// fall through

caseFragment.STARTED:

...

// fall through

caseFragment.ACTIVITY_CREATED:

...

// fall through

caseFragment.CREATED:

...

}

}

...

}

以上代码已经非常简化了,总结一下:

触发状态转移时,首先会判断 Fragment,如果已经处于目标状态 newState,则会跳过状态转移。然而,并不是 FragmentManager 里所有的 Fragment 都会执行状态转移,只有 「mAdded 为真 && mDetached 为假」 的 Fragment 才会更新到目标状态,其他 Fragment 会脱离宿主状态。最后,状态转移完成后会处理未执行的事务execPendingActions;,可见每次dispatchXXX 都会提供一次事务执行的窗口。

不同 Fragment 标志位(Detach / Remove / 返回栈)与最终状态的关系总结如下表:

情况判断描述最终状态
1f.mRemoving移除Fragment.INITIALIZING
2f.mRemoving && f.isInBackStack移除,但添加进返回栈Fragment.CREATED
3!f.mAdded || f.mDetached未添加Fragment.CREATED
4f.mAdded已添加newState(同步到宿主状态)

提示: 这些标志位可以通过事务进行干涉。

3.3 典型场景生命周期

基本规律:Activity 状态转移触发 Fragment 状态转移。

首次启动:

Activity - onCreate

Fragment - onAttach

Fragment - onCreate

Fragment - onCreateView

Fragment - onViewCreated

Activity - onStart

Fragment - onActivityCreated

Fragment - onStart

Activity - onResume

Fragment - onResume

-------------------------------------------------

退出:

Activity - onPause

Fragment - onPause

Activity - onStop

Fragment - onStop

Activity - onDestroy

Fragment - onDestroyView

Fragment - onDestroy

Fragment - onDetach

-------------------------------------------------

回到桌面:

Activity - onPause

Fragment - onPause

Activity - onStop

Fragment - onStop

-------------------------------------------------

返回:

Activity - onStart

Fragment - onStart

Activity - onResume

Fragment - onResume

4

Fragment 事务管理

现在,我们来讨论影响 Fragment 状态转移的第二个因素:事务。

4.1 事务概述

问题 1:事务的特性是什么? 事务是恢复和并发的基本单位,具备 4 个基本特性:

1、原子性:事务不可分割,要么全部完成,要么全部失败回滚;

2、一致性:事务执行前后数据都具有一致性;

3、隔离性:事务执行过程中,不受其他事务干扰;

4、持久性:事务一旦完成,对数据的改变就是永久的。在 Android 中体现为 Fragment 状态保存后,commit 提交事务会抛异常,因为这部分新提交的事务影响的状态无法保存。

Fragment 事务的作用: 使用事务 FragmentTransaction 可以动态改变 Fragment 状态,使得 Fragment 在一定程度脱离宿主的状态。不过,事务依然受到宿主状态约束,例如:当前 Activity 处于STARTED 状态,那么 addFragment 不会使得 Fragment 进入RESUME 状态。只有将来 Activity 进入RESUME 状态时,才会同步 Fragment 到最新状态。

4.2 你知道不同事务操作的区别吗?

add & remove:Fragment 状态在INITIALIZING与RESUMED之间转移。

detach & attach:Fragment 状态在CREATE 与RESUMED 之间转移。

replace: 先移除所有 containerId 中的实例,再 add 一个 Fragment。

show & hide: 只控制 Fragment 隐藏或显示,不会触发状态转移,也不会销毁 Fragment 视图或实例。

hide & detach & remove 的区别:hide 不会销毁视图和实例、detach 只销毁视图不销毁实例、remove 会销毁实例(自然也销毁视图)。不过,如果 remove 的时候将事务添加到回退栈,那么 Fragment 实例就不会被销毁,只会销毁视图。

下图描述了 Fragment 状态转移与宿主和事务的简单关系:

这里有一个让人摸不着头脑的问题,detach Fragment 并不会回调onDetach,因为 detach 只会转移到 CREATE 状态,而回调onDetach 需要转移到 INITIALIZING。不知道 Google 为什么要采用这么有歧义的命名。

detachFragment:

Fragment - onPause

Fragment - onStop

Fragment - onDestroyView

4.3 说说看不同事务提交方式的区别?

FragmentTransaction 定义了 5 种提交方式:

API描述是否同步
commit异步提交事务,不允许状态丢失异步
commitAllowingStateLoss异步提交事务,允许状态丢失异步
commitNow同步提交事务,不允许状态丢失同步
commitNowAllowingStateLoss同步提交事务,允许状态丢失同步
executePendingTransactions同步执行事务队列中的全部事务同步

需要注意的地方:

1、onSaveInstanceState保存状态后,事务形成的新状态是不会被保存的。在状态保存之后调用commit或commitNow 会抛异常。

FragmentManagerImpl.java

privatevoidcheckStateLoss{

if(mStateSaved || mStopped) {

thrownewIllegalStateException( "Can not perform this action after onSaveInstanceState");

}

}

2、使用commitNow或commitNowAllowingStateLoss 提交的事务不允许加入回退栈。

为什么有这个设计呢?可能是 Google 考虑到同时存在同步提交和异步提交的事务,并且两个事务都要加入回退栈时,无法确定哪个在上哪个在下是符合预期的,所以干脆禁止 commitNow 加入回退栈。如果确实有需要同步执行+回退栈的应用场景,可以采用commit + executePendingTransactions的取巧方法。相关源码体现如下:

BackStackRecord.java

@Override

publicvoidcommitNow{

disallowAddToBackStack;

mManager.execSingleAction( this, false);

}

@Override

publicvoidcommitNowAllowingStateLoss{

disallowAddToBackStack;

mManager.execSingleAction( this, true);

}

@NonNull

publicFragmentTransaction disallowAddToBackStack{

if(mAddToBackStack) {

thrownewIllegalStateException( "This transaction is already being added to the back stack");

}

mAllowAddToBackStack = false;

returnthis;

}

3、commitNow 和 executePendingTransactions 都是同步执行,有区别吗?

commitNow 是同步执行当前事务,而executePendingTransactions 是同步执行事务队列中的全部事务。

5

如何把 Fragment 加载到界面上?

5.1 添加方法

有两种方式可以将 Fragment 添加到 Activity 视图上:静态加载 + 动态加载。

静态加载: 静态加载是指在布局文件中使用 标签添加 Fragment 的方式,要点总结如下:

属性描述
classFragment 的全限定类名
android:nameFragment 的全限定类名(与 class 没有差别,但 class 优先)
android:idFragment 唯一标识
android:tagFragment 唯一标识(id 和 tag 至少设置一个)

举例:

<?xml version="1.0" encoding="utf-8"?>

< LinearLayoutxmlns:android= "http://schemas.android.com/apk/res/android"

android:orientation= "horizontal"

android:layout_width= "match_parent"

android:layout_height= "match_parent">

< fragmentandroid:name= "com.example.TestFragmentFragment"

android:id= "@+id/list"

android:layout_weight= "1"

android:layout_width= "0dp"

android:layout_height= "match_parent"/>

</ LinearLayout>

动态加载: 动态加载是指在代码中使用事务 FragmentTransaction 添加 Fragment 的方式。例如:

TextFragment fragment = newTextFragment;

fragmentTransaction. add(R.id.containerId, fragment);

fragmentTransaction.commit;

5.2 Fragment 静态加载源码分析

从布局文件添加 Fragment 本质上是 xml 解析为视图树的过程,这个过程由 LayoutInflater 完成。最终, 标签的解析工作最终是交给FragmentManager#onCreateView(...) 处理的,让我们来看看具体是如何处理的,源码如下:

FragmentManagerImpl.java

(已简化)

publicView onCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs){

1、解析属性

String fname = 解析 class属性

if( fname== null) {

fname = 解析 android:name 属性

}

intid = 解析 android:id 属性

String tag = 解析 android:tag 属性

2、根据 id 或 tag 重用已经创建的 Fragment

Fragment fragment = id != View.NO_ID ? findFragmentById(id) : null;

if(fragment == null&& tag != null) {

fragment = findFragmentByTag(tag);

}

if(fragment == null&& containerId != View.NO_ID) {

fragment = findFragmentById(containerId);

}

3、新建 Fragment

if(fragment == null) {

3.1反射创建 Fragment 实例

fragment = getFragmentFactory.instantiate(context.getClassLoader, fname);

3.2mFromLayout 设置为 true

fragment.mFromLayout = true;

fragment.mFragmentId = id != 0? id : containerId;

fragment.mContainerId = containerId;

fragment.mTag = tag;

fragment.mInLayout = true;

fragment.mFragmentManager = this;

fragment.mHost = mHost;

fragment.onInflate(mHost.getContext, attrs, fragment.mSavedFragmentState);

3.3添加 Fragment,立即状态转移

addFragment(fragment, true);

} elseif(fragment.mInLayout) {

...

} else{

...

}

4.1将 id 设置给 Fragment 根布局

if(id != 0) {

fragment.mView.setId(id);

}

4.2将 tag 设置给 Fragment 根布局

if(fragment.mView.getTag == null) {

fragment.mView.setTag(tag);

}

5、返回 Fragment 根布局

returnfragment.mView;

}

-> 3.3添加 Fragment,立即状态转移

publicvoidaddFragment(Fragment fragment, booleanmoveToStateNow) {

...

if(moveToStateNow) {

moveToState(fragment); // 状态转移

}

}

-> 状态转移

voidmoveToState(Fragment f, intnewState, ...) {

...

if(f.mState <= newState ) {

switch(f.mState) {

caseFragment.INITIALIZING:

if(nextState> Fragment.INITIALIZING) {

...

}

// fall through

caseFragment.CREATED:

if(f.mFromLayout && !f.mPerformedCreateView) { // 如果来自布局,并且未执行过 onCreateView

最终调用:

mView = onCreateView(inflater, container, savedInstanceState);

f.onViewCreated(f.mView, f.mSavedFragmentState);

提示:最终在 LayoutInflater 中执行 viewGroup.addView(view, params);

}

if(!f.mFromLayout) { // 不是来自布局

最终调用:

mView = onCreateView(inflater, container, savedInstanceState);

f.onViewCreated(f.mView, f.mSavedFragmentState);

if(container != null) {

container.addView(f.mView);

}

}

...

// fall through

caseFragment.ACTIVITY_CREATED:

...

// fall through

caseFragment.STARTED:

...

}

} else{

...

}

}

以上代码已经非常简化了,代码虽然长但是流程很清楚:

1、FragmentManager 根据布局中的 id 属性或 tag 属性来重用 Fragment,如果不存在则通过反射来创建 Fragment 实例。

2、设置 mFromLayout 为 true,并立即执行状态转移。在moveToState 的CREATE分支会根据 mFromLayout 判断:如果来自布局,并且未执行过onCreateView,才会回调 Fragment#onCreateView 创建 View 实例。

3、最终回溯到 LayoutInflater 中,执行ViewGroup#addView(mView),将 Fragment 根布局添加到父布局中(所以,我们不用在 Fragment 里创建的视图时调用addView)。

在我之前写的一篇文章里已经详细讨论过布局解析的全过程:Android | 带你探究 LayoutInflater 布局解析原理,关于 的部分在第 4.2 节,记得去看看。

5.3 Fragment 动态加载源码分析

Fragment 事务的源码在 第 4.4 节 已经讨论过了,我们知道了通过事务添加 / 移除的Fragment 最终还是会走到moveToState(...)来执行状态转移。在创建 View 实例后,mView 也会直接添加到 containerId 容器上。

5.4 静态加载和动态加载的区别体现在哪里?

静态加载和动态加载的主要区别体现在 执行加载操作的消息周期不同:静态加载和布局解析是在同一个 Handler 消息周期中,而动态加载和事务提交不一定在一个 Handler 消息周期中(取决于调用 commit 还是commitNow)。

5.5 静态加载和动态加载的优缺点?

这个问题看似合理,但其实经不起推敲,是个伪命题。较常见的说法是静态加载简单直接,而动态加载灵活性更高。 提出这个说法的人其实忽略了一点:从布局文件中静态加载的Fragment 也可以使用事务进行动态操作,静态加载也是具有灵活性的。

比较合理的问法是:静态加载和动态加载各适合什么场景?静态加载适合于界面初始化时就确定显示位置和时机的 Fragment,从布局文件中加载可以方便预览。相反地,动态加载适用于初始化时无法确定显示位置和时机的 Fragment,需要依赖代码中的判断条件动态判断。

演示:使用事务操作从布局文件中静态加载的 Fragment。

with( supportFragmentManager.beginTransaction()) {

val fragmentA = supportFragmentManager.findFragmentById(R.id.FragmentA)

if( null!= fragmentA) {

hide(fragmentA)

commit

}

}

6

setRetainInstance 到底做了什么?

事实上,从 androidx.fragment 1.3.0开始,setRetainInstance 这个 API 已经废弃了。不过,考虑到这个 API 的重要性,我们还是花费一点时间来回顾一下。

https://developer.android.google.cn/jetpack/androidx/releases/fragment#1.3.0

6.1 概述

问题 1:什么时候应该使用 setRetainInstance(true)?

答:在配置变更时(例如屏幕旋转),整个 Activity 需要销毁重建,顺带着 Activity 中的 Fragment 也需要销毁重建。而设置setRetainInstance(true)的 Fragment 对象在 Activity 销毁重建的过程中不会被销毁。

问题 2:setRetainInstance(true) 对 Fragment 生命周期的影响?

答:在 Activity 销毁时,Fragment 不会回调onDestroy,而是直接回调onDestroy + onDetach;在 Activity 重建时,Fragment 不会回调onCreate,而是直接调用onCreateView。

问题 3:为什么废弃setRetainInstance?

答:引入 ViewModel 后,setRetainInstanceAPI 开始变得鸡肋。ViewModel 已经提供了在 Activity 重建等场景下保持数据的能力,虽然setRetainInstance也具备相同功能,但需要利用 Fragment 来间接存储数据,使用起来不方便,存储粒度也过大。

6.2 setRetainInstance 核心源码分析

Fragment.java

@Deprecated

publicvoidsetRetainInstance( booleanretain) {

mRetainInstance = retain;

if(mFragmentManager != null) {

if(retain) {

mFragmentManager.addRetainedFragment( this);

} else{

mFragmentManager.removeRetainedFragment( this);

}

} else{

mRetainInstanceChangedWhileDetached = true;

}

}

FragmentManager.java

voidaddRetainedFragment(@ NonNullFragment f) {

mNonConfig.addRetainedFragment( f);

}

FragmentManagerViewModel.java

void addRetainedFragment( @NonNullFragment fragment) {

if(mIsStateSaved) {

if(FragmentManager.isLoggingEnabled(Log.VERBOSE)) {

Log.v(TAG, "Ignoring addRetainedFragment as the state is already saved");

}

return;

}

if(mRetainedFragments.containsKey(fragment.mWho)) {

return;

}

mRetainedFragments.put(fragment.mWho, fragment);

if(FragmentManager.isLoggingEnabled(Log.VERBOSE)) {

Log.v(TAG, "Updating retained Fragments: Added "+ fragment);

}

}

这段代码并不复杂,当我们调用Fragment#setRetainInstance(true)时,最终会将 Fragment 添加到一个 ViewModel 中。ViewModel 是具备在 Activity 重建是恢复数据的能力的,现在的问题转换为 ViewModel 为什么可以恢复数据?

简单来说,在 Activity 销毁时,最终会调用Activity#retainNonConfigurationInstances 保存 ActivityClientRecord,并托管给 ActivityManagerService。这个过程就相当于把Fragment 保存到更长的生命周期了。关于 ViewModel 的具体分析,我后面会专门写一篇文章,期待吗?

总结

我们前面讲了 Fragment 一些历史问题的由来,以及它的一些核心特性,包括生命周期、事务、加载方式和已过时的 setRetainInstance(true)。关于 Fragment 的话题还有很多,今天我们只讨论了其中最核心的部分,更多内容我后续会继续发布更多文章来讨论。

参考资料

Fragment 的过去、现在和将来 —— 谷歌开发者

Fragment 指南 —— 官方文档

https://developer.android.google.cn/guide/fragments

Activity 都重建了,你 Fragment 凭什么活着? —— Wan Android

https://www.wanandroid.com/wenda/show/11077

今天考察下 Fragment 相关两个不常见 API —— Wan Android

https://wanandroid.com/wenda/show/12229

Fragment 是如何被存储与恢复的? —— Wan Android

https://www.wanandroid.com/wenda/show/12574

Fragment 生命周期 —— 更木小八 著

Fragment 不为人知的细节 —— 三雒 著

https://mp.weixin.qq.com/s/y2JqIBR3mYQOzCEymv7DOQ

最后推荐一下我做的网站,玩Android: wanandroid.com,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!

Kotlin协程到底怎么处理异常?教你多种方案!

RecyclerView 中的秘密探索,起飞 Fling ~

Android组件化核心,全面掌握,够深入!

文章来源:你真的懂 Fragment 吗?Fragment 的过去、现在和未来~_视图

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值