Jetpack LiveData 使用與原理解析

一、引言

在 Android 開發中,數據的變化需要及時反映到界面上是一個常見的需求。然而,傳統的方式可能會導致代碼復雜、難以維護,并且容易出現內存泄漏等問題。Jetpack 組件中的 LiveData 為我們提供了一種優雅的解決方案,它是一種可觀察的數據持有者類,具有生命周期感知能力,能夠確保數據更新時只在合適的生命周期狀態下通知觀察者,從而避免了內存泄漏和空指針異常等問題。本文將詳細介紹 LiveData 的使用方法,并深入剖析其源碼原理。

二、LiveData 基本使用

2.1 添加依賴

要使用 LiveData,需要在項目的 build.gradle 文件中添加以下依賴:

implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.1'

2.2 創建 LiveData 對象

LiveData 是一個抽象類,通常使用它的子類 MutableLiveData 來創建可修改的 LiveData 對象。以下是一個簡單的示例:

import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModelclass MyViewModel : ViewModel() {// 創建一個 MutableLiveData 對象,用于存儲整數類型的數據val currentNumber: MutableLiveData<Int> by lazy {MutableLiveData<Int>().also {it.value = 0}}// 增加數字的方法fun incrementNumber() {currentNumber.value = (currentNumber.value ?: 0) + 1}
}

在這個示例中,我們創建了一個 MyViewModel 類,其中包含一個 MutableLiveData 類型的 currentNumber 對象,并提供了一個方法 incrementNumber 來更新該對象的值。

2.3 觀察 LiveData 對象

在 Activity 或 Fragment 中,我們可以通過 observe 方法來觀察 LiveData 對象的變化,并在數據更新時執行相應的操作。以下是在 Activity 中觀察 currentNumber 的示例:

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.lifecycle.ViewModelProvider
import com.example.livedatademo.databinding.ActivityMainBindingclass MainActivity : AppCompatActivity() {private lateinit var binding: ActivityMainBindingprivate lateinit var viewModel: MyViewModeloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)binding = ActivityMainBinding.inflate(layoutInflater)setContentView(binding.root)// 獲取 ViewModel 實例viewModel = ViewModelProvider(this).get(MyViewModel::class.java)// 觀察 LiveData 對象viewModel.currentNumber.observe(this) { newNumber ->// 當數據更新時,更新界面顯示binding.numberTextView.text = newNumber.toString()}// 設置按鈕點擊事件,調用 ViewModel 中的方法更新數據binding.incrementButton.setOnClickListener {viewModel.incrementNumber()}}
}

MainActivity 中,我們通過 observe 方法觀察 currentNumber 的變化,當 currentNumber 的值發生改變時,會觸發 lambda 表達式中的代碼,更新界面上的文本顯示。

2.4 其他使用場景

轉換 LiveData

