Fragment篇——FragmentManager分析及用例

说到管理Activity中的Fragment,自然就要重点说一下FragmentManager,之前已经说过了,getFragmentManager()获取到的FragmentManager支持原生的Fragment,而getSupportFragmentManager()支持的是v4包的Fragment。

获取Fragment的方法
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment
        android:id="@+id/fragment_study1"
        android:name="com.example.study.StudyFragment"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
   <fragment
        android:id="@+id/fragment_study2"
        android:name="com.example.study.StudyFragment"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <fragment
        android:tag="fragment_second"
        android:name="com.example.study.SecondFragment"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</LinearLayout>

获取Fragment的三种方式

fragmentManager = getSupportFragmentManager();
// 通过id查找对应的Fragment实例
fragment1 = (StudyFragment) fragmentManager.findFragmentById(R.id.fragment_study1);
// 通过tag找到对应的Fragment,在动态加载和静态加载中都可以使用
 secondFragment = (SecondFragment) fragmentManager.findFragmentByTag("fragment_second");
// 获取添加到FragmentManager的所有Fragment,通过index访问,0代表最早添加的Fragment
fragmentManager.getFragments()
FragmentManager是如何管理Fragment的

每次我们对Fragment的操作都需要通过FragmentTransaction,到了这里估计很多博客都已经说到了FragmentTransaction里面的几个操作方法,虽然在下也是要说的,顺便说一下Fragment管理的设计吧,不看不知道,看了才知道可牛逼哄哄了。

我们知道,没有FragmenActivity就没有Fragment。

那么为什么我们必须要继承自FragmentActivity呢,这就是Fragment设计上的便利了,开发者希望我们在使用Fragment的时候只需要关注对Fragment的操作,而Fragment的管理则交由FragmentActivity的FragmentManager来实现。
在FragmentAactivity这个类下面,当我们调用getSupportManager的时候

public class FragmentActivity extends BaseFragmentActivityJB implements
        ActivityCompat.OnRequestPermissionsResultCallback,
        ActivityCompatApi23.RequestPermissionsRequestCodeValidator {
    ···
final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
    
    ···

    public FragmentManager getSupportFragmentManager() {
        return mFragments.getSupportFragmentManager();
    }
    ···
}

我们可以看到FragmentActivity里面有一个FragmentController,这个FragmentController定义了所有对Fragment的管理操作,包括我们的Activity在onCreate,onResume,onDestroy等各种生命周期或回调对Fragment的影响,都是由这个类来控制的。

public class FragmentController {
    private final FragmentHostCallback<?> mHost;

    /**
     * Returns a {@link FragmentController}.
     */
    public static final FragmentController createController(FragmentHostCallback<?> callbacks) {
        return new FragmentController(callbacks);
    }

    /**
     * Returns a {@link FragmentManager} for this controller.
     */
    public FragmentManager getSupportFragmentManager() {
        //获取到FragmentManager对象
        return mHost.getFragmentManagerImpl();
    }

FragmentHostCallback是一个抽象类,负责调用各种各样的回调,这样的话,当Avtivity的状态,生命周期发生改变的时候,就可以通过这个回调接口进行统一管理,在上面提到的HostCallbacks是FragmentActivity里面的一个继承FragmentHostCallback的内部类。下面我们来看看FragmentHostCallback的默认实现

public abstract class FragmentHostCallback<E> extends FragmentContainer {
    private final Activity mActivity;
    ···
    // 实例化FragmentManager对象,FragmentManagerImpl是继承自FragmentManager抽象类的,对FragmentManager的各种方法提供具体实现
    final FragmentManagerImpl mFragmentManager = new FragmentManagerImpl();
    ···
}

FragmentManagerImpl里面的具体实现就是有关Fragment是如何运行的,各种各样的生命周期,判断Fragment的不同状态,切换状态,Transaction只是用作记录对Fragment的操作记录,最终调用commit的时候,实际上调用的还是FragmentManagerImpl的方法

// FragmentManager 的实现类
final class FragmentManagerImpl extends FragmentManager implements LayoutInflaterFactory {
    ···
    @Override
    public FragmentTransaction beginTransaction() {
        // 每次的FragmentTransaction都是独立的
        return new BackStackRecord(this);
    }
    ···
}

// Transaction的实现类
final class BackStackRecord extends FragmentTransaction implements
        FragmentManager.BackStackEntry, FragmentManagerImpl.OpGenerator {

    // 初始化的时候传入FragmentManagerImpl 的实例
    public BackStackRecord(FragmentManagerImpl manager) {
        mManager = manager;
    }

    @Override
    public int commit() {
        //返回栈id,要是不添加进栈,返回-1
        return commitInternal(false);
    }

    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);
        }
        mCommitted = true;
        //是否要添加到回退栈
        if (mAddToBackStack) {
            // 在回退栈中分配栈ID
            mIndex = mManager.allocBackStackIndex(this);
        } else {
            mIndex = -1;
        }
        //执行这个Transaction
        mManager.enqueueAction(this, allowStateLoss);
        return mIndex;
    }

}

