天天看点

FragmentManager的back stack是如何影响用户交互的

Fragment

既可以在布局文件中进行静态配置创建,也可以使用

FragmentManager

进行动态创建.在动态创建时, 可以通过调用

FragmentTransaction

addToBackStack

接口, 指定该FragmentTransaction对象添加到FragmentManager中的

back stack

中,这将对用户的操作(按back键)产生影响.本文档通过一个demo程序说明其中的一些细节.

Demo程序说明

包括一个Activity和四个Fragment, Activity中动态创建Fragment和销毁Fragment的操作.四个Fragment共用一个布局文件, 通过一个文本区域显示当前的Fragment名称.Activity类代码实现如下:

public class MainActivity extends Activity implements View.OnClickListener {
    private static final String TAG = MainActivity.class.getSimpleName();

    private int mAddedNum;
    private CheckBox mAddToBackStack;
    private CheckBox mPopFlag;
    private EditText mBackStackName;
    private FragmentManager mFragmentManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_demo);

        mAddToBackStack = findViewById(R.id.cb_add_to_back_stack);
        mPopFlag = findViewById(R.id.cb_include_flag);
        mBackStackName = findViewById(R.id.et_back_stack_name);

        findViewById(R.id.bt_add_fragment).setOnClickListener(this);
        findViewById(R.id.bt_pop_fragment).setOnClickListener(this);
        findViewById(R.id.bt_pop_fragment_with_name).setOnClickListener(this);

        mFragmentManager = getFragmentManager();
    }

    @Override
    public void onClick(View v) {
        final int id = v.getId();
        switch (id) {
            case R.id.bt_add_fragment:
                addFragment();
                break;

            case R.id.bt_pop_fragment:
                popBackStack();
                break;

            case R.id.bt_pop_fragment_with_name:
                popBackStackByNameOrId();
                break;
        }
    }

    private boolean needAddToBackStack() {
        return mAddToBackStack.isChecked();
    }

    private int getPopFlag() {
        if (mPopFlag.isChecked()) return FragmentManager.POP_BACK_STACK_INCLUSIVE;
        return 0;
    }

    private void addFragment() {
        Fragment fragment;
        String tag;
        switch (mAddedNum) {
            case 0:
                fragment = new Fragment1();
                tag = "Fragment1";
                break;

            case 1:
                fragment = new Fragment2();
                tag = "Fragment2";
                break;

            case 2:
                fragment = new Fragment3();
                tag = "Fragment3";
                break;
            case 3:
                fragment = new Fragment4();
                tag = "Fragment4";
                break;

            default:
                mAddedNum = 0;
                fragment = new Fragment1();
                tag = "Fragment1";
                break;
        }

        mAddedNum++;
        addFragment(R.id.fragment_container, fragment, tag);
    }

    private void addFragment(@IdRes int resId, @NonNull Fragment fragment, @NonNull String tag) {
        final FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
        fragmentTransaction.add(resId, fragment, tag);
        if (needAddToBackStack()) fragmentTransaction.addToBackStack(tag);
        final int commitId = fragmentTransaction.commit();
        Log.d(TAG, "Commit id:" + commitId);
    }

    private void popBackStack() {
        mFragmentManager.popBackStack();
    }

    private void popBackStackByNameOrId() {
        final int popFlag = getPopFlag();
        final String text = mBackStackName.getText().toString();
        if (text.startsWith("id:")) {
            final int id = Integer.valueOf(text.substring(3)).intValue();
            mFragmentManager.popBackStack(id, popFlag);
        } else if (text.startsWith("name:")) {
            final String name = text.substring(5);
            mFragmentManager.popBackStack(name, popFlag);
        }
    }
}
           

四个Fragment的代码实现如下:

public class Fragment1 extends BaseFragment {
    private static final String TAG = Fragment1.class.getSimpleName();

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return createView(inflater, container, TAG);
    }
}

public class Fragment2 extends BaseFragment {
    private static final String TAG = Fragment2.class.getSimpleName();

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return createView(inflater, container, TAG);
    }
}

public class Fragment3 extends BaseFragment {
    private static final String TAG = Fragment3.class.getSimpleName();

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return createView(inflater, container, TAG);
    }
}

public class Fragment4 extends BaseFragment {
    private static final String TAG = Fragment4.class.getSimpleName();

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return createView(inflater, container, TAG);
    }
}

public class BaseFragment extends Fragment {
    protected View createView(LayoutInflater inflater, ViewGroup container, String text) {
        final View view = inflater.inflate(R.layout.fragment_demo, container, false);
        ((TextView) view.findViewById(R.id.fragment_id)).setText(text);
        return view;
    }
}
           

