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方法了,這樣實現了對其生命週期的控制。
 
相關文章
相關標籤/搜索