kotlin與MVVM結合使用總結(三)

1. MVVM 架構詳細介紹及源碼層面理解

整體架構

MVVM(Model - View - ViewModel)架構是為了解決視圖和數據模型之間的耦合問題而設計的。它通過引入 ViewModel 作為中間層,實現了視圖和數據的分離,提高了代碼的可維護性和可測試性。

  • View(視圖層):在 Android 中,View 主要關聯 Activity、Fragment 以及 XML 布局文件等,負責呈現界面與響應用戶交互。像 Activity 里設置布局、初始化視圖元素,以及處理用戶點擊等操作都屬于 View 范疇。例如在一個登錄界面 Activity 里,布局文件定義了輸入框、按鈕等 UI 元素的樣式與位置,Activity 則處理按鈕點擊事件,這些都在 View 層完成。
  • Model(數據模型層):負責數據的獲取、存儲與處理。比如從網絡請求用戶信息、將數據存儲到本地數據庫等。在電商應用中,從服務器獲取商品列表數據,或是將用戶的購物車信息保存到本地數據庫,都由 Model 層執行。
  • ViewModel(視圖模型層):作為連接 View 與 Model 的紐帶,承擔關鍵的界面顯示邏輯處理任務。它從 Model 獲取數據,并將其轉換為適合 View 展示的形式。例如在新聞應用中,Model 獲取到原始新聞數據列表,ViewModel 可對數據進行加工,如截取新聞摘要、處理圖片鏈接等,讓數據能更好地在 View 中展示。在數據更新時,ViewModel 通過 LiveData 等機制通知 View 進行相應更新。從源碼層面看,ViewModel 借助 ViewModelProvider 來創建與管理。ViewModelProvider 內部運用 ViewModelStore 存儲 ViewModel 實例,確保配置變更(如屏幕旋轉)時,ViewModel 實例不會被銷毀,維持數據的穩定性。
觀察者模式在 MVVM 中的應用

在 MVVM 架構里,觀察者模式發揮著核心作用。ViewModel 持有數據,以 LiveData 為例,View 作為觀察者監聽 LiveData 數據變化。LiveData 內部維護了觀察者列表,當數據變更時,會調用?dispatchingValue?方法遍歷觀察者列表。在?considerNotify?方法中,判斷觀察者狀態,若活躍則通過?observer.mObserver.onChanged((T) mData)?通知觀察者,View 接收到通知后更新界面,實現了 View 與 ViewModel 低耦合通信,這在諸多面試真題里都有涉及,是理解 MVVM 架構的關鍵。ViewModel 是通過?ViewModelProvider?來創建和管理的。

2. LiveData 實例化方法及源碼分析

實例化方法
  • MutableLiveDataMutableLiveData?是?LiveData?的子類,它公開了?setValue()?和?postValue()?方法,允許外部修改其持有的數據。
MutableLiveData<String> liveData = new MutableLiveData<>();

在源碼中,MutableLiveData?只是簡單地繼承了?LiveData?并暴露了修改數據的方法。

public class MutableLiveData<T> extends LiveData<T> {@Overridepublic void postValue(T value) {super.postValue(value);}@Overridepublic void setValue(T value) {super.setValue(value);}
}
  • 使用?Transformations?類進行轉換Transformations?類提供了一些靜態方法,如?map()?和?switchMap(),可以對 LiveData 進行轉換。
LiveData<Integer> source = new MutableLiveData<>();
LiveData<String> transformed = Transformations.map(source, input -> "Transformed: " + input);

map()?方法的源碼實現如下:

public static <X, Y> LiveData<Y> map(LiveData<X> source,final Function<X, Y> mapFunction) {final MediatorLiveData<Y> result = new MediatorLiveData<>();result.addSource(source, new Observer<X>() {@Overridepublic void onChanged(@Nullable X x) {result.setValue(mapFunction.apply(x));}});return result;
}

3. LiveData 如何實現生命周期綁定問題

LiveData 生命周期綁定機制在面試中頻繁被問到,其實現依賴于 Android 的 Lifecycle 組件。

? ? ? ? ?當調用?LiveData.observe()?方法時,會創建?LifecycleBoundObserver?對象,它實現了?LifecycleEventObserver?接口來監聽?LifecycleOwner(如 Activity、Fragment)的生命周期變化。在?observe()?方法源碼中,先檢查?LifecycleOwner?狀態,若已銷毀則直接返回;否則創建?LifecycleBoundObserver?并添加到觀察者列表,同時將其注冊到?LifecycleOwner?的生命周期觀察者中。

