Android業務架構 · 基礎篇 · Jetpack四件套

語言: CN / TW / HK

highlight: a11y-dark theme: orange


【小木箱成長營】Android業務架構系列文章:

Android業務架構 · 提高篇 · MVC、MVP、MVVM和MVI四劍客

Android業務架構 · 實踐篇 · MVI+Jetpack+Kotlin手把手搭建直播應用App

Tips: 關注小木箱成長營公眾號, 回覆"業務架構"可免費獲取Android業務架構思維導圖。

一、序言

Hello,我是小木箱,歡迎來到小木箱成長營業務架構系列教程,今天分享的內容是業務架構 · 基礎篇 · Jetpack四件套。

2017年,Google釋出了Android Architecture Components,包括Room、LiveData、ViewModel和Paging等元件,旨在幫助開發者更輕鬆地實現MVVM架構。

2018年,Google在I/O大會上推出的一套Android開發元件庫,旨在幫助開發者更輕鬆、更高效地構建Android應用。

隨著時間的推移,Android Jetpack不斷地更新和增加新的元件,使得Android應用的開發更加高效、穩定和可維護。

今天的主題主要分為三個維度。第一個維度是4W2H分析Jetpack,第二個維度是Jetpack四件套。第三個維度是總結與展望。

其中,4W2H分析Jetpack主要針對Jetpack提出了6個高價值問題。

其中,Jetpack四件套列舉了LifeCycle、LiveData、ViewModel和DataBing四種常見的Jetpack工具包。

如果學完業務架構系列教程,那麼任何人都能完整構建一套適合企業業務背景的架構設計。

# 二、4W2H分析Jetpack

2.1 What: Jetpack是什麼?

Android Jetpack是一組Android軟體元件、工具和指南,它們可以幫助開發者構建高質量、穩定的Android應用程式。Jetpack中包含多個庫,它們旨在解決Android應用程式開發中的常見問題,並提供一致的API和開發體驗。

Jetpack中包含的庫包括: 1. ViewModel:幫助管理UI元件的生命週期並存儲和管理UI相關的資料。 1. LiveData:提供了響應式程式設計的功能,可以讓資料在資料來源發生變化時自動更新UI。 1. Room:提供了一個抽象層,可以讓開發者方便地訪問和管理SQLite資料庫。 1. Navigation:提供了一種簡單、一致的方式來處理應用程式的導航。 1. WorkManager:提供了一種簡單、可靠的方式來管理後臺任務。 除此之外,Jetpack還包括了諸如Paging、Data Binding、Preferences、Security等庫,這些庫都旨在簡化開發過程並提高應用程式的效能和可靠性。

2.2 Where: 什麼場景下使用Jetpack?

Jetpack適用於開發各種型別的Android應用程式,包括單頁面應用程式、多頁面應用程式、後臺任務應用程式等。下面是一些適合使用Jetpack的場景: 1. 構建大型應用程式:Jetpack提供了一些庫,如ViewModel、LiveData和Navigation,可以幫助開發者更好地管理應用程式的生命週期、狀態和導航,使得構建大型應用程式更加容易。

  1. 處理後臺任務:Jetpack中的WorkManager庫提供了一種簡單、可靠的方式來管理後臺任務,如資料同步、推送通知、檔案上傳等。
  2. 資料庫訪問:Jetpack中的Room庫提供了一個抽象層,可以讓開發者方便地訪問和管理SQLite資料庫,使得資料儲存和訪問更加容易。
  3. 響應式程式設計:Jetpack中的LiveData和Data Binding庫提供了響應式程式設計的功能,可以讓資料在資料來源發生變化時自動更新UI,提高應用程式的效能和可靠性。
  4. 程式碼重用:Jetpack中的各種庫都旨在解決Android應用程式開發中的常見問題,並提供一致的API和開發體驗,使得程式碼重用更加容易。

2.3 Why: 為什麼使用Jetpack?

以下是使用Jetpack的一些好處: 1. 更好的程式碼組織和可維護性:Jetpack提供了一組庫,這些庫旨在解決Android應用程式開發中的常見問題,如生命週期管理、資料儲存、後臺任務處理等。使用Jetpack可以使程式碼更加模組化,易於維護。 1. 更好的程式碼可讀性:Jetpack提供了一致的API和開發體驗,可以使程式碼更加易於理解和閱讀。 1. 更好的開發效率:Jetpack提供了一些庫和工具,如Navigation和Data Binding,可以幫助開發者更快地開發Android應用程式,提高開發效率。 1. 更好的效能和可靠性:Jetpack中的一些庫,如LiveData和WorkManager,提供了響應式程式設計和後臺任務處理的功能,可以提高應用程式的效能和可靠性。 1. 更好的向後相容性:Jetpack中的一些庫,如ViewModel和LiveData,提供了對Android不同版本的向後相容性支援,可以使開發者更容易地編寫適用於不同版本的Android系統的應用程式。 綜上所述,Jetpack可以幫助開發者更好地組織程式碼、提高開發效率、提高應用程式的效能和可靠性,並提供了對不同版本的Android系統的向後相容性支援。

2.4 Who: 什麼樣的團隊該使用Jetpack?

Jetpack適用於各種規模的Android開發團隊,特別是那些希望提高應用程式質量、開發效率和可維護性的團隊。以下是一些團隊適合使用Jetpack的場景:

  1. 大型團隊:Jetpack提供了一致的API和開發體驗,可以使大型團隊更容易協作開發,提高團隊的開發效率和程式碼質量。
  2. 跨職能團隊:Jetpack提供了一些庫和工具,如Navigation和Data Binding,可以幫助開發者更快地開發Android應用程式,使得跨職能團隊之間更容易協作。
  3. 需要提高應用程式效能和可靠性的團隊:Jetpack中的一些庫,如LiveData和WorkManager,提供了響應式程式設計和後臺任務處理的功能,可以提高應用程式的效能和可靠性。
  4. 需要提高程式碼可維護性的團隊:Jetpack提供了一些庫,如ViewModel和Room,可以幫助開發者更好地管理應用程式的狀態和資料,使得程式碼更易於維護。
  5. 需要保持向後相容性的團隊:Jetpack中的一些庫,如ViewModel和LiveData,提供了對Android不同版本的向後相容性支援,可以使開發者更容易地編寫適用於不同版本的Android系統的應用程式。   綜上所述,Jetpack適合各種規模和型別的Android開發團隊,特別是那些希望提高應用程式質量、開發效率和可維護性的團隊。 ##   2.5 How: 怎樣使用Jetpack?   以下是使用Jetpack的一般步驟:
  6. 新增Jetpack庫:Jetpack庫可以通過在build.gradle檔案中新增依賴項的方式進行新增。例如,新增Lifecycle庫的依賴項: ```

    dependencies { def lifecycle_version = "2.3.1"// ViewModel implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"// LiveData implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"// Lifecycle implementation "androidx.lifecycle:lifecycle-runtime:$lifecycle_version" } ``` 使用Jetpack庫:在新增Jetpack庫後,就可以在應用程式中使用Jetpack庫提供的功能了。例如,使用ViewModel庫建立一個ViewModel類:

```

import androidx.lifecycle.ViewModel

class MyViewModel : ViewModel() {
    // Add ViewModel logic here
}

```

