Fragment介绍

在很久以前,也就是我刚开始写Android时(大约在2012年的冬天……),那时候如果要实现像下面微信一样的Tab切换页面,需要继承TabActivity,然后使用TabHost,在TabHost中添加子Activity来实现

现在大家都知道,我们一般情况下会使用FragmentActivity加Fragment来实现,Fragment是Android 3.0新增的,另外我们的support v4包也提供能Fragment的支持,所以现在在所有版本的SDK中我们都可以使用Fragment。Fragment是Activity的一部分,其中一个很重要的需要大家掌握的就是关于Fragment的生命周期,当然这次我们不会讨论这个问题,不过提供一个图片供大家参考,图片来自xxv/android-lifecycle

从使用开始

FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.hide(firstStepFragment);
if (secondStepFragment==null){
ft.add(R.id.fl_content, secondStepFragment);
}else {
ft.show(secondStepFragment);
}
ft.commit();

一般我们会这样动态使用Fragment,从代码可以明显体现出这个功能是通过事务的方式执行的,但在一些情况下,我们执行commit()时,会出现异常,例如stackoverflow上的一个报错,解决办法很简单,用commitAllowingStateLoss方法代替commit即可。那这个异常是怎么产生的呢?今天我们从源码来看看它的发生

逐步参看源码

从FragmentActivity的getSupportFragmentManager方法开始:

public class FragmentActivity  {
final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
// ……
public FragmentManager getSupportFragmentManager() {
return mFragments.getSupportFragmentManager();
}
// ……
}
public class FragmentController {
private final FragmentHostCallback<?> mHost;
public FragmentManager getSupportFragmentManager() {
return mHost.getFragmentManagerImpl();
}
}
public abstract class FragmentHostCallback<E> extends FragmentContainer {
FragmentManagerImpl getFragmentManagerImpl() {
return mFragmentManager;
}
}

所以我们的FragmentTransaction是从FragmentManager的实现类FragmentManagerImpl的中的方法返回的,我们看一看FragmentManagerImpl源码中的beginTransaction方法:

final class FragmentManagerImpl extends FragmentManager implements LayoutInflaterFactory {
@Override
public FragmentTransaction beginTransaction() {
return new BackStackRecord(this);
}
}

可以看到,返回的是一个BackStackRecord,并且每一次调用都是最新实例化的,等下我们会看到,BackStackRecord的commit方法只能执行一次,或者会抛出一个异常。现在我们看一下我们关注的BackStackRecord的一些源码:

final class BackStackRecord extends FragmentTransaction implements FragmentManager.BackStackEntry, Runnable {

    final FragmentManagerImpl mManager;

    static final class Op {
Op next;
Op prev;
int cmd;
Fragment fragment;
int enterAnim;
int exitAnim;
int popEnterAnim;
int popExitAnim;
ArrayList<Fragment> removed;
} public BackStackRecord(FragmentManagerImpl manager) {
mManager = manager;
} @Override
public FragmentTransaction add(Fragment fragment, String tag) {
doAddOp(0, fragment, tag, OP_ADD);
return this;
} @Override
public FragmentTransaction add(int containerViewId, Fragment fragment) {
doAddOp(containerViewId, fragment, null, OP_ADD);
return this;
} @Override
public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) {
doAddOp(containerViewId, fragment, tag, OP_ADD);
return this;
} @Override
public FragmentTransaction remove(Fragment fragment) {
Op op = new Op();
op.cmd = OP_REMOVE;
op.fragment = fragment;
addOp(op); return this;
} @Override
public FragmentTransaction hide(Fragment fragment) {
Op op = new Op();
op.cmd = OP_HIDE;
op.fragment = fragment;
addOp(op); return this;
} @Override
public FragmentTransaction show(Fragment fragment) {
Op op = new Op();
op.cmd = OP_SHOW;
op.fragment = fragment;
addOp(op); return this;
} private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
final Class fragmentClass = fragment.getClass();
final int modifiers = fragmentClass.getModifiers();
if (fragmentClass.isAnonymousClass() || !Modifier.isPublic(modifiers)
|| (fragmentClass.isMemberClass() && !Modifier.isStatic(modifiers))) {
throw new IllegalStateException("Fragment " + fragmentClass.getCanonicalName()
+ " must be a public static class to be properly recreated from"
+ " instance state.");
} fragment.mFragmentManager = mManager; if (tag != null) {
if (fragment.mTag != null && !tag.equals(fragment.mTag)) {
throw new IllegalStateException("Can't change tag of fragment "
+ fragment + ": was " + fragment.mTag
+ " now " + tag);
}
fragment.mTag = tag;
} if (containerViewId != 0) {
if (containerViewId == View.NO_ID) {
throw new IllegalArgumentException("Can't add fragment "
+ fragment + " with tag " + tag + " to container view with no id");
}
if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) {
throw new IllegalStateException("Can't change container ID of fragment "
+ fragment + ": was " + fragment.mFragmentId
+ " now " + containerViewId);
}
fragment.mContainerId = fragment.mFragmentId = containerViewId;
} Op op = new Op();
op.cmd = opcmd;
op.fragment = fragment;
addOp(op);
} void addOp(Op op) {
if (mHead == null) {
mHead = mTail = op;
} else {
op.prev = mTail;
mTail.next = op;
mTail = op;
}
op.enterAnim = mEnterAnim;
op.exitAnim = mExitAnim;
op.popEnterAnim = mPopEnterAnim;
op.popExitAnim = mPopExitAnim;
mNumOp++;
} @Override
public int commit() {
return commitInternal(false);
} @Override
public int commitAllowingStateLoss() {
return commitInternal(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);
P 大专栏  从源码看commit和commitAllowingStateLoss方法区别rintWriter pw = new PrintWriter(logw);
dump(" ", null, pw, null);
}
mCommitted = true;
if (mAddToBackStack) {
mIndex = mManager.allocBackStackIndex(this);
} else {
mIndex = -1;
}
mManager.enqueueAction(this, allowStateLoss);
return mIndex;
}
}

