Android技術棧(四)Android Jetpack MVVM 徹底實踐

1 MVVM總覽

本文包含AndroidMVVM體系中的不少部分,主要對ViewModel+DataBinding+RxJava+LiveData+Lifecycle等筆者所使用的技術體系進行解析.java

本文字數較多,內容較爲完整而且後續還會追加更新,閱讀本篇文章須要較長時間,建議讀者分段閱讀.react

全部文字均爲我的學習總結和理解,僅供參考,若有紕漏還請指出,筆者不勝感激.android

1.1 配置環境

  • 筆者的Android Studio版本=3.2
  • Jetpack最低兼容到Android=2.1,API=7

1.2 爲何要選擇MVVM?

要回答這個問題首先就要介紹MVCMVP這兩種模式,從MVCMVVM其實你們想的都是怎麼把ModelView儘量的拆開(熟悉三者定義的朋友能夠跳過該節).git

1.2.1 MVC

MVCModel-View-Controller)即傳統Android開發中最經常使用的模式:github

  • 一般使用Activity/Fragment做爲Controller層,
  • android.view.View的子類以xml構建文件構建起的佈局做爲View
  • SQLite數據庫,網絡請求做爲Model層.

但因爲Activity/Fragment的功能過於強大而且實際上包含了部分View層功能,致使最後Activity/Fragment既承擔了View的責任,又承擔了Controller的責任.因此通常較複雜的頁面,Activity/Fragment很容易堆積代碼,最終致使Controller混雜了View層和業務邏輯(也就是大家所知道的一個Activity三千行)web

MVCView層與Model幾乎幾乎徹底沒有隔離,View層能夠直接操做Model層,Model層的回調裏也可能會直接給View賦值.Controller的概念被弱化,最後只剩下MV沒有C了.數據庫

這也將致使但你想把某個界面上的元素進行更新時,他會牽扯到一堆跟Model層相關的代碼,這個問題在你變動Model層的時候一樣也會出現,這個問題實際上是沒有很好的將邏輯分層致使的.編程

1.2.2 MVP

MVPModel-View-Presenter)架構設計,是當下最流行的開發模式,目前主要以Google推出的TodoMVP爲主,MVP不是一種框架,它實際上更相似一種分層思想,一種接口約定,具體體如今下面:api

  • 定義IView接口,而且在接口中約定View層的各類操做,使用android.view.View的子類以xml構建文件構建起的佈局Activity/Fragment做爲佈局控制器,實現IView這個View層的接口,View層的實際實現類保留一個IPresenter接口的實例.
  • 定義IPresenter接口,而且在接口中約定Presenter層的各類操做.可使用一個與View無關的類實現它,通常是XxxPresenterImpl.一般狀況下Presenter層會包含Model層的引用和一個IView接口的引用,但不該該直接或者間接引用Viewandroid.view.View的子類,甚至是操做的參數中也最好不要有android.view.View的子類傳進來,由於它應該只負責業務邏輯和數據的處理並經過統一的接口IView傳遞到View層.
  • 不須要爲Model層定義一個IModel的接口,這一層是改造最小的.之前該怎麼來如今也差很少該怎麼來.可是如今Presenter把它和View隔開了,Presenter就能夠做爲一段獨立的邏輯被複用.

MVP模式解決了MVC中存在的分層問題,Presenter層被突出強調,實際上也就是真正意義上實現了的MVC數組

可是MVP中其實仍然存在一些問題,好比當業務邏輯變得複雜之後,IPresenterIView層的操做數量可能將會成對的爆炸式增加,新增一個業務邏輯,可能要在兩邊增長數個通訊接口,這種感受很蠢.

而且,咱們要知道一個Presenter是要帶一個IView的,當一個Presenter須要被複用時,對應的View就要去實現全部這些操做,但每每一些操做不是必須實現的,這樣會留下一堆TODO,很難看.

1.2.3 MVVM

MVVMModel-View-ViewModel)由MVP模式演變而來,它由View層,DataBinding,ViewModel層,Model層構成,是MVP的升級版並由GoogleJetpack工具包提供框架支持:

  • View層包含佈局,以及佈局生命週期控制器(Activity/Fragment)
  • DataBinding用來實現View層與ViewModel數據的雙向綁定(但實際上在Android JetpackDataBinding只存在於佈局和佈局生命週期控制器之間,當數據變化綁定到佈局生命週期控制器時再轉發給ViewModel,佈局控制器能夠持有DataBindingViewModel不該該持有DataBinding)
  • ViewModelPresenter大體相同,都是負責處理數據和實現業務邏輯,可是ViewModel層不該該直接或者間接地持有View層的任何引用,由於一個ViewModel不該該直達本身具體是和哪個View進行交互的.ViewModel主要的工做就是將Model提供來的數據直接翻譯成View層可以直接使用的數據,並將這些數據暴露出去,同時ViewModel也能夠發佈事件,供View層訂閱.
  • Model層與MVP中一致.

MVVM的核心思想是觀察者模式,它經過事件和轉移View數據持有權來實現View層與ViewModel層的解耦.

MVVMView不是數據的實際持有者,它只負責數據如何呈現以及點擊事件的傳遞,不作的數據處理工做,而數據的處理者和持有者變成ViewModel,它經過接收View層傳遞過來的時間改變自身狀態,發出事件或者改變本身持有的數據觸發View的更新.

MVVM解決了MVP中的存在的一些問題,好比它無需定義接口,ViewModelView層完全無關更好複用,而且有GoogleAndroid Jetpack做爲強力後援.

可是MVVM也有本身的缺點,那就是使用MVVM的狀況下ViewModelView層的通訊變得更加困難了,因此在一些極其簡單的頁面中請酌情使用,不然就會有一種脫褲子放屁的感受,在使用MVP這個道理也依然適用.

2 DataBinding

2.1 坑

要用一個框架那麼就要先說它的點.那就是不建議在使用DataBinding的模塊同時使用apply plugin: 'kotlin-kapt'.

由於如今kapt還有不少Bug,使用kapt時,在WindowsDataBinding格式下的xml中若是包含有中文,會報UTF-8相關的錯誤.

筆者一開始猜測這是因爲JVM啓動參數沒有設置成-Dfile.encoding=UTF-8致使的,在gradle.properties中改過了,無果,Stack Overflow搜過了,沒找到,若是有大佬知道怎麼解決,還請指點一二

