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

Fragment既可以在布局文件中进行静态配置创建,也可以使用FragmentManager进行动态创建.在动态创建时, 可以通过调用FragmentTransactionaddToBackStack接口, 指定该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的布局文件为:




    

    

    

    

    

        

Fragment共用的布局文件为:




    


Activity启动的初始界面为:
FragmentManager的back stack是如何影响用户交互的_第1张图片

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按键操作, FragmentManagerback stack中的记录进行pop处理,如果栈中存在记录,函数popBackStackImmediate将返回true, 那么通常情况下, Activity将不会调用finishAfterTransition进行销毁.

场景一
在不选中复选框Add to back stack的前提下,点击两次add按钮, 根据代码逻辑, 将动态创建Fragment1Fragment2. 如果用户点击一次back键, 那么MainActivity将销毁,显示桌面.

场景二
在选中复选框Add to back stack的前提下,点击两次add按钮, 根据代码逻辑, 将动态创建Fragment1Fragment2. 显示结果为:
FragmentManager的back stack是如何影响用户交互的_第2张图片
如果用户点击一次back键,那么FragmentManager将对back stack进行pop处理, Fragment2出栈,只有Fragment1在栈中, 因此结果显示为:
FragmentManager的back stack是如何影响用户交互的_第3张图片
如果这时候用户再次点击一次back按键,因为Fragment1还在back stack中,所以Fragment1出栈,函数popBackStackImmediate返回值仍热为true, MainActivity仍然不调用函数finishAfterTransition进行销毁, 显示结果为MainActivity的初始界面:
FragmentManager的back stack是如何影响用户交互的_第4张图片
如果用户再次点击一次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, Fragment2Fragment3, 前两个Fragment同时添加到了back stack, Fragment3没有添加到back stack, 最终显示的是Fragment3的界面.
FragmentManager的back stack是如何影响用户交互的_第5张图片
如果用户点击一次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为0, 该索引对应的记录不会从back stack中pop出去,只是将该记录上面的记录pop出去.如果flagsPOP_BACK_STACK_INCLUSIVE,则该索引对应的记录也将被pop出去.

场景四
在选中复选框Add to back stack的前提下,点击四次add按钮,即将Fragment1, Fragment2,Fragment3Fragment4添加到了back stack中, 并且当前显示为Fragment4.FragmentManager的back stack中的情况为:
FragmentManager的back stack是如何影响用户交互的_第6张图片
如果通过FragmentManager调用mFragmentManager.popBackStack(1, 0),那么栈中的操作结果将是:
FragmentManager的back stack是如何影响用户交互的_第7张图片
如果通过FragmentManager调用mFragmentManager.popBackStack(1, FragmentManager.POP_BACK_STACK_INCLUSIVE),那么栈中的操作结果将是:
FragmentManager的back stack是如何影响用户交互的_第8张图片

FragmentManager的接口popBackStack()将把栈顶的记录pop出去, 而接口popBackStack(String name, int flags)popBackStack(int id, int flags)将有可能从栈中pop出多个记录.对于判断哪些记录需要pop操作的函数为(FragmentManager.java):

@SuppressWarnings("unused")
boolean popBackStackState(ArrayList records, ArrayList 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 mBackStackIndices;
ArrayList mAvailBackStackIndices;

分配索引时(FragmentManager.java):

public int allocBackStackIndex(BackStackRecord bse) {
    synchronized (this) {
        if (mAvailBackStackIndices == null || mAvailBackStackIndices.size() <= 0) {
            if (mBackStackIndices == null) {
                mBackStackIndices = new ArrayList();
            }
            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();
        }
        if (DEBUG) Log.v(TAG, "Freeing back stack index " + index);
        mAvailBackStackIndices.add(index);
    }
}

索引结构mBackStackIndices不再引用BackStackRecord, 但是该结构的size不会减小,而是把释放的索引保存在mAvailBackStackIndices中.

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

你可能感兴趣的:(Android)