使用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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...