当你面试的时候,被问到关于Fragment的种种

前言

不知道大家都没有本身特别的学习的方法,我是有吧全部的整理成笔记的习惯
好比今天讲解的关于Fragment的我会作成笔记git

因为文章有些地方代码过于太长了繁琐,因此部分省略掉了,敲了一下午眼睛和手脖子都酸了,至于省略的部分,对这些笔记,面试内容感兴趣的能够看笔记研究,欢迎留言
当你面试的时候,被问到关于Fragment的种种github

相关内容后续GitHub更新,想冲击金三银四的小伙伴能够找找看看,欢迎star
顺手留下GitHub连接,须要获取相关面试等内容的能够本身去找
https://github.com/xiangjiana/Android-MS面试

一丶Fragment 的使用

实现很简单,建立一个的布局,而后在 Activity 里点击时替换 Fragment异步

mFragmentManager = getSupportFragmentManager(); 
  mFragmentManager.beginTransaction() 
    .replace(R.id.fl_content, fragment) 
    .commitAllowingStateLoss();

代码很简单,核心就三步:ide

  1. 建立 Fragment
  2. 获取 FragmentManager
  3. 调用事务,添加、替换

咱们一步步来了解这背后的故事。
Fragment 你们应该比较熟悉,放到最后。
先来看看 FragmentManager
####二丶 FragmentManager布局

public abstract class FragmentManager {...}

FragmentManager 是一个抽象类,定义了一些和 Fragment 相关的操做和内部类/接口。post

2.1.定义的操做

FragmentManager 中定义的方法以下:学习

//开启一系列对 Fragments 的操做 
   public abstract FragmentTransaction beginTransaction(); 

   //FragmentTransaction.commit() 是异步执行的,若是你想当即执行,能够调用这个方法 
  public abstract boolean executePendingTransactions();

   //根据 ID 找到从 XML 解析出来的或者事务中添加的 Fragment 
   //首先会找添加到 FragmentManager 中的,找不到就去回退栈里找 
   public abstract Fragment findFragmentById(@IdRes int id); 

   //跟上面的相似,不一样的是使用 tag 进行查找 
   public abstract Fragment findFragmentByTag(String tag); 

   //弹出回退栈中栈顶的 Fragment,异步执行的 
   public abstract void popBackStack(); 

   //当即弹出回退栈中栈顶的,直接执行哦 
   public abstract boolean popBackStackImmediate();
   ......

能够看到,定义的方法有不少是异步执行的,后面看看它到底是如何实现的异步。动画

2.2.内部类/接口:this

  • BackStackEntryFragment 后退栈中的一个元素
  • onBackStackChangedListener:后退栈变更监听器
  • FragmentLifecycleCallbacks: FragmentManager 中的 Fragment 生命周期监听

    //后退栈中的一个元素 
    public interface BackStackEntry { 
    //栈中该元素的惟一标识 
    public int getId(); //获取 FragmentTransaction#addToBackStack(String) 设置的名称 public String getName(); 
    
    @StringRes 
    public int getBreadCrumbTitleRes(); 
    @StringRes 
    public int getBreadCrumbShortTitleRes(); 
    public CharSequence getBreadCrumbTitle();
    public CharSequence getBreadCrumbShortTitle(); 
    }

    能够看到 BackStackEntry 的接口比较简单,关键信息就是 ID 和 Name。

    //在 Fragment 回退栈中有变化时回调 
    public interface OnBackStackChangedListener { 
     public void onBackStackChanged(); 
    }
    //FragmentManager 中的 Fragment 生命周期监听 
     public abstract static class FragmentLifecycleCallbacks { 
        public void onFragmentPreAttached(FragmentManager fm, Fragment f, Context context) {} 
        public void onFragmentAttached(FragmentManager fm, Fragment f, Context context) {} 
        public void onFragmentCreated(FragmentManager fm, Fragment f, Bundle savedInstanceState) {} 
        public void onFragmentActivityCreated(FragmentManager fm, Fragment f, Bundle savedInstanceState) {} 
        public void onFragmentViewCreated(FragmentManager fm, Fragment f, View v, Bundle savedInstanceState) {} 
        public void onFragmentStarted(FragmentManager fm, Fragment f) {} 
        public void onFragmentResumed(FragmentManager fm, Fragment f) {} 
        public void onFragmentPaused(FragmentManager fm, Fragment f) {} 
        public void onFragmentStopped(FragmentManager fm, Fragment f) {} 
        public void onFragmentSaveInstanceState(FragmentManager fm, Fragment f, Bundle outState) {} 
        public void onFragmentViewDestroyed(FragmentManager fm, Fragment f) {} 
        public void onFragmentDestroyed(FragmentManager fm, Fragment f) {} 
        public void onFragmentDetached(FragmentManager fm, Fragment f) {} 
    } 
    }

    熟悉 Fragment 生命周期的同窗必定以为很面熟,这个接口就是为咱们提供一个 FragmentManager 所 有 Fragment 生命周期变化的回调。