結合Jetpack元件使用:Jetpack庫提供的元件可以結合使用,以提高應用程式的開發效率和可維護性。例如,使用ViewModel庫和LiveData庫實現一個響應式的使用者介面:

```

class MyActivity : AppCompatActivity() {

    private lateinit var viewModel: MyViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_my)

        // Get a reference to the ViewModel
        viewModel = ViewModelProvider(this).get(MyViewModel::class.java)

        // Observe a LiveData object in the ViewModel
        viewModel.someData.observe(this, Observer {
            // Update the UI with the new data
        })
    }
}

``` 以上是使用Jetpack的一般步驟。需要根據具體的Jetpack庫和應用程式需求進行相應的配置和程式碼實現。

2.6 How Much: 使用Jetpack業務價值

使用Jetpack可以帶來以下業務價值:

  1. 提高開發效率:Jetpack提供了一些開發工具和庫,例如ViewModel、LiveData和Room,可以減少重複的編寫程式碼,簡化開發流程,並提高開發效率。
  2. 提高應用程式質量:Jetpack提供了一致的API和開發體驗,可以減少由於人為因素引起的程式碼錯誤,提高應用程式的質量。
  3. 提高應用程式效能:Jetpack中的一些庫,例如Lifecycle和WorkManager,提供了響應式程式設計和後臺任務處理的功能,可以提高應用程式的效能和響應速度。
  4. 簡化應用程式架構:Jetpack提供了一些元件和庫,例如ViewModel和Data Binding,可以幫助開發者更好地管理應用程式的狀態和資料,並簡化應用程式的架構。
  5. 支援向後相容性:Jetpack中的一些庫,例如ViewModel和LiveData,提供了對Android不同版本的向後相容性支援,可以使開發者更容易地編寫適用於不同版本的Android系統的應用程式。

綜上所述,使用Jetpack可以帶來多種業務價值,可以提高應用程式的質量、效能和開發效率,同時簡化應用程式架構和支援向後相容性,可以使應用程式更易於維護和升級。

三、Jetpack四件套

3.1 LifeCycle

3.1.1 LifeCycle基礎定義

Android Jetpack Lifecycle是Android Jetpack元件庫中的一部分,Lifecycle是基於Android Framework中的Lifecycle概念而構建的。

Lifecycle提供了一種輕鬆管理元件(如Activity和Fragment)生命週期的方式,同時也支援自定義元件的生命週期。

Jetpack Lifecycle提供了一組類和介面,使得開發者可以在元件的生命週期各個階段執行相應的操作。

這些類和介面包括:

  • LifecycleOwner: 擁有生命週期的物件,通常是Activity和Fragment。
  • LifecycleObserver: 監聽元件的生命週期事件的觀察者物件。
  • Lifecycle: 元件的生命週期,包括CREATED、STARTED、RESUMED、PAUSED、STOPPED、DESTROYED等狀態。
  • LiveData: 一個可觀察的資料容器,可以在元件生命週期的不同階段更新資料。

使用Jetpack Lifecycle,可以更容易地避免記憶體洩漏和其他生命週期相關的問題。

例如,可以在元件被銷燬時自動釋放資源、取消網路請求等操作。

此外,Jetpack Lifecycle還提供了一種方式來建立自定義的生命週期狀態,以更好地滿足App的需求。

總之,Jetpack Lifecycle是Android Jetpack元件庫中的一個重要元件,可以幫助開發者更輕鬆地管理元件的生命週期,從而提高App的質量和效能。

3.1.2 LifeCycle基礎使用

在App的主Activity中實現一個簡單的計時器,當Activity處於前臺時,計時器會不斷遞增,當Activity被銷燬時,計時器將停止。

具體實現步驟如下:

  1. 在gradle檔案中新增Jetpack元件庫的依賴。

```

dependencies { implementation "androidx.lifecycle:lifecycle-extensions:2.4.0" } ```

  1. 建立一個名為Timer的Java類,並實現LifeCycleObserver介面。

```

public class Timer implements LifecycleObserver { private Handler handler; private int seconds = 0;

@OnLifecycleEvent(Lifecycle.Event.ON_START)public void startTimer() {
    handler = new Handler();
    handler.post(new Runnable() {
        @Overridepublic void run() {
            Log.d("Timer", "Seconds: " + seconds);
            seconds++;
            handler.postDelayed(this, 1000);
        }
    });
}

@OnLifecycleEvent(Lifecycle.Event.ON_STOP)public void stopTimer() {
    handler.removeCallbacksAndMessages(null);
    handler = null;
}

} ```

  1. 在MainActivity中新增LifecycleOwner,並在onCreate方法中新增Observer。

```

public class MainActivity extends AppCompatActivity {

@Overrideprotected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // 獲取LifecycleOwner物件LifecycleOwner lifecycleOwner = this;

    // 將Timer例項新增為Observer
    getLifecycle().addObserver(new Timer());

    // ...
}

// ...

} ```

這樣,當Activity處於前臺時,Timer例項中的startTimer方法會被呼叫,計時器會開始遞增;

當Activity被銷燬時,Timer例項中的stopTimer方法會被呼叫,計時器會停止。

這個例子展示瞭如何使用Jetpack LifeCycle元件來管理App元件的生命週期。

當App中存在需要在元件生命週期不同階段執行的操作時,使用LifeCycle可以更方便地實現這些操作,同時避免了一些常見的生命週期問題。

3.1.3 LifeCycle優勢劣勢

優勢

  1. 管理生命週期方便

使用LifeCycle元件可以更方便地管理App元件的生命週期,避免了一些常見的生命週期問題,如記憶體洩漏和空指標異常等。

  1. 模組化程式設計

使用LifeCycle元件可以將App的業務邏輯分解為模組化的元件,每個元件負責管理自己的生命週期,便於程式碼複用和維護。

  1. 規範化程式設計

使用LifeCycle元件可以規範化App的開發,使程式碼更易於閱讀、理解和維護。

  1. 支援多個元件

LifeCycle元件支援多個元件進行生命週期管理,可以輕鬆地在多個元件之間共享狀態和資料。

劣勢

  1. 需要繼承LifecycleObserver:在實現LifeCycle功能的時候,需要繼承LifecycleObserver介面,這會導致程式碼的繼承關係稍微有點複雜。
  2. 需要添加註釋:在使用LifeCycle元件的時候,需要新增一些註釋來指示方法是在什麼時候被呼叫,否則可能會出現一些難以診斷的問題。

3.1.4 LifeCycle應用場景

Jetpack LifeCycle元件的實際開發應用場景包括:

  1. Activity和Fragment生命週期管理:使用LifeCycle元件可以更方便地管理Activity和Fragment的生命週期,避免了一些常見的生命週期問題,如記憶體洩漏和空指標異常等。
  2. 後臺服務管理:使用LifeCycle元件可以更方便地管理後臺服務的生命週期,可以在App退出後自動停止後臺服務,避免了一些不必要的資源浪費。
  3. 資料庫連線管理:使用LifeCycle元件可以更方便地管理資料庫連線的生命週期,可以在App退出時自動關閉資料庫連線,避免了一些不必要的資源浪費。
  4. 網路請求管理:使用LifeCycle元件可以更方便地管理網路請求的生命週期,可以在Activity或Fragment銷燬時自動取消網路請求,避免了一些不必要的網路請求。
  5. 檢視控制器管理:使用LifeCycle元件可以更方便地管理檢視控制器的生命週期,可以在Activity或Fragment銷燬時自動清除檢視控制器的狀態,避免了一些不必要的狀態儲存和恢復操作。

