Android Architecture Components 系列(四)ViewModel


帶着下面的這個問題開始ViewModel的學習:
    ViewModel的生命周期是如何控制的,並且如何保證在一定范圍內的唯一性?
 
官方文檔里這樣寫到:
    The ViewModel class is designed to store and manage UI-related data in a lifecycle conscious way. The ViewModel class allows data to survive configuration changes such as screen rotations.
    ViewModel 簡單來說 這個類是設計用來存儲UI層的數據,以及管理對應的數據,並且這些數據不受配置變化的影響。能夠做到當數據修改的時候,可以馬上刷新Ui效果,比如屏幕的旋轉操作。
引言
    Android系統本身提供控件,比如Activity 和Fragment ,這些控件都是具有生命周期方法,這些生命周期方法被系統調用。
  •      activity or Fragment 不適於保存大量數據
    但是當這些控件因為一些原因被系統隨時銷毀或是重新創建時候,任何存放在這里的數據都有可能會丟失。舉個栗子,Activity中有一個查詢得到的用戶列表,這時候Activity被重建,新的Activity需要再次去獲取用戶數據。如果簡單的數據可以使用控件自帶的方法,將數據保存到onSaveInstances()方法中,在下次OnCreate()中重新將數據取出來,比如UI狀態這類少量數據是可以的,但是對於上述提到的大量的數據,比如列表數據,這樣做就很不合時宜了。
  •    在Activity中進行大量的耗時操作和數據的回調管理會造成大量的資源浪費
    另一個問題,經常需要在Activty中加載數據,這些數據一般是異步耗時操作,因為獲取數據需要聯網或是花費很長時間。當前的Activity就需要管理這些數據調用,否則可能產生內存泄露的問題。這些回調事件可能會非常耗時,這時候Ui組件管理這些調用的同時,在UI組件銷毀時候還需要清除這些調用。這就造成需要花費更多成本進行維護管理,而且在UI重建時候如configuration change,又需要再次重新調用,造成了很多資源的浪費。
  •     Activity的代碼臃腫造成了維護和測試的不友好
    同時Ui組件不僅僅只是用來加載數據,更要對用戶的操作作出響應和處理,還要加載其他資源,導致Ui類變的越來越大,越來越臃腫,這就是常說的上帝類。這種情況對代碼的維護和 測試 都是非常不友好的。
    前人在這些問題的基礎上開發出了MVP框架 ,創建相同類似於生命周期函數做代理,一方面減少Activity的代碼量,一方面優化了各個功能模塊的邏輯。
    
ViewModel
Google官方提出的AAC 的ViewModel 就是用於解決上述問題。
    ViewModel 用於為Ui組件提供管理數據,並且能夠在需要的時候扔能保持里面的數據。其提供的自動綁定的形式,當數據源有更新的時候可以自動立即的更新Ui效果。
下面看一個官方的小代碼實例:
    publicclass MyViewModel extends ViewModel { 
    privateMutableLiveData<List<User>> users; 
    publicLiveData<List<User>>getUsers() { 
        if(users ==null) { 
        users =newMutableLiveData<List<Users>>(); 
            loadUsers(); 
        } 
        returnusers; 
    } 
        privatevoidloadUsers() { 
        // do async operation to fetch users 
        } 
   }
You can then access the list from an activity as follows:
    Activity 訪問User List 數據 
publicclass MyActivity extends AppCompatActivity { 
    publicvoidonCreate(Bundle savedInstanceState) { 
        MyViewModel model = ViewModelProviders.of( this).get(MyViewModel.class); 
        model.getUsers().observe(this, users -> { 
            // update UI 
         }); 
       }
         @Override
        protected void onDestroy() {
            super.onDestroy();
            mViewModelStore.clear()
        }
  }
      當我們獲取ViewModel實例的時候,ViewModel 對象是通過ViewModelProvider保存在LifeCycle中,ViewModel會一直保存在LifeCycle中,直到Activity或是Fragment被銷毀掉,Framework會調用ViewModelStore的clear方法,也就是調用ViewModel的onCleared()方法來進行資源的清理,那么ViewModel 也會被銷毀的。
Caution: A ViewModel must never reference a view, Lifecycle, or any class that may hold a reference to the activity context.
ps:因為ViewModel的生命周期是和Activity分開的,所以在ViewModel中禁止引用任何View對象或者任何引用了Activity的Context的實例對象。如果ViewModel中需要Application的context可以繼承AndroidViewModel類。
那么思考 用戶主動按了返回Home鍵,主動銷毀了這個Activity呢?
這時候系統會調用ViewModel的onClear()方法 清除ViewModel中的數據。
    先上一張 ViewModel的生命周期示意圖:
    如圖 ,VIewModel相對於Activity 或是Fragment 的生命周期來說非常簡單,就一個生命周期函數:onCleared(),會在Activity的onDestroy()之后執行,那么是不是可以說在Fragment的生命周期函數內也是在onDestroy之后執行呢?
ViewModel的實現過程
    //獲取當前類的ViewModel提供者,之后在傳入需要獲得的ViewModel的類型
 MyViewModel model = ViewModelProviders.of(this) .get(MyViewModel.class); 
 
    解析:如果傳入的是this 是Fragment 就先判斷是否已經關聯到Activity上,沒有就拋出非法參數異常。之后在初始化一個sDefaultFactory對象,用於創建ViewModelProvider,並在viewModelProvider的構造函數中初始化一個ViewModelStores對象
 
 