若是你在模塊中同時使用kotlinDataBinding是能夠的,可是請必定不要使用kapt,除非JB那幫大佬搞定這些奇怪的問題.

這就意味這你全部的kotlin代碼都不能依賴註解處理器來爲你的代碼提供附加功能,可是你能夠把這些代碼換成等價的Java實現,它們能夠工做得很好.

2.2 DataBinding的兼容性

先說一點,DataBinding風格的xml會有"奇怪"的東西入侵Android原生的xml格式,這種格式LayoutInfalter是沒法理解,可是,當你對這些奇怪的xml使用LayoutInfalter#inflate時亦不會報錯,而且佈局也正常加載了,這是爲何呢?

這是由於在打包時,Gradle經過APT把你的DataBinding風格的xml所有翻譯了一遍,讓LayoutInfalter能讀懂他們,正是由於這個兼容的實現,而使得咱們能夠在使用和不使用DataBinding間自由的切換.

2.3 DataBinding風格的XML

要想使用DataBinding,先在模塊的build.gradle中添加

android{
    //省略...
    dataBinding {
        enabled = true
    }
}
複製代碼

來啓用DataBinding支持.

DataBinding不須要額外的類庫支持,它被附加在你的android插件中,它的版本號與你的android插件版本一致.

classpath 'com.android.tools.build:gradle:3.3.2'
複製代碼

DataBinding風格的xml中,最外層必須是layout標籤,而且不支持merge標籤,編寫xml就像下面這樣

<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <data>
        <variable
            name="text"
            type="String"/>
        <variable
            name="action"
            type="android.view.View.OnClickListener"/>
    </data>
    <TextView
        android:onClick="@{action}"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
<layout/>
複製代碼

2.3.1 變量領域

data標籤包裹的是變量領域,在這裏你可使用variable定義這個佈局所要綁定的變量類型,使用name來指定變量名,而後用type來指定其類型.

若是一些類型比較長,並且由須要常用你能夠像Java同樣使用import導入他們(java.lang.*會被默認導入),而後就不用寫出徹底限定名了,就像這樣

<import 
            type="android.view.View"
            alias="Action"/>
        <variable
            name="action"
            type="Action"/>
複製代碼

有必要時(好比名字衝突),你還能夠用Action爲一個類型指定一個別名,這樣你就能在下文中使用這個別名.

2.3.2 轉義字符

熟悉xml的同窗可能都知道<>xml中是非法字符,那麼要使用泛型的時候,咱們就須要使用xml中的轉義字符&lt;&gt;來進行轉義

//↓錯誤,編譯時會報錯×
        <variable
            name="list"
            type="java.util.List<String>"/>
        //↓正確,能夠經過編譯√
        <variable
            name="list"
            type="java.util.List&lt;String&gt;"/>
複製代碼

data標籤結束後就是本來的佈局編寫的位置了,這部分基本和之前差很少,只是加入了DataBinding表達式

<data>
        //......
    <data/>
    <TextView
        android:onClick="@{action}"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
複製代碼

2.3.3 DataBinding表達式

@{}包裹的位置被稱爲DataBinding表達式,DataBinding表達式幾乎支持Java全部的運算符,而且增長了一些額外的操做,這容許咱們在xml中有必定的Java編程體驗,學過Java web的同窗可能會以爲它很像JSP:

  • 不須要xml轉義的二元運算+,-,/,*,%,||,|,^,==
  • 須要xml轉義的二元運算&&,>> >>>,<<,>,<,>=,<=,與泛型同樣運算符>=,>,<,<=等,也是須要轉義的,&須要用&amp;轉義,這確實有些蹩腳,但這是xml的侷限性,咱們沒法避免,因此在DataBinding風格的xml中應該儘量的少用這些符號.
  • lambda表達式@{()->persenter.doSomething()}
  • 三元運算?:
  • null合併運算符??,若左邊不爲空則選擇左邊,不然選擇右邊
android:text="@{nullableString??`This a string`}"
複製代碼
  • 自動導入的context變量,你能夠在xml中的任意表達式使用context這個變量,該Context是從該佈局的根ViewgetContext獲取的,若是你設置了本身的context變量,那麼將會覆蓋掉它
  • 若表達式中有字符串文本xml須要特殊處理
