作者: Dawish_大D
簡書: http://www.lxweimin.com/u/40e1f22c2c53
(一)Android官方MVVM框架實(shí)現(xiàn)組件化之整體結(jié)構(gòu)
(二)Android官方MVVM框架實(shí)現(xiàn)組件化之ARouter串聯(lián)各模塊
目前的項(xiàng)目結(jié)構(gòu)圖置頂:Demo的Github地址: https://github.com/Dawish/GoogleArchitectureDemo
一、google官方MVVM框架講解
我前面對(duì)比了MVC和MVP《兩張圖看懂Android開發(fā)中MVC與MVP的區(qū)別》,可以相對(duì)于MVC
我們的MVP
是有多優(yōu)越,但是Android開發(fā)現(xiàn)在已經(jīng)開始流行了MVVM
,前不久google官方發(fā)布了MVVM的正式庫。官方的正式MVVM庫主要包括下面四個(gè):
其中只有ViewModel
是MVVM結(jié)構(gòu)中的一個(gè)組件,其他的三個(gè)都是輔助性質(zhì)的。
lifecycles
就是處理UI界面的生命周期,在26版本以后的Support庫中,AppCompatActivity
和SupportActivity
中都實(shí)現(xiàn)了LifecycleOwner
,內(nèi)部已經(jīng)對(duì)UI界面的生命周期做了處理了。
LiveData
是一個(gè)抽象類,我們可以存放UI頁面需要的數(shù)據(jù),就是把數(shù)據(jù)包裝在LiveData
中了,我們可以觀測LiveData
中的數(shù)據(jù)變化,但是LiveData
是跟UI的生命周期關(guān)聯(lián)的,當(dāng)UI頁面銷毀了,LiveData
的數(shù)據(jù)變化回調(diào)是不會(huì)執(zhí)行的。
Room
就是一個(gè)sqlite數(shù)據(jù)持久化庫,我們也可以使用別的ORM庫。
二、MVVM架構(gòu)優(yōu)勢
《兩張圖看懂Android開發(fā)中MVC與MVP的區(qū)別》 前面兩張圖真是了MVC和MVP的區(qū)別,我這里也來一張圖看看MVVM:
看上圖Model
和View
是不會(huì)發(fā)生關(guān)系的,ViewModel
是把View和Model關(guān)聯(lián)起來的加工廠:
MVVM優(yōu)勢總結(jié):
View
和Model
雙向綁定,一方的改變都會(huì)影響另一方,開發(fā)者不用再去手動(dòng)修改UI的數(shù)據(jù)。額,互相自動(dòng)的。不需要
findViewById
也不需要butterknife
,不需要拿到具體的View
去設(shè)置數(shù)據(jù)綁定監(jiān)聽器等等,這些都可以用DataBinding
完成。是不是很舒服?View
和Model
的雙向綁定是支持生命周期檢測的,不會(huì)擔(dān)心頁面銷毀了還有回調(diào)發(fā)生,這個(gè)由lifeCycle
完成。不會(huì)像
MVC
一樣導(dǎo)致Activity
中代碼量巨大,也不會(huì)像MVP
一樣出現(xiàn)大量的View
和Presenter
接口。項(xiàng)目結(jié)構(gòu)更加低耦合。更低的耦合把各個(gè)模塊分開開發(fā),分開測試,可以分給不同的開發(fā)人員來完成。
三、MVVM組件化示例項(xiàng)目架構(gòu)分析
下圖是項(xiàng)目模塊和工程之間的依賴關(guān)系:
下圖是工程Android Studio中的目錄結(jié)構(gòu):
3.1 各模塊和彼此之間的關(guān)系解釋:
lib_opensource
:第三方build.gradle依賴,本項(xiàng)目主要有support
、lifecycle
、room
、fresco
、retrofit
、okhttp
、RxJava
、ARouter
這些。lib_coremodel
: 存放MVVM中的Model
和ViewModel
兩個(gè)模塊,就是數(shù)據(jù)的處理和數(shù)據(jù)與UI頁面的綁定。依賴lib_opensource
庫。lib_common
: 公共庫,主要有各種base
,各種ui組件,自定義組件,公用的Activity
、公用的Fragment
,和公用的utils
等等。依賴lib_coremodel
庫。module_girls
: 妹子功能模塊,可以在library
和application
之間切換,自己可以是一個(gè)app
也可以成為別的app的
一個(gè)組件模塊。組件化編譯時(shí)為app,反之為module。module_news
: 新聞功能模塊,可以在library
和application
之間切換,自己可以是一個(gè)app
也可以成為別的app
的一個(gè)組件模塊。組件化編譯時(shí)為app,反之為module。app_universal
: 定制版本的app,組件化編譯時(shí)module_girls
和module_news
為app,所以不能把這兩個(gè)作為module加進(jìn)來編譯,所以組件化編譯時(shí)app_universal
要依賴lib_common
庫,反之就可以把module_girls
和module_news
作為module加進(jìn)來編譯。app_specific
: 定制版本的app,組件化編譯時(shí)module_girls
和module_news
為app,所以不能把這兩個(gè)作為module加進(jìn)來編譯,所以組件化編譯時(shí)app_specific
要依賴lib_common
庫,反之就可以把module_girls
和module_news
作為module加進(jìn)來編譯。
3.2 ARouter串聯(lián)各個(gè)模塊
使用ARouter
來跳轉(zhuǎn)Activity
和獲取Fragment
,記得看之前別人的組件化結(jié)構(gòu)文章,一直都在糾結(jié)Fragment
的獲取問題,我想說的是有了ARouter
來獲取Fragment
不是超級(jí)簡單么?
ARouter典型應(yīng)用
- 從外部URL映射到內(nèi)部頁面,以及參數(shù)傳遞與解析
- 跨模塊頁面跳轉(zhuǎn),模塊間解耦
- 攔截跳轉(zhuǎn)過程,處理登陸、埋點(diǎn)等邏輯
- 跨模塊API調(diào)用,通過控制反轉(zhuǎn)來做組件解耦
3.3 組件化編譯和非組件化編譯切換
我們?cè)诠こ谈夸浵碌?code>gradle.properties文件中加入一個(gè)Boolean
類型的變量,通過修改這個(gè)變量來識(shí)別編譯模式:
# 每次更改“isModule”的值后,需要點(diǎn)擊 "Sync Project" 按鈕
# isModule是“集成開發(fā)模式”和“組件開發(fā)模式”的切換開關(guān)
isModule=false
然后在 module_girls
和module_news
中的build.gradle
文件中支持切換:
if (isModule.toBoolean()) {
//組件化編譯時(shí)為application
apply plugin: 'com.android.application'
} else {
//非組件化編譯時(shí)為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()) {
//組件化編譯時(shí)為app,在對(duì)應(yīng)的AndroidManifest文件中需要寫ndroid.intent.action.MAIN入口Activity
manifest.srcFile 'src/main/module/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
//集成開發(fā)模式下排除debug文件夾中的所有Java文件
java {
//debug文件夾中放的是Application類,非組件化時(shí)不用有此類
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
}
上面看到了組件化和非組件化編譯會(huì)有不用的AndroidManifest
文件,組件化時(shí)需要debug
文件夾下面的application
類,非組件化時(shí)排除此文件夾。
-
module
下的AndroidManifest
文件是組件化app編譯時(shí)的,寫了MAIN
入口Activity
-
dubug
下是組件化app編譯時(shí)的Application
類,初始化作為一個(gè)app
運(yùn)行時(shí)需要的資源等等。在非組件化編譯在build.gradle
文件中排除debug
文件夾的所以東西。
3.4 最后預(yù)告:
后面會(huì)有一些列介紹在MVVM
組件化過程中使用ARouter
來跳轉(zhuǎn)Activity
和獲取Fragment
、DataBinding
實(shí)現(xiàn)數(shù)據(jù)和UI的互相綁定、Rxjava2
和Retrofit2
動(dòng)態(tài)數(shù)據(jù)獲取,和AndroidViewModel
的封裝。
下面貼貼一個(gè)lib_coremodel
庫中我封裝的AndroidViewModel
,用泛型來確定數(shù)據(jù)類型,并且是動(dòng)態(tài)URL獲取數(shù)據(jù):
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 {
//生命周期觀察的數(shù)據(jù)
private MutableLiveData<T> liveObservableData = new MutableLiveData<>();
//UI使用可觀察的數(shù)據(jù) ObservableField是一個(gè)包裝類
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;
}
/**
* 當(dāng)主動(dòng)改變數(shù)據(jù)時(shí)重新設(shè)置被觀察的數(shù)據(jù)
* @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();
}
}
Demo的Github地址: https://github.com/Dawish/GoogleArchitectureDemo