使用FragmentManager對Fragment的生命周期影響

来源:http://www.cnblogs.com/BobGo/archive/2017/09/09/7497286.html
-Advertisement-
Play Games

正常在Activity中使用Fragment的生命周期,第一次啟動過程是onAtach()-onCreate()-onCreateView()-onViewCreated()-onActivityCreated()-onStart()-onResume();隨著Activity被退棧銷毀,Fragm ...


  正常在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的相關切換方式有些相同,可參考。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 有時候會需要用到字元的ASCII碼,一時之間調試時可能會忘記字元與ASCII碼對應的數字。 最近喜歡用瀏覽器控制台直接跑JS代碼,將這個代碼直接貼到瀏覽器控制台,即可調試(谷歌瀏覽器快捷鍵 ctrl+shift+j) function GetAsciiCode(){ var str = prompt ...
  • 1 1 /* 2 CSS重置 3 * */ 4 5 body, 6 ul, 7 ol { 8 margin: 0px; 9 padding: 0px; 10 } 11 12 #flash { 13 width: 600px; 14 height: 300px; 15 margin: 100px;..... ...
  • 接著上文,重新在webpack文件夾下麵新建一個項目文件夾demo2,然後用npm init --yes初始化項目的package.json配置文件,然後安裝webpack( npm install [email protected] --save-dev ),然後創建基本的項目文件夾結構,好了,我們的又一 ...
  • webpack,我想大家應該都知道或者聽過,Webpack是前端一個工具,可以讓各個模塊進行載入,預處理,再進行打包。現代的前端開發很多環境都依賴webpack構建,比如vue官方就推薦使用webpack.廢話不多說,我們趕緊開始吧. 第一步、安裝webpack 新建文件夾webpack->再在we ...
  • 背景 之間在一篇介紹過 Table 組件《 React 實現一個漂亮的 Table 》 的文章中講到過,在企業級後臺產品中,用的最多且複雜的組件主要包括 Table、Form、Chart,在處理 Table 的時候我們遇到了很多問題。今天我們這篇文章主要是分享一下 Form 組件,在業務開發中, 相 ...
  • async await 解決非同步問題,這兩個關鍵字是es7提出的,所以測試,node和瀏覽器版本提高一些 async await 操作基於promise實現的 async await這兩個關鍵字是一起使用,分開使用會報錯 await 後面只能跟promise對象 不熟悉的promise非同步操作的朋友 ...
  • 今年6月份開始,我開始負責對“得到app”的android代碼進行組件化拆分,在動手之前我查閱了很多組件化或者模塊化的文章,雖然有一些收穫,但是很少有文章能夠給出一個整體且有效的方案,大部分文章都只停留在組件單獨調試的層面上,涉及組件之間的交互就很少了,更不用說組件生命周期、集成調試和代碼邊界這些最 ...
  • 本文提出的組件化方案demo已經開源,參見文章 "Android徹底組件化方案開源" 。 文末有羅輯思維“得到app”的招聘廣告,歡迎各路牛人加入!! 一、模塊化、組件化與插件化 項目發展到一定程度,隨著人員的增多,代碼越來越臃腫,這時候就必須進行模塊化的拆分。在我看來,模塊化是一種指導理念,其核心 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...