用單引號包圍外圍,表達式使用雙引號
android:text='@{"This a string"}'
或者使用`包圍字符串,對,就Esc下面那個鍵的符號
android:text="@{`This a string`}"
複製代碼
  • 判斷類型instanceof
  • 括號()
  • 空值null
  • 方法調用,字段訪問,以及GetterSetter的簡寫,好比User#getNameUser#setName如今均可以直接寫成@{user.name},這種表達式也是最簡單的表達式,屬於直接賦值表達式
  • 默認值default,在xml
`android:text="@{file.name, default=`no name`}"`
複製代碼
  • 下標[],不僅是數組,List,SparseArray,Map如今均可以使用該運算符
  • 使用@讀取資源文件,以下,可是不支持讀取mipmap下的文件
android:text="@{@string/text}"
//或者把它做爲表達式的一部分
android:padding="@{large? @dimen/large : @dimen/small}"
複製代碼

有一些資源須要顯示引用

類型 正常狀況 DataBinding表達式引用
String[] @array @stringArray
int[] @array @intArray
TypedArray @array @typedArray
ColorStateList @animator @stateListAnimator
StateListAnimator @color @colorStateList

還有一些操做是DataBinding表達式中沒有的,咱們沒法使用它們:

  • 沒有this
  • 沒有super
  • 不能建立對象new
  • 不能使用泛型方法的顯示調用Collections.<String>emptyList()

編寫簡單的DataBinding表達式,就像下面這樣

<data>
        <improt type="android.view.View"/>
        <variable
            name="isShow"
            type="Boolean"/>
    <data/>
    <TextView
        android:visibility="@{isShow?View.VISIBLE:View.GONE}"
        android:text="@{@string/text}"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
複製代碼

應該避免出現較爲複雜的DataBinding表達式,以所有都是直接賦值表達式爲佳,數據的處理應該交給佈局控制器或者ViewModel來作,佈局應該只負責渲染數據.

2.3.4 使用在Java中生成的ViewDataBinding

使用DataBindingAndroid Studio會爲每一個xml佈局生成一個繼承自ViewDataBinding的子類型,來幫助咱們將xml文件中定義的綁定關係映射到Java中.

好比,若是你有一個R.layout.fragment_main的佈局文件,那麼他就會爲你在當前包下生成一個,FragmentMainBindingViewDataBinding.

Java實化DataBinding風格xml佈局與傳統方式有所不一樣.

  • Actvity
private ActivityHostBinding mBinding;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_host);
    }
複製代碼
  • 在自定義ViewFragment
private FragmentMainBinding mBinding;
    
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater,
                             @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        mBinding = DataBindingUtil.inflate(inflater,
                R.layout.fragment_main,
                container,
                false);
        return mBinding.getRoot();
    }
複製代碼
  • 在已經使用普通LayoutInfalter實例化的View上(xml必須是DataBinding風格的,普通LayoutInflater實例化佈局時不會觸發任何綁定機制,DataBindingUtil#bind纔會發生綁定)
View view = LayoutInflater.from(context).inflate(R.layout.item_view,null,false);
ItemViewBinding binding = DataBindingUtil.bind(view);
複製代碼

你在xml設置的變量他會在這個類中爲你生成對應的GetterSetter.你能夠調用它們給界面賦值,好比以前的咱們定義的action.

//這裏的代碼是Java8的lambda
mBinding.setAction(v->{
    //TODO
})
複製代碼

2.3.5 使用BR文件

它還會爲你生成一個相似RBR文件,裏面包含了你在DataBinding風格xml中定義的全部變量名的引用(因爲使用的是APT生成,有時候須要Rebuild Project才能刷新),好比咱們以前的action,它會爲咱們生成BR.action,咱們能夠這麼使用它

mBinding.setVariable(BR.action,new View.OnClickListener(){
    @Override
    void onClick(View v){
        //TODO
    }
})
複製代碼

2.3.6 傳遞複雜對象

在以前給xml中的變量中賦值時,咱們用的都是一些相似String的簡單對象,其實咱們也能夠定義一些複雜的對象,一次性傳遞到xml佈局中

//java
public class File
{
    public File(String name,
                String size,
                String path)
                {
                    this.name = name;
                    this.size = size;
                    this.path = path;
                }
    public final String name;
    public final String size;
    public final String path; 
}
//xml
    <data>
        <variable 
            name="file"
            type="org.kexie.android.sample.bean.File"/>
    <data/>
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
            <TextView
                android:text="@{file.name}"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>
            <TextView
                android:text="@{file.size}"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>
            <TextView
                android:text="@{file.path}" 
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>
    <LinearLayout/>
複製代碼

我的認爲綁定到xml中的數據最好是不可變的,因此上面的字段中我使用了final,但這不是必須的,根據你本身的需求來進行定製

2.3.7 綁定並不是當即發生

這裏有一點值得注意的是,你給ViewDataBinding的賦值並非立刻生效的,而是在當前方法執行完畢回到事件循環後,並保證在下一幀渲染以前獲得執行,若是須要當即執行,請調用ViewDataBinding#executePendingBindings

2.3.8 使用android:id

若是你使用了android:id,那麼這個View就也能夠當成一個變量在下文的DataBinding表達式中使用,就像寫Java.它還會幫你View綁定到ViewDataBinding中,你能夠這麼使用它們

//xml
    <TextView
        android:id="@+id/my_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_context"/>
    <TextView
        android:id="@+id/my_text2"
        android:text="@{my_text.getText()}"
        android:layout_width="match_parent"
        android:layout_height="wrap_context"/>
    //在java中my_text被去掉下劃線,更符合java的命名習慣
    mBinding.myText.setText("This is a new text");
複製代碼

用過ButterKnife的同窗可能都知道,ButterKnife出過一次與gradle版本不兼容的事故,可是DataBinding是與gradle打包在一塊兒發佈的,通常不會出現這種問題,若是你不想用ButterKnife但有不想讓DataBinding的風格的寫法入侵你的xml太狠的話,只使用android:id將會是一個不錯的選擇.

2.4 正向綁定

某些第三方View是確定沒有適配DataBinding的,業界雖然一直說MVVM好,但如今MVP的開發方式畢竟仍是主流,雖然這種狀況咱們能夠用android:id,而後在Activity/Fragment中解決,但有時候咱們想直接在xml中配置,以消除一些樣板代碼,這時候就須要自定義正向綁定.

2.4.1 自定義正向綁定適配器

咱們可使用@BindingAdapter自定義在xml中可以使用的View屬性,名字空間是不須要的,加了反而還會給你警告.

@Target(ElementType.METHOD)
public @interface BindingAdapter {

    /**
     * 與此綁定適配器關聯的屬性。
     */
    String[] value();

    /**
     * 是否必須爲每一個屬性分配綁定表達式,或者是否能夠不分配某些屬性。
     * 若是爲false,則當至少一個關聯屬性具備綁定表達式時,將調用BindingaAapter。
     */
    boolean requireAll() default true;
}

//@BindingAdapter須要一個靜態方法,該方法的第一個參數是與該適配器兼容的View類型
//從第二個參數開始,依次是你自定義的屬性傳進來的值.
//使用requireAll來指定這些屬性是所有須要,仍是隻要一個就能夠
//若是requireAll = false,觸發適配器綁定時,沒有被設置的屬性將得到該類型的默認值
//框架優先使用自定義的適配器處理綁定

@BindingAdapter(value = {"load_async", "error_handler"},requireAll = true)
public static void loadImage(ImageView view, String url, String error) {
   Glide.with(view)
        .load(url)
        .error(Glide.with(view).load(error))
        .into(view);
}
//在xml中使用它(下面那兩個網址都不是實際存在的)
<ImageView
    load_async="@{`http://android.kexie.org/image.png`}"
    error_handler="@{`http://android.kexie.org/error.png`}"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>
複製代碼

2.4.2 第三方View適配

DataBinding風格的xml還能在必定程度上適配第三方View

//若是你的自定義View中有這麼一個Setter↓
    public class RoundCornerImageView extends AppCompatImageView{
        //......
        public void setRadiusDp(float dp){
            //TODO
        }
    }
    //那麼你能夠在xml中使用radiusDp來使用它
    <org.kexie.android.ftper.widget.RoundCornerImageView
        radiusDp="@{100}"
        android:id="@+id/progress"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center"
        android:scaleType="centerCrop"
        android:src="@drawable/progress"/>
    //它會本身爲你去找名稱爲setRadiusDp而且能接受100爲參數的方法.
複製代碼

2.4.3 xml中的屬性重定向

使用@BindingMethod來將xml屬性重定向:

@Target(ElementType.ANNOTATION_TYPE)
public @interface BindingMethod {
    //須要重定向的View類型
    Class type();
    //須要重定向的屬性名
    String attribute();
    //須要重定向到的方法名
    String method();
}
//這是DataBinding源碼中,DataBinding對於系統自帶的TextView編寫的適配器
//這是androidx.databinding.adapters.TextViewBindingAdapter的源碼
@BindingMethods({
        @BindingMethod(type = TextView.class, attribute = "android:autoLink", method = "setAutoLinkMask"),
        @BindingMethod(type = TextView.class, attribute = "android:drawablePadding", method = "setCompoundDrawablePadding"),
        @BindingMethod(type = TextView.class, attribute = "android:editorExtras", method = "setInputExtras"),
        //......
})
public class TextViewBindingAdapter {
    //......
}
//這樣就能夠創建起xml中屬性與View中Setter的聯繫
複製代碼

2.4.4 添加轉換層

使用@BindingConversion爲添加轉換層

@BindingConversion
public static ColorDrawable toDrawable(int color) { 
    return new ColorDrawable(color); 
}
//能夠把color整形轉換爲android:src可接受的ColorDrawable類型
//可是轉換隻適用於直接的賦值
//若是你寫了複雜的表達式,好比使用了?:這種三元運算符
//那就照顧不到你了
複製代碼

2.5 反向綁定

有正向綁定就必定有反向綁定,正向綁定和反向綁定一塊兒構成了雙向綁定.

在咱們以前編寫的DataBinding表達式中,好比TextViewandroid:text之類的屬性咱們都是直接賦值一個String過去的,這就是正向綁定,咱們給View的值可以直接反應到View上,而反向綁定就是View值的變化和也能反應給咱們.

2.5.1 使用雙向綁定

全部使用以前全部使用@{}包裹的都是正向綁定,而雙向綁定是@={},而且只支持變量,字段,Setter(好比User#setName,就寫@={user.name})的直接編寫而且不支持複雜表達式

2.5.2 兼容LiveData與ObservableField

實際上,android:text不僅能接受String,當使用雙向綁定時,它也能接受MutableLiveData<String>ObservableField<String>做爲賦值對象,這種賦值會將TextViewandroid:text的變化綁定到LiveData(其實是MutableLiveData)或者是ObservableField上,以便咱們在View的控制層(Activity/Fragment)更好地觀察他們的變化.

固然除了ObservableFieldandroidx.databinding包下還有不裝箱的ObservableInt,ObservableFloat等等.

可是爲了支持LiveData咱們必須開啓第二版的DataBinding APT.

在你的gradle.properties添加

android.databinding.enableV2=true
複製代碼

如今咱們能夠經過LiveData(其實是MutableLiveData)android:text的變化綁定到Activity/Fragment

//xml
<data>
    <variable
        name="liveText"
        type="MutableLiveData&lt;String&gt;">
<data/>
<TextView
    android:text="@={text}"
    android:layout_width="match_parent"
    android:layout_height="wrap_context"/>
//而後在Activity/Fragment中
MutableLiveData<String> liveText = new MutableLiveData<String>();
mBinding.setLiveText(liveText);
liveText.observe(this,text->{
   //TODO 觀察View層變化 
});
複製代碼

2.5.3 自定義反向綁定適配器

下面咱們回到androidx.databinding.adapters.TextViewBindingAdapter的源碼,繼續對自定義反向綁定適配器進行分析.

//咱們能夠看到源碼中使用了@InverseBindingAdapter自定義了一個反向綁定器
    //指定了其屬性以及相關聯的事件
    @InverseBindingAdapter(attribute = "android:text", event = "android:textAttrChanged")
    public static String getTextString(TextView view) {
        return view.getText().toString();
    }
    //併爲這個事件添加了一個可接受InverseBindingListener的屬性
    //爲了說明方便,下面的代碼已簡化,源碼並不是如此,但主要邏輯相同
    @BindingAdapter(value = {"android:textAttrChanged"})
    public static void setTextWatcher(TextView view , InverseBindingListener textAttrChanged){
        view.addTextChangedListener(new TextWatcher(){
            //......
             @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                textAttrChanged.onChange();     
            }
        });
    }
    //至此android:text的反向綁定完成
    //當你使用@={}時其實是用android:textAttrChanged屬性向TextView設置了TextWatcher
    //傳入的InverseBindingListener是反向綁定監聽器
    //當調用InverseBindingListener的onChange時
    //會調用@BindingAdapter所註解的方法將得到數據並寫回到變量中.
複製代碼

2.6 配合DataBinding打造通用RecyclerView.Adapter

下面進行一個小小的實戰吧,咱們能夠站在巨人的肩膀上造輪子.

//導入萬能適配器做爲基類,能夠大大豐富咱們通用適配器的功能
    implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.46'
複製代碼

因爲基類很強大因此代碼很少:

//X是泛型,能夠是你在item中所使用的java bean
public class GenericQuickAdapter<X>
        extends BaseQuickAdapter<X, GenericQuickAdapter.GenericViewHolder> {
    //BR中的變量名
    protected final int mName;
    //layoutResId是DataBinding風格的xml
    public GenericQuickAdapter(int layoutResId, int name) {
        super(layoutResId);
        mName = name;
        openLoadAnimation();
    }

    @Override
    protected void convert(GenericViewHolder helper, X item) {
        //觸發DataBinding
        helper.getBinding().setVariable(mName, item);
    }

    public static class GenericViewHolder extends BaseViewHolder {
        private ViewDataBinding mBinding;
        public GenericViewHolder(View view) {
            super(view);
            //綁定View得到ViewDataBinding
            mBinding = DataBindingUtil.bind(view);
        }

        @SuppressWarnings("unchecked")
        public <T extends ViewDataBinding> T getBinding() {
            return (T) mBinding;
        }
    }
}
//實例化
GenericQuickAdapter<File> adapter = new GenericQuickAdapter<>(R.layout.item_file,BR.file);
//在xml中使用起來就像這樣
<layout>
    <data>
        <variable 
            name="file"
            type="org.kexie.android.sample.bean.File"/>
    <data/>
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
            <TextView
                android:text="@{file.name}"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>
            <TextView
                android:text="@{file.size}"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>
            <TextView
                android:text="@{file.path}" 
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>
    <LinearLayout/>
<layout/>
複製代碼

3 Lifecycle