小结:
能够看到, FragmentManager 是一个抽象类,它定义了对一个 Activity/Fragment 中 添加进来的Fragment 列表、Fragment 回退栈的操做、管理。

2.3.实现类 FragmentManagerImpl

FragmentManager 定义的任务是由 FragmentManagerImpl 实现的。

主要成员:

final class FragmentManagerImpl extends FragmentManager implements 
  LayoutInflaterFactory { 
     ArrayList<OpGenerator> mPendingActions; 
     Runnable[] mTmpActions; 
     boolean mExecutingActions;
     ArrayList<Fragment> mActive; 
     ArrayList<Fragment> mAdded; 
     ArrayList<Integer> mAvailIndices; 
     ArrayList<BackStackRecord> mBackStack; 
     ArrayList<Fragment> mCreatedMenus;
  // Must be accessed while locked. 
     ArrayList<BackStackRecord> mBackStackIndices; 
     ArrayList<Integer> mAvailBackStackIndices;
     ArrayList<OnBackStackChangedListener> mBackStackChangeListeners; 
     private CopyOnWriteArrayList<Pair<FragmentLifecycleCallbacks, Boolean>> mLifecycleCallbacks; 
  //... 
  }

能够看到, FragmentManagerImpl 中定义了 添加的、活跃的。以及回退栈的列表,这和FragmentManager 的要求一致
当你面试的时候,被问到关于Fragment的种种
接着还有当前的状态,当前 Fragment 的起始 mParent,以及 FragmentManagermHostmContainer

FragmentContainer 就是一个接口,定义了关于布局的两个方法:

public abstract class FragmentContainer { 
     @Nullable 
     public abstract View onFindViewById(@IdRes int id); 
     public abstract boolean onHasView();
   }

FragmentHostCallback 就复杂一点了,它提供了 Fragment 须要的信息,也定义了 Fragment 宿主应该作的操做:

public abstract class FragmentHostCallback<E> extends FragmentContainer { 
     private final Activity mActivity; 
     final Context mContext; 
     private final Handler mHandler; 
     final int mWindowAnimations; 
     final FragmentManagerImpl mFragmentManager = new FragmentManagerImpl(); 
     //... 
  }

咱们知道,通常来讲 Fragment 的宿主就两种:

  1. Activity
  2. Fragment

好比 FragmentActivity 的内部类 HostCallbacks 就实现了这个抽象类:

class HostCallbacks extends FragmentHostCallback<FragmentActivity> { 
     public HostCallbacks() { 
        super(FragmentActivity.this /*fragmentActivity*/);
      }
      //... 

     @Override 
     public LayoutInflater onGetLayoutInflater() { 
        return 
  FragmentActivity.this.getLayoutInflater().cloneInContext(FragmentActivity.t his);
     }

    @Override 
    public FragmentActivity onGetHost() { 
       return FragmentActivity.this; 
    }
    ......
  }

咱们再看看他对 FragmentManager 定义的关键方法是如何实现的。

@Override 
  public FragmentTransaction beginTransaction() { 
     return new BackStackRecord(this); 
  }

beginTransaction() 返回一个新的 BackStackRecord ,咱们后面介绍。前面提到了, popBackStack() 是一个异步操做,它是如何实现异步的呢?