Activity的布局文件为:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="10dp">

    <CheckBox
        android:id="@+id/cb_add_to_back_stack"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:checked="false"
        android:text="Add to back stack"
        android:textAllCaps="false" />

    <CheckBox
        android:id="@+id/cb_include_flag"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:checked="false"
        android:text="Pop back stack record with include flag"
        android:textAllCaps="false" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Pop back stack record with name or ID:" />

    <EditText
        android:id="@+id/et_back_stack_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="id:0" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dp"
        android:orientation="horizontal">

        <Button
            android:id="@+id/bt_add_fragment"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="add"
            android:textAllCaps="false" />

        <Button
            android:id="@+id/bt_pop_fragment"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="pop"
            android:textAllCaps="false" />

        <Button
            android:id="@+id/bt_pop_fragment_with_name"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="popBy"
            android:textAllCaps="false" />

    </LinearLayout>

    <FrameLayout
        android:id="@+id/fragment_container"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:layout_marginTop="10dp"
        android:background="#BFBEBE">
    </FrameLayout>

</LinearLayout>
           

Fragment共用的布局文件为:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#5d8989">

    <TextView
        android:id="@+id/fragment_id"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        tools:text="Dynamic fragment"
        android:textSize="20sp" />

</RelativeLayout>
           

Activity启动的初始界面为:

FragmentManager的back stack是如何影响用户交互的

Fragment Manager Back Stack对用户back按键的响应

当用户按下

back

键时,

Activity

的默认执行为(Activity.java):

/**
 * Called when the activity has detected the user's press of the back
 * key.  The default implementation simply finishes the current activity,
 * but you can override this to do whatever you want.
 */
public void onBackPressed() {
    if (mActionBar != null && mActionBar.collapseActionView()) {
        return;
    }

    FragmentManager fragmentManager = mFragments.getFragmentManager();

    if (fragmentManager.isStateSaved() || !fragmentManager.popBackStackImmediate()) {
        finishAfterTransition();
    }
}
           

也就是说, 如果用户执行back按键操作,

FragmentManager

back stack

中的记录进行

pop

处理,如果栈中存在记录,函数

popBackStackImmediate

将返回

true

, 那么通常情况下, Activity将不会调用

finishAfterTransition

进行销毁.

场景一

在不选中复选框

Add to back stack

的前提下,点击两次

add

按钮, 根据代码逻辑, 将动态创建

Fragment1

Fragment2

. 如果用户点击一次

back

键, 那么

MainActivity

将销毁,显示桌面.

场景二

在选中复选框

Add to back stack

的前提下,点击两次

add

按钮, 根据代码逻辑, 将动态创建

Fragment1

Fragment2

. 显示结果为:

FragmentManager的back stack是如何影响用户交互的

如果用户点击一次

back

键,那么FragmentManager将对back stack进行pop处理, Fragment2出栈,只有Fragment1在栈中, 因此结果显示为:

FragmentManager的back stack是如何影响用户交互的

如果这时候用户再次点击一次back按键,因为Fragment1还在back stack中,所以Fragment1出栈,函数

popBackStackImmediate

返回值仍热为

true

, MainActivity仍然不调用函数

finishAfterTransition

进行销毁, 显示结果为MainActivity的初始界面:

FragmentManager的back stack是如何影响用户交互的

如果用户再次点击一次back按键,因为此时FragmentManager中的back stack中已经没有记录,函数

popBackStackImmediate

将返回

false

, 导致调用

finishAfterTransition

销毁MainActivity,显示桌面.

通过这两个场景, 基本说明了FragmentManager的back stack对用户的back按键的响应情况.点击back按键时, 会对back stack中的记录进行pop操作, 但是,新的栈顶的Fragment是否显示,取决于当前显示的Fragment与新的栈顶Fragment的添加顺序.

场景三

在选中复选框

Add to back stack

的前提下,点击两次

add

按钮, 然后取消复选框

Add to back stack

, 再点击一次

add

按钮.FragmentManager中依次添加了

Fragment1

,

Fragment2

Fragment3

, 前两个Fragment同时添加到了back stack, Fragment3没有添加到back stack, 最终显示的是Fragment3的界面.

FragmentManager的back stack是如何影响用户交互的

如果用户点击一次

back

按键,Fragment2将从栈中退出,当前显示界面不变,仍然为Fragment3.再次点击一次back按键,Fragment1将从栈中退出,当前显示界面不变,仍然为Fragment3, 再次点击一次back按键,因为栈中已经没有记录,MainActivity将销毁, 返回桌面.

Commit索引以及pop指定的记录

FragmentManager动态创建Fragment时, 如果该Fragment同时添加到back stack, 则

commit

函数的返回值为该

BackStackRecord

(back stack中的每个记录用该类描述)的索引, 如果该Fragment没有添加到back stack, commit函数返回值为

-1

.BackStackRecord的索引的一个作用是, 我们可以从back stack中pop出该索引对应的记录, 需要注意的是,根据栈的属性, 栈中该记录以上的记录也将被pop操作.BackStackRecord的索引是从0开始分配的.

FragmentManager

提供了接口

popBackStack(int id, int flags)