Android中,組件的管理組件的生命週期一直是一個比較麻煩的東西,而自Google推出Android Jetpack組件包以來,這個問題獲得的比較妥善的解決,Lifecycle組件後來也成爲Android Jetpack的核心。

3.1 導入

AndroidX爲例,要使用Lifecycle組件,先在模塊的build.gradle文件中添加依賴:

api 'androidx.lifecycle:lifecycle-extensions:2.1.0-alpha02'
複製代碼

因爲Lifecycle組件由多個包構成,使用api導入時便可將其依賴的包所有導入該模塊,包括commonlivedataprocessruntimeviewmodelservice等。

若是要使用Lifecycle中的註解,你還須要添加以下註解處理器,以便在編譯時,完成對相應註解的處理。

annotationProcessor 'androidx.lifecycle:lifecycle-compiler:2.0.0'
複製代碼

對於一個App來講,使用Lifecycle組件是沒有任何侵入性的,由於他已經自然的融合到Googleappcompat庫中了,而現在不管是什麼應用程序都幾乎離不開appcompat,能夠說集成Lifecycle只是啓用了以前沒用過的功能罷了。

3.2 LifecycleOwner

LifecycleOwnerLifecycle組件包中的一個接口,全部須要管理生命週期的類型都必須實現這個接口。

public interface LifecycleOwner
{
    /**
    * Returns the Lifecycle of the provider.
    *
    * @return The lifecycle of the provider.
    */
    @NonNull
    Lifecycle getLifecycle();
}
複製代碼

但其實不少時候咱們根本無需關心LifecycleOwner的存在。在Android中, FragmentActivityService都是具備生命週期的組件,可是Google已經讓他們都實現了LifecycleOwner這個接口,分別是androdx.fragment.app.FragmentAppCompatActivityandroidx.lifecycle.LifecycleService.

在項目中,只要繼承這些類型,能夠輕鬆的經過LifecycleOwner#getLifecycle()獲取到Lifecycle實例.這是一種解耦實現,LifecycleOwner不包含任何有關生命週期管理的邏輯,實際的邏輯都在Lifecycle實例中,咱們能夠經過傳遞Lifecycle實例而非LifecycleOwner來防止內存泄漏.

Lifecycle這個類的只有這三個方法:

@MainThread
public abstract void removeObserver(@NonNull LifecycleObserver observer);
@MainThread
@NonNull
public abstract State getCurrentState();
@MainThread
public abstract void addObserver(@NonNull LifecycleObserver observer); 
複製代碼

getCurrentState()能夠返回當前該LifecycleOwner的生命週期狀態,該狀態與LifecycleOwner上的某些回調事件相關,只會出現如下幾種狀態,在Java中以一個枚舉類抽象出來定義在Lifecycle類中。

public enum State
{       
        DESTROYED,
        INITIALIZED,
        CREATED,
        STARTED,
        RESUMED;
}
複製代碼
  • DESTROYED,在組件的onDestroy調用前,會變成該狀態,變成此狀態後將不會再出現任何狀態改變,也不會發送任何生命週期事件

  • INITIALIZED,構造函數執行完成後但onCreate未執行時爲此狀態,是最開始時的狀態

  • CREATED,在onCreate調用以後,以及onStop調用前會變成此狀態

  • STARTED,在onStart調用以後,以及onPause調用前會變成此狀態

  • RESUMED,再onResume調用以後會變成此狀態

addObserver,此方法能夠給LifecycleOwner添加一個觀察者,來接收LifecycleOwner上的回調事件。回調事件也是一個枚舉,定義在Lifecycle類中:

public enum Event
{
    /**
    * Constant for onCreate event of the {@link LifecycleOwner}.
    */
    ON_CREATE,
    /**
    * Constant for onStart event of the {@link LifecycleOwner}.
    */
    ON_START,
    /**
    * Constant for onResume event of the {@link LifecycleOwner}.
    */
    ON_RESUME,
    /**
    * Constant for onPause event of the {@link LifecycleOwner}.
    */
    ON_PAUSE,
    /**
    * Constant for onStop event of the {@link LifecycleOwner}.
    */
    ON_STOP,
    /**
    * Constant for onDestroy event of the {@link LifecycleOwner}.
    */
    ON_DESTROY,
    /**
    * An {@link Event Event} constant that can be used to match all events.
    */
    ON_ANY 
}
複製代碼

每種事件都對應着Fragment/Activity中的事件。

3.3 LifecycleObserver

LifecycleObserver是生命週期的觀察者,多是這個包中咱們最經常使用的接口了.

查看源碼得知,他就是一個空接口,不包含任何實現,可是若咱們想使用,仍是得繼承此接口。

public interface LifecycleObserver { }
複製代碼

繼承LifecycleObserver後使用@OnLifecycleEvent註解(這時以前申明得註解處理器派上了用場),並設置須要監聽的生命週期回調事件。

@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
public void test()
{
    ///TODO...
}
複製代碼

而後在Activity/Fragment中:

getLifecycle().addObserver(yourLifecycleObserver);
複製代碼

便可在運行時收到相應的的回調事件,可是注意添加@OnLifecycleEvent註解的方法應該是包內訪問權限或是public的,不然可能在編譯時會報錯,或者收不到回調。

若想在運行時移除LifecycleObserver,一樣也還有Lifecycle#removeObserver方法。

4 LiveData

LiveData是對Android組件生命週期感知的粘性事件,也就是說,在LiveData持有數據時,你去訂閱它就能收到他最後一次接收到的數據.在實戰中,咱們能用到的LiveData通常是它的兩個子類MutableLiveDataMediatorLiveData.

4.1 LiveData基本使用

咱們能夠經過LiveData#observe來觀察它所持有的值的變化,還能夠經過LiveData#getValue來直接獲取內部保存的值(非線程安全)

//LiveData 通常是用來給ViewModel保存數據的
public class MyViewModel extends ViewModel{
    private MutableLiveData<Boolean> mIsLoading = new MutableLiveData<>();
    LiveData<Boolean> isLoading(){
        return mIsLoading;
    }
}
//Activity/Fragment觀察ViewModel
mViewModel.isLoading().observe(this, isLoading -> {
    //TODO 發生在主線程,觸發相關處理邏輯
});
//LiveData是依賴Lifecycle實現的
//傳入的this是LifecycleOwner
//LiveData只會通知激活態的(STARTED和RESUMED)的LifecycleOwner
//而且在Activity/Fragment被重建也能從新接收到LiveData保存的數據
//在組件DESTROYED時,LiveData會把它移出觀察者列表