@Override 
  public void popBackStack() { 
     enqueueAction(new PopBackStackState(null, -1, 0), false); 
  }
  public void enqueueAction(OpGenerator 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<>(); 
         }
         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); 
         } 
     } 
  }

能够看到,调用到最后,是调用宿主中的 Handler来发送任务的,so easy 嘛。其余的异步执行也是相似,就不赘述了。

后退栈相关方法:

ArrayList<BackStackRecord> mBackStack; 
  @Override 
  public int getBackStackEntryCount() { 
     return mBackStack != null ? mBackStack.size() : 0; 
  }
  @Override 
  public BackStackEntry getBackStackEntryAt(int index) { 
     return mBackStack.get(index); 
  }

能够看到,开始事务和后退栈,返回/操做的都是 BackStackRecord ,咱们来了解了解它是何方神圣。

三丶事务

BackStackRecord 继承了 FragmentTransaction

final class BackStackRecord extends FragmentTransaction implements 
      FragmentManager.BackStackEntry, FragmentManagerImpl.OpGenerator {...}

先来看看 FragmentTransaction

3.1.FragmentTransaction

FragmentTransaction 定义了一系列对 Fragment 的操做方法:

//它会调用 add(int, Fragment, String),其中第一个参数传的是 0 
  public abstract FragmentTransaction add(Fragment fragment, String tag); 

  //它会调用 add(int, Fragment, String),其中第三个参数是 null 
  public abstract FragmentTransaction add(@IdRes int containerViewId, Fragment fragment); 

  //添加一个 Fragment 给 Activity 的最终实现 
  //第一个参数表示 Fragment 要放置的布局 id 
  //第二个参数表示要添加的 Fragment,【注意】一个 Fragment 只能添加一次 
  //第三个参数选填,能够给 Fragment 设置一个 tag,后续可使用这个 tag 查询它 
  public abstract FragmentTransaction add(@IdRes int containerViewId, Fragment fragment, @Nullable String tag);

  //调用 replace(int, Fragment, String),第三个参数传的是 null 
  public abstract FragmentTransaction replace(@IdRes int containerViewId, Fragment fragment);

  //替换宿主中一个已经存在的 fragment 
  //这一个方法等价于先调用 remove(), 再调用 add() public abstract FragmentTransaction replace(@IdRes int containerViewId, Fragment fragment, @Nullable String tag);
  //移除一个已经存在的 fragment 
  //若是以前添加到宿主上,那它的布局也会被移除 
  public abstract FragmentTransaction remove(Fragment fragment);

  //隐藏一个已存的 fragment 
  //其实就是将添加到宿主上的布局隐藏 
  public abstract FragmentTransaction hide(Fragment fragment);

  //显示前面隐藏的 fragment,这只适用于以前添加到宿主上的 fragment 
  public abstract FragmentTransaction show(Fragment fragment);

  //将指定的 fragment 将布局上解除 
  //当调用这个方法时,fragment 的布局已经销毁了 
  public abstract FragmentTransaction detach(Fragment fragment); 

  //当前面解除一个 fragment 的布局绑定后,调用这个方法能够从新绑定 
  //这将致使该 fragment 的布局重建,而后添加、展现到界面上 
  public abstract FragmentTransaction attach(Fragment fragment);

fragment 的操做基本就这几步,咱们知道,要完成对 fragment 的操做,最后还须要提交一下:

mFragmentManager.beginTransaction() 
         .replace(R.id.fl_child, getChildFragment())
   // .commit() 
         .commitAllowingStateLoss();
2.2.事务的四种提交方式

事务最终的提交方法有四种:

  1. commit()
  2. commitAllowingStateLoss()
  3. commitNow()
  4. commitNowAllowingStateLoss()

它们之间的特色及区别以下:

public abstract int commit();

commit() 在主线程中异步执行,其实也是 Handler 抛出任务,等待主线程调度执行。

注意:
commit() 须要在宿主 Activity 保存状态以前调用,不然会报错。
这是由于若是 Activity 出现异常须要恢复状态,在保存状态以后的 commit() 将会丢失,这和调用的初衷不符,因此会报错。