3.1.5 LifeCycle原理分析

類圖

LifecycleOwner表示擁有生命週期的元件,比如Activity和Fragment。

Lifecycle表示元件的生命週期,LifecycleObserver表示一個元件的生命週期觀察者。

LifecycleRegistry是Lifecycle介面的一個實現類,它維護了一個生命週期狀態機,用於記錄元件的生命週期狀態和生命週期事件。

LifecycleRegistry提供了一系列方法,用於管理元件的生命週期狀態和生命週期事件。當元件的生命週期事件發生變化時,LifecycleRegistry會自動更新狀態機,並通知所有的LifecycleObserver觀察者物件,以便它們可以相應地更新自己的狀態。

LifecycleOwner可以通過getLifecycle()方法獲取到一個Lifecycle物件,然後將自己的生命週期觀察者物件新增到Lifecycle物件中,從而實現對元件生命週期的監聽。

當元件的生命週期事件發生變化時,Lifecycle會自動通知所有的生命週期觀察者物件,以便它們可以相應地更新自己的狀態。

image.png

原始碼

Lifecycle庫的核心是Lifecycle介面和LifecycleObserver介面。

Lifecycle介面定義了一組方法,用於將LifecycleOwner與LifecycleObserver進行關聯。

``` public abstract class Lifecycle { //新增觀察者 @MainThread public abstract void addObserver(@NonNull LifecycleObserver observer); //移除觀察者 @MainThread public abstract void removeObserver(@NonNull LifecycleObserver observer); //獲取當前狀態 public abstract State getCurrentState();

//生命週期事件,對應Activity生命週期方法 public enum Event { ON_CREATE, ON_START, ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY, ON_ANY //可以響應任意一個事件 }

//生命週期狀態. (Event是進入這種狀態的事件)
public enum State {
    DESTROYED,
    INITIALIZED,
    CREATED,
    STARTED,
    RESUMED;

    //判斷至少是某一狀態
    public boolean isAtLeast(@NonNull State state) {
        return compareTo(state) >= 0;
    }
}

```

LifecycleObserver介面定義了一組回撥方法,用於接收LifecycleOwner的生命週期事件。

在Lifecycle庫的實現中,Lifecycle介面有兩個重要的實現類,分別是LifecycleRegistry和LifecycleOwner。

LifecycleRegistry實現了Lifecycle介面,並提供了一組方法,用於管理LifecycleOwner的生命週期狀態。

LifecycleOwner是一個介面,用於標識擁有生命週期狀態的物件,通常是Activity或Fragment。

``` //androidx.activity.ComponentActivity,這裡忽略了一些其他程式碼,我們只看Lifecycle相關 public class ComponentActivity extends androidx.core.app.ComponentActivity implements LifecycleOwner{ ...

private final LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
...
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mSavedStateRegistryController.performRestore(savedInstanceState);
    ReportFragment.injectIfNeededIn(this); //使用ReportFragment分發生命週期事件
    if (mContentLayoutId != 0) {
        setContentView(mContentLayoutId);
    }
}
@CallSuper
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
    Lifecycle lifecycle = getLifecycle();
    if (lifecycle instanceof LifecycleRegistry) {
        ((LifecycleRegistry) lifecycle).setCurrentState(Lifecycle.State.CREATED);
    }
    super.onSaveInstanceState(outState);
    mSavedStateRegistryController.performSave(outState);
}

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

} ```

在LifecycleRegistry中,有一個名為mObserverMap的成員變數,用於儲存LifecycleObserver物件和其關聯的EventObserver物件。

當LifecycleOwner的生命週期狀態更改時,LifecycleRegistry會自動呼叫mObserverMap中與之相關聯的EventObserver物件的相應方法,以便它們可以執行適當的操作。

``` //LifecycleRegistry.java //系統自定義的儲存Observer的map,可在遍歷中增刪 private FastSafeIterableMap mObserverMap = new FastSafeIterableMap<>();

public void handleLifecycleEvent(@NonNull Lifecycle.Event event) {
    State next = getStateAfter(event);//獲取event發生之後的將要處於的狀態
    moveToState(next);//移動到這個狀態
}

private void moveToState(State next) {
    if (mState == next) {
        return;//如果和當前狀態一致,不處理
    }
    mState = next; //賦值新狀態
    if (mHandlingEvent || mAddingObserverCounter != 0) {
        mNewEventOccurred = true;
        return;
    }
    mHandlingEvent = true;
    sync(); //把生命週期狀態同步給所有觀察者
    mHandlingEvent = false;
}

    private void sync() {
    LifecycleOwner lifecycleOwner = mLifecycleOwner.get();
    if (lifecycleOwner == null) {
        throw new IllegalStateException("LifecycleOwner of this LifecycleRegistry is already"
                + "garbage collected. It is too late to change lifecycle state.");
    }
    while (!isSynced()) {  //isSynced()意思是 所有觀察者都同步完了
        mNewEventOccurred = false;
        //mObserverMap就是 在activity中新增observer後 用於存放observer的map
        if (mState.compareTo(mObserverMap.eldest().getValue().mState) < 0) {
            backwardPass(lifecycleOwner);
        }
        Entry<LifecycleObserver, ObserverWithState> newest = mObserverMap.newest();
        if (!mNewEventOccurred && newest != null
                && mState.compareTo(newest.getValue().mState) > 0) {
            forwardPass(lifecycleOwner);
        }
    }
    mNewEventOccurred = false;
}
...

 static State getStateAfter(Event event) {
    switch (event) {
        case ON_CREATE:
        case ON_STOP:
            return CREATED;
        case ON_START:
        case ON_PAUSE:
            return STARTED;
        case ON_RESUME:
            return RESUMED;
        case ON_DESTROY:
            return DESTROYED;
        case ON_ANY:
            break;
    }
    throw new IllegalArgumentException("Unexpected event value " + event);
}

```

LifecycleRegistry還提供了一組方法,如handleLifecycleEvent()、getCurrentState()、addObserver()、removeObserver()等,用於管理元件的生命週期狀態和LifecycleObserver物件。

在Lifecycle庫的實現中,還有一些其他的類和介面,如GenericLifecycleObserver、FullLifecycleObserver、LifecycleEvent、EventObserver等,它們都是用於管理和處理元件生命週期事件的。

3.1.6 LifeCycle注意事項

3.1.6.1 不要在 onCreate() 方法中使用 Lifecycle 元件

Lifecycle 元件在 onCreate() 方法中尚未初始化完成,因此在該方法中使用它們可能會導致崩潰或不可預測的行為。建議在 onStart() 方法中使用 Lifecycle 元件。

3.1.6.2 不要手動呼叫 onDestroy() 方法

手動呼叫 onDestroy() 方法會破壞 Lifecycle 元件的生命週期,從而導致應用程式行為異常。Lifecycle 元件應該由系統自動管理,應該避免手動干預。

3.1.6.3 避免在 Fragment 中使用多個 LifecycleOwner

Fragment 自身就是一個 LifecycleOwner,因此不應該在 Fragment 中建立其他的 LifecycleOwner。這樣會導致多個 LifecycleOwner 之間的狀態不同步,從而導致應用程式出現問題。

