Android應用UI架構

這個標題聽起來可能有點大,其實這裏主要就是討論一個應用程序的UI組件,是全用Activity還是全用Fragment,或者是二者皆有,以及使用Activity和Fragment的一些注意事項。 


Activity or Fragment

從API 11,Honeycomb開始,Google在Android之中加入了Fragment,一個輕量級的,可複用的,模塊化的UI組件,並且官方也極力的推薦要儘可能的使用Fragment實現UI,儘量避免使用Activity,但是官方並沒有詳盡的解析爲什麼要這樣做,因此,引發了很多人的困惑和網絡上的爭論

一些討論

StackOverflow上面有很多類似的討論,比如這個這個,以及這篇博客這篇

總體的來講,也都是推薦使用Fragment,並儘可能的使用Fragment,除非你必須使用Activity,否則就使用Fragment。

主要的區別

要想真正的理解什麼時候該用Activity什麼時候該用Fragment,就要從根本上理解它們之間到底有什麼區別,以及各自所能做的事情:

Activity的優缺點

Activity是Android系統的四大核心組件之一,是唯一用戶可見的組件,是可視化應用程序的基礎,同時也是應用程序的主要入口。 它的優點是:

  • 方便使用,簡單,沒有版本限制
  • 可以處理系統相關的事情,比如窗體管理,對話框管理,與第三方應用交互,作爲應用的入口,處理系統事件等
  • 生命週期簡單明瞭
  • 不同的Activity實例的生命週期相互獨立
  • 有系統的ActivityManager來管理,創建和維護實例,以及棧

Activity的缺點:

  • 無法完全的控制,創建和各種狀態全部由Frameworks操控
  • 過於龐大和複雜
  • 傳遞參數相當費勁
  • launchMode和棧的管理過於複雜,讓人捉摸不透

所以,因爲它有缺點,所以它適合做爲第一級組件,而不適合到處都用。也因此有了Fragment的誕生。

Fragment的優缺點

Fragment號稱是輕量級的Activity,它有着Activity的部分功能,比如有生命週期,可以顯示UI元素,可以有控制邏輯,它有很多優點:

  • 可以像普通對象那樣自由的創建和控制
  • 因爲可以得到實例,所以傳遞參數等會更加的容易和方便
  • 不用處理系統相關的事情
  • 顯示方式可控制,替換,還是層疊,部分還是整體,都容易控制
  • 進出動畫都相對容易

可以看出Fragment是實現可複用的,模塊化UI的良好組件,熟悉iOS開發的人應該知道,這個Fragment跟iOS中的UIViewController是很相似的,都是MVC模式中的負責協調和控制的Controller。

實用的建議

那麼,到此,我想我們應該能夠總結出,到底什麼時候該用Activity,什麼時候該用Fragment了:

  • 一個應用程序必須至少要有一個Activity
  • 如果你需要開放入口給第三方應用,那麼要用Activity。比如原生應用中的聯繫人,信息,相機,圖庫等,因爲要留有接口給第三方使用,所以要用Activity。這也是所謂的組件級複用。
  • 如果要處理系統相關的事情,需要用Activity,比如橫豎屏,語言變化,鍵盤等
  • 如果非要在橫屏或豎屏顯示,也要用Activity
  • 如果感覺用Fragment來做有很多的不方便,或者很困難,出於各種原因吧。

除此外,就使用Fragment吧。因此,對於一個應用程序的UI架構就是一個Host的Activity+Fragment的方式來做。

使用Activity時的注意事項

Activity是學習Android開發的第一堂課,我相信有過Android開發經驗的人對Activity必定非常的熟悉了,對於使用Activity要注意:

  • 除非特別需要,否則要聲明configChagnes,至少orientation和keyboard以及keyboardHidden需要加上
  • 減小對Activity實例的引用,儘可能用getApplication()或者getApplicationContext()來做爲平臺接口需要的Context參數
  • 在AndroidManifest.xml的activity標籤中聲明全屏,透明或者沒有TitleBar的屬性,而不是在onCreate()中去requestWindowFeature。除非你需要動態控制
  • Activity之間傳遞參數用Intent,儘量傳基本數據類型和數據及ArrayList,和平臺提供的數據類型如Uri。對於簡單的POD(Plain Old Datastructure)數據類型,可以直接由基本數據組成,不必聲明對象。對於有必要存在的對象,可以實現Parcelable接口,以方便在Activity之間傳遞。

使用Fragment時的注意事項