public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {assertMainThread("observe");if (owner.getLifecycle().getCurrentState() == DESTROYED) {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);
}

LifecycleBoundObserver?的?onStateChanged?方法會在?LifecycleOwner?生命周期狀態變化時被調用。在此方法中,根據生命周期狀態決定是否更新觀察者。當狀態變為?DESTROYED?時,從 LiveData 的觀察者列表移除該觀察者,防止內存泄漏;當狀態為?STARTED?或?RESUMED?時,認為觀察者活躍,可接收數據更新。

class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {@NonNullfinal LifecycleOwner mOwner;LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) {super(observer);mOwner = owner;}@Overrideboolean shouldBeActive() {return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);}@Overridepublic void onStateChanged(@NonNull LifecycleOwner source,@NonNull Lifecycle.Event event) {if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {removeObserver(mObserver);return;}activeStateChanged(shouldBeActive());}@Overrideboolean isAttachedTo(LifecycleOwner owner) {return mOwner == owner;}@Overridevoid detachObserver() {mOwner.getLifecycle().removeObserver(this);}
}

這種機制確保 LiveData 僅在?LifecycleOwner?處于活躍狀態(STARTED?或?RESUMED)時更新觀察者,有效避免內存泄漏與空指針異常,是 LiveData 的重要特性。

4. LiveData 粘性事件的深入分析

粘性事件的概念

粘性事件是指當一個觀察者注冊到 LiveData 時,即使該 LiveData 在觀察者注冊之前已經有了更新,觀察者仍然會接收到這些之前的更新。這是因為 LiveData 會記錄最新的值,當有新的觀察者注冊時,會立即將最新的值發送給它。

源碼層面分析

在?LiveData?的?observe()?方法中,當新的觀察者注冊時,會調用?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;
}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()?方法中,會比較觀察者的?mLastVersion?和 LiveData 的?mVersion,如果?mLastVersion?小于?mVersion,則會調用觀察者的?onChanged()?方法,將最新的值發送給它。

解決粘性事件的方法

為了避免粘性事件的影響,可以考慮使用一些第三方庫,如?SingleLiveEvent?或自定義?LiveData?實現。以下是一個簡單的自定義?LiveData?實現,用于避免粘性事件:

import androidx.lifecycle.LiveData;import java.util.concurrent.atomic.AtomicBoolean;public class NonStickyLiveData<T> extends LiveData<T> {private final AtomicBoolean mPending = new AtomicBoolean(false);@Overridepublic void observeForever(@NonNull Observer<? super T> observer) {super.observeForever(new Observer<T>() {@Overridepublic void onChanged(T t) {if (mPending.compareAndSet(true, false)) {observer.onChanged(t);}}});}@Overridepublic void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {super.observe(owner, new Observer<T>() {@Overridepublic void onChanged(T t) {if (mPending.compareAndSet(true, false)) {observer.onChanged(t);}}});}@Overrideprotected void setValue(T value) {mPending.set(true);super.setValue(value);}@Overrideprotected void postValue(T value) {mPending.set(true);super.postValue(value);}
}

在這個自定義的?LiveData?中,使用?AtomicBoolean?來標記是否有新的值需要發送,只有當?mPending?為?true?時,才會調用觀察者的?onChanged()?方法,從而避免了粘性事件的影響。

粘性事件總結

? ? ? ? 在 LiveData 機制里,不活躍觀察者(對應?LifecycleOwner?處于?STOPPED?或?PAUSED?狀態)正常情況下不會接收數據更新事件。只有當觀察者再次變為活躍狀態時,LiveData 才會將最新數據發送給它。這是因為在?LifecycleBoundObserver?的?shouldBeActive?方法中,依據?LifecycleOwner?的當前生命周期狀態判斷觀察者是否活躍,不活躍則不進行數據分發。

? ? ? ?然而,LiveData 存在粘性事件問題,這在面試中常被提及。粘性事件指新觀察者注冊時,即便 LiveData 之前已有更新,觀察者仍會收到這些之前的更新數據。從源碼層面分析,在?LiveData?的?observe()?方法中,新觀察者注冊后會調用?dispatchingValue()?方法。在?dispatchingValue()?內部的?considerNotify()?方法里,通過比較觀察者的?mLastVersion?和 LiveData 的?mVersion?來決定是否通知觀察者。若?mLastVersion?小于?mVersion,則調用觀察者的?onChanged()?方法發送最新數據,導致粘性事件發生。