public abstract int commitAllowingStateLoss();

commitAllowingStateLoss() 也是异步执行,但它的不一样之处在于,容许在 Activity 保存状态以后调用,也就是说它遇到状态丢失不会报错。

所以咱们通常在界面状态出错是能够接受的状况下使用它。

public abstract void commitNow();

commitNow() 是同步执行的,当即提交任务。

前面提到 FragmentManager.executePendingTransactions() 也能够实现当即提交事务。但咱们通常建议使用 commitNow() , 由于另外那位是一会儿执行全部待执行的任务,可能会把当前全部的事务都一会儿执行了,这有可能有反作用。

此外,这个方法提交的事务可能不会被添加到 FragmentManger 的后退栈,由于你这样直接提交,有可能影响其余异步执行任务在栈中的顺序。

commit() 同样, commitNow() 也必须在 Activity 保存状态前调用,不然会抛异常。

public abstract void commitNowAllowingStateLoss();

同步执行的 commitAllowingStateLoss()
OK,了解了 FragmentTransaction 定义的操做,去看看咱们真正关心的、 beginTransaction()中返回的 BackStackRecord :

@Override 
  public FragmentTransaction beginTransaction() { 
     return new BackStackRecord(this);
   }
3.3事务真正实现/回退栈 BackStackRecord

BackStackRecord 既是对 Fragment 进行操做的事务的真正实现,也是 FragmentManager 中的回退栈的实现:

final class BackStackRecord extends 
        FragmentTransaction implements FragmentManager.BackStackEntry, FragmentManagerImpl.OpGenerator {...}

它的关键成员:

final FragmentManagerImpl mManager; 
  //Op 可选的状态值 
  static final int OP_NULL = 0; 
  static final int OP_ADD = 1; 
  static final int OP_REPLACE = 2;
  static final int OP_REMOVE = 3; 
  static final int OP_HIDE = 4; 
  static final int OP_SHOW = 5; 
  static final int OP_DETACH = 6; 
  static final int OP_ATTACH = 7; 

  ArrayList<Op> mOps = new ArrayList<>();
  static final class Op {
     int cmd; //状态 
     Fragment fragment; 
     int enterAnim; 
     int exitAnim; 
     int popEnterAnim;
     int popExitAnim; 
  }
  int mIndex = -1; 
  //栈中最后一个元素的索引
  }

能够看到 Op 就是添加了状态和动画信息的 FragmentmOps就是栈中全部的 Fragment。事务定义的方法它是如何实现的呢

先看添加一个 Fragment 到布局 add() 的实现:

@Override 
  public FragmentTransaction add(int containerViewId, Fragment fragment) { 
     doAddOp(containerViewId, fragment, null, OP_ADD); 
     return this; 
   ......
  }

能够看到添加一个 Fragment 到布局很简单,概况一下就是:
修改 fragmentManager 和 ID,构形成 Op,设置状态信息,而后添加到列表里。

添加完了看看替换 replace 的实现:

@Override 
  public FragmentTransaction replace(int containerViewId, Fragment fragment) { 
     return replace(containerViewId, fragment, null); 
  }
  @Override 
  public FragmentTransaction replace(int containerViewId, Fragment fragment, String tag) { 
     if (containerViewId == 0) { 
         throw new IllegalArgumentException("Must use non-zero containerViewId"); 
     }

     doAddOp(containerViewId, fragment, tag, OP_REPLACE);
     return this;
   }

太可怕了,也是调用上面刚提到的 doAddOp() ,不一样之处在于第四个参数为 OP_REPLACE ,看来以前小看了这个状态值!

再看其余方法的实现就很简单了,无非就是构造一个 Op,设置对应的状态值。

@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; 
  }

那这些状态值的不一样是何时起做用的呢?
别忘了咱们操做 Fragment 还有最后一步,提交。
看看这两个是怎么实现的:

@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"); 
     //...
     }
  }

