1. 程式人生 > >(一)Android官方MVVM框架實現元件化之整體結構

(一)Android官方MVVM框架實現元件化之整體結構

0-演示專案MVVM元件化架構圖

一、google官方MVVM框架講解

我前面對比了MVC和MVP《兩張圖看懂Android開發中MVC與MVP的區別》,可以相對於MVC我們的MVP是有多優越,但是Android開發現在已經開始流行了MVVM,前不久google官方釋出了MVVM的正式庫。官方的正式MVVM庫主要包括下面四個:
1-正式MVVM庫元件

其中只有ViewModel是MVVM結構中的一個元件,其他的三個都是輔助性質的。

lifecycles 就是處理UI介面的生命週期,在26版本以後的Support庫中,AppCompatActivitySupportActivity中都實現了LifecycleOwner,內部已經對UI介面的生命週期做了處理了。

LiveData是一個抽象類,我們可以存放UI頁面需要的資料,就是把資料包裝在LiveData中了,我們可以觀測LiveData中的資料變化,但是LiveData是跟UI的生命週期關聯的,當UI頁面銷燬了,LiveData的資料變化回撥是不會執行的。

Room 就是一個sqlite資料持久化庫,我們也可以使用別的ORM庫。

二、MVVM架構優勢

2-MVVM架構

看上圖ModelView是不會發生關係的,ViewModel是把View和Model關聯起來的加工廠:

3-ViewModel工廠

MVVM優勢總結:

  1. ViewModel雙向繫結,一方的改變都會影響另一方,開發者不用再去手動修改UI的資料。額,互相自動的。

  2. 不需要findViewById也不需要butterknife,不需要拿到具體的View去設定資料繫結監聽器等等,這些都可以用DataBinding完成。是不是很舒服?

  3. ViewModel的雙向繫結是支援生命週期檢測的,不會擔心頁面銷燬了還有回調發生,這個由lifeCycle完成。

  4. 不會像MVC一樣導致Activity中程式碼量巨大,也不會像MVP一樣出現大量的ViewPresenter介面。專案結構更加低耦合。

  5. 更低的耦合把各個模組分開開發,分開測試,可以分給不同的開發人員來完成。

三、MVVM元件化示例專案架構分析

下圖是專案模組和工程之間的依賴關係:
4-MVVM元件化示例專案架構圖
下圖是工程Android Studio中的目錄結構:
5-工程目錄結構

3.1 各模組和彼此之間的關係解釋:

  • lib_opensource :第三方build.gradle依賴,本專案主要有supportlifecycleroomfrescoretrofitokhttpRxJavaARouter這些。

  • lib_coremodel: 存放MVVM中的ModelViewModel兩個模組,就是資料的處理和資料與UI頁面的繫結。依賴lib_opensource庫。

  • lib_common : 公共庫,主要有各種base,各種ui元件,自定義元件,公用的Activity、公用的Fragment,和公用的utils等等。依賴lib_coremodel庫。

  • module_girls : 妹子功能模組,可以在libraryapplication之間切換,自己可以是一個app也可以成為別的app的一個元件模組。元件化編譯時為app,反之為module。

  • module_news : 新聞功能模組,可以在libraryapplication之間切換,自己可以是一個app也可以成為別的app的一個元件模組。元件化編譯時為app,反之為module。

  • app_universal : 定製版本的app,元件化編譯時 module_girlsmodule_news為app,所以不能把這兩個作為module加進來編譯,所以元件化編譯時app_universal要依賴lib_common庫,反之就可以把 module_girlsmodule_news作為module加進來編譯。

  • app_specific : 定製版本的app,元件化編譯時 module_girlsmodule_news為app,所以不能把這兩個作為module加進來編譯,所以元件化編譯時app_specific要依賴lib_common庫,反之就可以把 module_girlsmodule_news作為module加進來編譯。

3.2 ARouter串聯各個模組

使用ARouter來跳轉Activity和獲取Fragment,記得看之前別人的元件化結構文章,一直都在糾結Fragment的獲取問題,我想說的是有了ARouter來獲取Fragment不是超級簡單麼?

ARouter典型應用

  • 從外部URL對映到內部頁面,以及引數傳遞與解析
  • 跨模組頁面跳轉,模組間解耦
  • 攔截跳轉過程,處理登陸、埋點等邏輯
  • 跨模組API呼叫,通過控制反轉來做元件解耦

3.3 元件化編譯和非元件化編譯切換

我們在工程根目錄下的gradle.properties檔案中加入一個Boolean型別的變數,通過修改這個變數來識別編譯模式:

# 每次更改“isModule”的值後,需要點選 "Sync Project" 按鈕
# isModule是“整合開發模式”和“元件開發模式”的切換開關
isModule=false