FragmentManagerImpl的部分实现

    /**
     * 添加一个操作到待操作队列中
     *
     * @param action 添加的操作
     * @param allowStateLoss 是否允许丢失状态信息
     * @throws 如果Activity已经销毁了抛出IllegalStateException异常
     */
    public void enqueueAction(OpGenerator action, boolean allowStateLoss) {
        if (!allowStateLoss) {
            // 检查状态是否丢失,默认的commit实现会执行这一步
            checkStateLoss();
        }
        synchronized (this) {
            if (mDestroyed || mHost == null) {
                throw new IllegalStateException("Activity has been destroyed");
            }
            if (mPendingActions == null) {
                mPendingActions = new ArrayList<>();
            }
            // 添加到待操作队列中
            mPendingActions.add(action);
            scheduleCommit();      //执行Commit操作
        }
    }

    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);
                // 执行这个Runnable
                mHost.getHandler().post(mExecCommit);
            }
        }
    }

    //通过handler调用,在主线程运行
    public boolean execPendingActions() {
        //收集和执行延时的操作,这种延时是因为还没准备好??
        ensureExecReady(true);

        boolean didSomething = false;
       //根据事务对象生成待执行的操作,这个事务对象是FragmentTransaction的实现
        while (generateOpsForPendingActions(mTmpRecords, mTmpIsPop)) {
            mExecutingActions = true;
            try {
               //优化执行事务,里面的处理逻辑相当复杂
                optimizeAndExecuteOps(mTmpRecords, mTmpIsPop);
            } finally {
               //清空缓存事务队列
                cleanupExec();
            }
            didSomething = true;
        }
        //判断FragmentList是否需要延时,进而调用moveToState修改Fragment的状态,根据状态来触发Fragment的不同生命周期
        doPendingDeferredStart();

        return didSomething;
    }

上面的就是我们通过getSupportFragmentManager()获取到FragmentManager,然后再开启事务,提交事务所经历的代码流程。控制Fragment的生命周期的回调,通过FragmentManager的moveToState方法。

void moveToState(Fragment f, int newState, int transit, int transitionStyle, boolean keepActive)

关于Fragment的生命周期,要改篇再学习。现在继续来学习一下FragmentTransaction的一些常用方法

#将一个fragment实例添加到Activity里面指定id的容器中
add(Fragment fragment, String tag)
add(int containerViewId, Fragment fragment)
add(int containerViewId, Fragment fragment, String tag);
 #将一个fragment实例从FragmentManager的FragmentList中移除
remove(Fragment fragment);
#只控制Fragment的隐藏
hide(Fragment fragment)
#只控制Fragment的显示
show(Fragment fragment)
#清除视图,从containerid指定的Added列表移除,FragmentList依然保留
detach(Fragment fragment)
#创建视图,添加到containerid指定的Added列表,FragmentList依然保留
attach(Fragment fragment)
#替换containerViewId中的fragment,它会把containerViewId中所有fragment删除,然后添加当前的fragment
replace(int containerViewId, Fragment fragment)
replace(int containerViewId, Fragment fragment, String tag)
FragmentTransaction的用例

之前的例子都已经对FragmentTransaction的用法有过一些介绍了

//添加Fragment到FragmentList中
private void addFragment(Fragment fragment, String tag){
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        transaction.add(R.id.fragment_container,fragment,tag);
        transaction.commit();
    }

// 清空fragmentList的所有Fragment,替换成新的Fragment,注意Fragment里面的坑
private void replaceFragment(Fragment fragment, String tag){
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        transaction.replace(R.id.fragment_container,fragment,tag);
        transaction.commit();
    }