3.2 LiveData

3.2.1 LiveData基礎定義

Android Jetpack LiveData是一種用於管理應用程式介面和資料互動的元件。

LiveData是一種可觀察的資料持有者,用於在應用程式元件(如Activity、Fragment和Service)之間共享資料,並在資料發生更改時通知觀察者。

LiveData可以確保UI與資料的同步更新,避免了一些常見的錯誤,如記憶體洩漏和UI元件無法正確更新的問題。

LiveData具有生命週期感知功能,可以自動感知應用程式元件的生命週期,並在元件處於活動狀態時更新UI,而在元件處於非活動狀態時停止更新,從而有效地減少了資源消耗。

LiveData還提供了執行緒安全的訪問資料的機制,避免了多執行緒併發訪問的問題。

3.2.2 LiveData基礎使用

如何 TextView 控制元件的顯示內容呢?

首先,在 XML 佈局檔案中新增一個 TextView 控制元件:

<TextView android:id="@+id/text_view" android:layout_width="wrap_content" android:layout_height="wrap_content" />

然後,在 Activity 或 Fragment 中建立一個 LiveData 物件,用於更新 TextView 的顯示內容:

```

public class MyActivity extends AppCompatActivity {

private LiveData<String> mLiveData;
private TextView mTextView;

@Overrideprotected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    mTextView = findViewById(R.id.text_view);

    mLiveData = new MutableLiveData<>();
    mLiveData.observe(this, new Observer<String>() {
        @Overridepublic void onChanged(String s) {
            mTextView.setText(s);
        }
    });
}

} ```

在上述程式碼中,我們建立了一個 LiveData 物件,並將其與一個 TextView 控制元件關聯。當 LiveData 物件中的值發生變化時,我們使用 Observer 來監聽這個變化,然後更新 TextView 的顯示內容。

最後,我們可以在程式碼中通過 setValue() 或 postValue() 方法來更新 LiveData 物件的值,從而更新 TextView 的顯示內容。例如:

```

((MutableLiveData) mLiveData) .setValue("Hello, world!"); ```

以上就是一個簡單的 LiveData 案例,用於更新 TextView 控制元件的顯示內容。當 LiveData 中的值發生變化時,TextView 控制元件會自動更新顯示內容。

3.2.3 LiveData優勢劣勢

優勢

  1. 生命週期感知:LiveData 可以感知元件(如 Activity 或 Fragment)的生命週期,從而避免了由於 UI 元件的生命週期變化而引發的空指標異常和記憶體洩漏等問題。
  2. 資料更新通知:LiveData 可以在資料發生變化時自動通知所有觀察者更新資料,從而實現資料的實時更新和響應。
  3. 資料一致性:LiveData 可以確保在配置更改時保持資料的一致性,避免了資料丟失和重複載入等問題。
  4. 執行緒安全:LiveData 可以確保在主執行緒中更新 UI 介面,並支援在工作執行緒中進行非同步操作,從而避免了多執行緒資料競爭問題。
  5. 與 ViewModel 結合使用:LiveData 可以與 ViewModel 結合使用,實現資料與 UI 介面的分離,從而提高了程式碼的可維護性和可測試性。

劣勢

  1. 適用場景有限:LiveData 適用於資料變化頻繁、需要實時更新的場景,對於資料變化較少的場景,使用 LiveData 可能會增加程式碼複雜性。
  2. API 限制:LiveData 是 Android Jetpack 元件,只能在支援 Jetpack 的 Android 版本上使用,對於一些較老的 Android 版本可能需要使用其他技術方案。

3.2.4 LiveData應用場景

LiveData的應用場景包括但不限於以下幾個方面:

  1. 資料庫操作:LiveData可以與Room持久化庫結合使用,當資料庫中的資料發生變化時,LiveData會自動通知UI元件進行更新。
  2. 網路請求:LiveData可以與Retrofit網路請求庫結合使用,當網路請求的結果返回時,LiveData會自動通知UI元件進行更新。
  3. 資料共享:LiveData可以在不同的元件之間共享資料,例如,當一個Activity和一個Fragment需要共享資料時,可以將LiveData物件設定為一個公共的資料持有者。
  4. 資源釋放:LiveData可以在UI元件不再處於活動狀態時自動釋放資源,避免出現記憶體洩漏等問題。
  5. 程式碼簡潔:LiveData可以減少程式碼複雜度,通過資料觀察者的方式,避免手動編寫繁瑣的資料更新程式碼。

3.2.5 LiveData原理分析

LiveData是一種可以感知生命週期的資料持有者,它可以讓資料更新時通知UI介面進行更新,同時也能夠避免因為生命週期問題帶來的記憶體洩漏。

類圖

LiveData的簡化類圖:

image.png

在上面的類圖中,Observer是LiveData的觀察者介面,LiveData是可觀察資料的持有者類。LiveData具有生命週期感知能力,可以根據其生命週期狀態自動管理資料的訂閱和取消訂閱。MutableLiveData是LiveData的可變子類,允許更新LiveData持有的資料。LiveData的observe()方法用於註冊觀察者,setValue()和postValue()方法用於更新LiveData資料。

原始碼

LiveData的核心程式碼在androidx.lifecycle.LiveData類中,下面對LiveData的原始碼進行簡要分析:

  1. LiveData的基本結構

LiveData類是一個抽象類,它有一個泛型型別T,表示LiveData中儲存的資料型別。LiveData類內部維護了一個數據源(mData)和一個觀察者列表(mObservers),當LiveData中的資料發生改變時,會通知所有註冊的觀察者進行UI更新。

```

public abstract class LiveData { private static final Object NOT_SET = new Object(); private Object mData; private boolean mDispatchingValue; private int mActiveCount; private volatile Object mPendingData = NOT_SET; private volatile Object mVersion = new Object(); private final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor(); private final SafeIterableMap, ObserverWrapper> mObservers = new SafeIterableMap<>();

// ...

} ```

  1. 觀察者註冊

LiveData中的觀察者是通過observe()方法進行註冊的,這個方法接受一個LifecycleOwner物件和一個Observer物件。LifecycleOwner是一個具有生命週期的物件,當LifecycleOwner的生命週期結束時,LiveData會自動解註冊所有與該LifecycleOwner相關的觀察者,避免記憶體洩漏。

```

public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) { if (owner.getLifecycle().getCurrentState() == DESTROYED) { // ignorereturn; } // ...LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer); // ... } ```

  1. 觀察者解註冊

解註冊則是通過removeObserver()方法進行的,該方法接受一個Observer物件,用於從觀察者列表中刪除相應的觀察者。

```

public void removeObserver(@NonNull Observer<? super T> observer) { assertMainThread("removeObserver"); // ...mObservers.remove(observerWrapper); // ... } ```

  1. 資料更新和通知觀察者

LiveData中的資料更新是通過setValue()和postValue()方法進行的。setValue()方法是在主執行緒中進行呼叫的,它會直接更新LiveData中的資料並通知所有的觀察者進行UI更新;而postValue()方法是在非同步執行緒中進行呼叫的,它會將要更新的資料封裝成PendingPost物件,並提交給主執行緒的Handler進行處理。

```

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

protected void postValue(T value) { if (Looper.myLooper() != Looper.getMainLooper()) { // ... return; } mPendingData = value; if (mPendingData == NOT_SET) { // ignore return; } // ... mMainThreadExecutor.execute(mPostValueRunnable); } ```

當LiveData中的資料發生更新時,LiveData會通知所有的觀察者進行UI更新。LiveData使用了模板方法

設計模式中的觀察者模式,它將資料來源和觀察者進行了解耦。LiveData類中的dispatchingValue()方法就是通知觀察者進行UI更新的核心方法。

```

private void dispatchingValue(@Nullable LifecycleBoundObserver initiator) { if (mDispatchingValue) { mDispatchInvalidated = true; return; } mDispatchingValue = true; do { mDispatchInvalidated = false; if (initiator != null) { considerNotify(initiator); initiator = null; } else { Iterator, ObserverWrapper>> iterator = mObservers.iteratorWithAdditions(); while (iterator.hasNext()) { considerNotify(iterator.next().getValue()); if (mDispatchInvalidated) { break; } } } } while (mDispatchInvalidated); mDispatchingValue = false; } ```

dispatchingValue()方法中使用了一個迭代器遍歷所有的觀察者,然後呼叫considerNotify()方法進行UI更新。

```

private void considerNotify(ObserverWrapper observer) { if (!observer.mActive) { return; } if (!observer.shouldBeActive()) { observer.activeStateChanged(false); return; } if (observer.mLastVersion >= mVersion) { return; } observer.mLastVersion = mVersion; observer.mObserver.onChanged((T) mData); } ```

considerNotify()方法首先判斷觀察者是否處於活躍狀態,如果不是則直接返回;接著判斷觀察者是否應該處於活躍狀態,如果不是則呼叫activeStateChanged()方法將觀察者狀態更新為非活躍狀態;最後判斷資料版本號是否發生變化,如果發生變化則呼叫觀察者的onChanged()方法進行UI更新。

  1. 其他方法

除了以上核心方法之外,LiveData還提供了其他方法,例如getValue()方法用於獲取LiveData中儲存的資料;hasActiveObservers()方法用於判斷是否存在處於活躍狀態的觀察者等等。

```

public T getValue() { Object data = mData; if (data != NOT_SET) { return (T) data; } return null; }

public boolean hasActiveObservers() { return mActiveCount > 0; } ```

以上是對LiveData原始碼的簡要分析,LiveData使用了觀察者模式,可以感知生命週期並進行UI更新,避免了因為生命週期問題帶來的記憶體洩漏。

3.2.6 LiveData注意事項

資料倒灌

要解決LiveData資料倒灌問題,可以使用以下方法:

使用MediatorLiveData代替LiveData:MediatorLiveData可以作為一箇中間層,將多個LiveData物件的資料來源合併,從而避免資料倒灌問題。在UI元件的生命週期結束時,可以呼叫MediatorLiveData的removeSource()方法,將LiveData的資料來源從MediatorLiveData中移除。

在ViewModel中使用LiveData:將LiveData物件作為ViewModel中的成員變數,並在ViewModel中進行資料更新和觀察,可以避免LiveData資料倒灌問題。

在UI元件中使用自定義Observer:可以在自定義Observer中進行生命週期判斷,當UI元件的生命週期已經結束時,不再更新UI介面。

下面是使用自定義Observer解決LiveData資料倒灌問題的示例程式碼:

```

public class MyObserver<T> implements Observer<T> {

    private boolean mIsStarted = false;
    private Observer<T> mObserver;

    public MyObserver(Observer<T> observer) {
        mObserver = observer;
    }

    public void start() {
        mIsStarted = true;
    }

    public void stop() {
        mIsStarted = false;
    }

    @Overridepublic void onChanged(T t) {
        if (mIsStarted) {
            mObserver.onChanged(t);
        }
    }
}

```

在UI元件中使用自定義Observer時,需要在UI元件的生命週期開始和結束時,呼叫MyObserver的start()和stop()方法,從而控制資料更新的時機,避免LiveData資料倒灌問題。

```

public class MyActivity extends AppCompatActivity {

    private LiveData<Integer> mLiveData;
    private MyObserver<Integer> mObserver;

    @Overrideprotected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mLiveData = new MutableLiveData<>();
        mObserver = new MyObserver<>(integer -> {
            // 更新UI介面
        });
        mLiveData.observe(this, mObserver);
    }

    @Overrideprotected void onStart() {
        super.onStart();
        mObserver.start();
    }

    @Overrideprotected void onStop() {
        super.onStop();
        mObserver.stop();
    }
}

```

以上是解決LiveData資料倒灌問題的一些方法,也可以通過hook修改livedata原始碼observer.mLastVersion的值,使得if (observer.mLastVersion >= mVersion)成立,就不會導致沒有註冊觀察者,還能接收到訊息

#### setValue 不起效

呼叫LiveData的setValue()方法時,如果LiveData的觀察者處於啟用狀態,那麼LiveData會將最新的資料推送給觀察者,並在觀察者的回撥方法中更新UI介面。但是,如果LiveData的觀察者處於非啟用狀態,那麼LiveData不會將資料推送給觀察者,也不會更新UI介面。因此,如果呼叫setValue()方法後,UI介面沒有發生更新,可能是因為LiveData的觀察者處於非啟用狀態。

LiveData的觀察者處於非啟用狀態的原因可能有以下幾種:

  1. 觀察者沒有與LiveData建立連線:在呼叫LiveData的observe()方法時,需要將觀察者與LiveData建立連線,只有建立連線後,LiveData才能將資料推送給觀察者。如果沒有建立連線,那麼即使呼叫了setValue()方法,也無法更新UI介面。
  2. 觀察者的生命週期已經結束:LiveData的觀察者必須在其生命週期的活動狀態中才能接收到資料更新。如果觀察者的生命週期已經結束,那麼即使呼叫了setValue()方法,也無法更新UI介面。
  3. 觀察者處於非啟用狀態:LiveData的觀察者在其生命週期的啟用狀態中才能接收到資料更新。如果觀察者處於非啟用狀態,那麼即使呼叫了setValue()方法,也無法更新UI介面。

因此,如果呼叫setValue()方法後,UI介面沒有發生更新,可以檢查觀察者的連線狀態和生命週期狀態,確保LiveData的觀察者處於啟用狀態,並且已經與LiveData建立連線。如果仍然無法解決問題,可以考慮使用postValue()方法,該方法可以在UI執行緒空閒時更新LiveData的資料,並在觀察者處於啟用狀態時通知觀察者進行UI更新。

記憶體洩漏

LiveData的觀察者(Observer)預設是弱引用,但是如果觀察者沒有及時取消觀察,可能會導致記憶體洩漏。

#### 多次觀察

如果一個LiveData物件被多個觀察者同時觀察,那麼每個觀察者都會收到相同的資料更新,可能會導致UI介面多次更新,造成效能問題。

#### 生命週期不一致

LiveData是與生命週期相關聯的,如果UI元件的生命週期結束了,但是LiveData仍在傳送資料更新,那麼就會引發異常。

執行緒安全

LiveData預設在主執行緒進行資料更新,如果需要在後臺執行緒進行資料更新,就需要使用LiveData的postValue()方法,而不是setValue()方法。

避免重複觀察

如果在一個UI元件中多次觀察同一個LiveData物件,可能會導致重複觀察,造成效能問題和資料不一致問題。因此,可以使用ViewModel中的getLiveData()方法,保證同一個LiveData物件只被觀察一次。