前面已经介绍过了, FragmentManager.enqueueAction() 最终是使用 Handler 实现的异步执行。
如今的问题是执行的任务是啥?
答案就是 Handler 发送的任务 mExecCommit :
代码多了一点省略掉了,但咱们终于找到了最终的实现:Handler 异步发到主线,调度执行后,聚合、修改 Ops的状态,而后遍历、修改 Fragment 栈中的 View 的状态。

3.4.真正处理的部分

前面主要是对 Fragment 的包装类 Ops 进行一些状态修改,真正根据 Ops 状态进行操做在这个部分:

/**
   * Executes the operations contained within this transaction. The Fragment states will only 
   * be modified if optimizations are not allowed. 
   */ 
   void executeOps() { 
        final int numOps = mOps.size(); 
        for (int opNum = 0; opNum < numOps; opNum++) { 
             final Op op = mOps.get(opNum); 
             final Fragment f = op.fragment; 
             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; 
               default: 
                   throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
            }
            if (!mAllowOptimization && op.cmd != OP_ADD) { 
                mManager.moveFragmentToExpectedState(f); 
            } 
        }
        if (!mAllowOptimization) { 
            // Added fragments are added at the end to comply with prior behavior.mManager.moveToState(mManager.mCurState, true); 
        } 
  }

FragmentManager 对这些方法的实现也很简单,修改 Fragment 的状态值,好比remove(Fragment) :

public void removeFragment(Fragment fragment) { 
     if (DEBUG) Log.v(TAG, "remove: " + fragment + " nesting=" + fragment.mBackStackNesting); 
     final boolean inactive = !fragment.isInBackStack(); 
     if (!fragment.mDetached || inactive) { 
        if (mAdded != null) { 
            mAdded.remove(fragment); 
        }
        if (fragment.mHasMenu && fragment.mMenuVisible) { 
            mNeedMenuInvalidate = true; 
        }
        fragment.mAdded = false; //设置属性值 
        fragment.mRemoving = true; 
     } 
  }

代码很长,先省略掉......但作的事情很简单:

  1. 根据状态调用对应的生命周期方法
  2. 若是是新建立的,就把布局添加到 ViewGroup

四丶总结

OK,看完这篇文章,相信对开头提出的问题你已经有了答案,这里再总结一下。
Fragment、FragmentManager、FragmentTransaction 关系

  • Fragment
    • 实际上是对 View 的封装,它持有 view, containerView, fragmentManager,<br/>childFragmentManager 等信息
  • FragmentManager
    • 是一个抽象类,它定义了对一个 Activity/Fragment 中 添加进来的 Fragment 列表、Fragment 回退栈的操做、管理方法
    • 还定义了获取事务对象的方法
    • 具体实如今 FragmentImpl
  • FragmentTransaction
    • 定义了对 Fragment 添加、替换、隐藏等操做,还有四种提交方法
    • 具体实现是在 BackStackRecord

Fragment 如何实现布局的添加替换
经过得到当前 Activity/FragmentFragmentManager/ChildFragmentManager,进而拿到事务的实
现类 BackStackRecord,它将目标 Fragment 构形成 Ops(包装Fragment和状态信息),而后提交给FragmentManager 处理。

若是是异步提交,就经过 Handler 发送 Runnable 任务,FragmentManager 拿到任务后,先处理 Ops
状态,而后调用 moveToState() 方法根据状态调用 Fragment 对应的生命周期方法,从而达到Fragment 的添加、布局的替换隐藏等。

下面这张图从下往上看就是一个 Fragment 建立经历的方法:
当你面试的时候,被问到关于Fragment的种种

嵌套 Fragment的原理

也比较简单,Fragment 内部有一个 childFragmentManager,经过它管理子 Fragment
在添加子 Fragment 时,把子 Fragment 的布局 add 到父 Fragment 便可

因为不少代码太长了,敲了一下午,眼睛和手都酸了,对这样感兴趣的能够拿这份笔记本身研究,有不懂的欢迎留言
当你面试的时候,被问到关于Fragment的种种

知识汇总的PDF相关内容后续GitHub更新,想冲击金三银四的小伙伴能够找找看看,欢迎star
顺手留下GitHub连接,须要获取相关面试等内容的能够本身去找
https://github.com/xiangjiana/Android-MS

相关文章
相关标签/搜索