开篇
又是周末了,有一段时间没有给童鞋们分享点什么东西了。今天熬夜给童鞋们分享一个Fragment回退栈管理。
意欲何为
Fragment是3.0API加入的组件,它已被广泛用于应用开发中。support-v4包迭代到当前版本,已经是非常成熟非常好用的一个组件了。但是,API里提供的往往不能满足现实开发中。今天就说说Fragment回退栈的管理。
先看看以下需求:
1、像Activity一样可以正常回退
2、部分Fragment缓存,部分Fragment不缓存
3、部分Fragment不加入回退栈
BackStackRecord源码分析
且看support-v4(27.1.1)源码:
一、FragmentMananger
之beginTransaction()
:
@Override public FragmentTransaction beginTransaction() { return new BackStackRecord(this); }
二、BackStackRecord
继承FragmentTransaction
:
final class BackStackRecord extends FragmentTransaction implements FragmentManager.BackStackEntry, FragmentManagerImpl.OpGenerator { //省略部分代码 @Override public FragmentTransaction addToBackStack(String name) { if (!mAllowAddToBackStack) { throw new IllegalStateException( "This FragmentTransaction is not allowed to be added to the back stack."); } mAddToBackStack = true; mName = name; return this; } //省略部分代码}
1、实现了
addToBackStack(@Nullable String name)
方法。标识本次transaction是被加入回退栈中的。当然,前提是本次是允许被加入回退栈的。
@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; }
2、实现
replace(int containerViewId, Fragment fragment, String tag)
方法。其中调用了doAddOp(containerViewId, fragment, tag, OP_REPLACE);
。opcmd是OP_REPLACE
。且往下看在doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd)
中做了什么样的逻辑操作:
private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) { final Class fragmentClass = fragment.getClass(); final int modifiers = fragmentClass.getModifiers(); //匿名类、非public类、成员类且非static类, //如果是这三种类其中之一都会抛出异常 //当且仅当基础类才有可能是匿名类、成员类 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; } //新增一条操作记录 addOp(new Op(opcmd, fragment)); }
3、我们很清楚地看到每一次replace都会新增一条操作记录:
addOp(new Op(opcmd, fragment));
。Op
是BackStackRecord
中的一个内部静态类。
static final class Op { int cmd; Fragment fragment; int enterAnim; int exitAnim; int popEnterAnim; int popExitAnim; Op() { } Op(int cmd, Fragment fragment) { this.cmd = cmd; this.fragment = fragment; } }
4、每一条操作记录都有
fragment
实例,这是强引用。这里的cmd
就是前面传递进来的OP_REPLACE
。
ArrayList<Op> mOps = new ArrayList<>(); void addOp(Op op) { mOps.add(op); op.enterAnim = mEnterAnim; op.exitAnim = mExitAnim; op.popEnterAnim = mPopEnterAnim; op.popExitAnim = mPopExitAnim; }
5、本次
op
被存储到操作列表中。
三、提交本次transaction:
@Override public int commit() { 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); pw.close(); } mCommitted = true; if (mAddToBackStack) { mIndex = mManager.allocBackStackIndex(this); } else { mIndex = -1; } mManager.enqueueAction(this, allowStateLoss); return mIndex; }
6、如果允许本次
op
加入回退栈,则分配本次op
的在回退栈中的序号。交给FragmentManager执行本次事务:mManager.enqueueAction(this, allowStateLoss);
。this
指向OpGenerator实例。
/** * An add or pop transaction to be scheduled for the UI thread. */ interface OpGenerator { /** * Generate transactions to add to {@code records} and whether or not the transaction is * an add or pop to {@code isRecordPop}. * * records and isRecordPop must be added equally so that each transaction in records * matches the boolean for whether or not it is a pop in isRecordPop. * * @param records A list to add transactions to. * @param isRecordPop A list to add whether or not the transactions added to records is * a pop transaction. * @return true if something was added or false otherwise. */ boolean generateOps(ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop); }
7、Api注释很明白。开头就已经告诉我们:
BackStackRecord implements FragmentManagerImpl.OpGenerator
。看看BackStackRecord
中generateOps(ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop)
方法的实现:
/** * Implementation of {@link FragmentManagerImpl.OpGenerator}. * This operation is added to the list of pending actions during {@link #commit()}, and * will be executed on the UI thread to run this FragmentTransaction. * * @param records Modified to add this BackStackRecord * @param isRecordPop Modified to add a false (this isn't a pop) * @return true always because the records and isRecordPop will always be changed */ @Override public boolean generateOps(ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop) { if (FragmentManagerImpl.DEBUG) { Log.v(TAG, "Run: " + this); } records.add(this); isRecordPop.add(false); if (mAddToBackStack) { //添加到FragmentManager回退栈管理中 mManager.addBackStackState(this); } return true; }
四、回退AppCompatActivity
的void onBackPressed()
:
/** * Take care of popping the fragment back stack or finishing the activity * as appropriate. */ @Override public void onBackPressed() { FragmentManager fragmentManager = mFragments.getSupportFragmentManager(); final boolean isStateSaved = fragmentManager.isStateSaved(); if (isStateSaved && Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1) { // Older versions will throw an exception from the framework // FragmentManager.popBackStackImmediate(), so we'll just // return here. The Activity is likely already on its way out // since the fragmentManager has already been saved. return; } if (isStateSaved || !fragmentManager.popBackStackImmediate()) { super.onBackPressed(); } }
fragmentManager.popBackStackImmediate()
回退到回退栈里的上一次操作。
BackStackRecord
就分析到这里了。FragmentManager
就是管理和执行BackStackRecord
并在UI线程中显示Fragment
界面。总的来说,每一次transaction
可能有多个op
,而FragmentManager
可能会有N条BackStackRecord
。
基于Api的Fragment回退栈效果
实现方式一以及效果图
int index = -1; ArrayMap<Integer, String> contentCache = new ArrayMap<>(); private void showNext1(){ Fragment fragment = new DefaultFragment(); //绑定data String content = "fragment" + index; Bundle bundle = new Bundle(); bundle.putString(DefaultFragment.EXTRA_CONTENT, content); fragment.setArguments(bundle); contentCache.put(index, content); FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); transaction.addToBackStack(null); transaction.replace(R.id.fragment_container, fragment); transaction.commit(); }
,在所有Fragment都加入回退栈后,方式一能正常回退。
实现方式二以及效果图
int index = -1; ArrayMap<Integer, String> contentCache = new ArrayMap<>(); private void showNext2(){ boolean returnable = new Random().nextBoolean(); Fragment fragment = new DefaultFragment(); //绑定data String content = "fragment" + index + (returnable ? "" : "\u2000X"); Bundle bundle = new Bundle(); bundle.putString(DefaultFragment.EXTRA_CONTENT, content); fragment.setArguments(bundle); contentCache.put(index, content); FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); if (returnable) transaction.addToBackStack(null); else transaction.disallowAddToBackStack(); transaction.replace(R.id.fragment_container, fragment); transaction.commit(); }
,请告诉我这是什么鬼?很显然,当部分Fragment不加入回退栈时回退是有问题的。还是我哪里漏写code了?(如果这是这样,大神可以在评论中给我回复,非常感谢!)
在回头看看需求,我就想说一句:MMP。
很显然,API这时候就不能满足我们的日常开发了。不过别慌,接下来福利来了。
...
新福利:效果截屏
作者:JustinRoom
链接:https://www.jianshu.com/p/cf32e55864aa
共同学习,写下你的评论
评论加载中...
作者其他优质文章