Android Jetpack之Lifecycles使用
Lifecycle-aware
相關的元件能做出一些操作來響應其他構件1 中的狀態變化,比如Activity
或者Fragment
。Lifecycle-aware
相關的元件能夠幫助我們獲得更好的程式碼結構,更少的程式碼,使程式碼更易維護。
一種常見的方式(構件狀態監聽的方式)是在Activity
和Fragment
的生命週期方法中實現依賴的操作。但是,這種方式導致不良的程式碼結構以及錯誤的增加。通過使用Lifecycle-aware
相關的元件,可以將依賴狀態的程式碼從生命週期的方法中移除。
android.arch.lifecycle
包提供了類和介面,讓我們可以構建根據Activity
或Fragment
Lifecycle-aware
的程式碼。
Android框架中定義的大多數應用程式元件都附加了生命週期。生命週期由作業系統或執行在程序中的框架程式碼管理。它們是Android工作原理的核心,我們的應用程式必須遵守Android的工作原理。否則可能會觸發記憶體洩漏甚至應用程式崩潰。
假如我們有一個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
}
}
即使這個例子看起來很好。但在真實的應用程式中,會有太多UI的管理和其他構件響應當前狀態的生命週期的呼叫。管理多個構件會在生命週期方法中放置大量程式碼,例如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
主要使用兩個列舉來跟蹤其關聯的構件的生命週期狀態。
一個類可以通過在方法上添加註解來監聽構件的宣告週期的狀態。然後通過呼叫Lifecycle
的 addObserver()
方法傳遞觀察者的物件。如下:
public class MyObserver implements LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
public void connectListener() {
...
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
public void disconnectListener() {
...
}
}
myLifecycleOwner.getLifecycle().addObserver(new MyObserver());
LifecycleOwner
此介面從各個類(如Fragment
和AppCompatActivity
)中抽象出生命週期的所有者,並允許編寫與其一起使用的構件。任何自定義的應用的類都可以實現LifecycleOwner
介面。
實現LifecycleObserver
的構件與實現LifecycleOwner
的構件可以無縫協作,因為所有者提供生命週期,觀察者可以註冊觀察。(解耦)。
對於位置跟蹤的例子,我們可以讓MyLocationListener
類實現LifecycleObserver
並且在activity的onCreate()
的方法中初始化它。這允許MyLocationListener
類自給自足,這意味著響應生命週期狀態變化的邏輯在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;
if (lifecycle.getCurrentState().isAtLeast(STARTED)) {
// connect if not connected
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
void stop() {
// disconnect if connected
}
}
通過此實現,我們的LocationListener
類完全可以識別生命週期。如果我們想要在其他的Activity
或者Fragment
中使用LocationListener
類,僅僅需要初始化就可以了。所有設定和拆卸操作都由類本身管理。
如果庫提供了需要使用Android生命週期的類,建議使用生命週期Lifecycle-aware
的元件。庫客戶端可以輕鬆地整合這些元件,而無需在客戶端進行手動生命週期的管理。
實現自定義LifecycleOwner
從support庫26.1.0的版本開始,Fragment
和Activity
均實現了LifecycleOwner
介面。
如果有自定義類,想要實現LifecycleOwner
,可以使用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)儘可能精簡,UI控制器不應該獲取資料,使用
ViewModel
去獲取資料,並且觀察LiveData
物件將變更對映到View上。 -
盡力去寫資料驅動的UI,UI控制器的責任是當資料變更時更新View,或者使用者操作通知
ViewModel
。 -
把資料邏輯放在
ViewModel
類中,ViewModel
應該充當UI控制器和應用程式其餘部分之間的聯結器。但需要注意,ViewModel
責任不是獲取資料(例如從網路獲取資料)。相反,ViewModel
應呼叫適當的構件來獲取資料,然後將結果提供給UI控制器。 -
使用Data Binding來維護檢視和UI控制器之間的簡潔的介面。這可以使檢視更具宣告性,並最大限度地減少在
Activity
和Fragment
中所需的更新程式碼。如果使用Java程式語言執行此操作,使用Butter Knife
之類的庫來避免樣板程式碼並且具有更好的抽象。 -
如果UI很複雜,請考慮建立一個 presenter類來處理UI修改。這可能是一項艱鉅的任務,但它可以使您的UI元件更容易測試。
-
避免在
ViewModel
中引用View
或Activity
上下文。如果ViewModel
週期大於Activity
週期2(在配置更改的情況下),Activity
將洩漏並且垃圾收集器不能回收此Activity
。
使用Lifecycle-aware元件的場景
Lifecycle-aware
元件在各種情況下都可以更輕鬆地管理生命週期。比如:
- 在粗粒度和細粒度位置更新之間切換。使用
Lifecycle-aware
元件在您的位置應用程式可見時啟用細粒度位置更新,並在應用程式位於後臺時切換到粗粒度更新。LiveData
一個Lifecycle-aware
元件,應用在使用者位置變更時自動更新UI。 - 停止和開始視訊緩衝,使用
Lifecycle-aware
元件儘可能地啟動視訊緩衝,但推遲播放直到應用程式完全啟動。還可以使用Lifecycle-aware
元件在銷燬應用程式時終止緩衝。 - 啟動和停止網路連線。使用
Lifecycle-aware
元件在應用程式處於前臺時啟用網路資料的實時更新(流式傳輸),並在應用程式進入後臺時自動暫停。 - 暫停和恢復動畫drawables。當應用程式在後臺時,使用
Lifecycle-aware
元件動畫可繪製的內容,並在應用程式位於前臺後恢復可繪製的內容。
處理stop事件
當Lifecycle
屬於AppCompatActivity
或Fragment
時3,在呼叫AppCompatActivity
或Fragment
的onSaveInstanceState()
時,Lifecycle
的狀態將更改為CREATED
,並且會派發ON_STOP
事件。(為什麼要這麼做,下面給出瞭解釋。)
通過onSaveInstanceState()
儲存Fragment
或AppCompatActivity
的狀態時,在呼叫ON_START
事件之前,UI認為是不可變的。儲存狀態後嘗試修改UI可能會導致應用程式的導航狀態不一致。這就是為什麼應用程式在儲存狀態後執行FragmentTransaction
,則FragmentManager
會丟擲異常的原因。
如果觀察者關聯Lifecycle
不是處於 STARTED
或者之後的狀態,LiveData
會通過禁止呼叫其觀察者來防止這種邊緣情況。此情況時,在決定呼叫其觀察者之前可以呼叫isAtLeast()
方法。
不幸的是,在onSaveInstanceState()
之後才會呼叫AppCompatActivity
的onStop()
方法,在不允許UI狀態更改(onSaveInstanceState()
之後)但是Lifecycle
尚未移到CREATED
狀態(AppCompatActivity
的onStop()
方法尚未呼叫)的存在間隙。
為了防止這個問題,版本beta2及其更低的版本中,Lifecycle
類將狀態標記為CREATED
,但沒有派發事件,因此即使在系統呼叫onStop()
之前未派發事件,任何檢查當前狀態的程式碼都會獲得實際值。
不幸的是,這個解決方案有兩個主要問題 :
- 在API23及更低版本,Android系統實際上儲存了活動的狀態(呼叫了
onSaveInstanceState()
),即使它被另一個Activity
部分覆蓋。換句話說,Android系統呼叫onSaveInstanceState
但它不一定呼叫onStop()
。這會建立一個潛在的長間隔,即使無法修改其UI狀態,但觀察者仍然認為生命週期處於活躍狀態。 - 任何想要向
LiveData
暴露類似行為的類,都必須提供Lifecycle
版本 beta 2及更低版本提供的解決方法。
Notice : 為了簡化流程並提供與舊版本的更好相容性,從版本1.0.0-rc1開始,當
onSaveInstanceState()
呼叫時,不會等待onStop()
的呼叫,Lifecycle
物件標記為CREATED
,並且通知ON_STOP
。這不太可能影響程式碼,但需要注意這一點,它與API26及更低版本的Activity
類中的呼叫順序不匹配。
其他資源
嘗試使用Lifecycle
元件請看codelab。
Lifecyle-aware
元件是Android Jetpack的一部分,可以在Sunflower例子中看到時如何使用它們的。