然後在 module_girlsmodule_news中的build.gradle檔案中支援切換:

if (isModule.toBoolean()) {
    //元件化編譯時為application
    apply plugin: 'com.android.application'
} else {
    //非元件化編譯時為library
    apply plugin: 'com.android.library'
}

android {
    compileSdkVersion build_versions.target_sdk
    buildToolsVersion build_versions.build_tools

    defaultConfig {
        minSdkVersion build_versions.min_sdk
        targetSdkVersion build_versions.target_sdk
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

        //ARouter
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [moduleName: project.getName()]
            }
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    dataBinding {
        enabled = true
    }
    lintOptions {
        abortOnError false
    }
    sourceSets {
        main {
            if (isModule.toBoolean()) {
                //元件化編譯時為app,在對應的AndroidManifest檔案中需要寫ndroid.intent.action.MAIN入口Activity
                manifest.srcFile 'src/main/module/AndroidManifest.xml'
            } else {
                manifest.srcFile 'src/main/AndroidManifest.xml'
                //整合開發模式下排除debug資料夾中的所有Java檔案
                java {
                    //debug資料夾中放的是Application類,非元件化時不用有此類
                    exclude 'debug/**'
                }
            }
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    api project(':lib_coremodel')
    api project(':lib_common')
    implementation 'com.android.support:support-v4:26.1.0'
    annotationProcessor deps.arouter.compiler
}

上面看到了元件化和非元件化編譯會有不用的AndroidManifest檔案,元件化時需要debug資料夾下面的application類,非元件化時排除此資料夾。
6-元件化非元件化編譯切換

  • module下的AndroidManifest檔案是元件化app編譯時的,寫了MAIN入口Activity
  • dubug下是元件化app編譯時的Application類,初始化作為一個app執行時需要的資源等等。在非元件化編譯在build.gradle檔案中排除debug資料夾的所以東西。

3.4 最後預告:

後面會有一些列介紹在MVVM元件化過程中使用ARouter來跳轉Activity和獲取FragmentDataBinding實現資料和UI的互相繫結、Rxjava2Retrofit2動態資料獲取,和AndroidViewModel的封裝。

下面貼貼一個lib_coremodel庫中我封裝的AndroidViewModel,用泛型來確定資料型別,並且是動態URL獲取資料:

package google.architecture.coremodel.viewmodel;

import android.app.Application;
import android.arch.lifecycle.AndroidViewModel;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData;
import android.databinding.ObservableField;
import android.support.annotation.NonNull;

import java.io.IOException;
import java.lang.reflect.ParameterizedType;

import google.architecture.coremodel.datamodel.http.ApiClient;
import google.architecture.coremodel.datamodel.http.ApiConstants;
import google.architecture.coremodel.datamodel.http.service.DynamicApiService;
import google.architecture.coremodel.util.JsonUtil;
import io.reactivex.Observer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import okhttp3.ResponseBody;

/**
 * Created by dxx on 2017/11/20.
 */

public class BaseViewModel<T> extends AndroidViewModel {

    //生命週期觀察的資料
    private MutableLiveData<T>  liveObservableData = new MutableLiveData<>();
    //UI使用可觀察的資料 ObservableField是一個包裝類
    public ObservableField<T> uiObservableData = new ObservableField<>();

    private final CompositeDisposable mDisposable = new CompositeDisposable();

    private static final MutableLiveData ABSENT = new MutableLiveData();
    {
        //noinspection unchecked
        ABSENT.setValue(null);
    }


    public BaseViewModel(@NonNull Application application, String fullUrl) {
        super(application);
        ApiClient.initService(ApiConstants.GankHost, DynamicApiService.class).getDynamicData(fullUrl).subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer<ResponseBody>() {
            @Override
            public void onSubscribe(Disposable d) {
                mDisposable.add(d);
            }

            @Override
            public void onNext(ResponseBody value) {
               if(null != value){
                   try {
                       liveObservableData.setValue(JsonUtil.Str2JsonBean(value.string(), getTClass()));
                   } catch (IOException e) {
                       e.printStackTrace();
                   }
               }
            }

            @Override
            public void onError(Throwable e) {

            }

            @Override
            public void onComplete() {

            }
        });
    }

    /**
     * LiveData支援了lifecycle生命週期檢測
     * @return
     */
    public LiveData<T> getLiveObservableData() {
        return liveObservableData;
    }

    /**
     * 當主動改變資料時重新設定被觀察的資料
     * @param product
     */
    public void setUiObservableData(T product) {
        this.uiObservableData.set(product);
    }

    public Class<T> getTClass(){
        Class<T> tClass = (Class<T>)((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        return tClass;
    }

    @Override
    protected void onCleared() {
        super.onCleared();
        mDisposable.clear();
    }
}