使用FragmentManager对Fragment的生命周期影响

  正常在Activity中使用Fragment的生命周期,第一次启动过程是onAtach()-onCreate()-onCreateView()-onViewCreated()-onActivityCreated()-onStart()-onResume();随着Activity被退栈销毁,Fragment的声明周期依次为onPause()-onStop()-onDestroyView()-onDestroy()-onDetach();

  如果在Fragment中使用EventBus等通过反射进行的操作,在Fragment执行完onCreate()之后就会直接调用反射相关方法,由于还没有走onCreateView()等方法创建视图,所以在反射相关方法中如果直接做UI层更新就会出现空指针异常等情况。这个Bug引导我开始关注FragmentManager的原理。

  下面是Bug的使用场景:在一个Activity中创建AFragment和BFragment和一个相对应的两个Button,在点击AButton时显示AFragment,点击BButton是显示BFragment,同时在BFragment中执行相应操作后,使用EventBus发送一个event,之后在Activity和AFragment中分别接收这个event,并执行相应操作使得界面显示到AFragment并对AFragment中的数据进行更新。由于Activity中使用FragmentManager对两个Fragment进行切换,所以在AFragment接收event时有各种问题。原始未修复代码如下:

 1 SecondActivity.class
 2     @Override
 3     protected void onCreate(Bundle savedInstanceState) {
 4         super.onCreate(savedInstanceState);
 5         EventBus.getDefault().register(this);
 6         setContentView(R.layout.activity_second);
 7         but1= (Button) findViewById(R.id.but1);
 8         but2= (Button) findViewById(R.id.but2);
 9         but1.setOnClickListener(this);
10         but2.setOnClickListener(this);
11         aFragment=AFragment.newInstance(null, null);
12         bFragment=BFragment.newInstance(null, null);
13         changeFragment(aFragment);
14 //        addFragmentTransaction(aFragment);
15 //        addFragmentTransaction(bFragment);
16 
17     }
18 
19     @Subscribe(threadMode = ThreadMode.MAIN)
20     public void handlerEvent(FragmentEvent event){
21         Log.i(TAG, "SecondActivity.onMainThread: event="+event);
22         changeFragment(aFragment);
23     }
24 
25     private void changeFragment(Fragment f){
26         changeFragment1(f);
27     }
28 
29     private void changeFragment1(Fragment f){
30         Log.i(TAG, "SecondActivity.changeFragment1: f="+f.getClass().getName());
31         FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
32         transaction.replace(R.id.fl_container, f);
33 //        transaction.addToBackStack(f.getClass().getName());
34         transaction.commit();
35         if(f.getClass().getName().equals(aFragment.getClass().getName())){
36             bFragment.setUserVisibleHint(false);
37             aFragment.setUserVisibleHint(true);
38         }else {
39             bFragment.setUserVisibleHint(true);
40             aFragment.setUserVisibleHint(false);
41         }
42     }
 1 AFragment.class
 2 
 3     private TextView titleTV;
 4     @Override
 5     public void onCreate(Bundle savedInstanceState) {
 6         Log.i(TAG, "AFragment.onCreate: ");
 7         super.onCreate(savedInstanceState);
 8         if (getArguments() != null) {
 9             mParam1 = getArguments().getString(ARG_PARAM1);
10             mParam2 = getArguments().getString(ARG_PARAM2);
11         }
12         EventBus.getDefault().register(this);
13     }
14 
15     @Override
16     public View onCreateView(LayoutInflater inflater, ViewGroup container,
17                              Bundle savedInstanceState) {
18         Log.i(TAG, "AFragment.onCreateView: ");
19         // Inflate the layout for this fragment
20         return inflater.inflate(R.layout.fragment_a, container, false);
21     }
22 
23     @Override
24     public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
25         Log.i(TAG, "AFragment.onViewCreated: ");
26         super.onViewCreated(view, savedInstanceState);
27         titleTV=(TextView)view.findViewById(R.id.tv_fa_title);
28     }
29 
30     @Override
31     public void onDestroy() {
32         Log.i(TAG, "AFragment.onDestroy: ");
33         super.onDestroy();
34         EventBus.getDefault().unregister(this);
35     }
36     @Subscribe(threadMode = ThreadMode.MAIN)
37     public void handlerEvent(FragmentEvent event){
38         Log.i(TAG, "AFragment.handlerFragmentEvent: event="+event);
39         Log.i(TAG, "AFragment.handlerEvent: titleTv="+titleTV);
40         titleTV.setText(event.toString());
41     }
 1 BFragment.class
 2 
 3     private TextView titleTV;
 4     @Override
 5     public View onCreateView(LayoutInflater inflater, ViewGroup container,
 6                              Bundle savedInstanceState) {
 7         // Inflate the layout for this fragment
 8         return inflater.inflate(R.layout.fragment_b, container, false);
 9     }
10 
11     @Override
12     public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
13         super.onViewCreated(view, savedInstanceState);
14         titleTV= (TextView) view.findViewById(R.id.tv_fb_title);
15         titleTV.setOnClickListener(new View.OnClickListener() {
16             @Override
17             public void onClick(View v) {
18                 Log.i(TAG, "BFragment.onClick: postEvent");
19                 EventBus.getDefault().post(new FragmentEvent(23));
20             }
21         });
22     }