LiveData 提供了一些轉換方法,如 mapswitchMap,用于對 LiveData 中的數據進行轉換。以下是使用 map 方法的示例:

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import androidx.lifecycle.ViewModelclass MyViewModel : ViewModel() {private val _inputData = MutableLiveData<String>()val outputData: LiveData<String> = Transformations.map(_inputData) { input ->// 對輸入數據進行轉換"Transformed: $input"}fun setInputData(input: String) {_inputData.value = input}
}

在這個示例中,我們使用 Transformations.map 方法將 _inputData 中的數據進行轉換,并將轉換后的結果存儲在 outputData 中。

合并 LiveData

可以使用 MediatorLiveData 來合并多個 LiveData 對象,當其中任何一個 LiveData 對象的數據發生變化時,MediatorLiveData 都會收到通知。以下是一個簡單的示例:

import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModelclass MyViewModel : ViewModel() {private val _source1 = MutableLiveData<String>()private val _source2 = MutableLiveData<String>()val mergedData: MediatorLiveData<String> = MediatorLiveData<String>().apply {// 添加第一個數據源addSource(_source1) { value ->value?.let {this.value = "Source 1: $it"}}// 添加第二個數據源addSource(_source2) { value ->value?.let {this.value = "Source 2: $it"}}}fun setSource1Data(data: String) {_source1.value = data}fun setSource2Data(data: String) {_source2.value = data}
}

在這個示例中,MediatorLiveData 合并了 _source1_source2 兩個 LiveData 對象,當其中任何一個數據源的數據發生變化時,mergedData 都會更新。

三、LiveData 源碼原理解析

3.1 LiveData 核心類結構

LiveData 主要涉及以下幾個核心類:

  • LiveData:抽象類,定義了 LiveData 的基本行為和接口。
  • MutableLiveDataLiveData 的子類,提供了 setValuepostValue 方法用于更新數據。
  • Observer:觀察者接口,用于定義數據更新時的回調方法。
  • LifecycleBoundObserver:實現了 Observer 接口,并且具有生命周期感知能力,確保只在合適的生命周期狀態下通知觀察者。

3.2 數據更新機制

setValue 方法

setValue 方法用于在主線程中更新 LiveData 的值,并通知所有活躍的觀察者。以下是 setValue 方法的源碼:

@MainThread
protected void setValue(T value) {assertMainThread("setValue");mVersion++;mData = value;dispatchingValue(null);
}

setValue 方法中,首先會檢查當前線程是否為主線程,然后更新數據的版本號和數據值,最后調用 dispatchingValue 方法來通知觀察者。

postValue 方法

postValue 方法用于在非主線程中更新 LiveData 的值,它會將更新操作發送到主線程中執行。以下是 postValue 方法的源碼:

protected void postValue(T value) {boolean postTask;synchronized (mDataLock) {postTask = mPendingData == NOT_SET;mPendingData = value;}if (!postTask) {return;}ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}private final Runnable mPostValueRunnable = new Runnable() {@SuppressWarnings("unchecked")@Overridepublic void run() {Object newValue;synchronized (mDataLock) {newValue = mPendingData;mPendingData = NOT_SET;}setValue((T) newValue);}
};

postValue 方法中,會將新的值存儲在 mPendingData 中,并通過 ArchTaskExecutormPostValueRunnable 發送到主線程中執行。在 mPostValueRunnable 中,會調用 setValue 方法來更新數據并通知觀察者。

3.3 觀察者注冊與通知機制

observe 方法

observe 方法用于注冊一個具有生命周期感知能力的觀察者。以下是 observe 方法的源碼:

@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {assertMainThread("observe");if (owner.getLifecycle().getCurrentState() == DESTROYED) {// 如果 LifecycleOwner 已經銷毀,直接返回return;}LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);if (existing != null && !existing.isAttachedTo(owner)) {throw new IllegalArgumentException("Cannot add the same observer"+ " with different lifecycles");}if (existing != null) {return;}owner.getLifecycle().addObserver(wrapper);
}

observe 方法中,首先會檢查當前線程是否為主線程,然后檢查 LifecycleOwner 的狀態。如果 LifecycleOwner 已經銷毀,則直接返回。接著會創建一個 LifecycleBoundObserver 對象,并將其存儲在 mObservers 中。最后,將 LifecycleBoundObserver 注冊到 LifecycleOwner 的生命周期中。

dispatchingValue 方法

dispatchingValue 方法用于通知觀察者數據更新。以下是 dispatchingValue 方法的源碼:

private void dispatchingValue(@Nullable ObserverWrapper initiator) {if (mDispatchingValue) {mDispatchInvalidated = true;return;}mDispatchingValue = true;do {mDispatchInvalidated = false;if (initiator != null) {considerNotify(initiator);initiator = null;} else {for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {considerNotify(iterator.next().getValue());if (mDispatchInvalidated) {break;}}}} while (mDispatchInvalidated);mDispatchingValue = false;
}

dispatchingValue 方法中,會遍歷所有的觀察者,并調用 considerNotify 方法來通知它們數據更新。

considerNotify 方法

considerNotify 方法用于檢查觀察者的生命周期狀態,并在合適的狀態下通知觀察者。以下是 considerNotify 方法的源碼:

private void considerNotify(ObserverWrapper observer) {if (!observer.mActive) {return;}if (!observer.shouldBeActive()) {observer.activeStateChanged(false);return;}if (observer.mLastVersion >= mVersion) {return;}observer.mLastVersion = mVersion;// 調用觀察者的 onChanged 方法通知數據更新observer.mObserver.onChanged((T) mData);
}

considerNotify 方法中,會檢查觀察者的活躍狀態和版本號,如果觀察者不活躍或者版本號已經是最新的,則不會通知觀察者。否則,會調用觀察者的 onChanged 方法通知數據更新。

四、總結

LiveData 是 Jetpack 組件中一個非常實用的工具,它通過生命周期感知能力和數據更新通知機制,為開發者提供了一種簡潔、安全的方式來處理數據和界面的交互。通過 setValuepostValue 方法可以更新數據,通過 observe 方法可以注冊觀察者。在源碼層面,LiveData 通過 LifecycleBoundObserver 實現了生命周期感知,確保只在合適的生命周期狀態下通知觀察者。合理使用 LiveData 可以提高代碼的可維護性和穩定性,避免內存泄漏和空指針異常等問題。在實際開發中,結合 ViewModel 等其他 Jetpack 組件,可以構建出更加高效、健壯的 Android 應用。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/75710.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/75710.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/75710.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Unity2D 五子棋 + Photon聯網雙人對戰

開發環境配置 Unity版本2022.3 創建Photon賬號以及申請Photon中國區服務 官網申請賬號&#xff1a;Multiplayer Game Development Made Easy Photon Engine 中國區服務&#xff1a; 光子引擎photonengine中文站 成都動聯無限科技有限公司(vibrantlink.com) 導入PUN2插件以及…

(UI自動化測試web端)第二篇:元素定位的方法_css定位之屬性選擇器

看代碼里的【find_element_by_css_selector( )】( )里的表達式怎么寫&#xff1f; 文章介紹了第四種寫法屬性選擇器 &#xff0c;你要根據網頁中的實際情況來判斷自己到底要用哪一種方法來進行元素定位。每種方法都要多練習&#xff0c;全都熟了之后你在工作當中使用起來元素定…

預編譯能否 100%防 sql 注入?

&#x1f31f; 什么是 SQL 注入&#xff1f; SQL 注入&#xff08;SQL Injection&#xff09;是指攻擊者利用特殊輸入&#xff0c;讓數據庫執行它本來不應該執行的代碼&#xff0c;從而獲取或篡改數據。 就像在考試的時候偷偷改題目&#xff0c;讓老師改成你想要的內容&#…

第十五章 | Layer2、Rollup 與 ZK 技術實戰解析

&#x1f4da; 第十五章 | Layer2、Rollup 與 ZK 技術實戰解析 ——構建下一代高性能區塊鏈應用&#xff0c;從 Solidity 到 zkSync&#xff01; ? 本章導讀 Layer2 和零知識證明&#xff08;ZK&#xff09;正成為區塊鏈發展的核心方向。 隨著主網 Gas 居高不下、TPS 無法滿…

2025-03-26 學習記錄--C/C++-PTA 6-3 求鏈式表的表長

合抱之木&#xff0c;生于毫末&#xff1b;九層之臺&#xff0c;起于累土&#xff1b;千里之行&#xff0c;始于足下。&#x1f4aa;&#x1f3fb; 一、題目描述 ?? 6-3 求鏈式表的表長 本題要求實現一個函數&#xff0c;求鏈式表的表長。 函數接口定義&#xff1a; &…

【Linux】Linux_Ubuntu與Windows之間的文件傳輸

一、Linux終端命令的復制粘貼 1.打開linux 終端&#xff0c;輸入以下命令&#xff1a;&#xff08;注意&#xff0c;需要聯網&#xff09; 2.命令行下載&#xff1a; sudo apt-get autoremove open-vm-tools 3.命令行安裝&#xff1a; sudo apt-get install open-vm-tools-…

Python Sanic面試題及參考答案

目錄 Sanic 的事件循環機制與 uvloop 的關系 Sanic 的 Request/Response 對象生命周期如何管理?如何訪問請求上下文? 對比 Sanic 與 Flask/Django 的異步處理模型差異 Sanic 的 Blueprint 機制如何實現模塊化路由?如何處理跨藍圖中間件? 如何在 Sanic 中實現 WebSocket…

算法每日一練 (18)

&#x1f4a2;歡迎來到張翊塵的技術站 &#x1f4a5;技術如江河&#xff0c;匯聚眾志成。代碼似星辰&#xff0c;照亮行征程。開源精神長&#xff0c;傳承永不忘。攜手共前行&#xff0c;未來更輝煌&#x1f4a5; 文章目錄 算法每日一練 (18)刪除并獲得點數題目描述解題思路解題…

VsCode啟用右括號自動跳過(自動重寫) - 自錄制gif演示

VsCode啟用右括號自動跳過(自動重寫) - 自錄制gif演示 前言 不知道大家在編程時候的按鍵習慣是怎樣的。輸入完左括號后編輯器一般會自動補全右括號&#xff0c;輸入完左括號的內容后&#xff0c;是按→跳過右括號還是按)跳過右括號呢&#xff1f; for (int i 0; i < a.s…

用Python和Stable Diffusion生成AI動畫:從圖像到視頻的全流程指南

引言 本文將演示如何通過Python代碼實現基于文本提示的AI動畫生成。我們將使用Stable Diffusion生成連貫圖像幀,結合OpenCV合成視頻,最終實現一個可自定義的動畫生成 pipeline。 一、環境準備 1. 依賴安裝 # 安裝核心庫 pip install diffusers transformers torch numpy …

【Git 常用指令速查表】

Git 常用指令速查表 Git 常用指令速查表目錄1. 初始化倉庫2. 提交代碼流程3. 分支管理4. 遠程倉庫操作5. 撤銷操作6. 查看狀態與日志7. 其他實用指令完整操作示例常用場景速查表 Git 常用指令速查表 目錄 初始化倉庫提交代碼流程分支管理遠程倉庫操作撤銷操作查看狀態與日志其…

分布式爬蟲框架Scrapy-Redis實戰指南

引言 在當今數字化的時代背景下&#xff0c;互聯網技術的蓬勃興起極大地改變了旅游酒店業的運營模式與市場格局。作為旅游產業鏈中的關鍵一環&#xff0c;酒店業的興衰與互聯網技術的應用程度緊密相連。分布式爬蟲技術&#xff0c;尤其是基于 Scrapy 框架的 Scrapy-Redis 擴展…

爬蟲:scrapy面試題大全(60個scrapy經典面試題和詳解)

更多內容請見: 爬蟲和逆向教程-專欄介紹和目錄 文章目錄 1. 什么是Scrapy?2. Scrapy 框架的組件及其作用?3. Scrapy的工作流程是什么?(運行機制)4. 如何創建一個Scrapy項目?5. 如何定義一個Spider?6. 如何在Scrapy中提取數據?7. Scrapy中的Item是什么?8. Scrapy中的P…

Leetcode12-整數轉羅馬數字

題目鏈接&#xff1a;12. 整數轉羅馬數字 - 力扣&#xff08;LeetCode&#xff09; 看題目限制輸入1 < num < 3999&#xff0c;就直接用暴力法寫了&#xff0c;還比較簡單 代碼&#xff1a; char* intToRoman(int num) {char *res (char*)malloc(100);int index 0;i…

WebMvcConfigurer 的 addResourceLocations

在 Spring Boot 的 addResourceLocations 方法中&#xff0c;file: 是一個 URL 前綴&#xff0c;用于指示資源的位置是本地文件系統路徑。以下是詳細解釋&#xff1a; 一、file: 的作用 file: 是 Java 中用于表示本地文件系統的 URL 前綴。它告訴 Spring Boot&#xff0c;資源…

Spring Boot響應壓縮配置與優化

一、核心工作機制 1.1 自動協商觸發條件 Spring Boot的響應壓縮功能基于智能協商機制&#xff0c;需同時滿足以下條件方可觸發&#xff1a; 客戶端支持&#xff1a;請求頭包含Accept-Encoding: gzip/deflate數據量閾值&#xff1a;響應體大小超過預設值&#xff08;默認2KB&…

JavaScript 改變 HTML 樣式

JavaScript 改變 HTML 樣式 JavaScript 改變 HTML 樣式的核心是通過操作 DOM 元素的 CSS 屬性或 類名 實現動態視覺效果。以下是具體方法與場景解析: 一、直接修改元素的 style 屬性 通過 DOM 元素的 style 屬性直接設置內聯樣式,優先級最高: // 修改單個樣式 document.…

【vue】vue + vant實現上傳圖片添加水印

目錄 方法1&#xff1a;使用HTML2canvas 說明&#xff1a; 優點 缺點 依賴安裝 方法2&#xff1a;使用canvas結合vant中組件 增加水印方法 在vue組件中使用 要點 方法1&#xff1a;使用HTML2canvas 使用html2canvas來處理水印的生成&#xff0c;需要就給水印元素轉換為…

【深度破解】爬蟲反反爬核心技術實踐:驗證碼識別與指紋偽裝

一、反爬技術體系全景圖 現代Web應用的常見反爬手段&#xff1a; mermaid&#xff1a; graph TDA[反爬體系] --> B[行為特征檢測]A --> C[驗證碼體系]A --> D[指紋追蹤]B --> B1[請求頻率]B --> B2[鼠標軌跡]B --> B3[頁面停留時間]C --> C1[圖形驗證碼…

deepseek(2)——deepseek 關鍵技術

1 Multi-Head Latent Attention (MLA) MLA的核心在于通過低秩聯合壓縮來減少注意力鍵&#xff08;keys&#xff09;和值&#xff08;values&#xff09;在推理過程中的緩存&#xff0c;從而提高推理效率&#xff1a; c t K V W D K V h t c_t^{KV} W^{DKV}h_t ctKV?WDKVht?…