使用 Life-Aware 元件處理生命週期   Android Jetpack 的一部分。

生命週期感知元件會根據其他元件的生命週期狀態變化 (例如活動和片段),採取因應的動作。這些元件可協助您產生更井然有序且通常更精簡的程式碼,維護起來更輕鬆。

常見的模式為在活動和片段的生命週期方法中導入依附元件的操作。然而,此模式會造成程式碼的組織欠佳及出錯。透過使用生命週期感知元件,您可以將依附元件的程式碼移出生命週期方法,然後將其移入元件本身。

androidx.lifecycle 套件提供類別和介面,可讓您建構「生命週期感知」元件,這是可以根據活動或片段的目前生命週期狀態來自動調整其行為的元件。

在 Android 架構中定義的大多數應用程式元件均附有生命週期資訊。生命週期是由作業系統或程序中執行的架構程式碼所管理。這些是 Android 運作的核心,您的應用程式也必須遵循這些核心。否則可能會觸發記憶體流失,甚至導致應用程式當機。

假設我們有一個在螢幕上顯示裝置位置的活動。常見的導入方式如下:

Kotlin

internal class MyLocationListener(
        private val context: Context,
        private val callback: (Location) -> Unit
) {

    fun start() {
        // connect to system location service
    }

    fun stop() {
        // disconnect from system location service
    }
}

class MyActivity : AppCompatActivity() {
    private lateinit var myLocationListener: MyLocationListener

    override fun onCreate(...) {
        myLocationListener = MyLocationListener(this) { location ->
            // update UI
        }
    }

    public override fun onStart() {
        super.onStart()
        myLocationListener.start()
        // manage other components that need to respond
        // to the activity lifecycle
    }

    public override fun onStop() {
        super.onStop()
        myLocationListener.stop()
        // manage other components that need to respond
        // to the activity lifecycle
    }
}

Java

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()) 中放置大量程式碼,因此難以維護。

此外,也不保證元件會在活動或片段停止前啟動。如果我們需要執行長期執行的作業 (例如 onStart() 中的某些設定檢查),則更是如此。這可能會導致在 onStart() 之前 onStop() 方法完成的競爭狀況,並讓元件的使用時間超出所需的時間。

Kotlin

class MyActivity : AppCompatActivity() {
    private lateinit var myLocationListener: MyLocationListener

    override fun onCreate(...) {
        myLocationListener = MyLocationListener(this) { location ->
            // update UI
        }
    }

    public override fun onStart() {
        super.onStart()
        Util.checkUserStatus { result ->
            // what if this callback is invoked AFTER activity is stopped?
            if (result) {
                myLocationListener.start()
            }
        }
    }

    public override fun onStop() {
        super.onStop()
        myLocationListener.stop()
    }

}

Java

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();
    }
}

androidx.lifecycle 套件提供類別和介面,可協助您以彈性且限定的方式解決這些問題。

生命週期

Lifecycle 類別保存元件的生命週期狀態相關資訊 (例如活動或片段),並允許其他物件來觀測此狀態。

Lifecycle 使用兩個主要列舉來追蹤相關元件的生命週期狀態:

事件
從架構和 Lifecycle 類別分派的生命週期事件。這些事件與活動和片段中的回呼事件對應。
狀態
Lifecycle 物件追蹤的元件目前狀態。
生命週期狀態流程圖
圖 1.構成 Android 活動生命週期的狀態和事件

請將狀態視為圖表的節點,並請將事件視為節點間的邊緣效應。

類別可以導入 DefaultLifecycleObserver 並覆寫對應的方法 (例如 onCreateonStart 等),藉此監控元件的生命週期狀態。然後如要新增觀測器,請呼叫 Lifecycle 類別的 addObserver() 方法,並傳送觀測器的執行個體,如以下範例所示:

Kotlin

class MyObserver : DefaultLifecycleObserver {
    override fun onResume(owner: LifecycleOwner) {
        connect()
    }

    override fun onPause(owner: LifecycleOwner) {
        disconnect()
    }
}

myLifecycleOwner.getLifecycle().addObserver(MyObserver())

Java

public class MyObserver implements DefaultLifecycleObserver {
    @Override
    public void onResume(LifecycleOwner owner) {
        connect()
    }

    @Override
    public void onPause(LifecycleOwner owner) {
        disconnect()
    }
}

myLifecycleOwner.getLifecycle().addObserver(new MyObserver());

在上述範例中,myLifecycleOwner 物件會導入 LifecycleOwner 介面,詳情請見下一節。

LifecycleOwner

LifecycleOwner 是單一方法介面,表示類別具有 Lifecycle。有一種必須由類別導入的 getLifecycle() 方法。如果您嘗試管理整個應用程式程序的生命週期,請參閱 ProcessLifecycleOwner

此介面可提取個別類別 (例如 FragmentAppCompatActivity) 的 Lifecycle 擁有權,並允許編寫與其運作的元件。任何自訂應用程式類別都可以導入 LifecycleOwner 介面。

導入 DefaultLifecycleObserver 的元件可與導入 LifecycleOwner 的元件完美搭配運作,因為擁有者可以提供方便觀測器註冊觀看的生命週期。

以位置追蹤範例來說,我們可以將 MyLocationListener 類別導入 DefaultLifecycleObserver,然後再使用活動的 Lifecycle onCreate()方法將其初始化。此做法可讓 MyLocationListener 類別發揮自給自足的能力,這表示為了因應生命週期狀態的變化而回應的邏輯是在 MyLocationListener 中而非在活動中宣告。將個別元件儲存在其專屬邏輯中,就能更輕鬆地管理活動和片段邏輯。

Kotlin

class MyActivity : AppCompatActivity() {
    private lateinit var myLocationListener: MyLocationListener

    override fun onCreate(...) {
        myLocationListener = MyLocationListener(this, lifecycle) { location ->
            // update UI
        }
        Util.checkUserStatus { result ->
            if (result) {
                myLocationListener.enable()
            }
        }
    }
}

Java

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 目前未處於良好狀態,請勿叫用特定回呼。例如,如果回呼在儲存活動狀態後執行片段交易,就會觸發當機,因此我們不想叫用該回呼。

為了簡化這個範例,Lifecycle 類別可以允許其他物件查詢目前狀態。

Kotlin

internal class MyLocationListener(
        private val context: Context,
        private val lifecycle: Lifecycle,
        private val callback: (Location) -> Unit
): DefaultLifecycleObserver {

    private var enabled = false

    override fun onStart(owner: LifecycleOwner) {
        if (enabled) {
            // connect
        }
    }

    fun enable() {
        enabled = true
        if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
            // connect if not connected
        }
    }

    override fun onStop(owner: LifecycleOwner) {
        // disconnect if connected
    }
}

Java

class MyLocationListener implements DefaultLifecycleObserver {
    private boolean enabled = false;
    public MyLocationListener(Context context, Lifecycle lifecycle, Callback callback) {
       ...
    }

    @Override
    public void onStart(LifecycleOwner owner) {
        if (enabled) {
           // connect
        }
    }

    public void enable() {
        enabled = true;
        if (lifecycle.getCurrentState().isAtLeast(STARTED)) {
            // connect if not connected
        }
    }

    @Override
    public void onStop(LifecycleOwner owner) {
        // disconnect if connected
    }
}

透過這項導入,我們的 LocationListener 類別為完全地生命週期感知。如果我們需要從其他活動或片段使用 LocationListener,只需要將其初始化即可。所有設定和卸除作業都是由類別自行管理。

如果程式庫提供的類別需要與 Android 生命週期搭配運作,建議您使用生命週期感知元件。程式庫用戶端可讓您輕鬆整合這些元件,而無須在用戶端手動管理生命週期。

導入自訂 LifecycleOwner

支援資料庫 26.1.0 以上版本的片段和活動已導入 LifecycleOwner 介面。

如果想要建立 LifecycleOwner 的自訂類別,可以使用 LifecycleRegistry 類別,但您必須將事件轉寄至該類別,如下列程式碼範例所示:

Kotlin

class MyActivity : Activity(), LifecycleOwner {

    private lateinit var lifecycleRegistry: LifecycleRegistry

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        lifecycleRegistry = LifecycleRegistry(this)
        lifecycleRegistry.markState(Lifecycle.State.CREATED)
    }

    public override fun onStart() {
        super.onStart()
        lifecycleRegistry.markState(Lifecycle.State.STARTED)
    }

    override fun getLifecycle(): Lifecycle {
        return lifecycleRegistry
    }
}

Java

public class MyActivity extends Activity implements LifecycleOwner {
    private LifecycleRegistry lifecycleRegistry;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        lifecycleRegistry = new LifecycleRegistry(this);
        lifecycleRegistry.markState(Lifecycle.State.CREATED);
    }

    @Override
    public void onStart() {
        super.onStart();
        lifecycleRegistry.markState(Lifecycle.State.STARTED);
    }

    @NonNull
    @Override
    public Lifecycle getLifecycle() {
        return lifecycleRegistry;
    }
}