3.2.7 EventBus vs Otto vs RxJava vs LiveData

image.png

3.3 ViewModel

3.3.1 ViewModel基礎定義

Android Jetpack ViewModel 是一種用於管理 UI 相關資料的元件,它可以在螢幕旋轉等配置更改時儲存資料並重新建立 Activity 或 Fragment。ViewModel 是一個以生命週期感知的方式來儲存和管理 UI 相關資料的類。

ViewModel 是一個專門用於儲存和管理與 UI 相關的資料的類,它們可以在 Activity 或 Fragment 重新建立時保持資料的完整性,並且可以避免因為 Activity 或 Fragment 生命週期的變化而導致的資料丟失。ViewModel 還提供了一種在 Activity 和 Fragment 之間共享資料的機制。

在 ViewModel 中儲存的資料是不受 Activity 或 Fragment 生命週期的影響的,這意味著在旋轉螢幕或者配置更改時,ViewModel 中的資料將不會被清除,可以保持完整性,從而可以在 Activity 或 Fragment 重新建立時繼續使用。

ViewModel 通常與 LiveData 或 RxJava 等響應式程式設計庫一起使用,以實現資料的實時更新和響應。

3.3.2 ViewModel基礎使用

如何在 ViewModel 中儲存和管理資料,並在 Activity 或 Fragment 重新建立時保持資料的完整性。

  1. 建立一個名為MyViewModel的ViewModel類

```

public class MyViewModel extends ViewModel {

private MutableLiveData<Integer> count = new MutableLiveData<>();

public void setCount(int count) {
    this.count.setValue(count);
}

public LiveData<Integer> getCount() {
    return count;
}

} ```

在這個 ViewModel 類中,我們建立了一個 MutableLiveData 型別的 count 變數,並在 setCount() 方法中設定它的值,同時在 getCount() 方法中將其作為 LiveData 返回,以實現資料的實時更新和響應。

  1. 在 Activity或Fragment中使用ViewModel

```

public class MyActivity extends AppCompatActivity {

private MyViewModel viewModel;

@Overrideprotected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    viewModel = ViewModelProviders.of(this).get(MyViewModel.class);
    viewModel.getCount().observe(this, count -> {
        // 更新 UI
    });
}

public void onButtonClicked(View view) {
    int count = viewModel.getCount().getValue() + 1;
    viewModel.setCount(count);
}

} ```

在這個 Activity 中,我們使用 ViewModelProviders.of() 方法獲取 MyViewModel 的例項,並將其與該 Activity 繫結。

然後,我們可以使用 getCount() 方法獲取 count 變數的值,並在 setCount() 方法中設定其值。

通過呼叫 getCount().observe() 方法,我們可以在 LiveData 資料變化時更新 UI。

這個案例演示瞭如何使用 Android Jetpack ViewModel 來管理 UI 相關的資料,並在 Activity 或 Fragment 重新建立時保持資料的完整性。

使用 ViewModel 可以幫助我們更好地管理和保持與 UI 相關的資料,並提高應用程式的穩定性和可靠性。

3.3.3 ViewModel優勢劣勢

優勢

  1. 保持資料的完整性:ViewModel 可以在螢幕旋轉等配置更改時儲存資料並重新建立 Activity 或 Fragment,從而保持資料的完整性,避免因為生命週期變化而導致的資料丟失。
  2. 簡化程式碼:使用 ViewModel 可以將 UI 相關的資料與 UI 控制元件分離,避免了在 Activity 或 Fragment 中處理資料邏輯的繁瑣程式碼,使程式碼更加清晰、簡潔。
  3. 支援資料共享:ViewModel不會隨著Activity的螢幕旋轉而銷燬,減少了維護狀態的程式碼成本(資料的儲存和讀取、序列化和反序列化);
  4. 支援響應式程式設計:ViewModel 可以與 LiveData 或 RxJava 等響應式程式設計庫一起使用,實現資料的實時更新和響應。

劣勢

  1. 不適合處理長時間執行的任務:ViewModel 主要用於管理 UI 相關的資料,如果需要處理長時間執行的任務,需要在 ViewModel 中使用非同步任務或者單獨使用 Service。
  2. 資料持久化需要額外處理:ViewModel 中儲存的資料只是暫時性的,如果需要長期儲存資料,需要將資料儲存到資料庫或者 SharedPreferences 中。
  3. 需要了解生命週期:ViewModel 是以生命週期感知的方式來儲存和管理資料的,因此需要了解 Activity 或 Fragment 的生命週期以便正確使用 ViewModel。

3.3.4 ViewModel應用場景

下面是一些實際開發過程中可能使用 ViewModel 的使用場景:

  1. 資料持久化:如果需要在應用程式的不同頁面之間共享資料,並且希望在 Activity 或 Fragment 重新建立時保持資料的完整性,可以使用 ViewModel 來儲存和管理資料。

  2. 處理螢幕旋轉等配置更改:當應用程式的螢幕旋轉或其他配置更改時,Activity 或 Fragment 可能會被銷燬並重新建立,此時可以使用 ViewModel 來儲存和恢復資料,避免因為生命週期變化而導致的資料丟失。

  3. 分離 UI 與資料:將 UI 相關的資料與 UI 控制元件分離,避免在 Activity 或 Fragment 中處理資料邏輯的繁瑣程式碼,使程式碼更加清晰、簡潔。

  4. 實現響應式程式設計:ViewModel 可以與 LiveData 或 RxJava 等響應式程式設計庫一起使用,實現資料的實時更新和響應,從而提高應用程式的效能和使用者體驗。

  5. 避免記憶體洩漏:將資料儲存在 ViewModel 中可以避免由於對 Activity 或 Fragment 的引用而導致的記憶體洩漏,從而提高了應用程式的穩定性和可靠性。

3.3.5 ViewModel原理分析

類圖

  • ViewModel類是一個抽象類,包含了一個onCleared()方法,該方法會在ViewModel不再被使用時被呼叫,用於釋放資源和清除狀態。
  • AndroidViewModel是ViewModel的一個子類,它包含一個Application物件,用於在ViewModel中訪問應用程式的上下文。
  • ViewModelProvider是一個幫助類,用於獲取ViewModel例項。
  • ViewModelProvider.Factory是一個介面,用於建立ViewModel例項。

ViewModel.png

原理

Jetpack ViewModel 原始碼位於 androidx.lifecycle.ViewModel 包中,它包含了兩個類:ViewModel 和 ViewModelProvider。

每個Activity都會繫結一個ViewModelStore,ViewModelStore通過HashMap儲存ViewModel和String名稱。ViewModel的持久化依賴ViewModelStore的儲存和獲取。

ViewModel 類的主要作用是定義一個用於儲存 UI 元件資料的容器,並在 UI 元件生命週期變化時進行管理。具體來說,ViewModel 類繼承了 Android 的 ViewModel 類,並添加了一些額外的方法,用於實現以下功能:

  1. 快取 UI 元件資料
  2. 在 UI 元件銷燬後清理資料
  3. 在 UI 元件重建時恢復資料