Fragment是由FragmentManager來管理的,每一個Activity有一個FragmentManager,管理着一個Fragment的棧,所以,Activity是系統級別的,由系統來管理ActivityManager,棧也是系統範圍的。而Fragment則是每個Activity範圍內的。

  • 同一個Activity中,只能有一個ID或TAG標識的Fragment實例。

    這很容易理解,同一個範圍內,有標識的實例肯定是要唯一才行(否則還要標識幹嘛)這個在佈局中經常犯錯,在佈局中寫Fragment最好不要加ID或者TAG,否則很容易出現不允許創建的錯誤。我的原則是如果放在佈局中,就不要加ID和TAG;如果需要ID和TAG就全用代碼控制。創建新實例前先到FragmentManager中查找一番,這也正是有標識的意義所在。

  • 一個Activity中有一個Fragment池,實例不一定會被銷燬,可能會保存在池中。這個跟第一點差不多。就好比系統會緩存Activity的實例一樣,FragmentManager也會緩存Fragment實例,以方便和加速再次顯示。

  • 如前所述,FragmentManager的作用範圍是整個Activity,所以,某一個佈局ID,不能重複被Fragment替換。

    通常顯示Fragment有二種方式,一種是層疊到某個佈局上,或者把某個佈局上面的Fragment替換掉,但是這個佈局不能出現二次,比如佈局A中有ID爲id的區域,要顯示爲Fragment,此佈局A,只能在一個Activity中顯示一個,否則第二個id區域不能被Fragment成功替換。因爲雖有二個ID佈局的實例,但ID是相同的,對FragmentManager來說是一樣的,它會認爲只有一個,因爲它看的是佈局的ID,而不是佈局的實例。

  • Fragment的生命週期反應Activity的生命週期。

    Fragment在顯示和退出時會走一遍完整的生命週期。此外,正在顯示時,就跟Activity的一樣,Activity被onPause,裏面的Fragment就onPause,以此類推,由此帶來的問題就是,比如你在onStart()裏面做了一些事情,那麼,當宿主Activity被擋住,又出現時(比如接了個電話),Fragment的onStart也會被高到,所以你要想到,這些生命週期不單單在顯示和退出時會走到。

  • Fragment的對用戶可見性。

    這個問題出現在有Fragment棧的時候,也就是說每個Fragment不知道自己是否真的對用戶可見。比如現在是Fragment A,又在其上面顯示了Fragment B,當B顯示後,A並不知道自己上面還有一個,也不知道自己對用戶不可見了,同樣再有一個C,B也不知。C退出後,B依然不知自己已在棧頂,對用戶可見,B退後,A也不知。也就是說Fragment顯示或者退出,棧裏的其他Fragment無法感知。這點就不如Activity,a被b蓋住後,a會走到onStop(),同樣c顯示後,b也能通過onStop()感知。Fragment可以從FragmentManager監聽BackStackState的變化,但它只告訴你Stack變了,不告訴你是多了,還是少,還有你處的位置。有一個解決方案就是,記錄頁面的Path深度,再跟Fragment所在的Stack深度來比較,如果一致,那麼這個Fragment就在棧頂。因爲每個頁面的Path深度是固定的,而Stack深度是不變化的,所以這個能準確的判斷Fragment是否對用戶可見,當然,這個僅針對整個頁面有效,對於佈局中的一個區域是無效的。

  • Fragment的事件傳遞。

    對於層疊的Fragment,其實就相當於在一個FrameLayout裏面加上一堆的View,所以,如果處於頂層的Fragment沒處理點擊事件,那麼事件就會向下層傳遞,直到事件被處理。比如有二個Fragment A和B,B在A上面,B只有一個簡單的TextView且沒處理事件,那麼點擊B時,會發現A裏的View處理了事件。這個對於Activity也不會發生,因爲事件不能跨窗體傳播,上面的Activity沒處理事件,也不會傳給下面的Activity,即使它可見。解決之法,就是讓上面的Fragment的根佈局吃掉事件,爲每個根ViewGroup添加onClick=“true”。

  • 與第三方Activity交互。與第三方交互,仍要採用Android的標準startActivityForResult()和onActivityResult()這二個方法來進行。但對於Fragment有些事情需要注意,Fragment也有這二個方法,但是爲了能正確的讓Fragment收到onActivityResult(),需要:

    1. 宿主Activity要實現一個空的onActivityResult(),裏面調用super.onActivityResult()
    2. 調用Fragment#startActivityForResult()而不是用Activity的 當然,也可以直接使用Activity的startActivityForResult(),那樣的話,就只能在宿主Activity裏處理返回的結果了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章