為解決粘性事件問題,常見方法如下:

  • 使用?SingleLiveEvent:自定義一個繼承自?MutableLiveData?的類,重寫相關方法確保事件只被消費一次。例如在一些開源項目中,SingleLiveEvent?類通過設置標志位,在?observe()?方法中判斷標志位,僅在首次觀察時觸發數據更新,后續不再響應之前的粘性數據。
  • 使用?Event?包裝類:將數據包裝在?Event?類中,通過標記數據是否已被處理來避免重復觸發。在觀察者獲取數據時,先檢查標記位,若未處理則處理數據并設置標記位,防止重復處理粘性數據。
  • 使用?MediatorLiveDataMediatorLiveData?可監聽其他 LiveData 變化,并在必要時過濾粘性事件。通過添加源 LiveData 的觀察者,在數據變化時進行相應處理,如更新自身數據后移除源 LiveData,避免粘性事件傳遞給新觀察者。

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

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

相關文章

A系統使用iframe嵌套B系統時登錄跨域問題!

我這邊兩個項目都是獨立的&#xff0c;問題是做了跨域配置之后點擊登錄接口調用成功但是頁面沒有跳轉進去 顯示以下報錯 這個錯誤明確指出了問題的核心原因&#xff1a;由于跨站點Cookie設置未正確聲明SameSiteNone&#xff0c;導致瀏覽器攔截了Cookie。這是現代瀏覽器&#x…

消息唯一ID算法參考

VUE // src/utils/idGenerator.js/*** 雪花算法風格的 ID 生成器**//*** 前綴 w代表web端,m代表手機端**/ const DEFAULT_PREFIX = w; const DEFAULT_TOTAL_LENGTH = 16; const CHARS

《WebGIS之Vue零基礎教程》(5)計算屬性與偵聽器