来pop指定索引的记录.FragmentManager还提供了接口

popBackStackImmediate(String name, int flags)

根据记录的名称进行pop操作, 二者内在逻辑几乎完全相同.当调用这两个接口时, 其第二个输入参数

flags

的含义是,如果flags为

, 该索引对应的记录不会从back stack中pop出去,只是将该记录上面的记录pop出去.如果

flags

POP_BACK_STACK_INCLUSIVE

,则该索引对应的记录也将被pop出去.

场景四

在选中复选框

Add to back stack

的前提下,点击四次

add

按钮,即将

Fragment1

,

Fragment2

,

Fragment3

Fragment4

添加到了back stack中, 并且当前显示为

Fragment4

.FragmentManager的back stack中的情况为:

FragmentManager的back stack是如何影响用户交互的

如果通过FragmentManager调用

mFragmentManager.popBackStack(1, 0)

,那么栈中的操作结果将是:

FragmentManager的back stack是如何影响用户交互的

如果通过FragmentManager调用

mFragmentManager.popBackStack(1, FragmentManager.POP_BACK_STACK_INCLUSIVE),

那么栈中的操作结果将是:

FragmentManager的back stack是如何影响用户交互的

FragmentManager的接口

popBackStack()

将把栈顶的记录pop出去, 而接口

popBackStack(String name, int flags)

popBackStack(int id, int flags)

将有可能从栈中pop出多个记录.对于判断哪些记录需要pop操作的函数为(FragmentManager.java):

@SuppressWarnings("unused")
boolean popBackStackState(ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop,
        String name, int id, int flags) {
    if (mBackStack == null) {
        return false;
    }
    if (name == null && id < 0 && (flags & POP_BACK_STACK_INCLUSIVE) == 0) {
        int last = mBackStack.size() - 1;
        if (last < 0) {
            return false;
        }
        records.add(mBackStack.remove(last));
        isRecordPop.add(true);
    } else {
        int index = -1;
        if (name != null || id >= 0) {
            // If a name or ID is specified, look for that place in
            // the stack.
            index = mBackStack.size()-1;
            while (index >= 0) {
                BackStackRecord bss = mBackStack.get(index);
                if (name != null && name.equals(bss.getName())) {
                    break;
                }
                if (id >= 0 && id == bss.mIndex) {
                    break;
                }
                index--;
            }
            if (index < 0) {
                return false;
            }
            if ((flags&POP_BACK_STACK_INCLUSIVE) != 0) {
                index--;
                // Consume all following entries that match.
                while (index >= 0) {
                    BackStackRecord bss = mBackStack.get(index);
                    if ((name != null && name.equals(bss.getName()))
                            || (id >= 0 && id == bss.mIndex)) {
                        index--;
                        continue;
                    }
                    break;
                }
            }
        }
        if (index == mBackStack.size()-1) {
            return false;
        }
        for (int i = mBackStack.size() - 1; i > index; i--) {
            records.add(mBackStack.remove(i));
            isRecordPop.add(true);
        }
    }
    return true;
}
           

该函数执行完毕后,输出参数

records

中将保存需要pop出的

BackStackRecord

对象. 如果没有指定

name

或者

id

时, 从back stack栈顶pop出一个记录, 否则根据

name

或者

id

,并结合

flags

找出需要pop的记录.

BackStackRecord的索引分配

每个添加到back stack的

BackStackRecord

对象都要分配索引.如果一直是向back stack添加BackStackRecord对象,其所以将从0开始连续增加进行分配. 如果back stack发生了pop操作,则pop出的记录的索引将会被回收,用作下一次的分配.

FragmentManager

类中的如下两个成员变量控制BackStackRecord的索引分配(FragmentManager.java):

// Must be accessed while locked.
ArrayList<BackStackRecord> mBackStackIndices;
ArrayList<Integer> mAvailBackStackIndices;
           

分配索引时(FragmentManager.java):

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

即如果

mAvailBackStackIndices

有空闲的索引(说明之前发生过pop操作), 则从其中分配索引, 否则,从索引结构

mBackStackIndices

中分配索引.

当back stack发生pop操作时, 释放BackStackRecord的索引(FragmentManager.java):

public void freeBackStackIndex(int index) {
    synchronized (this) {
        mBackStackIndices.set(index, null);
        if (mAvailBackStackIndices == null) {
            mAvailBackStackIndices = new ArrayList<Integer>();
        }
        if (DEBUG) Log.v(TAG, "Freeing back stack index " + index);
        mAvailBackStackIndices.add(index);
    }
}
           

索引结构

mBackStackIndices

不再引用BackStackRecord, 但是该结构的

size

不会减小,而是把释放的索引保存在

mAvailBackStackIndices

中.

尾声

FragmentManager的接口popBackStack是异步调用, 其同步调用接口为popBackStackImmediate. fragmentTransaction的commit接口也是异步调用, 其同步调用接口为commitNow.