倆個工廠方法用於創建ViewModelStore ,並區分傳入的是Activity 還是 Fragment
以傳入的是Activity為例:
    創建FragmentManager對象,並獲取,查找當前的Activity有沒有添加過HoldFragment, 如果沒有呢則去還沒有添加的Activity/Fragment 的 HoldFragment列表中查詢,看看有沒有已經創建的HoldFragment。如果沒有就創建一個新的HoldFragment ,同時給Application注冊一個Activity的生命監聽器,再把創建餓的HoldFragment添加到緩存列表中。
HoldFragment()又是如何操作的呢?
 
   在onCreate方法中執行了將在未添加到Activity或是Fragment的HolderFragment列表中刪除當前的Activity 或是Fragment。
    在onDestroy方法中執行了ViewModel的clear方法,當Ui組件被銷毀的時候自動通知Lifecycle進行解除綁定清除ViewModel資源的操作。
     簡單總結以上內容:
 
  • 查找當前的Activity/Fragment中是否有已經添加的HoldFragment,有則返回。
  • 查找當前的Activity/Fragment是否有已經創建但是並未添加的HoldFragment,有則返回。
  • 注冊Activity/Fragment的生命周期監聽。
  • 創建新的HoldeFragment,並添加的緩存列表。
  • HoldFragment在關聯到Activity/Fragment之后會在緩存中去掉當前的Activity/Fragment對應的HoldFragment
  • HoldFragment在onDestory的時候會調用其成員變量mViewStore的clear方法。
回到之前創建ViewModelProvider的地方:
/**
* Creates {@code ViewModelProvider}, which will create {@code ViewModels} via the given
* {@code Factory} and retain them in the given {@code store}.
*
* @param store   {@code ViewModelStore} where ViewModels will be stored.
* @param factory factory a {@code Factory} which will be used to instantiate
*                new {@code ViewModels}
*/
public ViewModelProvider(@NonNull ViewModelStorestore, @NonNull Factory factory) {
    mFactory = factory;
    this.mViewModelStore= store;
}
    構造方法中先給兩個成員變量賦值,然后通過ViewModelStore的get方法獲取ViewModel對象
        viewModel = mFactory .create(modelClass);
        mViewModelStore.put(key,viewModel);
    如果獲取不到傳入類的ViewModel 就通過工廠類Factory創建一個新的viewModel 通過put方法添加到ViewModelStore中。
簡而言之就是利用Fragment的方式去獲取生命周期,然后再利用工廠類創建ViewModel。
關於在一定范圍內的唯一性,因為ViewModelStore是HoldFragment的成員變量,HoldFragment是通過FragmentManager添加到指定的Activity/Fragment,那么對於當前的宿主,只有一個HoldFragment,也就只有一個ViewModelStore,同時也就只有一個ViewModel。
 
ViewModel的在Fragment間的數據分享
     有時候一個Activity中的兩個或多個Fragment需要分享數據或者相互通信,這樣就會帶來很多問題,比如數據獲取,相互確定生命周期。
        ViewModel可以很好的解決該類問題。有兩個Fragment,一個Fragment提供點擊每個item顯示的詳情,另一個Fragment提供一個列表。那兩個的交互代碼應該是如何表現的呢?
實例代碼如下:
//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; 
    } 
//第一個Fragment
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); 
                    }); 
        } 
   } 
    //第二個Fragment
public class DetailFragment extends LifecycleFragment { 
    public void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        SharedViewModel model = ViewModelProviders.of(getActivity())
            .get(SharedViewModel.class); 
        model.getSelected().observe(this, { 
                    item -> // update UI 
            }); 
        }
    }
兩個Fragment都是通過getActivity()來獲取 ViewModelProvider。這意味着兩個Activity都是獲取的屬於同一個Activity的同一個ShareViewModel實例。
這樣做優點如下:
  • Activity不需要寫任何額外的代碼,也不需要關心Fragment之間的通信。
  • Fragment不需要處理除SharedViewModel以外其他的代碼。這兩個Fragment不需要知道對方是否存在。
  • Fragment的生命周期不會相互影響,即使用其他Fragment替換其中的一個Fragment,另一個依然能也不受影響。
 

ViewModel和SavedInstanceState對比

    最后前文提到保存簡單的數據可以使用Activity自帶的SavedInstanceState方法,那這個和viewMOdel的區別是?
    ViewModel使得在屏幕旋轉等操作時候保存數據變得很便捷,但是這不能用於應用被系統kill時的持久化數據。舉個簡單的例子,有一個界面展示國家信息。不應該把整個國家信息放到SavedInstanceState里,而是把國家對應的id放到SavedInstanceState,等到界面恢復時,再通過id去獲取詳細的信息。這些詳細的信息應該被存放在數據庫中。說到數據庫,下篇文章將會介紹Android Architecture Components提供的Room來操作數據庫。
 
小結
    ViewModel其實就是通過給宿主添加Fragment的方式來獲取宿主的生命周期。在HoldFragment中持有一個集合用於保存當前宿主的ViewModel,只需要在onDestroy方法中調用集合的clear方法,就能間接調用到ViewModel的onCleared方法了,這樣實現了對其生命周期的控制。
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM