1. 程式人生 > >Android ViewModel詳解

Android ViewModel詳解

1. ViewModel概述

ViewModel類被設計為通過lifecycle感知的方式儲存和管理UI相關資料。ViewModel類允許資料在配置更改(如螢幕旋轉)中保活。

注意:要將ViewModel匯入到Android專案中,請參見將元件新增到專案中

Android框架管理UI控制器的生命週期,如activities和fragments。該框架可以決定銷燬或重新建立UI控制器,以響應某些完全超出您控制的使用者操作或裝置事件。

如果系統銷燬或重新建立UI控制器,則在其中儲存的任何與UI相關的臨時資料都會丟失。例如,您的應用程式可能在一個Activity中包含使用者的列表。當配置更改時候activity會重新建立,新Activity必須重新獲取使用者列表。對於簡單資料,該Activity可以使用onSaveInstanceState()方法並從onCreate()中的bundle恢復其資料,但是這種方法僅適用於可以序列化/反序列化的少量資料,而不適用於潛在的大量資料,如使用者列表或點陣圖。

另一個問題是UI控制器經常需要進行非同步呼叫,這可能需要一些時間才能返回。UI控制器需要管理這些呼叫,並確保在控制器銷燬的時候,系統能清理它們,以避免潛在的記憶體洩漏。這種管理需要大量的維護,在配置更改時需要重新建立物件的情況下,這是資源的浪費,因為物件可能必須重新發出它已經發出的呼叫。

UI控制器(如activities和fragments)主要用於顯示UI資料、對使用者動作作出反應或處理與作業系統間的通訊(如許可權請求)。要求UI控制器還負責從資料庫或網路載入資料,這些都造成了控制器程式碼急劇膨脹。將過多的職責分配給UI控制器會導致一個類試圖自己處理應用程式的所有工作,而不是將工作委託給其他類。以這種方式向UI控制器分配過度的職責也會使測試變得更加困難。

將檢視資料所有權與UI控制器邏輯分離是更容易和更有效的。

2. 實現一個ViewModel

架構元件為UI控制器提供ViewModel幫助類,用於負責為UI準備資料。ViewModel物件在配置更改期間自動保留,以便它們儲存的資料可立即用於下一個activity或fragment例項。例如,如果需要在app中顯示使用者列表,請確保將獲取和保持使用者列表的責任分配給ViewModel,而不是activity或fragment,如下面的示例程式碼所示:

public class MyViewModel extends ViewModel {
    private MutableLiveData<List<User>> users;
    public LiveData<List<User>> getUsers() {
        if (users == null) {
            users = new MutableLiveData<List<User>>();
            loadUsers();
        }
        return users;
    }

    private void loadUsers() {
        // Do an asynchronous operation to fetch users.
    }
}

然後,可以從以下活動訪問列表:

public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        // Create a ViewModel the first time the system calls an activity's onCreate() method.
        // Re-created activities receive the same MyViewModel instance created by the first activity.

        MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
        model.getUsers().observe(this, users -> {
            // update UI
        });
    }
}

如果activity被重新建立,它將接收由第一個活動建立的相同的MyViewModel例項。當所有者activity finished時,框架呼叫ViewModel物件的onCleared()方法,以便它可以清理資源。

注意:ViewModel絕不能引用view、Lifecycle或任何可能引用了activity上下文的類

ViewModel物件被設計為Lifecycle超出views或LifecycleOwners的特殊的例項。這個設計還意味著你可以編寫測試來更容易地覆蓋ViewModel,因為它不知道view和Lifecycle物件。ViewModel物件可以包含LifecycleObservers,如LiveData物件。然而,ViewModel物件絕不可能觀察到具有生命週期感知變數(如LiveData物件)的變化。如果ViewModel需要應用程式上下文,例如查詢系統服務,那麼它可以繼承AndroidViewModel類,並具有接收Application的建構函式,因為Application類繼承了Context

3. ViewModel的生命週期

在獲ViewModel時,ViewModel物件被視為Lifecycle傳遞給ViewModelProvider。ViewModel一直保留在記憶體中,直到它的作用域永久消失:在activity的情況下,當它finishes時,而在fragment的情況下,當它被detached時。

image

圖1說明了一個activity的各種生命週期狀態,因為它經歷了一個旋轉,然後finished。該插圖還顯示了與activity關聯的ViewModel的生命週期。這個特殊的圖表說明了activity的狀態。相同的基本狀態適用於fragment的生命週期。

通常,當系統首次呼叫Activity物件的onCreate()方法時,通常會請求ViewModel。系統可能在活動的整個生命週期中多次呼叫onCreate(),例如當裝置螢幕被旋轉時。從你第一次請求ViewModel時,ViewModel一直存在,直到activity完成並銷燬。

4. 在fragments之間共享資料

一個activity中的兩個或多個fragments需要相互通訊是很常見的。設想一個主細節fragments的常見情況,其中有一個fragment,其中使用者從列表中選擇項,另一個fragment顯示所選擇的內容。這種情況從來都是很重要的,因為兩個fragments都需要定義一些介面描述,並且所有者activity必須將兩者繫結在一起。此外,兩個fragment都必須處理其他fragment尚未建立或可見的場景。

可以通過使用ViewModel物件來解決這個共同的疼痛點。這些fragments可以使用其activity範圍內的共享ViewModel來處理此通訊,如下面的示例程式碼所示:

public class SharedViewModel extends ViewModel {
    private final MutableLiveData<Item> selected = new MutableLiveData<Item>();

    public void select(Item item) {
        selected.setValue(item);
    }

    public LiveData<Item> getSelected() {
        return selected;
    }
}


public class MasterFragment extends Fragment {
    private SharedViewModel model;
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        itemSelector.setOnClickListener(item -> {
            model.select(item);
        });
    }
}

public class DetailFragment extends Fragment {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        model.getSelected().observe(this, item -> {
           // Update the UI.
        });
    }
}

注意,在獲取ViewModelProvider時,兩個fragments都使用getActivity()。結果,兩個fragments都接收到與activity相關的SharedViewModel例項。

這種方法提供了以下好處:

  • activity不需要做任何事情,也不需要知道關於通訊的任何事。

  • 除了SharedViewModel契約之外,Fragments不需要互相瞭解。如果其中一個fragment消失,另一個像往常一樣繼續工作。

  • 每個fragment都有自己的生命週期,不受另一個生命週期的影響。如果一個fragment替換了另一個fragment,則UI繼續工作而不會出任何問題。

5. ViewModel替換Loaders

CursorLoader這樣的Loader類經常被用來保持app的UI中的資料與資料庫同步。您可以使用ViewModel和其他幾個類來替換loader。使用ViewModel將UI控制器與資料載入操作分離,這意味著類之間的強引用更少。

在使用loaders的一種常見方法中,app可能使用CursorLoader來觀察資料庫的內容。當資料庫中的值發生變化時,loader將自動觸發資料的重新載入並更新UI:

image

圖2. loaders載入資料

ViewModel使用RoomLiveData來替換loader。ViewModel能確保資料倖存於裝置配置更改。當資料庫改變時,Room通知您的LiveData,LiveData又用修改後的資料更新UI。

image

圖3. ViewModel載入資料

6. 附加資源

這篇部落格文章描述瞭如何使用一個LiveData的ViewModel來代替AsyncTaskLoader

隨著資料變得越來越複雜,您可能會選擇單獨的類來載入資料。ViewModel的目的是封裝UI控制器的資料,讓資料在配置更改時繼續存活。有關如何跨配置更改載入、儲存和管理資料的資訊,請參閱儲存UI狀態

Android應用程式體系結構指南建議建立一個倉庫類來處理這些功能。

ViewModel是一個Android Jetpack架構元件。在Sunflower演示應用程式中使用了它。