1. 程式人生 > >Android Jetpack之Lifecycles使用

Android Jetpack之Lifecycles使用

Lifecycle-aware相關的元件能做出一些操作來響應其他構件1 中的狀態變化,比如Activity或者FragmentLifecycle-aware相關的元件能夠幫助我們獲得更好的程式碼結構,更少的程式碼,使程式碼更易維護。

一種常見的方式(構件狀態監聽的方式)是在ActivityFragment的生命週期方法中實現依賴的操作。但是,這種方式導致不良的程式碼結構以及錯誤的增加。通過使用Lifecycle-aware相關的元件,可以將依賴狀態的程式碼從生命週期的方法中移除。

android.arch.lifecycle包提供了類和介面,讓我們可以構建根據ActivityFragment

的當前生命週期狀態自動調整其行為的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(),這使得程式碼難以維護。

此外,無法保證元件在ActivityFragment停止之前啟動。如果我們需要執行長時間執行的操作則尤其如此,例如在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

    此生命週期的事件是通過框架和Lifecycle類來分發的。這些事件與ActivityFragment中的事件回撥相對應。

  • State

    Lifecycle物件跟蹤的構件的當前的狀態。 Event和State轉換

一個類可以通過在方法上添加註解來監聽構件的宣告週期的狀態。然後通過呼叫LifecycleaddObserver()方法傳遞觀察者的物件。如下:

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

此介面從各個類(如FragmentAppCompatActivity)中抽象出生命週期的所有者,並允許編寫與其一起使用的構件。任何自定義的應用的類都可以實現LifecycleOwner介面。

實現LifecycleObserver的構件與實現LifecycleOwner的構件可以無縫協作,因為所有者提供生命週期,觀察者可以註冊觀察。(解耦)。

對於位置跟蹤的例子,我們可以讓MyLocationListener類實現LifecycleObserver並且在activity的onCreate()的方法中初始化它。這允許MyLocationListener類自給自足,這意味著響應生命週期狀態變化的邏輯在MyLocationListener而不是activity中宣告。各個構件儲存自己的邏輯使得ActivityFragment的邏輯更易於管理。

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的版本開始,FragmentActivity均實現了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控制器之間的簡潔的介面。這可以使檢視更具宣告性,並最大限度地減少在ActivityFragment中所需的更新程式碼。如果使用Java程式語言執行此操作,使用Butter Knife之類的庫來避免樣板程式碼並且具有更好的抽象。

  • 如果UI很複雜,請考慮建立一個 presenter類來處理UI修改。這可能是一項艱鉅的任務,但它可以使您的UI元件更容易測試。

  • 避免在ViewModel中引用ViewActivity上下文。如果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屬於AppCompatActivityFragment3,在呼叫AppCompatActivityFragmentonSaveInstanceState()時,Lifecycle的狀態將更改為CREATED,並且會派發ON_STOP事件。(為什麼要這麼做,下面給出瞭解釋。)

通過onSaveInstanceState()儲存FragmentAppCompatActivity的狀態時,在呼叫ON_START事件之前,UI認為是不可變的。儲存狀態後嘗試修改UI可能會導致應用程式的導航狀態不一致。這就是為什麼應用程式在儲存狀態後執行FragmentTransaction,則FragmentManager會丟擲異常的原因。

如果觀察者關聯Lifecycle 不是處於 STARTED或者之後的狀態,LiveData會通過禁止呼叫其觀察者來防止這種邊緣情況。此情況時,在決定呼叫其觀察者之前可以呼叫isAtLeast()方法。

不幸的是,在onSaveInstanceState()之後才會呼叫AppCompatActivityonStop()方法,在不允許UI狀態更改(onSaveInstanceState()之後)但是Lifecycle 尚未移到CREATED狀態(AppCompatActivityonStop()方法尚未呼叫)的存在間隙。

為了防止這個問題,版本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例子中看到時如何使用它們的。

  1. 我們通常說的元件為庫,這裡翻譯成構件是指某個類,與元件不同。 ↩︎

  2. 長週期的物件持有短週期的物件,會導致短週期的物件洩露。 ↩︎

  3. AppCompatActivity和Fragment屬於support庫中的類。 ↩︎