Android Jetpack架構組件之 ViewModel (源碼篇)

一、前言

最近簡單看了下google推出的框架Jetpack,感受此框架的內容能夠對平時的開發有很大的幫助,也能夠解決不少開發中的問題,對代碼的邏輯和UI界面實現深層解耦,打造數據驅動型UI界面。java

Android Architecture組件是Android Jetpack的一部分,它們是一組庫,旨在幫助開發者設計健壯、可測試和可維護的應用程序,包含一下組件:bash

上述時Android Architecture所提供的架構組件,本文主要從使用和源碼的角度瞭解一下ViewModel組件的機制架構

二、VIewModel簡介

Android框架管理UI控制器的生命週期,例如活動和片斷。框架能夠決定銷燬或從新建立UI控制器以響應徹底不受您控制的某些用戶動做或設備事件,那設想一種狀況當用戶在界面操做錄入了一些信息後,由於某種緣由致使Activity從新建立,那此時用戶寫好的信息呢?若是要重頭再來可能有的用戶就會不耐煩了,進而減小了使用。。。,可能有人說,可使用該 onSaveInstanceState()方法並從包中恢復其數據 onCreate(),但此方法僅適用於能夠序列化而後反序列化的少許數據,若是要恢復的數據量比較大,此時就時VIewModel的厲害之處了。併發

ViewModel之因此能在Activity重建時保存並恢復數據,由於Activity初次建立時會初始化建立VIewModel,在Activity銷燬時,ViewModel對象不會銷燬,在新的Activity從新建立後,仍然會執行以前的獲取ViewModel的過程,Android系統採起了處理機制,使如今拿到的ViewModel就是前一次建立的對象,設想一下數據都儲存在VIewModel中,而兩次拿到的都是同一個VIewModel,那顯示的數據天然就和以前的同樣嘍,這裏先放一張系統處理ViewModel的建立、存儲和獲取流程圖:app

三、ViewModel的使用

  • 建立ViewModel類繼承系統的ViewModel
class Model : ViewModel() {
    var textName = "Empty"
}複製代碼
  • 在使用的Activity、Fragment中獲取ViewModel
val model = ViewModelProviders.of(this)[Model::class.java]複製代碼
  • 設置ViewModel中儲存的數據
tvModel.text = model.textName複製代碼
  • 實例:先添加一個按鈕,點擊按鈕會修改Model中textName的值,而後在旋轉手機,若是顯示的爲第二次設置的值,那麼說明數據被保存且恢復了;
btn_change.setOnClickListener {
            model.textName = "Change = 22222"
            tvModel.text = model.textName
        }複製代碼

  • 注意:由於ViewModel在Activity銷燬時是不會從新建立的,這也意味者ViewModel中不能夠引用Activity的對象,不然會有內存泄露的問題,那麼當Model中須要Context呢?Android爲咱們提供了AndroidViewModel,只需繼承AndroidViewModel便可

四、生命週期

  • ViewModel對象的範圍是在獲取ViewModel時傳遞給ViewModelProvider的Lifecycle生命週期
  • ViewModel在內存中直到Activity銷燬或Fragment被移除
  • 系統首次調用活動對象的onCreate()方法時,一般會請求ViewModel
  • 系統可能會在整個活動的整個生命週期中屢次調用onCreate(),例如當設備屏幕旋轉時
  • ViewModel從第一次請求ViewModel直到活動完成並銷燬時存在


五、源碼解析

5.1 、ViewModelProviders.of(this)[Model::class.java]框架

從上面的方法中能夠看出ViewModel的獲取過程分爲兩步:ide

  • 獲取ViewProvider:

ViewModelProviders提供四個構造方法建立VIewProvider,兩個帶有factory兩個沒有,不過沒有factory的其實使用的是默認的Factory,因此四個方法基本一致只是Fragment和Activity的區分源碼分析

@NonNull
@MainThread
public static ViewModelProvider of(@NonNull Fragment fragment) {
        return of(fragment, null);
    }

   
@NonNull
@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity) {
        return of(activity, null);
    }

@NonNull
@MainThread
public static ViewModelProvider of(@NonNull Fragment fragment, @Nullable Factory factory) {
        Application application = checkApplication(checkActivity(fragment));
        if (factory == null) {
            factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
        }
        return new ViewModelProvider(ViewModelStores.of(fragment), factory);
    }

@NonNull
@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity,
            @Nullable Factory factory) {
        Application application = checkApplication(activity);
        if (factory == null) {
            factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
        }
        return new ViewModelProvider(ViewModelStores.of(activity), factory);
    }複製代碼
  • 獲取ViewModelStore:由前面的源碼能夠知道建立ViewProvider時傳入兩個參數:ViewModelStore 和 Factory;顯然從名字就能夠看出他們的做用,Factory負責建立,ViewModelStore負責存儲
@NonNull
    @MainThread
    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        ViewModel viewModel = mViewModelStore.get(key);

        if (modelClass.isInstance(viewModel)) {
            //noinspection unchecked
            return (T) viewModel;
        } else {
            //noinspection StatementWithEmptyBody
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }

        viewModel = mFactory.create(modelClass);
        mViewModelStore.put(key, viewModel);
        //noinspection unchecked
        return (T) viewModel;
    }複製代碼

上面的執行是,先去ViewModelStore中獲取,若是爲空就調用Factory的create()建立ViewModel,並儲存在VIewmoStore中,與咱們所想一致;post

  • ViewModelProviders.of(this)[Model::class.java]方法,忽略建立和儲存細節,執行邏輯爲:

5.2 、VIewModelStore測試

  • ViewModelStore.of(this)