1 計算屬性 1) 什么是計算屬性 :::info 計算屬性就是基于現有屬性計算后的屬性 ::: 2) 計算屬性的作用 計算屬性用于對原始數據的再次加工 3) 案例 :::warning **需求** 實現如下效果 ::: 使用表達式實現 html Document 請輸入一個字符串: 反轉后的字符串: {{msg.split(…

洞悉 NGINX ngx_http_access_module基于 IP 的訪問控制實戰指南

一、模塊概述 ngx_http_access_module 是 NGINX 核心模塊之一&#xff0c;用于基于客戶端 IP 地址或 UNIX 域套接字限制訪問。它通過簡單的 allow/deny 規則&#xff0c;對請求進行最先匹配原則的過濾。與基于密碼&#xff08;auth_basic&#xff09;、子請求&#xff08;auth…

數據中臺-數據質量管理系統:從架構到實戰

一、數據質量管理系統核心優勢解析? ? (一)可視化驅動的敏捷數據治理? 在數據治理的復雜流程中,Kettle 的 Spoon 圖形化界面堪稱一把利器,為數據工程師們帶來了前所未有的便捷體驗。想象一下,你不再需要花費大量時間和精力去編寫冗長且復雜的 SQL 腳本,只需通過簡單…

數據分析之 商品價格分層之添加價格帶

在分析貨品數據的時候&#xff0c;我們會對商品的價格進行分層匯總&#xff0c;也叫價格帶&#xff0c;?? 一、價格帶的定義?? ??價格帶&#xff08;Price Band&#xff09;??&#xff1a;將商品按價格區間劃分&#xff08;如0-50元、50-100元、100-200元等&#xff…

Maven 依賴范圍(Scope)詳解

Maven 依賴范圍&#xff08;Scope&#xff09;詳解 Maven 是一個強大的項目管理工具&#xff0c;廣泛用于 Java 開發中構建、管理和部署應用程序。在使用 Maven 構建項目時&#xff0c;我們經常需要引入各種第三方庫或框架作為項目的依賴項。通過在 pom.xml 文件中的 <depe…

vue3實現v-directive;vue3實現v-指令;v-directive不觸發

文章目錄 場景&#xff1a;問題&#xff1a;原因&#xff1a;? 場景&#xff1a; 列表的操作列有按鈕&#xff0c;通過v-directive指令控制按鈕顯隱&#xff1b;首次觸發了v-directive指令&#xff0c;控制按鈕顯隱正常&#xff1b;但是再次點擊條件查詢后&#xff0c;列表數…

數據結構【樹和二叉樹】

樹和二叉樹 前言1.樹1.1樹的概念和結構1.2樹的相關術語1.3樹的表示方法1.4 樹形結構實際運用場景 2.二叉樹2.1二叉樹的概念和結構2.2二叉樹具備以下特點&#xff1a;2.3二叉樹分類 3.滿二叉樹4.完全二叉樹5.二叉樹性質6.附&#xff1a;樹和二叉樹圖示 前言 歡迎蒞臨姜行運主頁…

css面板視覺高度

css面板視覺高度 touch拖拽 在手機端有時候會存在實現touch上拉或者下拉的樣式操作 此功能實現可以參考&#xff1a; https://blog.csdn.net/u012953777/article/details/147465162?spm1011.2415.3001.5331 面板視覺高度 前提需求&#xff1a; 1、展示端分為兩部分&…

【Linux系統】詳解Linux權限

文章目錄 前言一、學習Linux權限的鋪墊知識1.Linux的文件分類2.Linux的用戶2.1 Linux下用戶分類2.2 創建普通用戶2.3 切換用戶2.4 sudo&#xff08;提升權限的指令&#xff09; 二、Linux權限的概念以及修改方法1.權限的概念2.文件訪問權限 和 訪問者身份的相關修改&#xff08…

路由器的基礎配置全解析:靜態動態路由 + 華為 ENSP 命令大全

&#x1f680; 路由器的基礎配置全解析&#xff1a;靜態&動態路由 華為 ENSP 命令大全 &#x1f310; 路由器的基本概念&#x1f4cd; 靜態路由配置&#x1f4e1; 動態路由協議&#xff1a;RIP、OSPF、BGP&#x1f5a5; 華為 ENSP 路由器命令大全&#x1f539; 路由器基本…

詳細圖解 Path-SAM2: Transfer SAM2 for digital pathology semantic segmentation

? 背景動機 數字病理中的語義分割&#xff08;semantic segmentation&#xff09;是非常關鍵的&#xff0c;比如腫瘤檢測、組織分類等。SAM&#xff08;Segment Anything Model&#xff09;推動了通用分割的發展&#xff0c;但在病理圖像上表現一般。 病理圖像&#xff08;Pa…

初識Redis · 哨兵機制

目錄 前言&#xff1a; 引入哨兵 模擬哨兵機制 配置docker環境 基于docker環境搭建哨兵環境 對比三種配置文件 編排主從節點和sentinel 主從節點 sentinel 模擬哨兵 前言&#xff1a; 在前文我們介紹了Redis的主從復制有一個最大的缺點就是&#xff0c;主節點掛了之…

HTTP header Cookie 和 Set-Cookie

RFC 6265: HTTP State Management Mechanismhttps://www.rfc-editor.org/rfc/rfc6265 Set-Cookie 響應頭 服務器使用 Set-Cookie 響應頭向客戶端&#xff08;通常是瀏覽器&#xff09;發送 Cookie。 基本格式&#xff1a; Set-Cookie: <cookie名稱><cookie值>;…

【Unity完整游戲開發案例】從0做一個太空大戰游戲

1.實現飛機移動控制 // 這個腳本實現控制飛機前后移動&#xff0c;方向由鼠標控制 //1.WS控制前后移動2.鼠標控制上下左右旋轉3.AD控制傾斜 using System.Collections; using System.Collections.Generic; using UnityEngine;public class PlayerController : MonoBehav…

【C++】C++11新特性(一)

文章目錄 列表初始化initializer_list左值引用和右值引用 列表初始化 在 C98 中可以使用{}對數組或者結構體元素進行統一的列表初始值設定 struct Point {int _x;int _y; }; int main() {int array1[] { 1, 2, 3, 4, 5 };int array2[5] { 0 };Point p { 1, 2 };return 0; …

小黑享受思考心流: 73. 矩陣置零

小黑代碼 class Solution:def setZeroes(self, matrix: List[List[int]]) -> None:"""Do not return anything, modify matrix in-place instead."""items []m len(matrix)n len(matrix[0])for i in range(m):for j in range(n):if not m…

精益數據分析(19/126):走出數據誤區,擁抱創業愿景

精益數據分析&#xff08;19/126&#xff09;&#xff1a;走出數據誤區&#xff0c;擁抱創業愿景 在創業與數據分析的探索之旅中&#xff0c;我們都渴望獲取更多知識&#xff0c;少走彎路。今天&#xff0c;我依然帶著和大家共同進步的想法&#xff0c;深入解讀《精益數據分析…

循環神經網絡RNN---LSTM

一、 RNN介紹 循環神經網絡&#xff08;Recurrent Neural Network&#xff0c;簡稱 RNN&#xff09;是一種專門用于處理序列數據的神經網絡&#xff0c;在自然語言處理、語音識別、時間序列預測等領域有廣泛應用。 傳統神經網絡 無法訓練出具有順序的數據。模型搭建時沒有考…