FragmentManager
顾名思义是用来管理fragment
的.本文主要讨论FragmentManager外在和内在相关联的一些类和属性.
FragmentManager宿主环境
FragmentManager宿主环境指的是其管理类,通常在Activity
中通过接口getSupportFragmentManager
或者getFragmentManager
获得FragmentManager对象, 可以认为Activity是FragmentManager的宿主环境类.FragmentManager宿主环境类状态变化时(如onStart
, onResume
, onStop
等回调),FragmentManager的状态也随之更新(FragmentManager类有一个成员变量mCurState
来记录当前状态),同时会同步其管理的所有Fragment
的状态. 从用户的角度来看, FragmentManager的宿主环境类管理了一系列Fragment,并且Fragment随着FragmentMannager的宿主环境类的状态变化而变化.
三者间的关系如下图所示:
也就是说FragmentManager的宿主环境类,可以是Activity,也可以是Fragment. 这就意味着一个Fragment可以作为容器,管理其他一组Fragment.Fragment类中必然也存在FragmentManager对象.
FragmentManager初始化
FragmentManager必须初始化完成,才能管理Fragment. 这里的初始化完成指的是通过构造器创建FragmentManager对象以后, 调用其attach
方法.
场景一: 在Activity中初始化
如果FragmentManager是宿主类是Activity,可以在Activity中使用getFragmentManager获得FragmentManager对象.Activity类中存在成员变量(Activity.java):
final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
HostCallbacks
类构造函数(Activity.java):
class HostCallbacks extends FragmentHostCallback<Activity> {
public HostCallbacks() {
super(Activity.this /*activity*/);
}
...
}
FragmentHostCallback
类构造函数(FragmentHostCallback.java):
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();
/** The loader managers for individual fragments [i.e. Fragment#getLoaderManager()] */
private ArrayMap<String, LoaderManager> mAllLoaderManagers;
/** Whether or not fragment loaders should retain their state */
private boolean mRetainLoaders;
/** The loader manger for the fragment host [i.e. Activity#getLoaderManager()] */
private LoaderManagerImpl mLoaderManager;
private boolean mCheckedForLoaderManager;
/** Whether or not the fragment host loader manager was started */
private boolean mLoadersStarted;
public FragmentHostCallback(Context context, Handler handler, int windowAnimations) {
this((context instanceof Activity) ? (Activity)context : null, context,
chooseHandler(context, handler), windowAnimations);
}
FragmentHostCallback(Activity activity) {
this(activity, activity /*context*/, activity.mHandler, 0 /*windowAnimations*/);
}
FragmentHostCallback(Activity activity, Context context, Handler handler,
int windowAnimations) {
mActivity = activity;
mContext = context;
mHandler = handler;
mWindowAnimations = windowAnimations;
}
...
}
FragmentController
类构造函数(FragmentController.java):
public class FragmentController {
private final FragmentHostCallback<?> mHost;
/**
* Returns a {@link FragmentController}.
*/
public static final FragmentController createController(FragmentHostCallback<?> callbacks) {
return new FragmentController(callbacks);
}
private FragmentController(FragmentHostCallback<?> callbacks) {
mHost = callbacks;
}
/**
* Returns a {@link FragmentManager} for this controller.
*/
public FragmentManager getFragmentManager() {
return mHost.getFragmentManagerImpl();
}
...
}
HostCallbacks
类对象代表具体的宿主环境上下文.FragmentController
类中存在一个成员变量字段mHost
引用HostCallbacks
对象.FragmentHostCallback
类中创建的FragmentManagerImpl
是FragmentManager
的实现类.所以通过一系列包装,Activity类中相当于存在一个FragmentManager成员变量.
在Activity类的attach
方法(在Activity的创建过程中, 由AMS
在Activity进程中调用)中(Activity.java):
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
...
}
函数调用mFragments.attachHost(null /*parent*/)
的实现为(FragmentController.java):
/**
* Attaches the host to the FragmentManager for this controller. The host must be
* attached before the FragmentManager can be used to manage Fragments.
* */
public void attachHost(Fragment parent) {
mHost.mFragmentManager.attachController(
mHost, mHost /*container*/, parent);
}
即调用FragmentManagerImpl
对象的相应函数进行初始化赋值(FragmentManager.java):
public void attachController(FragmentHostCallback<?> host, FragmentContainer container,
Fragment parent) {
if (mHost != null) throw new IllegalStateException("Already attached");
mHost = host;
mContainer = container;
mParent = parent;
mAllowOldReentrantBehavior = getTargetSdk() <= Build.VERSION_CODES.N_MR1;
}
总结:
- 在Activity创建过程中,当其回调到
onCreate
时,其FragmentManager对象已经准备就绪了.
场景二:在Fragment中初始化
在Fragment宿主情况下,可以调用其getChildFragmentManager
获得FragmentManager对象.这是Fragment中私有的FragmentManager对象(Fragment.java):
/**
* Return a private FragmentManager for placing and managing Fragments
* inside of this Fragment.
*/
final public FragmentManager getChildFragmentManager() {
if (mChildFragmentManager == null) {
instantiateChildFragmentManager();
if (mState >= RESUMED) {
mChildFragmentManager.dispatchResume();
} else if (mState >= STARTED) {
mChildFragmentManager.dispatchStart();
} else if (mState >= ACTIVITY_CREATED) {
mChildFragmentManager.dispatchActivityCreated();
} else if (mState >= CREATED) {
mChildFragmentManager.dispatchCreate();
}
}
return mChildFragmentManager;
}
void instantiateChildFragmentManager() {
mChildFragmentManager = new FragmentManagerImpl();
mChildFragmentManager.attachController(mHost, new FragmentContainer() {
@Override
@Nullable
public <T extends View> T onFindViewById(int id) {
if (mView == null) {
throw new IllegalStateException("Fragment does not have a view");
}
return mView.findViewById(id);
}
@Override
public boolean onHasView() {
return (mView != null);
}
}, this);
}
在这种情况下, 当调用getChildFragmentManager时,才创建私有的FragmentManager对象,并调用其attach方法.此时需要确认的一个问题是, mHost
是什么时候赋值的. 或者从更高层面,Fragment的getChildFragmentManager
在Fragment的哪些状态回调中可以调用. 在onAttach
, onCreate
, onCreateView
, 亦或onActivityCreated
中是否都可以调用getChildFragmentManager
?这个问题结合下面谈到的Fragment的创建过程进行说明.这里先给出结论.
总结:
- Fragment中通过
getChildFragmentManager
接口获取其私有的FragmentManager对象时, 才会创建该FragmentManager对象,并调用attach
方法. getChildFragmentManager
接口可以在Fragment的最早的状态回调函数onAttach
中调用,当然在onCreate等其他状态正确的回调函数中调用也是可以的.
Fragment创建过程
每个Fragment都有其归属的FragmentManager, 当Fragment创建时, FragmentManager会根据自己当前状态, 将Fragment迁移到对应的状态.
如果Fragment是通过在布局文件中静态配置创建的,当回调自定义的Activity的onCreate
时, 执行setContentView
函数,会执行FragmentManager
的onCreateView
函数(FragmentManager.java):
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
if (!"fragment".equals(name)) {
return null;
}
String fname = attrs.getAttributeValue(null, "class");
TypedArray a = context.obtainStyledAttributes(attrs, FragmentTag.Fragment);
if (fname == null) {
fname = a.getString(FragmentTag.Fragment_name);
}
int id = a.getResourceId(FragmentTag.Fragment_id, View.NO_ID);
String tag = a.getString(FragmentTag.Fragment_tag);
a.recycle();
if (!Fragment.isSupportFragmentClass(mHost.getContext(), fname)) {
// Invalid support lib fragment; let the device's framework handle it.
// This will allow android.app.Fragments to do the right thing.
return null;
}
int containerId = parent != null ? parent.getId() : 0;
if (containerId == View.NO_ID && id == View.NO_ID && tag == null) {
throw new IllegalArgumentException(attrs.getPositionDescription()
+ ": Must specify unique android:id, android:tag, or have a parent with an id for " + fname);
}
// If we restored from a previous state, we may already have
// instantiated this fragment from the state and should use
// that instance instead of making a new one.
Fragment fragment = id != View.NO_ID ? findFragmentById(id) : null;
if (fragment == null && tag != null) {
fragment = findFragmentByTag(tag);
}
if (fragment == null && containerId != View.NO_ID) {
fragment = findFragmentById(containerId);
}
if (FragmentManagerImpl.DEBUG) Log.v(TAG, "onCreateView: id=0x"
+ Integer.toHexString(id) + " fname=" + fname
+ " existing=" + fragment);
if (fragment == null) {
fragment = mContainer.instantiate(context, fname, null);
fragment.mFromLayout = true;
fragment.mFragmentId = id != 0 ? id : containerId;
fragment.mContainerId = containerId;
fragment.mTag = tag;
fragment.mInLayout = true;
fragment.mFragmentManager = this;
fragment.mHost = mHost;
fragment.onInflate(mHost.getContext(), attrs, fragment.mSavedFragmentState);
addFragment(fragment, true);
} else if (fragment.mInLayout) {
// A fragment already exists and it is not one we restored from
// previous state.
throw new IllegalArgumentException(attrs.getPositionDescription()
+ ": Duplicate id 0x" + Integer.toHexString(id)
+ ", tag " + tag + ", or parent id 0x" + Integer.toHexString(containerId)
+ " with another fragment for " + fname);
} else {
// This fragment was retained from a previous instance; get it
// going now.
fragment.mInLayout = true;
fragment.mHost = mHost;
// If this fragment is newly instantiated (either right now, or
// from last saved state), then give it the attributes to
// initialize itself.
if (!fragment.mRetaining) {
fragment.onInflate(mHost.getContext(), attrs, fragment.mSavedFragmentState);
}
}
// If we haven't finished entering the CREATED state ourselves yet,
// push the inflated child fragment along. This will ensureInflatedFragmentView
// at the right phase of the lifecycle so that we will have mView populated
// for compliant fragments below.
if (mCurState < Fragment.CREATED && fragment.mFromLayout) {
moveToState(fragment, Fragment.CREATED, 0, 0, false);
} else {
moveToState(fragment);
}
if (fragment.mView == null) {
throw new IllegalStateException("Fragment " + fname
+ " did not create a view.");
}
if (id != 0) {
fragment.mView.setId(id);
}
if (fragment.mView.getTag() == null) {
fragment.mView.setTag(tag);
}
return fragment.mView;
}
在Fragment第一次创建时, 执行fragment = mContainer.instantiate(context, fname, null);
创建Fragment对象.
该函数的实现为(FragmentContainer.java):
public Fragment instantiate(Context context, String className, Bundle arguments) {
return Fragment.instantiate(context, className, arguments);
}
进而调用Fragment的静态instantiate
函数, 其实现为(Fragment.java):
public static Fragment instantiate(Context context, String fname, @Nullable Bundle args) {
try {
Class<?> clazz = sClassMap.get(fname);
if (clazz == null) {
// Class not found in the cache, see if it's real, and try to add it
clazz = context.getClassLoader().loadClass(fname);
sClassMap.put(fname, clazz);
}
Fragment f = (Fragment) clazz.getConstructor().newInstance();
if (args != null) {
args.setClassLoader(f.getClass().getClassLoader());
f.setArguments(args);
}
return f;
} catch (ClassNotFoundException e) {
throw new InstantiationException("Unable to instantiate fragment " + fname
+ ": make sure class name exists, is public, and has an"
+ " empty constructor that is public", e);
} catch (java.lang.InstantiationException e) {
throw new InstantiationException("Unable to instantiate fragment " + fname
+ ": make sure class name exists, is public, and has an"
+ " empty constructor that is public", e);
} catch (IllegalAccessException e) {
throw new InstantiationException("Unable to instantiate fragment " + fname
+ ": make sure class name exists, is public, and has an"
+ " empty constructor that is public", e);
} catch (NoSuchMethodException e) {
throw new InstantiationException("Unable to instantiate fragment " + fname
+ ": could not find Fragment constructor", e);
} catch (InvocationTargetException e) {
throw new InstantiationException("Unable to instantiate fragment " + fname
+ ": calling Fragment constructor caused an exception", e);
}
}
即创建Fragment对象,使用的是无参数的构造函数.Fragment对象创建以后, FragmentManager调用makeActive
记录该Fragment对象, 然后调用moveToState
更新其状态.该函数的输入参数newState
是要更新到的目的状态,Fragment对象创建时,其状态为Fragment.INITIALIZING
, 该状态下的处理逻辑为(FragmentManager):
case Fragment.INITIALIZING:
if (newState > Fragment.INITIALIZING) {
if (DEBUG) Log.v(TAG, "moveto CREATED: " + f);
if (f.mSavedFragmentState != null) {
f.mSavedFragmentState.setClassLoader(mHost.getContext()
.getClassLoader());
f.mSavedViewState = f.mSavedFragmentState.getSparseParcelableArray(
FragmentManagerImpl.VIEW_STATE_TAG);
f.mTarget = getFragment(f.mSavedFragmentState,
FragmentManagerImpl.TARGET_STATE_TAG);
if (f.mTarget != null) {
f.mTargetRequestCode = f.mSavedFragmentState.getInt(
FragmentManagerImpl.TARGET_REQUEST_CODE_STATE_TAG, 0);
}
f.mUserVisibleHint = f.mSavedFragmentState.getBoolean(
FragmentManagerImpl.USER_VISIBLE_HINT_TAG, true);
if (!f.mUserVisibleHint) {
f.mDeferStart = true;
if (newState > Fragment.STOPPED) {
newState = Fragment.STOPPED;
}
}
}
f.mHost = mHost;
f.mParentFragment = mParent;
f.mFragmentManager = mParent != null
? mParent.mChildFragmentManager : mHost.getFragmentManagerImpl();
// If we have a target fragment, push it along to at least CREATED
// so that this one can rely on it as an initialized dependency.
if (f.mTarget != null) {
if (mActive.get(f.mTarget.mIndex) != f.mTarget) {
throw new IllegalStateException("Fragment " + f
+ " declared target fragment " + f.mTarget
+ " that does not belong to this FragmentManager!");
}
if (f.mTarget.mState < Fragment.CREATED) {
moveToState(f.mTarget, Fragment.CREATED, 0, 0, true);
}
}
dispatchOnFragmentPreAttached(f, mHost.getContext(), false);
f.mCalled = false;
f.onAttach(mHost.getContext());
if (!f.mCalled) {
throw new SuperNotCalledException("Fragment " + f
+ " did not call through to super.onAttach()");
}
if (f.mParentFragment == null) {
mHost.onAttachFragment(f);
} else {
f.mParentFragment.onAttachFragment(f);
}
dispatchOnFragmentAttached(f, mHost.getContext(), false);
if (!f.mIsCreated) {
dispatchOnFragmentPreCreated(f, f.mSavedFragmentState, false);
f.performCreate(f.mSavedFragmentState);
dispatchOnFragmentCreated(f, f.mSavedFragmentState, false);
} else {
f.restoreChildFragmentState(f.mSavedFragmentState);
f.mState = Fragment.CREATED;
}
f.mRetaining = false;
}
// fall through
代码f.mHost = mHost;
将赋值Fragment的mHost
, 分别执行f.onAttach(mHost.getContext());
和f.performCreate(f.mSavedFragmentState);
来回调Fragment的onAttach
和onCreate
方法. 当Fragment.INITIALIZING
状态下的逻辑执行完毕以后,Fragment对象的状态将更新为Fragment.CREATED
状态.需要注意的是这里的每个case
语句都没有break
, 都是fall through
接着执行下一个状态的逻辑, 直到Fragment迁移到目的状态newState
.在Fragment.CREATED
状态下,将回调Fragment的onCreateView
,onViewCreated
,onActivityCreated
和onStart
.
当使用FragmentManager动态创建Fragment时,与静态创建的区别是, 我们自己通过new
创建Fragment对象,后续流程都是相同的, 包括调用makeActive
, moveToState
.
总结:
- Fragment对象的
mHost
对象的赋值是在其onAttach
回调之前进行的.因此可以解释之前getChildFragmentManager
的调用时机的问题.
使用Fragment私有的FragmentManager
通过一个demo程序说明使用Fragment的私有FragmentManager, 在Fragment中管理一组Fragment.下面是demo程序的相关代码. 主MainActivity:
public class MainActivity extends Activity {
private static final String TAG = MainActivity.class.getSimpleName();
private Fragment mFragmentContainer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_demo);
mFragmentContainer = getFragmentManager().findFragmentById(R.id.fragment_container);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
boolean backPressHandled = false;
if (keyCode == KeyEvent.KEYCODE_BACK) {
backPressHandled = ((FragmentContainer) mFragmentContainer).pressBack();
}
if (backPressHandled) return true;
return super.onKeyDown(keyCode, event);
}
}
其布局文件为:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<fragment
android:id="@+id/fragment_container"
android:name="com.fragment.demo.FragmentContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
一组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;
}
}
这一组Fragment共用一个布局文件, 通过一个文本区别显示不同的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>
一个作为容器使用的Fragment, 其实现为:
public class FragmentContainer extends Fragment implements View.OnClickListener{
private static final String TAG = FragmentContainer.class.getSimpleName();
private int mAddedNum;
private CheckBox mAddToBackStack;
private FragmentManager mFragmentManager;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.fragment_container_demo, container, false);
mAddToBackStack = view.findViewById(R.id.cb_add_to_back_stack);
view.findViewById(R.id.bt_add_fragment).setOnClickListener(this);
mFragmentManager = getChildFragmentManager();
return view;
}
@Override
public void onClick(View v) {
final int id = v.getId();
switch (id) {
case R.id.bt_add_fragment:
addFragment();
break;
}
}
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, fragment, tag);
}
private boolean needAddToBackStack() {
return mAddToBackStack.isChecked();
}
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);
}
public boolean pressBack() {
final int counts = mFragmentManager.getBackStackEntryCount();
if (counts > 0) {
mFragmentManager.popBackStack();
return true;
}
return false;
}
}
其布局文件为:
<?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" />
<Button
android:id="@+id/bt_add_fragment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="add"
android:textAllCaps="false" />
<FrameLayout
android:id="@+id/fragment"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginTop="10dp"
android:background="#BFBEBE">
</FrameLayout>
</LinearLayout>
启动后的初始界面如下所示:
关于FragmentManager的back stack的细节可以参考FragmentManager的back stack是如何影响用户交互的 .
在不选择复选框 Add to back stack
的情况下, 点击add
按钮两次,将Fragment1
和Fragment2
添加到FragmentContainer
的私有FragmentManager中.显示界面如下所示:
如果这时用户按下back键,MainActivity将销毁,显示桌面.
在选择复选框 Add to back stack
的情况下, 点击add按钮两次,将Fragment1
和Fragment2
添加到FragmentContainer
的私有FragmentManager中.显示界面如下所示:
如果用户按下back键,将显示Fragment1
.也就是说FragmentContainer
的私有FragmentManager的back stack进行了pop处理.
对用户按下back
键的处理的关键是,在MainActivity中覆写onKeyDown
方法,调用Fragment的私有FragmentManager进行back stack
的pop
处理, 如果其back stack中存在BackStackRecord
,直接返回true
, 而不再调用super.onKeyDown.
如果back stack中不存在BackStackRecord, 调用super.onKeyDown
.如果不在MainActivity中覆写onKeyDown
方法, 在向FragmentContainer类中添加Fragment时,无论是否添加到其私有FragmentManager的back stack,用户按下back键时,MainActivity都将销毁,程序退回桌面.
使用support兼容库时的FragmentManager行为
在我们实现的Activity对象中, 存在一个FragmentManager成员变量,可以使用接口getFragmentManager
获取其引用.但是当我们的Activity继承AppCompatActivity
后,也就是说使用support
兼容库以后,也可以调用getSupportFragmentManager
接口获得FragmentManager引用. 这两个接口返回的是不同的FragmentManager对象,也就是说,此时我们的Activity中存在两个FragmentManager成员变量.下面通过一个demo程序说明这个情况.
主Activity:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = MainActivity.class.getSimpleName();
private CheckBox mAddToBackStack;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_demo);
mAddToBackStack = findViewById(R.id.cb_add_to_back_stack);
findViewById(R.id.bt_add_fragment).setOnClickListener(this);
findViewById(R.id.bt_add_compat_fragment).setOnClickListener(this);
}
@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_add_compat_fragment:
addCompatFragment();
break;
}
}
private boolean needAddToBackStack() {
return mAddToBackStack.isChecked();
}
private void addFragment() {
final android.app.FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
final NonCompatFragment fragment = new NonCompatFragment();
fragmentTransaction.add(R.id.fragment, fragment, "NonCompatFragment");
if (needAddToBackStack()) fragmentTransaction.addToBackStack("NonCompatFragment");
final int commitId = fragmentTransaction.commit();
Log.d(TAG, "addFragment. Commit id:" + commitId);
}
private void addCompatFragment() {
final android.support.v4.app.FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
final CompatFragment fragment = new CompatFragment();
fragmentTransaction.add(R.id.compat_fragment, fragment, "CompatFragment");
if (needAddToBackStack()) fragmentTransaction.addToBackStack("CompatFragment");
final int commitId = fragmentTransaction.commit();
Log.d(TAG, "addCompatFragment. Commit id:" + commitId);
}
}
其布局文件:
<?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" />
<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 fragment"
android:textAllCaps="false" />
<Button
android:id="@+id/bt_add_compat_fragment"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="add compat fragment"
android:textAllCaps="false" />
</LinearLayout>
<FrameLayout
android:id="@+id/fragment"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginTop="10dp"
android:background="#BFBEBE">
</FrameLayout>
<FrameLayout
android:id="@+id/compat_fragment"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginTop="10dp"
android:background="#BFBEBE">
</FrameLayout>
</LinearLayout>
继承自support库中的Fragment:
package com.fragment.demo;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
public class CompatFragment extends Fragment {
private static final String TAG = CompatFragment.class.getSimpleName();
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.fragment_demo, container, false);
((TextView) view.findViewById(R.id.fragment_id)).setText(TAG);
return view;
}
}
直接继承SDK的Fragment:
package com.fragment.demo;
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
public class NonCompatFragment extends Fragment {
private static final String TAG = NonCompatFragment.class.getSimpleName();
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.fragment_demo, container, false);
((TextView) view.findViewById(R.id.fragment_id)).setText(TAG);
return view;
}
}
两个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>
demo程序启动的初始界面如下所示:
选中复选框Add to back stack
, 点击按钮 add fragment
和 add compat fragment
,添加这两个Fragmemt, 界面如下所示:
如果用户按下back
键, 将会是什么情况?如果先点击add compat fragment
按钮,再点击add fragment
按钮,用户按下back
键, 将会出现什么情况?
如果这两个FragmentManager中的back stack
中存在BackStackRecord
, 首先从getSupportFragmentManager
接口对应的FragmentManager的back stack中进行pop操作,然后从getFragmentManager
接口对应的FragmentManager的back stack中进行pop操作. 因此, 和这里的add compat fragment
按钮和add fragment
按钮的点击的先后顺序没有关系.当我们的MainActivity继承自AppCompatActivity
时, 用户的back
按键操作会首先执行FragmentActivity
的onBackPressed
,其实现为(FragmentActivity.java):
@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();
}
}
所以getSupportFragmentManager
对应的FragmentManager中的back stack先进行pop操作, 如果其back stack为空的情况下,才会调用super.onBackPressed();
执行Activity
的onBackPressed
.
MainActivity中存在两个不同的FragmentManager的情况, 其实可以从下面几点都可以证明:
- 使用
logcat
分别打印getSupportFragmentManager
和getFragmentManager
返回的FragmentManager
对象, 检查其哈希是否相同. - 分别检查两个FragmentManager的
commit
的返回值,back stack
第一次添加记录时,返回值为0
. 两个不同的FragmentManager对应的是不同的back stack,则各自首次commit
调用都应该返回0
. - 正如demo程序所展示的, 用户的
back
按键操作结果和点击按钮add fragment
和add compat fragment
的顺序无关,也间接说明这是两个不同的back stack
.