上述過程當中使用ViewModelStore.of(this) 建立ViewModelStore,方法源碼:

@NonNull
    @MainThread
    public static ViewModelStore of(@NonNull FragmentActivity activity) {
        if (activity instanceof ViewModelStoreOwner) {
            return ((ViewModelStoreOwner) activity).getViewModelStore();
        }
        return holderFragmentFor(activity).getViewModelStore();
    }複製代碼

先判斷Activity是否爲 ViewModelStoreOwner,若是是直接獲取其中的ViewModelStore,不然調用holderFragmentFor(activity).getViewModelStore()獲取

  • holderFragmentFor(activity).
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public static HolderFragment holderFragmentFor(FragmentActivity activity) {
        return sHolderFragmentManager.holderFragmentFor(activity);
    }

// HolderFragmentManger

 HolderFragment holderFragmentFor(FragmentActivity activity) {
            FragmentManager fm = activity.getSupportFragmentManager();
            HolderFragment holder = findHolderFragment(fm); // 根據TAG查找Fragment
            if (holder != null) {
                return holder;
            }
            holder = mNotCommittedActivityHolders.get(activity); // 從儲存的集合中獲取
            if (holder != null) {
                return holder;
            }

            if (!mActivityCallbacksIsAdded) {
                mActivityCallbacksIsAdded = true;
                activity.getApplication().registerActivityLifecycleCallbacks(mActivityCallbacks);
            }
            holder = createHolderFragment(fm);  // 建立HolderFragment
            mNotCommittedActivityHolders.put(activity, holder); // 保存到集合
            return holder;
        }複製代碼
  • findHolderFragment() & createHolderFragment()
private static HolderFragment findHolderFragment(FragmentManager manager) {
            if (manager.isDestroyed()) {
                throw new IllegalStateException("Can't access ViewModels from onDestroy");
            }
             // 根據TAG查找
            Fragment fragmentByTag = manager.findFragmentByTag(HOLDER_TAG);
            ...
            return (HolderFragment) fragmentByTag;
        }


 private static HolderFragment createHolderFragment(FragmentManager fragmentManager) {
            HolderFragment holder = new HolderFragment();
            fragmentManager.beginTransaction().add(holder, HOLDER_TAG).commitAllowingStateLoss();   // 設置TAG
            return holder;
        }複製代碼

5.三、HolderFragment

上面的過程都是獲取或建立HolderFragment的過程,有沒有想過咱們要的是儲存ViewModel的地方,爲何一值在操做Fragment,答案就在其中:

  • 首先記得前面的判斷嗎?instanceof ViewModelStoreOwner?相信此時應該會想到HolderFragment就是ViewModelStoreOwner的實現類了吧
public class HolderFragment extends Fragment implements ViewModelStoreOwner {
}複製代碼
  • HolderFragment中保存了ViewStore的實例,
private ViewModelStore mViewModelStore = new ViewModelStore();

    @NonNull
    @Override
    public ViewModelStore getViewModelStore() {
        return mViewModelStore;
    }複製代碼
  • 獲取ViewStore的執行邏輯:

  • ViewStore是如何獲取、儲存Viewmodel的呢?
private final HashMap<String, ViewModel> mMap = new HashMap<>();

    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.put(key, viewModel);
        if (oldViewModel != null) {
            oldViewModel.onCleared();
        }
    }

    final ViewModel get(String key) {
        return mMap.get(key);
    }複製代碼

整個ViewModel的源碼分析到此結束了,總結一下內部的儲存邏輯

  1. 根據傳入的Activity獲取、建立、添加並已鍵值對保存Fragment
  2. 獲取Fragment中保存的ViewStore對象(ViewStore中使用Map儲存ViewModel)
  3. 建立ViewProvider實例,ViewProvider中封裝了獲取的ViewStore和建立用的Factory
  4. 從VIewStore的Map中或Factory的create()中獲取ViewModel

六、思考

  • 如何保證兩次建立的activity(旋轉先後)獲取到的爲同一個ViewModel,

在上面獲取Fragment中時,建立過HolderFragment後保存在Map中,如今咱們看一下這個map

private Map<Activity, HolderFragment> mNotCommittedActivityHolders = new HashMap<>();複製代碼

Map的鍵就是Activity的實例,因此不管多少次建立都是此Activity的實例,也就得到惟一的一個對應的Fragment

  • 針對上面的特性是否有其餘用處

按照上一個問題的邏輯,主要是傳入的Activity的對象一致,那獲取到就是同一個Fragment,存儲的也是同一個VIewStore,那設想一下,若是一個Activity中有多個Fragment,利用這個特性就能夠實現數據交互了;例如:兩個Fragment之間。一個顯示標題列表,點擊某一個標題,另外一個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;
    }
}


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.
        });
    }
}複製代碼
  1. 上述代碼中使用了LiveData,暫且把他當作一個觀察改變併發射數據的功能,在第一個Fragment中修改,第二個Fragment也會響應變化;
  2. 在獲取ViewModelProvider時,這兩個片斷都使用getActivity() 所以,這兩個片斷都會收到相同的SharedViewModel實例,該實例的做用域爲Acyivity
  3. 使用ViewModel共享數據的好處,兩個Fragment之間,Fragment和Activity之間無需任何聯繫,實現真正的解耦,每一個片斷都有其本身的生命週期,而且不受其餘生命週期的影響

到此ViewModel的介紹完成了,將ViewModel和LiveData或其餘組件聯合使用,構造數據驅動型的界面,相信必定會帶來不同的體驗。

相關文章
相關標籤/搜索