硬核講解 Jetpack 之 LifeCycle 使用篇

秉心說TM發表於2019-12-15

大家好,我是 LifeCycle ,來自 Jetpack 生態鏈的最底端 。

我的作用是感知元件 (Activity/Fragment) 生命週期 ,並在合適的生命週期執行你分配給我的任務。我堅持貫徹 Jetpack 的 Slogan ,Less Code ,less bug ! 用上我,包你線下無崩潰,線上無 Bug,每天準時下班,走上人生巔峰,贏取白富美 ,自動省略 300 字 ......

好像有點跑題,拽回來。

雖然我自嘲來自 Jetpack 生態鏈最底端,但其實我是 Jetpack 大家族中最不可或缺的元件 。左邊看看 LiveData ,右邊看看 ViewModel ,它們露出一副不屑的表情,“沒有你的日子裡,大家不是一樣過得好好的 !”

說的好像有那麼一點道理,我第一次出現還是在 Support 庫年代的 26.1.0 版本。在這之前,大家是如何感知宣告週期的呢?就在這時,一位髮量依舊濃密的程式設計師從黑暗中甩出了他的祖傳程式碼。

public class LocationUtil {

    public void startLocation() {
        ......
    }

    public void stopLocation() {
        ......
    }

}
複製程式碼

然後呢,你得這麼用。

public class LocationActivity extends AppCompatActivity {

    private LocationUtil locationUtil = new LocationUtil();

    @Override
   public void  onResume(){
       locationUtil.startLocation();
       ......
   }

   @Override
   public void onPause(){
       locationUtil.stopLocation();
       .....
   }
}
複製程式碼

這位程式設計師吹噓道,“我的 LocationUtil 久經考驗,完美解決生命週期問題,絕不會記憶體洩漏 !”

我不禁嗤之以鼻,你是單身久了一個人擼程式碼擼習慣了吧 !沒錯,你一個人用是挺好的。但是對於一個現代化大型專案來說,假如有二十個頁面需要使用你的 LocationUtil,這二十個頁面又分配給了五個程式設計師來完成。你能保證所有的生命週期程式碼都如你所願的被新增了嗎?又或者你已經離職了,又沒有留下詳盡的文件,殊不知哪天就得因為莫名其妙的記憶體洩漏被新員工問候。

再來一個極端情況,該死的產品經理(甩鍋)讓你在分別在 onCreate()onDestroy 中開啟和關閉定位,而不是原來的 onResume()onPause() 中了。這一刻,你是不是有種想掏出四十米大刀的感覺。

你這個寫法,嘟嚕嘟嚕一大串程式碼,還這麼脆弱 !

他漲紅了臉,只能無力的辯解,“你行你上,show me your code !”

我早有準備,拿程式碼說話。首先讓 LocationUtil 實現 LifeCycleObserver 介面。

class LocationUtil( ) : LifeCycleObserver {
  @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
  fun startLocation( ){
    ......
  }

  @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
  fun stopLocation( ){
    ......
  }
}
複製程式碼

然後對於任何實現了 LifecycleOwner 介面的生命週期元件,如果需要使用 LocationUtil 的話,只需要新增如下一行程式碼即可。

lifecycle.addObserver(LocationUtil( ))
複製程式碼

我讓 LocationUtil 這種第三方元件在自己內部就可以拿到生命週期,這樣原來需要在生命週期元件 Activity/Fragment 進行的生命週期邏輯處理,在元件內部就可以直接完成,進一步解耦。即使面對上面說到的變態產品經理的需求,你也只需要修改 LocationUtil 的內部實現,外部無需做任何修改。看嘛,很多時候自己覺得別人的需求很不合理,其實還是自己的問題。

四周鴉雀無聲,應該都被我鎮住了。這只是我的最基本用法,下面再來一些進階的,注意,別眨眼。

監聽應用前後臺切換

一些銀行 app 都有這樣的功能,當切換應用到後臺,會給使用者一個提示,"xx 手機銀行正在後臺執行,請注意安全!" 。用我 LifeCycle 來實現的話,異常的簡單。

class KtxAppLifeObserver : LifecycleObserver {

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    fun onForeground() {
        Ktx.app.toast("應用進入前臺")
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    fun onBackground() {
        Ktx.app.toast("應用進入後臺")
    }
}
複製程式碼

然後在應用啟動時,如 ContentProvier,Application 中 ,呼叫如下程式碼:

 ProcessLifecycleOwner.get().lifecycle.addObserver(KtxAppLifeObserver())
複製程式碼

通過程式碼你也發現了是通過我的家族成員 ProcessLifecycleOwner 來實現的。它可以感知整個應用程式的生命週期,這樣一來,監聽應用前後臺切換就輕而易舉了。

全域性管理 Activity

相信你們都做過這樣的功能,特定情況下要 finish 掉開啟過的所有 Activity,或者要關閉指定的 Activity 。通常的做法是在 BaseActivity 的生命週期回撥中儲存已經開啟或關閉的 Activity 。雖然實現也還算優雅,但是如何給日益臃腫的 BaseActivity 減負呢?又到我 LifeCycle 出場了。

class KtxLifeCycleCallBack : Application.ActivityLifecycleCallbacks {

    override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
        KtxManager.pushActivity(activity)
        "onActivityCreated : ${activity.localClassName}".loge()
    }

    override fun onActivityStarted(activity: Activity) {
        "onActivityStarted : ${activity.localClassName}".loge()
    }

    override fun onActivityResumed(activity: Activity) {
        "onActivityResumed : ${activity.localClassName}".loge()
    }

    override fun onActivityPaused(activity: Activity) {
        "onActivityPaused : ${activity.localClassName}".loge()
    }

    override fun onActivityDestroyed(activity: Activity) {
        "onActivityDestroyed : ${activity.localClassName}".loge()shejiyuanze
        KtxManager.popActivity(activity)
    }

    override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle?) {
    }

    override fun onActivityStopped(activity: Activity) {
        "onActivityStopped : ${activity.localClassName}".loge()
    }
}
複製程式碼

通過我的另一個家族成員,Application.ActivityLifecycleCallbacks ,你可以監聽到所有的 Activity 的所有生命週期。在 Application 中註冊這個 Callback 就行了。

application.registerActivityLifecycleCallbacks(KtxLifeCycleCallBack())
複製程式碼

根據 單一職責原則 ,一個類做一件事情,這種寫法將 Activity 的管理職責集中到一個類中,降低程式碼耦合。而原來的寫法有兩個問題,第一,你必須得繼承 BaseActivity 才能具備管理當前 Activity 的功能。這又涉及到合作開發,不想繼承,或者忘記繼承。第二,讓 BaseActivity 承擔了過多的職責,並不符合基本的設計原則。

自動處理生命週期的 Handler

來自 程式亦非猿 的創意。在 onDestroy 方法裡移除 Handler 的訊息, 無需額外手動處理,避免記憶體洩露。

public class LifecycleHandler extends Handler implements LifecycleObserver {

    private LifecycleOwner lifecycleOwner;

    public LifecycleHandler(final LifecycleOwner lifecycleOwner) {
        this.lifecycleOwner = lifecycleOwner;
        addObserver();
    }

    public LifecycleHandler(final Callback callback, final LifecycleOwner lifecycleOwner) {
        super(callback);
        this.lifecycleOwner = lifecycleOwner;
        addObserver();
    }

    public LifecycleHandler(final Looper looper, final LifecycleOwner lifecycleOwner) {
        super(looper);
        this.lifecycleOwner = lifecycleOwner;
        addObserver();
    }

    public LifecycleHandler(final Looper looper, final Callback callback, final LifecycleOwner lifecycleOwner) {
        super(looper, callback);
        this.lifecycleOwner = lifecycleOwner;
        addObserver();
    }

    private void addObserver() {
        notNull(lifecycleOwner);
        lifecycleOwner.getLifecycle().addObserver(this);
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    private void onDestroy() {
        removeCallbacksAndMessages(null);
        lifecycleOwner.getLifecycle().removeObserver(this);
    }
}
複製程式碼

更智慧的事件匯流排

說到事件匯流排框架的發展史,推薦美團技術團隊的一篇文章 Android訊息匯流排的演進之路:用LiveDataBus替代RxBus、EventBus 。無論是 EventBus,還是 RxBus,其實都不具備生命週期感知能力,體現在程式碼上就是需要顯示呼叫反註冊方法。而 LiveDataBus 基於基於 LiveData 實現,LiveData 又通過 LifeCycle 獲取了生命週期感知能力,無需手動反註冊,無記憶體洩露風險。

LiveEventBus
	.get("key_name", String.class)
	.observe(this, new Observer<String>() {
	    @Override
	    public void onChanged(@Nullable String s) {
	    }
	});
複製程式碼

隨時訂閱,自動取消訂閱,就是這麼簡單。

配合協程使用

協程作為 Android 非同步處理方案的新貴,不僅僅是我 LifeCycle ,ViewModel,LIveData 等其他 Jetpack 元件都為其提供了特定的支援。

要使用 LifeCycle 的協程支援,需要新增 androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-alpha01或更高版本。該支援庫為每個 LifeCycle 物件定義了 LifecycleScope 。在此範圍內啟動的協程會在 LifeCycle 被銷燬時自動取消。你可以通過 lifecycle.coroutineScopelifecycleOwner.lifecycleScope 屬性訪問 Lifecycle 的 CoroutineScope。下面是一個簡單的示例:

class MyFragment: Fragment() {
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            viewLifecycleOwner.lifecycleScope.launch {
                val params = TextViewCompat.getTextMetricsParams(textView)
                val precomputedText = withContext(Dispatchers.Default) {
                    PrecomputedTextCompat.create(longTextContent, params)
                }
                TextViewCompat.setPrecomputedText(textView, precomputedText)
            }
        }
    }
複製程式碼

更多用法可以檢視官網相關介紹 將 Kotlin 協程與架構元件一起使用

最後

有了 LifeCycle ,你可以做什麼?

寫更少的程式碼,犯更少的錯 。

生命週期的處理趨於標準化,這也正是 Jetpack 想帶給我們的,讓開發趨於標準化的同時可以犯更少的錯誤,減少崩潰和記憶體洩漏。瀏覽一下你的專案,如果還有未使用 LifeCycle 解決生命週期問題的地方,趕緊替換吧 !

最後推薦一個我的 Jetpack MVVM 開源專案,wanandroid , 更多 bug ,等你發現。

有耐心看到這的讀者,肯定要吐槽了,一點也不 硬核 !這也不能賴我,使用篇,怎麼硬核嘛。

一切盡在 硬核講解 Jetpack 之 LifeCycle 原始碼篇 ,敬請期待 !

文章首發微信公眾號: 秉心說TM , 專注 Java 、 Android 原創知識分享,LeetCode 題解。

更多最新原創文章,掃碼關注我吧!

硬核講解 Jetpack 之 LifeCycle 使用篇

相關文章