//固然你也能夠不關聯LifecycleOwner,讓訂閱一直保持.
//須要這樣時須要使用observeForever
mViewModel.isLoading().observeForever(isLoading -> {
    //TODO
});
//這個訂閱永遠不會被取消
//除非你顯示調用LiveData#removeObserver
複製代碼

4.2 MutableLiveData

顧名思義就是可變的LiveData,基類LiveData默認是不可變的,MutableLiveData開放了可以改變其內部所持有數據的接口.

public class MutableLiveData<T> extends LiveData<T> {
    /**
     * Creates a MutableLiveData initialized with the given {@code value}.
     *
     * @param value initial value
     */
    public MutableLiveData(T value) {
        super(value);
    }
    /**
     * Creates a MutableLiveData with no value assigned to it.
     */
    public MutableLiveData() {
        super();
    }
    @Override
    public void postValue(T value) {
        super.postValue(value);
    }
    @Override
    public void setValue(T value) {
        super.setValue(value);
    }
}
複製代碼

分別是postValuesetValue,其中setValue內部檢查線程是否爲主線程,不容許在子線程中使用,用了就報錯.postValue會將值經過主線程的Handler轉發到主線程上.

LiveData能夠有初始值,也能夠沒有,若是在沒有初始值的狀況下被訂閱,則訂閱者不會收到任何的值.

4.3 MediatorLiveData

MediatorLiveData繼承自MutableLiveData,它主要用來實現多個LiveData數據源的合併.

public class MediatorLiveData<T> extends MutableLiveData<T> {
    private SafeIterableMap<LiveData<?>, Source<?>> mSources = new SafeIterableMap<>();
    @MainThread
    public <S> void addSource(@NonNull LiveData<S> source, @NonNull Observer<? super S> onChanged) {
        Source<S> e = new Source<>(source, onChanged);
        Source<?> existing = mSources.putIfAbsent(source, e);
        if (existing != null && existing.mObserver != onChanged) {
            throw new IllegalArgumentException(
                    "This source was already added with the different observer");
        }
        if (existing != null) {
            return;
        }
        if (hasActiveObservers()) {
            e.plug();
        }
    }
    @MainThread
    public <S> void removeSource(@NonNull LiveData<S> toRemote) {
        Source<?> source = mSources.remove(toRemote);
        if (source != null) {
            source.unplug();
        }
    }

    @CallSuper
    @Override
    protected void onActive() {
        for (Map.Entry<LiveData<?>, Source<?>> source : mSources) {
            source.getValue().plug();
        }
    }

    @CallSuper
    @Override
    protected void onInactive() {
        for (Map.Entry<LiveData<?>, Source<?>> source : mSources) {
            source.getValue().unplug();
        }
    }

    private static class Source<V> implements Observer<V> {
        final LiveData<V> mLiveData;
        final Observer<? super V> mObserver;
        int mVersion = START_VERSION;

        Source(LiveData<V> liveData, final Observer<? super V> observer) {
            mLiveData = liveData;
            mObserver = observer;
        }

        void plug() {
            mLiveData.observeForever(this);
        }

        void unplug() {
            mLiveData.removeObserver(this);
        }

        @Override
        public void onChanged(@Nullable V v) {
            if (mVersion != mLiveData.getVersion()) {
                mVersion = mLiveData.getVersion();
                mObserver.onChanged(v);
            }
        }
    }
}
複製代碼

它比MutableLiveData多了兩個方法addSourceremoveSource,經過這兩個方法咱們能夠將其餘LiveData合併到此LiveData上,當其餘LiveData發生改變時,此LiveData就能收到通知.

@MainThread
public <S> void addSource(@NonNull LiveData<S> source, @NonNull Observer<? super S> onChanged)
@MainThread
public <S> void removeSource(@NonNull LiveData<S> toRemote)
複製代碼

經過查看源碼,咱們能夠知道在有觀察者時LiveData#onActive會被回調,MediatorLiveData會在內部迭代,用observeForever訂閱全部被合併進來的LiveData,這樣就能接收全部LiveData的變化,在沒有觀察者時LiveData#onInactive會被回調,此時執行反操做removeObserver.

4.4 變換

使用androidx.lifecycle.Transformations這個工具類能夠將持有一種類型的LiveData轉換爲另外一種LiveData.他有相似於RxJava的使用方式.

LiveData<Boolean> boolLiveData = getBoolLiveData();
LiveData<String> stringLiveData = Transformations.map(boolLiveData,bool->Boolean.toString(bool));
複製代碼

上面只是一個演示,實際上能夠執行更爲複雜的邏輯,而且這種轉換是惰性的,在沒有激活態觀察者時,這種轉換不會發生.

5 ViewModel

5.1 自定義ViewModel

ViewModel其實沒什麼可說的,其源碼主要的部分其實就只有這些

public abstract class ViewModel {
    protected void onCleared() {
    }
}
複製代碼

簡直一目瞭然,咱們能夠在ViewModel上使用LiveData做爲字段保存數據,並編寫業務邏輯(數據處理邏輯).就像這樣

public class MyViewModel extends ViewModel
{
    public MutableLiveData<String> username = new MutableLiveData<>();
    public MutableLiveData<String> password = new MutableLiveData<>();
    public MutableLiveData<String> text = new MutableLiveData<>();
    public void action1(){
        //TODO
    }
    public void initName(){
        username.setValue("Luke Luo");
    }
    //......
    @Override
    protected void onCleared() {
        //TODO 清理資源
    }
}
複製代碼

onCleared會在組件銷燬的時候回調,咱們能夠重寫這個方法在ViewModel銷燬時添加一些自定義清理邏輯.

ViewModel還有一個子類AndroidViewModel也是一目瞭然,只是保存了Application實例而已.

public class AndroidViewModel extends ViewModel {
    @SuppressLint("StaticFieldLeak")
    private Application mApplication;

    public AndroidViewModel(@NonNull Application application) {
        mApplication = application;
    }

    /**
     * Return the application.
     */
    @SuppressWarnings("TypeParameterUnusedInFormals")
    @NonNull
    public <T extends Application> T getApplication() {
        //noinspection unchecked
        return (T) mApplication;
    }
}
複製代碼

5.2 自定義ViewModel構造方式