//移除指定的Fragment
private void removeFragment(Fragment fragment){
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        transaction.remove(fragment);
        transaction.commit();
    }

//把Fragment设置成显示状态,但是并没有添加到FragmentList中
private void showFragment(Fragment fragment){
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        transaction.show(fragment);
        transaction.commit();
    }

//把Fragment设置成显示状态,但是并没有添加到FragmentList中
private void hideFragment(Fragment fragment){
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        transaction.hide(fragment);
        transaction.commit();
    }

// 效果和show相近,创建视图,添加到containerid指定的Added列表,FragmentList依然保留,但是会引起生命周期的变化
private void attachFragment(Fragment fragment){
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        transaction.attach(fragment);
        transaction.commit();
    }

// 效果和hide相近,清除视图,从containerid指定的Added列表移除,FragmentList依然保留,但是会引起生命周期的变化
private void detachFragment(Fragment fragment){
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        transaction.detach(fragment);
        transaction.commit();
    }

虽然新的Android版本中,Fragment在replace的时候不再重新实例化了(据说在旧的版本会出现这个问题,目前我使用的API 25 ),但是我们还是建议采用show/hide的方式来控制Fragment的显示和隐藏。还有一点需要注意的是,attach和detach会改变Fragment在Added列表中的顺序,从而也会改变Fragment显示的顺序

FragmentTransaction的事务回退栈

上面介绍了常用的用法,接下来要介绍的是FragmentTransaction的事务回滚,FragmentTransaction把事务添加到回退栈中,只需要在调用 transaction.commit()之前,调用以下代码

//tag标记这个用于标记这个事务
transaction.addToBackStack(String tag);

加入回退栈的时候,调用commit方法会返回一个index,作为事务的id,否则返回-1。使用popBackStack方法进行回退,弹出回退栈。

//默认将最上层的操作弹出回退栈
popBackStack()
//使用commit返回的事务id
popBackStack(int id, int flags);  
//使用加入回退栈时的tag值
popBackStack(String name, int flags);  

flag的取值,当取值0时,表示除了指定这一层之上的所有层都退出栈,指定的这一层为栈顶层;当取值POP_BACK_STACK_INCLUSIVE时,表示连着指定的这一层一起退出栈;

需要注意的是,使用popBackStack()来弹出栈内容的话,调用该方法后会将事物操作插入到FragmentManager的操作队列,只有当轮询到该事物时才能执行。如果想立即执行事物的话,需要使用下面几个对应的方法:

popBackStackImmediate()  
popBackStackImmediate(String tag)  
popBackStackImmediate(String tag, int flag)  
popBackStackImmediate(int id, int flag)

在FragmentActivity的onBackPressed()方法内可以看到,当popBackStackImmediate返回true的情况,则不会执行Activity的onBackPressed()方法

    @Override
    public void onBackPressed() {
        if (!mFragments.getSupportFragmentManager().popBackStackImmediate()) {
            super.onBackPressed();
        }
    }

2、回退栈(back stack)状态改变监听
FragmentManager还为我们提供了监控回退栈状态改变的方法:

addOnBackStackChangedListener(listener);//添加监听器  
removeOnBackStackChangedListener(listener);//移除监听器  

通过添加监听器,就可以在回退栈内容改变时,及时收到通知;
(1)、OnCreate()中:
为fragmentManger添加一个监听器:

FragmentManager manager = getSupportFragmentManager();  
listener = new FragmentManager.OnBackStackChangedListener() {  
      
    @Override  
    public void onBackStackChanged() {  
        // TODO Auto-generated method stub  
        Log.d("qijian","backstack changed");  
    }  
};  
manager.addOnBackStackChangedListener(listener);

(2)、当onDestory()中将监听器remove掉:

protected void onDestroy() {  
    // TODO Auto-generated method stub  
    super.onDestroy();  
    FragmentManager manager = getSupportFragmentManager();  
    manager.removeOnBackStackChangedListener(listener);  
}

大家一定要注意,不管是这里的回退栈的监听还是其它的监听器,在页面对应的销毁时,都要记得remove掉,不然会造成页面不释放,这也是造成OOM的问题之一。

参考文章:
Fragment详解之四——管理Fragment(2)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354

推荐阅读更多精彩内容