通過Lifecycle-Aware 元件處理生命週期[翻譯]
引入概念
-
Lifecycle解決的問題:
- 用於響應、管理其他應用元件(如
Activity
和Fragment
)的改變狀態,相對於我們自己寫事件監聽回撥介面,Lifecycle
會更加簡潔、易於管理。 - 大部分應用元件都存在於
Android Framework
,生命週期繫結在此之上,並且直接由系統或者由應用程序框架管理,因此必須遵循它們的規則,避免記憶體洩露和應用崩潰。
- 用於響應、管理其他應用元件(如
-
實際場景:我們需要在
Activity
中顯示裝置的位置,通常會這樣實現:
class MyLocationListener { public MyLocationListener(Context context, Callback callback) { // ... } void start() { // connect to system location service } void stop() { // disconnect from system location service } } class MyActivity extends AppCompatActivity { private MyLocationListener myLocationListener; @Override public void onCreate(...) { myLocationListener = new MyLocationListener(this, (location) -> { // update UI }); } @Override public void onStart() { super.onStart(); myLocationListener.start(); // manage other components that need to respond // to the activity lifecycle } @Override public void onStop() { super.onStop(); myLocationListener.stop(); // manage other components that need to respond // to the activity lifecycle } }
- 貌似看起來很不錯,但是在實際應用中,最終會存在太多用於管理其他元件生命週期狀態的呼叫,管理多個元件時會在生命週期方法中放置大量程式碼,例如
onStart()
和onStop()
,這使得它們難以維護。 - 此外,無法保證元件在
Activity
或Fragment
停止之前啟動,這在我們需要執行耗時長的操作時尤為真實,比如我們在onStart()
中檢查某些配置,這就可能在當onStop()
在onStart()
之間完成的情況下 產生競爭條件,最終導致元件存活的時間比實際需要長。如下示例:
class MyActivity extends AppCompatActivity { private MyLocationListener myLocationListener; public void onCreate(...) { myLocationListener = new MyLocationListener(this, location -> { // update UI }); } @Override public void onStart() { super.onStart(); Util.checkUserStatus(result -> { // what if this callback is invoked AFTER activity is stopped? if (result) { myLocationListener.start(); } }); } @Override public void onStop() { super.onStop(); myLocationListener.stop(); } }
- 針對以上問題,
android.arch.lifecycle
包提供了可彈性、解耦地解決這些問題的類和介面。
Lifecycle
的概念
-
lifecycle
是一個持有元件生命週期(Activity
,Fragment
)狀態的類,並且允許其他物件觀察這一狀態。 -
lifecycle
使用兩個主要列舉類來處理與之繫結的元件的生命週期狀態。- Event : 這個事件由系統框架和
Lifecyle
類分發,並且會對映到Activit
和Fragment
的回撥事件上。 - State : 代表當前
LIfecycle
物件正在處理的元件的狀態。
思考`States`節點以及`Events`事件
- Event : 這個事件由系統框架和
-
通過給方法添加註解的方式可以使這個類具備監聽元件生命週期的能力,然後通過
Lifecycle#addObserver()
新增觀察者即可賦予其他物件這個觀察能力,如下示例:// 作為觀察者,我們需要實現 LifecycleObserver 介面 public class MyObserver implements LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) // 在onResume時執行 public void connectListener() { ... } @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) // 在onPause時執行 public void disconnectListener() { ... } } // 新增一個觀察者,使得這個觀察者也可以監聽元件狀態變化 myLifecycleOwner.getLifecycle().addObserver(new MyObserver());
LifeOwner
的概念
-
LifeOwner
只包含一個getLifecycle()
方法,用於獲取Lifecycle
,使用時必須實現這個方法。 如果想要管理整個應用程序的生命週期,可以使用ProcessLifecycleOwner
代替 。 -
這個介面從
Activity
和Fragment
等中抽取了Lifecycle
的所有權,並且允許編寫元件來與之配合, 任何自定義的應用類都可以實現LifecOwner
介面 。 -
實現了
LifecycleOwner
的元件與實現了LifecycleObserver
的元件運作方式是無縫銜接的的, 因為Owner
用於提供事件,而Observer
用於註冊、監聽事件 。
-
在前面我們定義了一個實現了
LifecycleObserver
介面的MyLocationLIstener
類,我們可以如下面程式碼這樣在onCreate
中初始化,這意味響應生命週期變化的邏輯都提取到了MyLocationLIstener
,而不是全部擠在Activity
中,可見這樣可以極大簡化Activity
和Fragment
的程式碼邏輯。class MyActivity extends AppCompatActivity { private MyLocationListener myLocationListener; public void onCreate(...) { myLocationListener = new MyLocationListener(this, getLifecycle(), location -> { // update UI }); Util.checkUserStatus(result -> { if (result) { myLocationListener.enable(); } }); } }
- 為了避免在
Lifecycle
的不合適狀態下執行回撥,比如如果這個回撥用於在Activity
儲存狀態後執行Fragment
的轉場切換,就會觸發崩潰,因此我們千萬不要執行這個回撥。為了簡單處理這個問題,Lifecycle
允許其他物件檢視當前狀態。
class MyLocationListener implements LifecycleObserver { private boolean enabled = false; public MyLocationListener(Context context, Lifecycle lifecycle, Callback callback) { ... } @OnLifecycleEvent(Lifecycle.Event.ON_START) void start() { if (enabled) { // connect } } public void enable() { enabled = true; // 檢視Lifecycle的當前狀態 if (lifecycle.getCurrentState().isAtLeast(STARTED)) { // connect if not connected } } @OnLifecycleEvent(Lifecycle.Event.ON_STOP) void stop() { // disconnect if connected } }
- 通過上面實現,我們的
MyLocationListener
就完全可以管理生命週期了,如果想要在其他Activity
或Fragment
中使用它,那麼只需要初始化一下就行了,其他處理操作都會在它內部處理。 - 如果一個類庫提供需要結合
Android
生命週期的處理類,那麼建議使用Lifecycle-aware
元件,這樣的話類庫客戶端就可以輕易地整合這些元件而不需要手動地在客戶端處理生命週期管理工作。
- 為了避免在
- 實現自定義的 LifecycleOwner
- 在
Support Library 26.1.0
以及上版本中,Fragment
和Activity
已經實現了LifecycleOwner
介面。 - 如果需要自定義實現一個
LifecycleOwner
,那麼可以使用LifecycleRegistry
類,但是你需要傳送事件到LifecycleRegistry
類中,如下示例:
public class MyActivity extends Activity implements LifecycleOwner { private LifecycleRegistry mLifecycleRegistry; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mLifecycleRegistry = new LifecycleRegistry(this); mLifecycleRegistry.markState(Lifecycle.State.CREATED); } @Override public void onStart() { super.onStart(); mLifecycleRegistry.markState(Lifecycle.State.STARTED); } @NonNull @Override public Lifecycle getLifecycle() { return mLifecycleRegistry; } }
- 在
lifecycle-aware 元件最佳實踐
-
儘可能保證
UI
控制器,如Activity
和Fragment
的簡潔性 ,它們不應該請求它們自身的資料,而應交給ViewModel
去做,並觀察一個LiveData
物件,用來將變化返回給UI
檢視。 -
儘量編寫資料驅動型(
data-driven
)UI
,這種形式下,UI
控制器只需要負責在資料改變時更新檢視,或者通知使用者動作給ViewModel
-
將資料邏輯放到
ViewModel
類,ViewModel
應當用作UI
控制器和應用其他部分的聯結器 ,但是注意,ViewModel
不負責請求資料(比如網路請求等),相反,它只是呼叫資料請求模組去請求資料,然後將資料結果返回給UI
控制器。 -
使用
DataBinding
來維持檢視與UI
控制器間的簡潔性 。它可以可簡化檢視的宣告和檢視更新時所需在UI
控制器中編寫的程式碼, 如果喜歡使用Java
,那麼建議使用類似於ButterKnife
之類的類庫來避免編寫無聊的宣告程式碼 ,並且它可以實現更好的抽象。 -
如果
UI
很複雜,可以考慮建立一個Presenter
類來處理UI
更改操作 ,這可能很費事,但可以使UI
元件更易於測試。 -
禁止在
ViewModel
中引用View
或者Activity
上下文(context
) ,否則如果ViewModel
生命週期比Activity
長時(比如configuration change
的情況),Activity
就會記憶體洩露而不被GC
了。
lifecycle-aware 元件使用場景
lifecycle-aware
元件可以在各種場景中讓生命週期的管理更簡單,比如以下場景:
- 粗略定位(
coarse-grained
)與高精度定位(fine-grained
)之間的更新狀態切換 。使用lifecycle-aware
元件在應用處於前臺時開啟高精度定位,而在後臺時開啟粗略定位,可以結合LiveData
來實現狀態改變時更新UI
的操作。 - 開啟和關閉視訊緩衝。 比如使用
lifecycle-aware
元件儘快開啟視訊緩衝,而延遲到應用完全啟動後才真正播放視訊,同樣也可以在應用關閉時終止緩衝動作。 - 開啟和關閉網路連線。 使用
lifecycle-aware
元件進行動態更新網路資料,如應用處於前臺時自動載入資料,而應用切換至後臺時自動暫停載入。 - 啟動和暫停
Drawable
動畫。 前臺時播放動畫,後臺是暫停動畫。
處理 onStop 事件
當 Lifecycle
關聯到 AppCompatActivity
或 Fragment
時,它的狀態會切換到 CREATED
,而 ON_STOP
狀態則是會在 AppCompatActivity
或 Fragment
的 onSaveInstanceState()
被呼叫是觸發。
如果 AppCompatActivity
或 Fragment
是通過 onSaveInstanceState()
中儲存狀態的,那麼在 ON_START
被呼叫之前,它們的 UI
狀態都會被認定為不可變的( immutable
)。這時如果嘗試在 UI
狀態儲存後修改 UI
的話,就會導致應用導航狀態不一致,這也就是為什麼在狀態儲存後執行 FragmentTransaction
, FragmentManager
會拋異常的原因了,具體看 commit()方法 。
如果 LiveData
的已經關聯到 Lifecycle
的 Observer
還沒到到達 STARTED
狀態的話, LiveData
可以通過終止 observer
的呼叫來避免上述邊角情況的發生,這是因為 LiveData
會在執行 Observer
之前先呼叫 isAtLeast()
確定狀態,然後再決定是否執行。
然而不幸的是, AppCompatActivity
的 onStop()
方法實在 onSaveInstanceState()
之後呼叫的,這種情況就導致已經儲存的 UI
狀態不允許改變,而 Lifecycle
又還沒有到達 STARTED
狀態。
為了避免這個問題的發生,在版本 beta2
及之前的 Lifecycle
類都會將這一狀態標記為 CREATED
,而不分發這一事件,這樣,任何檢查當前狀態的程式碼都能拿到真實狀態值,即使這一事件還沒有被分發,直到系統呼叫 onStop()
方法。
然而又不幸的是,這個解決方案有兩大問題:
- 在
API 23
及之前的版本,Android
系統確實會儲存Activity
的狀態,即使是由其他AActivity
轉換的部分,也就是說,系統呼叫onSaveInstanceState()
,但是確實沒有呼叫onStop()
必要。這造成了一個潛在的長間隔期,而在這個間隔期之間,observer
一直會認為Lifecycle
是活動的,即使UI
狀態已經不能被改變了。 - 任何想要暴露給
LiveData
類似行為的類都必須實現Lifecycle
在beta2
及之前版本所提供的解決方案。
Note: 為了簡化流程併兼容老版本, 請直接從版本 1.0.0-rc1
開始使用 , Lifecycle
物件會被標記為 CREATED
,並且會在 onSaveInstanceState()
被呼叫是標記為 ON_STOP
狀態,而無需等待 onStop()
的呼叫。雖然這並不會影響我們的程式碼,但是確是我們需要注意的,因為它沒有遵循 API 26
及以前版本中 Activity
的生命週期呼叫次序