ViewModelProvider 類的主要作用是建立 ViewModel 例項,併為其提供一個唯一的 key,用於在 Activity 或 Fragment 重建時找回已經存在的 ViewModel 例項。ViewModelProvider 類的實現方式比較簡單,主要是通過一個 HashMap 來儲存 ViewModel 例項,並提供了一些方法,用於建立和查詢 ViewModel 例項。

下面是 ViewModel 類的原始碼分析:

```

open class ViewModel : ViewModelStoreOwner { private val mViewModelStore = ViewModelStore()

@CallSuperoverride fun onCleared() {
    mViewModelStore.clear()
}

fun getViewModelStore(): ViewModelStore {
    return mViewModelStore
}

} ```

ViewModel 類實現了 ViewModelStoreOwner 介面,並在內部維護了一個 ViewModelStore 物件,用於儲存 UI 元件資料。在 ViewModel 被銷燬時,會呼叫 onCleared() 方法來清空 ViewModelStore 中的資料。getViewModelStore() 方法用於返回 ViewModelStore 物件。

ViewModelStoreOwner 介面的實現程式碼如下:

```

interface ViewModelStoreOwner { fun getViewModelStore(): ViewModelStore } ```

ViewModelProvider 類的原始碼分析如下:

```

class ViewModelProvider private constructor( private val mFactory: Factory, private val mViewModelStore: ViewModelStore ) { // 快取 ViewModel 例項的 HashMapprivate val mViewModels = HashMap()

companion object {
    private val sCache = HashMap<String, ViewModelProvider>()

    /**
     * 返回當前 Activity 或 Fragment 的 ViewModelProvider 例項
     */@MainThreadfun of(owner: ViewModelStoreOwner): ViewModelProvider {
        return of(owner, FactoryHolder.DEFAULT_FACTORY)
    }

    /**
     * 返回當前 Activity 或 Fragment 的 ViewModelProvider 例項,可指定 Factory
     */@MainThreadfun of(owner: ViewModelStoreOwner, factory: Factory): ViewModelProvider {
        val store = owner.getViewModelStore()

        // 先從快取中查詢 ViewModelProvider 例項var viewModelProvider = sCache[store.mKey]
        if (viewModelProvider == null) {
            viewModelProvider = ViewModelProvider(factory, store)
            sCache[store.mKey] = viewModelProvider
        }

        return viewModelProvider
    }
}

/**
 * 返回指定 key 的 ViewModel 例項,如果不存在則通過 Factory
 */

fun get(key: String, modelClass: Class): T { var viewModel = mViewModels[key] if (modelClass.isInstance(viewModel)) { @Suppress("UNCHECKED_CAST") return viewModel as T }

// 建立新的 ViewModel 例項,並將其新增到快取中
viewModel = mFactory.create(modelClass)
mViewModels[key] = viewModel

return viewModel

}

/* * Factory 介面,用於建立 ViewModel 例項 / interface Factory { fun create(modelClass: Class): T }

/* * FactoryHolder 類,用於提供預設的 Factory 實現 / private object FactoryHolder { val DEFAULT_FACTORY: Factory = object : Factory { override fun create(modelClass: Class): T { try { return modelClass.newInstance() } catch (e: IllegalAccessException) { throw RuntimeException(e) } catch (e: InstantiationException) { throw RuntimeException(e) } } } } } ```

ViewModelProvider 類包含了一個 HashMap,用於快取 ViewModel 例項。

它的 of() 方法用於建立 ViewModelProvider 例項,並通過 ViewModelStore 物件來區分不同的 Activity 或 Fragment。get() 方法用於返回指定 key 的 ViewModel 例項,並在快取中查詢是否存在對應的例項。如果快取中不存在該例項,則呼叫 Factory 介面來建立一個新的 ViewModel 例項,並將其新增到快取中。

Factory 介面提供了一個 create() 方法,用於建立 ViewModel 例項。FactoryHolder 類用於提供預設的 Factory 實現,它使用 Java 的反射機制來建立 ViewModel 例項。 最後,需要注意的是,ViewModelStore 和 ViewModelProvider 都是執行緒安全的,可以在多個執行緒中同時使用。

這意味著在多執行緒環境下,可以使用 ViewModel 來快取和共享資料,從而減少資料的重複載入和提升應用程式的效能。

3.3.6 ViewModel注意事項

使用了錯誤的 ViewModel:

在某些情況下,可能會需要多個 ViewModel 來管理不同的 UI 資料。如果使用了錯誤的 ViewModel 來管理資料,可能會導致資料丟失或邏輯錯誤。因此,在使用 ViewModel 時應該清楚每個 ViewModel 的作用,避免出現混淆。

ViewModel 與生命週期的關係

ViewModel 的生命週期不同於 Activity 和 Fragment,它是被系統快取的,因此可能會出現資料被清除的情況。在使用 ViewModel 時應該注意它的生命週期,及時儲存資料並恢復資料。

使用無效的上下文

ViewModel 需要一個有效的上下文來建立例項,如果使用無效的上下文可能會導致 ViewModel 建立失敗。因此,在使用 ViewModel 時應該注意上下文的有效性,避免出現建立失敗的情況。

使用了錯誤的作用域

ViewModel 的作用域應該與需要管理的 UI 元件的生命週期相同,如果使用了錯誤的作用域,可能會導致資料被清除或者生命週期不一致。因此,在使用 ViewModel 時應該選擇正確的作用域,避免出現問題。

3.4 DataBinding

3.4.1 DataBinding基礎定義

DataBinding提供了一種宣告性的方式將佈局檔案中的UI元件和應用程式的資料模型繫結在一起。

通過DataBinding,開發者可以將UI元件的值繫結到資料模型中的屬性,使得在更新UI時不需要手動更新每個元件的值。

DataBinding庫通過生成一個繫結類來實現UI和資料模型之間的繫結。

這個繫結類是在編譯時自動生成的,它使用了資料模型和UI元件之間的繫結表示式,以便在執行時執行資料繫結。

3.4.2 DataBinding基礎使用

技術需求

使用DataBinding實現一個簡單的計數器功能。在每次更新計數器的值時,DataBinding會自動更新TextView的值,使得開發者不需要手動更新UI元件的值,可以更加專注於業務邏輯的實現

XML

```

    <TextView
        android:id="@+id/tvCounter"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{counter.toString()}" />

    <Button
        android:id="@+id/btnIncrease"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="+" />

    <Button
        android:id="@+id/btnDecrease"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="-" />
</LinearLayout>

```

Code

建立DataBinding例項,並將activity_main.xml佈局檔案與DataBinding例項繫結。然後,將計數器的值賦給DataBinding例項的counter變數,併為增加和減少按鈕設定點選事件。在點選事件中,更新計數器的值,並將新的值賦給DataBinding例項的counter變數。最後,呼叫DataBinding例項的executePendingBindings()方法,將UI元件的值更新到最新的值

``` class MainActivity : AppCompatActivity() {

private lateinit var binding: ActivityMainBinding

private var counter = 0

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

    // 建立DataBinding例項並與佈局檔案繫結
    binding = DataBindingUtil.setContentView(this, R.layout.activity_main)

    // 設定初始值
    binding.counter = counter

    // 為按鈕設定點選事件
    binding.btnIncrease.setOnClickListener {
        counter++
        binding.counter = counter
        binding.executePendingBindings()
    }

    binding.btnDecrease.setOnClickListener {
        counter--
        binding.counter = counter
        binding.executePendingBindings()
    }
}

} ```