可以看到,不管我们执行add、remove、hide、show中的哪一个方法,最终都会执行addOp方法,这个方法会生成一个双向链表的数据结果,具体的对象就是Op,对于不同的方法,Op中的cmd这个值是不一样的。大致的流程是这样的,我们调用add、remove、hide、show等方法后,会生成不同的操作命令,然后这些操作命令形成一个双向链表,其中任何一个操作命令,我们都可以知道它的前一个和后一个是什么命令。

最关键的部分来了,我们的commit和commitAllowingStateLoss也出现了,可以看到,最终两个方法都会调用commitInternal方法,只是传入的参数不同,我们也可以看到commitInternal方法的第一句有一个判断,也就是上面我们提到的,如果再执行事务的commit或者commitAllowingStateLoss方法,会抛出一个IllegalStateException("commit already called")异常,这也是我们会经常遇见的,所以们在使用Fragment时,每一次都需要调用beginTransaction方法生成新的事务,然后再commit,不能同一个事务commit两次

接着往下看,刚刚看到commit和commitAllowingStateLoss唯一的不同就是在调用commitInternal时,传入的参数不同,而在commitInternal方法中,用到了这个参数的是这个方法的倒数第二句代码:mManager.enqueueAction(this, allowStateLoss);,mManager就是我们的FragmentManagerImpl,我们看看这个类中的enqueueAction方法干了什么:

final class FragmentManagerImpl extends FragmentManager implements LayoutInflaterFactory {

    public void enqueueAction(Runnable action, boolean allowStateLoss) {
if (!allowStateLoss) {
checkStateLoss();
}
synchronized (this) {
if (mDestroyed || mHost == null) {
throw new IllegalStateException("Activity has been destroyed");
}
if (mPendingActions == null) {
mPendingActions = new ArrayList<Runnable>();
}
mPendingActions.add(action);
if (mPendingActions.size() == 1) {
mHost.getHandler().removeCallbacks(mExecCommit);
mHost.getHandler().post(mExecCommit);
}
}
} private void checkStateLoss() {
if (mStateSaved) {
throw new IllegalStateException(
"Can not perform this action after onSaveInstanceState");
}
if (mNoTransactionsBecause != null) {
throw new IllegalStateException(
"Can not perform this action inside of " + mNoTransactionsBecause);
}
}
}

报错的地方出来了,就是在我们checkStateLoss方法中,因为执行commit方法时传入的参数为false,所以会执行checkStateLoss,在checkStateLoss方法中会抛出两个异常,一个是因为mStateSaved为true,一个是因为mNoTransactionsBecause不为空,那么接下来我们就分别看一下为什么会出现这两种情况

mStateSaved什么时候为true

checkStateLoss方法中mStateSaved只要为true,我们调用commit就会抛出异常,所以寻找问题就很简单了,看看什么情况下mStateSaved的值会被赋为true。通过查看FragmentManagerImpl的源码,这两个方法被执行时,mStateSaved被赋为了true:

static final boolean HONEYCOMB = android.os.Build.VERSION.SDK_INT >= 11;