在Activity中使用FragmentManager进行切换布局,有两种方式,一是布局替换,即FragmentManager.replace()相关方法,第二种是布局显隐,即FragmentManager.hide()和FragmentManager.show()等相关方法。原始代码中使用replace()直接替换Fragment,这种方式导致每次替换的Fragment都是从onAttach()开始重新执行,所以之前在BFragment中发送event时,当前的AFragment还没有创建,也就不会执行对event的处理操作。这样就有两种解决方案。

 

方案一:使用EventBus的postSticky()方法延缓发送event,而在AFragment中接收event时注解为sticky=true,默认为false。所以更新代码如下

1 AFragment.class
2     @Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
3     public void handlerEvent(FragmentEvent event){
4         Log.i(TAG, "AFragment.handlerFragmentEvent: event="+event);
5         Log.i(TAG, "AFragment.handlerEvent: titleTv="+titleTV);
6         titleTV.setText(event.toString());
7     }
 1 BFragment.class
 2     @Override
 3     public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
 4         super.onViewCreated(view, savedInstanceState);
 5         titleTV= (TextView) view.findViewById(R.id.tv_fb_title);
 6         titleTV.setOnClickListener(new View.OnClickListener() {
 7             @Override
 8             public void onClick(View v) {
 9                 Log.i(TAG, "BFragment.onClick: postEvent");
10                     EventBus.getDefault().postSticky(new FragmentEvent(23));
11             }
12         });
13     }

使用方案一走日志发现在AFragment中的确已经正常接收到了event并对其进行了处理,但是处理event之后又从onCreateView()开始执行更新视图等一系列操作,导致event更新的视图被更新之后的视图所覆盖,处理这个问题的话,只要在handlerEvent中先不着急更新view,而是新创建一个全局event来存储当前的event,在onViewCreated()中判断全局event如果非空就更新数据。方案一最终修改代码如下:

 1 AFragment.class
 2     private FragmentEvent mEvent;
 3     @Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
 4     public void handlerEvent(FragmentEvent event){
 5         Log.i(TAG, "AFragment.handlerFragmentEvent: event="+event);
 6         Log.i(TAG, "AFragment.handlerEvent: titleTv="+titleTV);
 7 //        titleTV.setText(event.toString());
 8         mEvent=event;
 9     }
10     @Override
11     public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
12         Log.i(TAG, "AFragment.onViewCreated: ");
13         super.onViewCreated(view, savedInstanceState);
14         titleTV=(TextView)view.findViewById(R.id.tv_fa_title);
15         if(mEvent!=null){
16             titleTV.setText(mEvent.toString());
17         }
18     }

 

方案二:使用FragmentManager的显隐方法而不是替换方法切换Fragment,使用hide()和show(),必须要先调用add()把Fragment添加到FragmentManager中,而此时切换两个Fragment,并不会重新执行onCreate()--onResume()等一系列流程,只有在Activity中显示调用Fragment的setUserVisibleHint()方法表示当前Fragment的显隐,说明Fragment已经绑定到Activity中,其生命周期只有在Activity的onResume()之中保存。方案二修改后的代码如下:

 1 SecondActivity.class
 2     @Override
 3     protected void onCreate(Bundle savedInstanceState) {
 4         super.onCreate(savedInstanceState);
 5         EventBus.getDefault().register(this);
 6         setContentView(R.layout.activity_second);
 7         but1= (Button) findViewById(R.id.but1);
 8         but2= (Button) findViewById(R.id.but2);
 9         but1.setOnClickListener(this);
10         but2.setOnClickListener(this);
11         aFragment=AFragment.newInstance(null, null);
12         bFragment=BFragment.newInstance(null, null);
13         changeFragment(aFragment);
14         addFragmentTransaction(aFragment);
15         addFragmentTransaction(bFragment);
16 
17     }
18     private void addFragmentTransaction(Fragment fragment) {
19         FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
20         transaction.add(R.id.fl_container, fragment);
21         transaction.commit();
22     }
23     private void changeFragment(Fragment f){
24         changeFragment2(f);
25     }
26 
27     private void changeFragment2(Fragment f){
28         Log.i(TAG, "SecondActivity.changeFragment2: f="+f.getClass().getName());
29         FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
30         if(f.getClass().getName().equals(aFragment.getClass().getName())){
31             transaction.hide(bFragment);
32             transaction.show(aFragment);
33             bFragment.setUserVisibleHint(false);
34             aFragment.setUserVisibleHint(true);
35         }else {
36             transaction.hide(aFragment);
37             transaction.show(bFragment);
38             aFragment.setUserVisibleHint(false);
39             bFragment.setUserVisibleHint(true);
40         }
41         transaction.commit();
42     }

以上两种解决方案各有优缺点,总体来说,如果想每次Fragment显示时都要重新更新View,使用方案一的方式更好,但是方案一如果多次切换,由于之前的Fragment已经与当前Activity解除绑定,所以没有了引用的地方,但是仍然存在内存中,如果系统释放内存不及时,就会有内存泄漏的隐患;同样对于方案二来说,每次切换不会重新更新界面,所以如果想在显隐时做些操作,只能显示调用Fragment的setUserVisibleHint()方法并重写该方法以做操作。说到方案二的这种形式,和ViewPager的相关切换方式有些相同,可参考。

posted on 2017-09-09 15:00  白少木丿  阅读(1744)  评论(0编辑  收藏  举报

导航