3.4.3 DataBinding優勢劣勢

優勢

  1. 簡化程式碼

    使用DataBinding可以減少findViewById()方法的呼叫,使得程式碼更加簡潔和易讀。開發者可以通過DataBinding直接訪問佈局檔案中的UI元件,而不需要手動查詢和設定UI元件的屬性。

  2. 雙向繫結

    DataBinding支援雙向繫結,可以將UI元件的值繫結到資料模型中的屬性,也可以將資料模型的屬性繫結到UI元件的值。這種雙向繫結可以幫助開發者更加方便地實現UI和資料模型之間的同步。

  3. 減少錯誤

    DataBinding可以減少程式碼中的錯誤,因為使用DataBinding可以消除某些手動編寫的程式碼。例如,DataBinding可以自動生成一些程式碼,幫助開發者繫結UI元件和資料模型,減少手動編寫程式碼的機會。

  4. 效能優化

    DataBinding可以帶來一些效能優化,例如通過生成繫結類來減少程式碼的執行時間。DataBinding還支援懶載入,可以在需要時才載入資料模型和UI元件。

劣勢

  1. 相容性問題

    DataBinding庫需要使用較新的Android Gradle外掛和Android SDK版本,這可能導致一些相容性問題。如果開發者使用較老的Android版本,可能需要進行一些相容性處理。

  2. 構建時間增加

    使用DataBinding可能會增加應用程式的構建時間,因為DataBinding庫需要生成繫結類。在編譯時需要分離和處理xml檔案,增加了編譯時間。

  3. 難以除錯

    由於DataBinding庫使用了程式碼生成,因此在除錯時可能會出現一些困難。例如,開發者可能會發現在使用DataBinding時,偵錯程式可能會跳過某些程式碼行,或者某些變數的值可能無法正確顯示。

  4. 增大包的體積

    佔用大量記憶體,每個可觀測資料物件都會對應一個監聽器WeakListener物件。

3.4.4 DataBinding應用場景

  1. MVVM架構:DataBinding可以幫助開發者實現MVVM架構中的檢視模型(ViewModel)和檢視(View)之間的繫結。通過DataBinding,開發者可以將檢視和資料模型分離,提高程式碼的可讀性和可維護性。
  2. 佈局優化:DataBinding可以幫助開發者實現佈局優化,例如通過使用繫結表示式和條件語句來控制UI元件的可見性。通過佈局優化,可以使得應用程式的UI更加靈活和可定製。
  3. 多語言支援:DataBinding可以幫助開發者實現多語言支援,例如通過使用繫結表示式來動態設定UI元件的文字。通過多語言支援,可以使得應用程式的使用者介面更加友好和易用。
  4. 動態主題:DataBinding可以幫助開發者實現動態主題,例如通過使用繫結表示式來動態設定UI元件的顏色和樣式。通過動態主題,可以使得應用程式的UI更加美觀和精緻。

3.4.5 DataBinding原理分析

類圖

原始碼

DataBinding的原理主要包括以下幾個方面:

  1. 佈局檔案的解析和生成:在DataBinding中,佈局檔案會被解析並生成對應的ViewDataBinding類。這個類包含了佈局檔案中定義的所有UI元件的引用,以及繫結到這些UI元件的資料物件。
  2. 資料物件的繫結:DataBinding會在編譯時生成程式碼來完成UI元件和資料物件的繫結。生成的程式碼會被包含在BR類中。這個類包含了應用程式中所有用於繫結的變數的引用。當資料物件發生變化時,DataBinding會自動更新UI元件的值。
  3. 觀察者模式的應用:DataBinding使用觀察者模式來保持UI和資料之間的同步。當資料物件發生變化時,DataBinding會自動更新UI元件的值。在DataBinding中,資料物件是被觀察的物件,而UI元件是觀察者。
  4. 雙向繫結的實現:DataBinding支援雙向繫結,即當UI元件的值發生變化時,DataBinding會自動更新資料物件的值。這是通過使用雙向繫結介面卡來實現的。介面卡會在UI元件的值發生變化時自動更新資料物件的值。
  5. 屬性轉換器的使用:DataBinding還支援屬性轉換器,可以用來將資料物件中的值轉換為UI元件可以顯示的值。例如,可以使用屬性轉換器將日期格式化為特定的格式。
  6. 資料繫結表示式的應用:DataBinding還支援資料繫結表示式,可以用來在佈局檔案中動態計算UI元件的值。例如,可以使用資料繫結表示式將兩個文字框中的值相加並將結果顯示在第三個文字框中。

總的來說,Android Jetpack DataBinding的原理是基於資料繫結和觀察者模式的。它通過生成程式碼來完成UI元件和資料物件之間的繫結,並使用觀察者模式來保持UI和資料之間的同步。同時,DataBinding還支援雙向繫結、屬性轉換器和資料繫結表示式等特性,使得它可以滿足更加複雜的UI和資料互動需求。

3.4.6 DataBinding注意事項

  1. 錯誤:Cannot find the setter for attribute ‘XXX’ with parameter type XXX。這個錯誤通常是由於繫結表示式中的變數型別不正確或佈局檔案中的屬性名稱不正確導致的。可以檢查變數型別和屬性名稱是否正確。
  2. 錯誤:Could not find accessor XXX。這個錯誤通常是由於佈局檔案中使用了不存在的變數或方法導致的。可以檢查佈局檔案中使用的變數和方法是否存在。
  3. 錯誤:Variable XXX has incompatible type XXX。這個錯誤通常是由於DataBinding的變數型別和佈局檔案中的變數型別不一致導致的。可以檢查DataBinding程式碼中變數的型別和佈局檔案中變數的型別是否一致。
  4. 錯誤:DataBinding不支援lambda表示式。如果使用lambda表示式,編譯時會出現錯誤。可以使用方法引用或匿名內部類來替代lambda表示式。
  5. 效能問題:在使用資料繫結表示式時,要注意表示式的複雜度。複雜的表示式可能會影響應用程式的效能。可以嘗試將複雜的表示式分解成更簡單的表示式,以提高應用程式的效能。
  6. 混淆問題:如果應用程式使用了程式碼混淆,那麼需要將DataBinding的類排除在混淆範圍之外,否則會導致編譯錯誤或執行時異常。
  7. ViewStub問題:在使用DataBinding的佈局檔案中,不能包含ViewStub,否則會導致編譯錯誤。

3.4.7 ButterKnife VS ViewBinding VS RoboBinding VS DataBinding

image.png

四、總結與展望

《Android業務架構 · 基礎篇 · Jetpack四件套》一文首先通過4W2H全方位的講解了Jepack對Android業務開發的價值,然後通過基礎定義、基礎使用、優劣勢分析、應用場景、原理分析、注意事項等多維度分析了Jetpack四件套。

在我們工作中,可維護、可擴充套件、可測試和可重用的業務架構對於提高應用程式的質量和效率意義非凡,而JetPack是幫助開發者快速地組織和管理應用程式的程式碼的工具包。

這也是小木箱強烈建議大家學習Jetpack很重要的原因。希望通過這篇文章能夠讓你意識到Jetpack對業務開發的重要性。

提高篇將介紹MVC、MVP、MVVM和MVI四劍客,同樣是Android業務架構核心內容。 今天就到這裡啦,我是 小木箱,我們下一篇見~