咱們能夠經過ViewModelProviders來獲取ViewModel,這樣獲取的ViewModel會綁定組件的生命週期(即在銷燬時自動調用onCleared)

mViewModel = ViewModelProviders.of(this).get(CustomViewModel.class);
複製代碼

AndroidLifecycle實現中框架向Activity中添加了一個繼承了系統FragmentReportFragment來彙報組件的生命週期,若是你使用的是appcompatFragment,那麼它對你就是不可見的,因此必定要避免使用系統的Fragment(在API28中已被標記爲棄用).

ViewModel經過Lifecycle來管理自身釋放,在組件的ON_DESTROY事件來到時,它的onCleared()也會被調用.

若是你想有自定義構造函數參數的ViewModel那你就得繼承ViewModelProvider.AndroidViewModelFactory

//自定義構造函數的ViewModel
public class NaviViewModel extends AndroidViewModel
{
    private AMapNavi mNavi;
    public NaviViewModel(AMapNavi navi,Application application)
    {
        super(application);
        mNavi = navi;
    }
    //......
}


//繼承並重寫create
public final class NaviViewModelFactory
        extends ViewModelProvider.AndroidViewModelFactory
{
    private final AMapNavi navi;
    private final Application application;


    public NaviViewModelFactory(@NonNull Context context, AMapNavi navi)
    {
        super((Application) context.getApplicationContext());
        this.application = (Application) context.getApplicationContext();
        this.navi = navi;
    }

    @NonNull
    @Override
    public <T extends ViewModel> T create(@NonNull Class<T> modelClass)
    {
        try
        {
            Constructor<T> constructor = modelClass
                    .getConstructor(Application.class, AMapNavi.class);
            return constructor.newInstance(application, navi);
        } catch (Exception e)
        {
            return super.create(modelClass);
        }
    }
}
//使用
NaviViewModelFactory factory = new NaviViewModelFactory(context, navi);
mViewModel = ViewModelProviders.of(this, factory).get(NaviViewModel.class);
複製代碼

說白了就是反射調用構造函數建立,也是一目瞭然.

6 RxJava

本篇文章只是針對響應式編程在MVVM體系下的應用,不對RxJava展開深度討論,可是後面還會專門出一篇文章討論RxJava的有關知識.

RxJavaMVVM中主要用於發佈事件,下面是須要注意的一些點.

6.1 使用AutoDispose

RxJava是響應式編程這種思想在JVM這個平臺上的實現,因此它一開始並無爲Android平臺的特色而作出優化.

就像上面所介紹過的同樣,Android的組件是有明確的生命週期的,若是在組件銷燬後,RxJava仍有後臺線程在運行且你的Observer引用了你的Activity,就會形成內存泄漏.

但其實RxJava是提供了釋放機制的,那就是Disposeable,只不過這個實現這個機制的邏輯須要咱們手動在Activity#onDestroy中進行硬編碼,這會帶來大量的樣板代碼.

爲了解決這一局面,在Android Jetpack尚未誕生的時候,有大神開發了RxLifecycle,可是這個框架須要強制繼承基類,對於一些現有項目的改造來講,實際上是不太友好的,我的感受並無從根本上解決問題.

Android Jetpack誕生後AutoDispose給了咱們另一條出路.它使用RxJava2中的as運算符,將訂閱者轉換成可以自動釋放訂閱者對象.

在你的build.gradle中添加依賴:

implementation 'io.reactivex.rxjava2:rxjava:2.2.6'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
implementation 'com.uber.autodispose:autodispose:1.1.0'
implementation 'com.uber.autodispose:autodispose-android-archcomponents:1.1.0'
複製代碼

一個簡單的示例:

Observable.just(new Object())
            //使用AutoDispose#autoDisposable
            //並使用AndroidLifecycleScopeProvider#form
            //指定LifecycleOwner和須要在哪個事件進行銷燬
            //關鍵↓是這行
            .as(AutoDispose.autoDisposable(AndroidLifecycleScopeProvider.from(activity, Lifecycle.Event.ON_DESTROY)))
            .subscribe();
複製代碼

上面代碼的時間訂閱將會在組件的Lifecycle.Event.ON_DESTROY事件來到時被釋放,固然你也能夠指定其餘事件時釋放.

6.2 防止多重點擊

首先你可使用JW大神RxBinding來實現這一需求,可是今天咱們不討論RxBinding,由於網上的討論RxBinding的文章已經太多了,隨便抓一篇出來都已經很是優秀.

今天咱們模仿RxBinding實現一個簡單的,輕量化的,基於Java動態代理的,而且兼容全部第三方View所自定義Listener接口的防止多重點擊機制.

二話不說先上代碼:

import androidx.collection.ArrayMap;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
import com.uber.autodispose.AutoDispose;
import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider;
import io.reactivex.subjects.PublishSubject;

import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import static io.reactivex.android.schedulers.AndroidSchedulers.mainThread;

public final class RxOnClick<X>
{
    //默認最低的可取的時間
    private static final int MINI_TIME = 200;

    private final Class<X> mInterface;

    private X mInner;

    private LifecycleOwner mOwner;

    private int mTime;

    private Lifecycle.Event mEvent;

    private RxOnClick(Class<X> type)
    {
        mInterface = type;
    }
    //從一個建立接口類型建立
    public static <X> RxOnClick<X> create(Class<X> type)
    {
        return new RxOnClick<>(type);
    }
    //實際處理事件的Listener
    public RxOnClick<X> inner(X inner)
    {
        mInner = inner;
        return this;
    }
    //依附於的組件也就是LifecycleOwner
    public RxOnClick<X> owner(LifecycleOwner owner)
    {
        mOwner = owner;
        return this;
    }
    //只去time毫秒內的第一個結果做爲有效結果
    public RxOnClick<X> throttleFirst(int time)
    {
        mTime = time;
        return this;
    }
    //在哪個事件進行釋放
    public RxOnClick<X> releaseOn(Lifecycle.Event event)
    {
        mEvent = event;
        return this;
    }
    //建立代理類實例
    @SuppressWarnings("unchecked")
    public X build()
    {
        //檢查參數
        if (mInterface == null || !mInterface.isInterface())
        {
            throw new IllegalArgumentException();
        }
        if (mTime < MINI_TIME)
        {
            mTime = MINI_TIME;
        }
        if (mEvent == null)
        {
            mEvent = Lifecycle.Event.ON_DESTROY;
        }
        if (mOwner == null || mInner == null)
        {
            throw new IllegalStateException();
        }
        //用反射遍歷獲取全部方法
        Map<Method, PublishSubject<Object[]>> subjectMap = new ArrayMap<>();
        for (Method method : mInterface.getDeclaredMethods())
        {
            PublishSubject<Object[]> subject = PublishSubject.create();
            subject.throttleFirst(mTime, TimeUnit.MILLISECONDS)
                    .observeOn(mainThread())
                    .as(AutoDispose.autoDisposable(AndroidLifecycleScopeProvider.from(mOwner, mEvent)))
                    .subscribe(args -> method.invoke(mInner, args));
            subjectMap.put(method, subject);
        }
        //使用動態代理代理代理該接口並使用PublishSubject進行轉發
        return (X) Proxy.newProxyInstance(mInterface.getClassLoader(),
                new Class[]{mInterface},
                (proxy, method, args) -> {
                    //Object類的方法直接調用
                    if (Object.class.equals(method.getDeclaringClass()))
                    {
                        return method.invoke(proxy, args);
                    }
                    //不然轉換爲Rx事件流
                    PublishSubject<Object[]> subject = subjectMap.get(method);
                    if (subject != null)
                    {
                        subject.onNext(args);
                    }
                    return null;
                });
    }
}
複製代碼

