本文从几个实验开始,到无UI的Fragment,详解Fragment使用过程中可能忽略的相关用法和各种坑。
Fragment全解析系列(一):那些年踩过的坑
Fragment全解析系列(二):正确的使用姿势
Android中保存和恢复Fragment状态的最好方法
Hide\Show只影响可见性,并不会影响生命周期。
**
* Hides an existing fragment. This is only relevant for fragments whose
* views have been added to a container, as this will cause the view to
* be hidden.
*
* @param fragment The fragment to be hidden.
*
* @return Returns the same FragmentTransaction instance.
*/
public abstract FragmentTransaction hide(Fragment fragment);
/**
* Shows a previously hidden fragment. This is only relevant for fragments whose
* views have been added to a container, as this will cause the view to
* be shown.
*
* @param fragment The fragment to be shown.
*
* @return Returns the same FragmentTransaction instance.
*/
public abstract FragmentTransaction show(Fragment fragment);
add和replace的区别在哪里?,下面做几个实验,从生命周期的角度来分析。
假如Activity中的FrameLayout布局R.id.container是Fragment的容器。现在替换为FragmentA,然后从FragmentA中打开FragmentB,过程即:
FrameLayout→FragmentA→FragmentB
第一阶段FrameLayout→[add(),FragmentA]的log:
11-06 20:30:29.497 21080-21080/name.free.fragmentdemo D/AAAAA: onStart
11-06 20:30:29.498 21080-21080/name.free.fragmentdemo D/AAAAA: onCreateView
11-06 20:30:29.501 21080-21080/name.free.fragmentdemo D/AAAAA: onViewCreated
11-06 20:30:29.501 21080-21080/name.free.fragmentdemo D/AAAAA: onResume
第二阶段[add(),FragmentA]→[add(),FragmentB]的log:
11-06 20:31:13.707 21080-21080/name.free.fragmentdemo D/BBBBB: onStart
11-06 20:31:13.707 21080-21080/name.free.fragmentdemo D/BBBBB: onCreateView
11-06 20:31:13.720 21080-21080/name.free.fragmentdemo D/BBBBB: onViewCreated
11-06 20:31:13.721 21080-21080/name.free.fragmentdemo D/BBBBB: onResume
结果是FragmentA仍然停留在onResume()阶段,两个Fragment叠加在一起。
如果此时熄灭屏幕,FragmentA和FragmentB都会先后进入到onPause()和onStop()的阶段。
11-06 20:32:57.127 21080-21080/name.free.fragmentdemo D/AAAAA: onPause
11-06 20:32:57.127 21080-21080/name.free.fragmentdemo D/BBBBB: onPause
11-06 20:32:57.203 21080-21080/name.free.fragmentdemo D/AAAAA: onStop
11-06 20:32:57.203 21080-21080/name.free.fragmentdemo D/BBBBB: onStop
亮屏后FragmentA和FragmentB又分别恢复到onResume()的阶段。
11-06 20:34:04.212 21080-21080/name.free.fragmentdemo D/AAAAA: onResume
11-06 20:34:04.212 21080-21080/name.free.fragmentdemo D/BBBBB: onResume
这个时候,按下一次Back键,FragmentA和FragmentB都交替进入销毁阶段,之后完全退出了该Activity。
11-06 20:35:16.876 21080-21080/name.free.fragmentdemo D/AAAAA: onPause
11-06 20:35:16.876 21080-21080/name.free.fragmentdemo D/BBBBB: onPause
11-06 20:35:17.247 21080-21080/name.free.fragmentdemo D/AAAAA: onStop
11-06 20:35:17.247 21080-21080/name.free.fragmentdemo D/BBBBB: onStop
11-06 20:35:17.248 21080-21080/name.free.fragmentdemo D/AAAAA: onDestroyView
11-06 20:35:17.248 21080-21080/name.free.fragmentdemo D/AAAAA: onDestroy
11-06 20:35:17.248 21080-21080/name.free.fragmentdemo D/BBBBB: onDestroyView
11-06 20:35:17.248 21080-21080/name.free.fragmentdemo D/BBBBB: onDestroy
11-06 20:40:50.849 28011-28011/name.free.fragmentdemo D/AAAAA: onPause
11-06 20:40:50.849 28011-28011/name.free.fragmentdemo D/AAAAA: onStop
11-06 20:40:50.849 28011-28011/name.free.fragmentdemo D/AAAAA: onDestroyView
11-06 20:40:50.850 28011-28011/name.free.fragmentdemo D/BBBBB: onStart
11-06 20:40:50.850 28011-28011/name.free.fragmentdemo D/BBBBB: onCreateView
11-06 20:40:50.852 28011-28011/name.free.fragmentdemo D/BBBBB: onViewCreated
11-06 20:40:50.852 28011-28011/name.free.fragmentdemo D/BBBBB: onResume
11-06 20:40:50.991 28011-28011/name.free.fragmentdemo D/AAAAA: onDestroy
可以看出,FragmentA在FragmentB启动前进入到onDestroyView(),在FragmentB进入onResume()后,FragmentA进入到onDestroy(),完全销毁。
这个时候,按下一次Back键,FragmentB进入销毁阶段,之后完全退出了该Activity。
与FrameLayout→add()→FragmentA→replace()→FragmentB相同。
与FrameLayout→add()→FragmentA→add()→FragmentB相同。
实验室1不同的地方是,第二阶段还是第三阶段,按back键时,会先执行出栈操作,销毁FragmentA。
11-13 19:42:38.840 28278-28278/name.free.fragmentdemo D/AAAAA: onPause
11-13 19:42:38.840 28278-28278/name.free.fragmentdemo D/AAAAA: onStop
11-13 19:42:38.840 28278-28278/name.free.fragmentdemo D/AAAAA: onDestroyView
11-13 19:42:39.009 28278-28278/name.free.fragmentdemo D/AAAAA: onDestroy
与实验室1不同的地方是,replace()执行后,FragmentA没有执行onDestory(),因为FragmentA在栈中被保存。
11-13 20:24:13.757 11013-11013/name.free.fragmentdemo D/AAAAA: onStart
11-13 20:24:13.757 11013-11013/name.free.fragmentdemo D/AAAAA: onCreateView
11-13 20:24:13.766 11013-11013/name.free.fragmentdemo D/AAAAA: onViewCreated
11-13 20:24:13.766 11013-11013/name.free.fragmentdemo D/AAAAA: onResume
11-13 20:24:17.477 11013-11013/name.free.fragmentdemo D/AAAAA: onPause
11-13 20:24:17.477 11013-11013/name.free.fragmentdemo D/AAAAA: onStop
11-13 20:24:17.477 11013-11013/name.free.fragmentdemo D/AAAAA: onDestroyView
11-13 20:24:17.479 11013-11013/name.free.fragmentdemo D/BBBBB: onStart
11-13 20:24:17.479 11013-11013/name.free.fragmentdemo D/BBBBB: onCreateView
11-13 20:24:17.481 11013-11013/name.free.fragmentdemo D/BBBBB: onViewCreated
11-13 20:24:17.481 11013-11013/name.free.fragmentdemo D/BBBBB: onResume
当执行back键后,如同实验5,FragmentA出栈,执行了onDestroy()函数。
第1阶段和第2阶段与实验1相同,不同指出是FragmentA与FragmentB先后入栈。
执行完毕后按Back键,FragmentB先出栈,同时被销毁。
再按Back键,FragmentA出栈被销毁,同时退出Activity。
这里FragmentA、FragmentB先后入栈,FragmentA执行到onDestoryView阶段。
按Back键后,FragmentB会先出栈,并执行到onDestoryView阶段,FragmentA会从onViewCreated阶段恢复到onResume(),然后FragmentB会执行onDestroy()。
再按Back键,FragmentA会出栈,然后执行到onDestory。整个过程如下:
11-13 20:37:58.568 20671-20671/name.free.fragmentdemo D/AAAAA: onStart
11-13 20:37:58.569 20671-20671/name.free.fragmentdemo D/AAAAA: onCreateView
11-13 20:37:58.571 20671-20671/name.free.fragmentdemo D/AAAAA: onViewCreated
11-13 20:37:58.572 20671-20671/name.free.fragmentdemo D/AAAAA: onResume
11-13 20:38:00.428 20671-20671/name.free.fragmentdemo D/AAAAA: onPause
11-13 20:38:00.428 20671-20671/name.free.fragmentdemo D/AAAAA: onStop
11-13 20:38:00.428 20671-20671/name.free.fragmentdemo D/AAAAA: onDestroyView
11-13 20:38:00.429 20671-20671/name.free.fragmentdemo D/BBBBB: onStart
11-13 20:38:00.429 20671-20671/name.free.fragmentdemo D/BBBBB: onCreateView
11-13 20:38:00.434 20671-20671/name.free.fragmentdemo D/BBBBB: onViewCreated
11-13 20:38:00.434 20671-20671/name.free.fragmentdemo D/BBBBB: onResume
11-13 20:38:15.976 20671-20671/name.free.fragmentdemo D/BBBBB: onPause
11-13 20:38:15.976 20671-20671/name.free.fragmentdemo D/BBBBB: onStop
11-13 20:38:15.976 20671-20671/name.free.fragmentdemo D/BBBBB: onDestroyView
11-13 20:38:15.977 20671-20671/name.free.fragmentdemo D/AAAAA: onCreateView
11-13 20:38:15.981 20671-20671/name.free.fragmentdemo D/AAAAA: onViewCreated
11-13 20:38:15.981 20671-20671/name.free.fragmentdemo D/AAAAA: onResume
11-13 20:38:16.160 20671-20671/name.free.fragmentdemo D/BBBBB: onDestroy
11-13 20:38:39.930 20671-20671/name.free.fragmentdemo D/AAAAA: onPause
11-13 20:38:39.930 20671-20671/name.free.fragmentdemo D/AAAAA: onStop
11-13 20:38:39.930 20671-20671/name.free.fragmentdemo D/AAAAA: onDestroyView
11-13 20:38:40.083 20671-20671/name.free.fragmentdemo D/AAAAA: onDestroy
/**
* Take care of popping the fragment back stack or finishing the activity
* as appropriate.
*/
@Override
public void onBackPressed() {
if (!mFragments.getSupportFragmentManager().popBackStackImmediate()) {
super.onBackPressed();
}
}
在非兼容模式下,是Activity:
/**
* 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;
}
if (!mFragments.getFragmentManager().popBackStackImmediate()) {
finishAfterTransition();
}
}
可见,按下Back键等同于执行了一次Fragment出栈操作。这里就引出了出栈的几个方法:
/**
* Pop the top state off the back stack. This function is asynchronous -- it
* enqueues the request to pop, but the action will not be performed until the
* application returns to its event loop.
*/
public abstract void popBackStack();
/**
* Like {@link #popBackStack()}, but performs the operation immediately
* inside of the call. This is like calling {@link #executePendingTransactions()}
* afterwards.
* @return Returns true if there was something popped, else false.
*/
public abstract boolean popBackStackImmediate();
/**
* Pop the last fragment transition from the manager's fragment
* back stack. If there is nothing to pop, false is returned.
* This function is asynchronous -- it enqueues the
* request to pop, but the action will not be performed until the application
* returns to its event loop.
*
* @param name If non-null, this is the name of a previous back state
* to look for; if found, all states up to that state will be popped. The
* {@link #POP_BACK_STACK_INCLUSIVE} flag can be used to control whether
* the named state itself is popped. If null, only the top state is popped.
* @param flags Either 0 or {@link #POP_BACK_STACK_INCLUSIVE}.
*/
public abstract void popBackStack(String name, int flags);
/**
* Like {@link #popBackStack(String, int)}, but performs the operation immediately
* inside of the call. This is like calling {@link #executePendingTransactions()}
* afterwards.
* @return Returns true if there was something popped, else false.
*/
public abstract boolean popBackStackImmediate(String name, int flags);
/**
* Pop all back stack states up to the one with the given identifier.
* This function is asynchronous -- it enqueues the
* request to pop, but the action will not be performed until the application
* returns to its event loop.
*
* @param id Identifier of the stated to be popped. If no identifier exists,
* false is returned.
* The identifier is the number returned by
* {@link FragmentTransaction#commit() FragmentTransaction.commit()}. The
* {@link #POP_BACK_STACK_INCLUSIVE} flag can be used to control whether
* the named state itself is popped.
* @param flags Either 0 or {@link #POP_BACK_STACK_INCLUSIVE}.
*/
public abstract void popBackStack(int id, int flags);
/**
* Like {@link #popBackStack(int, int)}, but performs the operation immediately
* inside of the call. This is like calling {@link #executePendingTransactions()}
* afterwards.
* @return Returns true if there was something popped, else false.
*/
public abstract boolean popBackStackImmediate(int id, int flags);
其中popBackStack()是异步方法,只是把出栈这个动作加到了消息队列的顶部;而popBackStackImmediate()是同步方法,立即执行出栈动作。
在实验8中,FragmentA的视图会销毁,进入回退栈。第一次按Back键,FragmentB出栈并被销毁,而后FragmentA重建视图,并进入到onResume()阶段。这种情况下,没有调用onSaveInstanceState(Bundle outState)函数,那么FragmentA如何保存临时数据呢?
更常见的场景是:Fragment中有一个后台进程,当横竖屏切换时,Activityhe和其中的Fragment会销毁重建,后台进程不容易在横竖屏切换时不受影响。
这里引出无UI的Fragment的用法。无UI的Fragment主要用于保存后台数据和进程,需要为Fragment设置一个属性:
/**
* Control whether a fragment instance is retained across Activity
* re-creation (such as from a configuration change). This can only
* be used with fragments not in the back stack. If set, the fragment
* lifecycle will be slightly different when an activity is recreated:
*
* - {@link #onDestroy()} will not be called (but {@link #onDetach()} still
* will be, because the fragment is being detached from its current activity).
*
- {@link #onCreate(Bundle)} will not be called since the fragment
* is not being re-created.
*
- {@link #onAttach(Activity)} and {@link #onActivityCreated(Bundle)} will
* still be called.
*
*/
public void setRetainInstance(boolean retain) {
mRetainInstance = retain;
}
如果设置为true,则当Activity重建时该Fragment并不会销毁。这里以一个实例来讲解,该实例是Google官方提供的,(地址:FragmentRetainInstance.java)。
public class FragmentRetainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FragmentManager fragmentManager=getFragmentManager();
if (savedInstanceState == null) {
fragmentManager.beginTransaction().add(android.R.id.content, new UiFragment()).commit();
}
}
public static class UiFragment extends Fragment implements TaskFragment.TaskCallbacks{
RetainedFragment mWorkFragment;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_retain_instance, container, false);
// Watch for button clicks.
Button button = (Button)v.findViewById(R.id.restart);
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
mWorkFragment.restart();
}
});
return v;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
FragmentManager fm = getFragmentManager();
mWorkFragment = (RetainedFragment)fm.findFragmentByTag("work");
// If not retained (or first time running), we need to create it.
if (mWorkFragment == null) {
mWorkFragment = new RetainedFragment();
// Tell it who it is working with.
//设置TargetFragment!
mWorkFragment.setTargetFragment(this, 0);
fm.beginTransaction().add(mWorkFragment, "work").commit();
}
TaskFragment taskFragment=(TaskFragment)fm.findFragmentByTag("work2");
if (taskFragment==null){
taskFragment=new TaskFragment();
taskFragment.setTargetFragment(this,1);
fm.beginTransaction().add(taskFragment,"work2").commit();
}
}
@Override
public void onPostExecute() {
Toast.makeText(getActivity(),"From TaskFragment!",Toast.LENGTH_SHORT).show();
}
}
public static class RetainedFragment extends Fragment {
ProgressBar mProgressBar;
int mPosition;
boolean mReady;
boolean mQuiting;
final Thread mThread = new Thread() {
@Override
public void run() {
int max = 10000;
while (true) {
// Update our shared state with the UI.
synchronized (this) {
// Our thread is stopped if the UI is not ready
// or it has completed its work.
while (!mReady || mPosition >= max) {
if (mQuiting) {
return;
}
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// Now update the progress. Note it is important that
// we touch the progress bar with the lock held, so it
// doesn't disappear on us.
mPosition++;
max = mProgressBar.getMax();
mProgressBar.setProgress(mPosition);
}
// Normally we would be doing some work, but put a kludge
// here to pretend like we are.
synchronized (this) {
try {
wait(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);//关键语句
mThread.start();
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mProgressBar = (ProgressBar)getTargetFragment().getView().findViewById(
R.id.progress_horizontal);
// We are ready for our thread to go.
synchronized (mThread) {
mReady = true;
mThread.notify();
}
}
@Override
public void onDestroy() {
// Make the thread go away.
synchronized (mThread) {
mReady = false;
mQuiting = true;
mThread.notify();
}
super.onDestroy();
}
@Override
public void onDetach() {
// This fragment is being detached from its activity. We need
// to make sure its thread is not going to touch any activity
// state after returning from this function.
synchronized (mThread) {
mProgressBar = null;
mReady = false;
mThread.notify();
}
super.onDetach();
}
public void restart() {
synchronized (mThread) {
mPosition = 0;
mThread.notify();
}
}
}
}
该实例是在UiFragment中添加了一个无UI的RetainedFragment,该RetainedFragment中开启了一个耗时的线程,并设置setRetainInstance(true),表示不随着Activity、UiFragment的重建而销毁。这样,当UiFragment重建时,就能保持ProgressBar的进度。
最后总结下,使用无UI的Fragment的情况如下:
在API超过17之后,Fragment可以作为其他Fragment的容器。举个例子:
/**
* Created by mi on 16-11-13.
* 使用getChildFragmentManager()
*/
public class ChildFragmentActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FragmentManager fragmentManager=getFragmentManager();
if (savedInstanceState == null) {
fragmentManager.beginTransaction().add(android.R.id.content, new ContainerFragment()).commit();
}
}
public static class ContainerFragment extends Fragment{
private FragmentManager childFragmentManager;
private static final String TAG="Child";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
childFragmentManager=getChildFragmentManager();//注意这里
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.layout_container_activity, container, false);
Button button= (Button) v.findViewById(R.id.change);
button.setText("Child Fragment");
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
FragmentTransaction fragmentTransaction = childFragmentManager.beginTransaction();
fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
Fragment childFragment=childFragmentManager.findFragmentByTag(TAG);
if (childFragment==null){
childFragment=new ChildFragment();
Bundle bundle = new Bundle();
bundle.putString("Child", "Child");
childFragment.setArguments(bundle );
fragmentTransaction.add(R.id.container, childFragment,TAG);
}
fragmentTransaction.commit();
}
});
return v;
}
}
public static class ChildFragment extends Fragment{
private String tag;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle bundle=getArguments();
tag=bundle.getString(ContainerFragment.TAG);
Log.d(tag,"onStart");
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.layout_fragment, container, false);
Log.d(tag,"onViewCreated");
TextView textView=(TextView)v.findViewById(R.id.text);
textView.setText(tag);
return v;
}
}
}
如果Fragment作为容器时,Activity可以不需要布局文件,而是直接替换android.R.id.content,它是Activity的根布局。
fragmentManager.beginTransaction().add(android.R.id.content, new ContainerFragment()).commit();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
//transaction.setCustomAnimations可以设置自定义动画。
fragmentManager.addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
@Override
public void onBackStackChanged() {
Log.d(TAG, "onBackStackChanged()");
}
});
/**
* Second fragment with a menu.
*/
public static class Menu2Fragment extends Fragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);//注意这里
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
menu.add("Menu 2").setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// TODO Auto-generated method stub
Toast.makeText(getActivity(), "Menu ", Toast.LENGTH_SHORT).show();
return super.onOptionsItemSelected(item);
}
}
如果觉得写得不错,可以扫描我的微信二维码请我喝咖啡哦~
或者点击 打赏地址 请我喝杯茶~