Parcelable saveAllState() {
execPendingActions();
if (HONEYCOMB) {
mStateSaved = true;
} // 下面的代码省略……
} public void dispatchStop() {
mStateSaved = true;
moveToState(Fragment.STOPPED, false);
}

那么什么时候会执行FragmentManagerImpl的这两个方法呢,通过查看,它们都是在我们的FragmentActivity的生命周期函数中被调用的:

public class FragmentActivity{
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Parcelable p = mFragments.saveAllState();
if (p != null) {
outState.putParcelable(FRAGMENTS_TAG, p);
}
if (mPendingFragmentActivityResults.size() > 0) {
outState.putInt(NEXT_CANDIDATE_REQUEST_INDEX_TAG, mNextCandidateRequestIndex); int[] requestCodes = new int[mPendingFragmentActivityResults.size()];
String[] fragmentWhos = new String[mPendingFragmentActivityResults.size()];
for (int i = 0; i < mPendingFragmentActivityResults.size(); i++) {
requestCodes[i] = mPendingFragmentActivityResults.keyAt(i);
fragmentWhos[i] = mPendingFragmentActivityResults.valueAt(i);
}
outState.putIntArray(ALLOCATED_REQUEST_INDICIES_TAG, requestCodes);
outState.putStringArray(REQUEST_FRAGMENT_WHO_TAG, fragmentWhos);
}
} @Override
protected void onStop() {
super.onStop(); mStopped = true;
mHandler.sendEmptyMessage(MSG_REALLY_STOPPED); mFragments.dispatchStop();
}
}

一个是FragmentActivity的onSaveInstanceState方法,它被执行后,只要是Android3.0以后都会将mStateSaved赋为true,当onStop方法执行时,mStateSaved在任何情况下都会被赋为true,我们先暂停一下看看另一个异常

mNoTransactionsBecause什么时候不为空

一般情况下,我们的mNoTransactionsBecause的值一直都为null,只有当我们使用了Loader时,mNoTransactionsBecause才可能会被赋值,具体的代码就不再像上面那样这么细的看了,大家有兴趣可以参阅相关源码,不过我们需要道Loader是个什么东西,才能更好的理解,大家可以看看这篇文章,讲的比较详细和清楚。

为什么commit会抛出异常

刚才我们看了异常的抛出的具体位置和引发条件,那么为什么commit会抛出异常呢,而commitAllowingStateLoss不会呢?我们都知道Activity在资源不足的情况下会被销毁,在销毁之前,会调用onSaveInstanceState,将fragments、views等保存下来,当Activity再被创建时,可以将保存的状态取出来重新装载Activity的状态,当onSaveInstanceState执行后,Activity的状态保存下来了,这个时候我们再调用commit,这个FragmentTransaction事务不会被保存下来,Android为了避免丢失,就给我抛出了一个异常,当然我们可以不在乎这个丢失,所以可以调用commitAllowingStateLoss方法。那么另外一个异常的原因呢?看完上面我提到的那篇文章,你应该知道Loader是为了供我们去异步访问一些数据,而上面的mNoTransactionsBecause代表了Loader的不同状态,如果在执行异步操作,我们commit,新的状态和Loader执行完的状态可能不是预期的,所以这时Android也会抛出一个异常IllegalStateException("Can not perform this action inside of " + mNoTransactionsBecause)