上面類在設計上採用了Builder模式,因此它實際是一個Builder.

其核心原理就是使用Java的動態代理機制建立Listener的代理類,代理類不處理事件,而是將事件經過PublishSubject(釋放訂閱後接收到的事件)轉換爲RxJava事件流推送到真正處理事件的Listener上.

這樣咱們就能夠在這個事件流上對事件作手腳了,而且這樣還能兼容RxBinding所不能兼容的第三方自定義View.

好比上面就加入了xxx毫秒內只取第一次點擊和綁定組件的生命週期,用起來的時候就像是下面,依然很是簡潔而且很是的有用:

View.OnClickListener listener = RxOnClick
                .create(View.OnClickListener.class)
                .owner(this)
                .inner(v -> {
                    //TODO
                })
                .build();

複製代碼

7 使用MVVM改造Android現有體系

筆者就Android現有體系下的各類類庫框架,經過本身實踐的得出的經驗將其進行以下歸類,觀點僅供參考,在實踐中應該視項目特色進行適當進行改造.

7.1 View層

現有體系下的內容:

  • Activity/Fragment(佈局生命週期與邏輯控制器)
  • android.view.View及其子類

設計原則:

  • View層不該該承擔處理數據的責任,它應該只負責數據如何顯示.
  • 它不該該直接持有Model層的任何引用,也不該該直接持有Model層的數據.
  • View層正常的行爲應該是觀察某個ViewModel,間接獲取該ViewModelModel層中獲取並處理過能在View層上直接顯示的數據,數據由ViewModel保存,這樣能夠保證在Activity重建時頁面上有關的數據不會丟失並且也不會形成View層與Model層的耦合.

7.2 DataBinding

現有體系下的內容:

  • Jetpack DataBinding 函數庫
  • ViewAdapter
  • ......

設計原則:

  • 理想狀態下,DataBindingView構建的關係應該是數據驅動的,即只要數據不改變View層實現的變動不會致使邏輯的從新編寫(如把TextView改爲EditText也不須要修改一行代碼).
  • 雖然DataBinding函數庫已經完成了大多數DataBinding應該作的事,可是不要爲了數據驅動而排斥使用android:id來獲取View並對View直接賦值,雖然這不夠數據驅動,可是適當使用是能夠的,畢竟AndroidView層目前尚未辦法作到徹底的數據驅動(主要是第三方庫的兼容問題).
  • Adapter應該屬於DataBinding的一種,與DataBinding函數庫中生成的DataBinding相同,它也是使用數據來觸發View層的改變.因此儘量不要把它寫到ViewModel中,但這不是必須的,作在對List操做要求比較高的狀況下能夠寫到ViewModel中,但要保證一個原則——ViewModel應該只負責提供數據,而不該該知道這些數據要與何種View進行交互.

7.3 事件傳遞

現有體系下的內容:

  • EventBus事件總線
  • RxJava事件流

設計原則:

  • Jetpack中實現的LiveData可以很好的做爲數據持有者,而且是生命週期感知的,可是有些時候咱們須要向View層發送一些單次的數據,這時LiveData並不可以很好地工做.RxjavaEventBus是更好的選擇.

7.4 ViewModel層

現有體系下的內容:

  • Jetpack ViewModel
  • Jetpack LiveData
  • 用於將Model數據轉換成View能直接顯示的數據的工具類
  • ......

設計原則:

  • ViewModel一般應該使用LiveData持有View層數據的實際控制權
  • ViewModel能夠包含操做,可是ViewModel不該該直接或者間接地引用View,即便是方法中的參數也最好不要,由於ViewModel不該該知道本身究竟是與哪個View進行交互.
  • ViewModelModel的關係應該是——將Model層產生的數據翻譯View層可以直接消化吸取的數據。
  • ViewModel能夠向View層發送事件,而後View能夠訂閱這些事件以收到ViewModel層的通知.

7.5 Model層

現有體系下的內容:

  • 部分與Activity無關的系統服務
  • Room(SQLite數據庫)
  • Retrofit(網絡數據)
  • SharedPreferences
  • ......

設計原則:

  • 涉及Activity請必定不要包含進來,如WindowManager,它們屬於View層.
  • Model層主要是原始數據的來源,因爲存儲格式/傳輸格式顯示格式存在的巨大差別,View層每每並不能很好的直接消化這些數據,這時就須要一個中間人做爲翻譯,由此抽象出了ViewModel.

8 實戰

我編寫了一個簡單的FTP客戶端做爲本次MVVM博文的演示Demo,該項目簡單實踐了QMUI+MVVM+DataBinding+RxJava+LiveData+Room的技術棧並由kotlinJava混編寫成,支持斷點續傳,代碼質量比較通常,有愛自取.

9 參考資料以及擴展閱讀

10 結語

有些日子沒更新文章了,最近發生了一些事讓筆者完全從無限的狂熱中冷靜下來,開始耐心作事.

本篇文章多達10000+字,感謝您在百忙之中抽空觀看.全部內容均爲我的學習總結與理解,僅供參考.

若是喜歡個人文章別忘了給我點個,拜託了這對我來講真的很重要.

相關文章
相關標籤/搜索