生命週期感知元件的最佳做法

  • 請盡量保持使用者介面控制器 (活動與片段) 精簡。他們不應試圖取得自己的資料;請改用 ViewModel 進行監控,並觀測 LiveData 物件以將變更反映回檢視畫面。
  • 嘗試編寫以數據為準的使用者介面,讓使用者介面控制器負責在資料變更時更新檢視畫面,或是將使用者動作傳回至 ViewModel
  • 請將資料邏輯放入 ViewModel 類別。ViewModel 應做為使用者介面控制器和其他應用程式之間的連接器。不過,ViewModel 不負責擷取資料 (例如從網路擷取)。反而,ViewModel 應呼叫適當的元件以擷取資料,然後將結果傳回使用者介面控制器。
  • 使用「資料繫結」來維護檢視畫面和使用者介面控制器之間乾淨的介面。如此一來,您的檢視畫面就能宣告該陳述式,並盡量減少在活動和片段中必須寫入的更新程式碼。如果您偏好使用 Java 程式設計語言執行此動作,請使用 Butter Knife 等程式庫,避免使用樣板程式碼,並讓抽象程序獲得更佳成效。
  • 如果您的 UI 較為複雜,請考慮建立 presenter 類別來處理 UI 的修改作業。這項工作可能十分困難,但可使 UI 元件更易於測試。
  • 避免在 ViewModel 中參照 ViewActivity 結構定義。如果 ViewModel 超越於活動外 (設定變更時),活動就會外洩,且垃圾收集器未妥善處理。
  • 使用 Kotlin 協同程式 管理長時間執行的工作,以及其他可能非同步執行的作業。

生命週期感知元件的用途

生命週期感知元件能讓您在各種情況下更輕鬆地管理生命週期。下列為幾個範例:

  • 在粗略和精確位置更新間切換。使用生命週期感知元件在位置應用程式顯示時啟用精確的位置更新,並在應用程式於背景運作時切換至粗略的更新。LiveData 是生命週期感知元件,可讓應用程式在使用者變更位置時自動更新使用者介面。
  • 停止及啟動影片緩衝處理。使用生命週期感知元件盡快啟動影片緩衝,但延遲撥放直到應用程式完全啟動。您也可以使用生命週期感知元件在應用程式遭到刪除時終止緩衝作業。
  • 啟動及停止網路連線。使用生命週期感知元件在應用程式於前景運作時啟用網路資料的最新即時資訊 (串流),以及在應用程式於背景運作時自動暫停。
  • 暫停及恢復動畫可繪項目。使用生命週期感知元件,即可在應用程式位於背景時暫停動畫可繪項目,並在應用程式位於前景時恢復可繪項目。

處理停止事件

Lifecycle 屬於 AppCompatActivityFragment 時,Lifecycle 的狀態會變更為 CREATED,且會在呼叫 AppCompatActivityFragmentonSaveInstanceState() 時調度 ON_STOP 事件。

透過 onSaveInstanceState() 儲存 FragmentAppCompatActivity 的狀態時,在呼叫 ON_START 前將其使用者介面視為不可變動。如果嘗試在儲存狀態後修改使用者介面,可能會導致應用程式的導覽狀態不一致,這就是應用程序在儲存狀態後運行 FragmentTransaction 時,FragmentManager會引發異常的原因。詳情請參閱 commit()

若觀測器的相關 Lifecycle 不是最低可接受的 STARTED,則 LiveData 透過嚴禁呼叫其觀測器來避免方塊外的極端案例。幕後作業會先呼叫 isAtLeast(),然後再決定叫用其觀測器。

很抱歉,在 onSaveInstanceState()「後」呼叫 AppCompatActivityonStop() 方法,雖然無法改變使用者介面的狀態,但 Lifecycle 尚未移至 CREATED 狀態。

如要避免這個問題,beta2 版本的 Lifecycle 類別並將狀態標示為 CREATED 但不傳送事件,如此一來,檢查目前狀態的所有程式碼都會取得實際值,即使是在系統呼叫 onStop() 之前也不會分派事件。

但這項解決方案有下列兩個主要問題:

  • 在 API 級別 23 和更低層級時,Android 系統實際上只會儲存活動狀態,即使該活動「部分」涵蓋其他活動。換句話說,Android 系統會呼叫 onSaveInstanceState(),但不一定需呼叫 onStop()。此建立潛在的長時間間隔,即使無法修改使用者介面狀態,觀測器仍會認為生命週期已啟用。
  • 任何想要向 LiveData 類別公開類似行為的類別,都必須導入 Lifecycle beta 2 以下版本提供的解決方法。

其他資源

如要進一步瞭解如何透過生命週期感知元件處理生命週期,請參考下列其他資源。

範例

程式碼研究室

網誌