从源码看commit和commitAllowingStateLoss方法区别的更多相关文章

  1. 从源码看String,StringBuffer,StringBuilder的区别

    前言 看了一篇文章,大概是讲面试中的java基础的,有如题这么个面试题.我又翻了一些文章看了下,然后去看源码.看一下源码大概能更加了解一些. String String类是final的,表示不可被继承 ...

  2. 从源码看Azkaban作业流下发过程

    上一篇零散地罗列了看源码时记录的一些类的信息,这篇完整介绍一个作业流在Azkaban中的执行过程,希望可以帮助刚刚接手Azkaban相关工作的开发.测试. 一.Azkaban简介 Azkaban作为开 ...

  3. 解密随机数生成器(二)——从java源码看线性同余算法

    Random Java中的Random类生成的是伪随机数,使用的是48-bit的种子,然后调用一个linear congruential formula线性同余方程(Donald Knuth的编程艺术 ...

  4. 从源码看JDK提供的线程池(ThreadPoolExecutor)

    一丶什么是线程池 (1)博主在听到线程池三个字的时候第一个想法就是数据库连接池,回忆一下,我们在学JavaWeb的时候怎么理解数据库连接池的,数据库创建连接和关闭连接是一个比较耗费资源的事情,对于那些 ...

  5. 框架源码系列五:学习源码的方法(学习源码的目的、 学习源码的方法、Eclipse里面查看源码的常用快捷键和方法)

    一. 学习源码的目的 1. 为了扩展和调优:掌握框架的工作流程和原理 2. 为了提升自己的编程技能:学习他人的设计思想.编程技巧 二. 学习源码的方法 方法一: 1)掌握研究的对象和研究对象的核心概念 ...

  6. Netty 源码剖析之 unSafe.write 方法

    前言 在 Netty 源码剖析之 unSafe.read 方法 一文中,我们研究了 read 方法的实现,这是读取内容到容器,再看看 Netty 是如何将内容从容器输出 Channel 的吧. 1. ...

  7. 从微信小程序开发者工具源码看实现原理(一)- - 小程序架构设计

    使用微信小程序开发已经很长时间了,对小程序开发已经相当熟练了:但是作为一名对技术有追求的前端开发,仅仅熟练掌握小程序的开发感觉还是不够的,我们应该更进一步的去理解其背后实现的原理以及对应的考量,这可能 ...

  8. 从微信小程序开发者工具源码看实现原理(四)- - 自适应布局

    从前面从微信小程序开发者工具源码看实现原理(一)- - 小程序架构设计可以知道,小程序大部分是通过web技术进行渲染的,也就是最终通过浏览器的dom tree + cssom来生成渲染树:既然最终是通 ...

  9. 从源码看Flask框架配置管理

    1 引言 Flask作为Python语言web开发的三大顶梁柱框架之一,对于配置的管理当然必不可少.一个应用从开发到测试到最后的产品发布,往往都需要多种不同的配置,例如是否开启调试模式.使用哪个数据库 ...

随机推荐

  1. MySQL空洞问题解决

    原因:Mysql对于BLOB/TEXT值在执行大量删除操作时可能会引起空洞.空洞就是数据虽然删除了,但是依然占用服务器物理空间,会导致性能底下. 解决办法:定期使用OPTIMIZE TABLE进行碎片 ...

  2. 机器学习分布式框架horovod安装 (Linux环境)

    1.openmi 下载安装 下载连接: https://download.open-mpi.org/release/open-mpi/v4.0/openmpi-4.0.1.tar.gz 安装命令 1 ...

  3. 开始新建AEM站点-周末教程

    Getting Started Developing AEM Sites - WKND Tutorial 开始新建AEM站点-周末教程 The goal for this multi-part tut ...

  4. 和我一起从0学算法(C语言版)(一)

    第一章 排序 第一节 简化版桶排法 友情提示:此文章分享给所有小白,大牛请绕路! 生活中很多地方需要使用排序,价格的由低到高.距离的由远及近等,都是排序问题的体现.如果排序量较少,依靠个人能力很容易实 ...

  5. Java中常用的API(一)——Object

    概述 如果要问Java为什么是用起来非常舒服的语言,那很大一部分的功劳就是JavaAPI的.API定义了许多封装好的类和方法供我们使用,来处理特定的问题,所以学习常用的API是非常重要的. 同时,面向 ...

  6. centos6.5源码升级内核

    centos6.5源码升级内核 升级前 系统版本:  CentOS5.5 内核版本:  2.6.18-194.el5 升级前做过简单配置文件修改 yum -y upgrade    升级后 系统版本: ...

  7. 题解 P6004 【[USACO20JAN]Wormhole Sort S】

    这题真的是非常标准的模板题啊 看到连最少的边,第一时间会想到 \(kruskal\) .这道题的难点其实就一个:你要注意到连边权最大的边使整个图联通 为什么:题意是第i个点想走到 \(pos[i]\) ...

  8. C盘满了解决办法之pagefile.sys文件

    pagefile.sys文件一般存在于C盘,只有点击了隐藏属性才能看见. 这个文件一般比较大,它是系统创建虚拟内存页面的文件.平时大家使用软件的时候对于产生大量的临时数据,这些数据需要占用大量内存,如 ...

  9. 剑指offer【09】- 跳台阶

    题目:一只青蛙一次可以跳上1级台阶,也可以跳上2级.求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果). 对于本题,前提只有 一次 1阶或者2阶的跳法. a.如果两种跳法,1阶或者 ...

  10. 在python实现加密的方式总结

    基础知识扫盲 对称加密 对称密钥加密 , 又叫私钥加密.即信息发送的方和接受方用一个密钥去加密和揭秘数据. 最大的优势是 加解密速度快,适合对大量数据进行加密, 对称加密的缺点是密钥的管理和分配, 换 ...