MVVM:MVVM設計模式的一套快速開發庫,整合Okhttp+RxJava+Retrofit+Glide等主流模塊,知足平常開發需求。使用該框架能夠快速開發一個高質量、易維護的Android應用。html
ARouter:阿里出的一個用於幫助 Android App 進行組件化改造的框架 —— 支持模塊間的路由、通訊、解耦。java
MVVM + ARouter:MVVM模式 + 組件化方案,前者是設計模式,後者是方案架構,二者並用,相得益彰。有這兩個框架做支撐,事半功倍,可快速開發組件化應用。android
ps:具體的技能點使用這裏就不講解的git
如圖:github
關於app的入口module
整個項目的底層mvvm封裝,並不涉及到項目相關的任何業務代碼
關於項目公用的業務代碼
登陸模塊
個人模塊
存放Application類的相關代碼,好比Activity/Fragment生命週期監聽。
存放dagger注入的相關代碼
提供須要注入的橋樑
提供須要注入的實體
提供App須要的單例類,好比網絡請求/數據庫
提供Activity類
提供Fragment類
提供ViewModel類
主要是activity和fragment
全部的viewmodel
這裏主要仍是網絡的請求apiservice,實際上是省略了model模塊
MVVM能夠看做是一種特殊的MVP(Passive View)模式,或者說是對MVP模式的一種改良。數據庫
MVVM表明的是Model-View-ViewModel,這裏須要解釋一下什麼是ViewModel。ViewModel的含義就是 "Model of View",視圖的模型。它的含義包含了領域模型(Domain Model)和視圖的狀態(State)。 在圖形界面應用程序當中,界面所提供的信息可能不單單包含應用程序的領域模型。還可能包含一些領域模型不包含的視圖狀態,例如電子表格程序上須要顯示當前排序的狀態是順序的仍是逆序的,而這是Domain Model所不包含的,但也是須要顯示的信息。設計模式
能夠簡單把ViewModel理解爲頁面上所顯示內容的數據抽象,和Domain Model不同,ViewModel更適合用來描述View。api
config.gradle =>三方依賴庫和版本管理,統一放在該文件中網絡
config_build.gradle =>整個項目(app/module) 通用的gradle配置。架構
component_build.gradle =>組件化開關的配置,lib和application的切換以及清單文件的配置
component_build代碼以下:
if (isBuildModule.toBoolean()) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
apply from: "../config_build.gradle"
android {
//這裏進行設置使用單獨運行仍是合併運行的Manifest.xml
sourceSets {
main {
jniLibs.srcDirs = ['libs']
if (isBuildModule.toBoolean()) {
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/release/AndroidManifest.xml'
}
}
}
}
複製代碼
公司採用jenkins打包,配置三種類型productFlavor,方便提測和發版,一種開發環境,一種測試環境,一種正式環境,這樣經過不一樣的命令行便可打包不一樣環境的apk
productFlavors {
versionDev{
dimension "verison"
buildConfigField "boolean","VERSION_ONLINE", "false"
buildConfigField "boolean","VERSION_TEST", "false"
//manifestPlaceholders = rootProject.ext.debugPlaceholders
}
versionTest {
dimension "verison"
buildConfigField "boolean","VERSION_ONLINE", "false"
buildConfigField "boolean","VERSION_TEST", "true"
//manifestPlaceholders = rootProject.ext.debugPlaceholders
}
versionOnline{
dimension "verison"
buildConfigField "boolean","VERSION_ONLINE", "true"
buildConfigField "boolean","VERSION_TEST", "true"
//manifestPlaceholders = rootProject.ext.releasePlaceholders
}
}
複製代碼
打包命令行:
* 1,開發環境打包 gradlew clean assembleVersionDevDebug或者gradlew clean assembleVersionDevRealease
* 2,測試環境打包 gradlew clean assembleVersionTestDebug或者gradlew clean assembleVersionTestRealease
* 3,正式環境打包 gradlew clean assembleVersionOnlineRelease
複製代碼
代碼配置:
object HttpUrlConstants {
//開發環境
private const val DEV_BASE_URL = "https://wanandroid.com/dev"
//測試環境
private const val TEST_BASE_URL = "https://wanandroid.com/test"
//正式環境
private const val RELIASE_BASE_URL = "https://wanandroid.com/online"
//獲取bse_url
fun getBaseUrl(): String = if (BuildConfig.VERSION_ONLINE) RELIASE_BASE_URL else{
if (BuildConfig.VERSION_TEST) TEST_BASE_URL else DEV_BASE_URL
}
複製代碼
gradle配置
每一個模塊的gradle文件須要引入公共的component_build.gradle,而且須要配置productFlavors,另外還須要統一資源前綴,否則打包容易出現資源衝突
例如login模塊的gradle文件
apply from: "../component_build.gradle"
android {
resourcePrefix "login_" //給 Module 內的資源名增長前綴, 避免資源名衝突
flavorDimensions "verison"
productFlavors {
versionDev{
dimension "verison"
}
versionTest {
dimension "verison"
}
versionOnline{
dimension "verison"
}
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
}
複製代碼
manifest配置
組件在本身的AndroidManifest.xml各自配置,application標籤無需添加屬性,也不須要指定activity的intent-filter。當合並打包時,gradle會將每一個組件的AndroidManifest合併到宿主App中。
文件位置:src/main/release/AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.mou.login">
<application>
<meta-data
android:name="com.mou.login.core.GlobalConfiguration"
android:value="ConfigModule"/>
<activity android:name=".mvvm.view.LoginActivity"
android:label="登陸"
android:screenOrientation="portrait"/>
</application>
</manifest>
複製代碼
組件獨立運行時,就須要單獨的一個AndroidManifest.xml做爲調試用。
文件位置:src/main/debug/AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.mou.login">
<application
android:name=".core.App">
<!-- 每一個業務組件須要聲明兩個 ConfigModule, CommonSDK 的 ConfigModule 和 業務組件本身的 ConfigModule
CommonSDK 的 ConfigModule 含有有每一個組件均可共用的配置信息, 業務組件本身的 ConfigModule 含有本身獨有的配置
信息, 這樣便可重用代碼, 又能夠容許每一個組件可自行管理本身獨有的配置信息, 若是業務組件沒有獨有的配置信息則只須要
聲明 CommonSDK 的 ConfigModule -->
<meta-data
android:name="com.fortunes.commonsdk.core.GlobalConfiguration"
android:value="ConfigModule" />
<meta-data
android:name="com.mou.login.core.GlobalConfiguration"
android:value="ConfigModule" />
<meta-data
android:name="design_width_in_dp"
android:value="360"/>
<meta-data
android:name="design_height_in_dp"
android:value="640"/>
<activity android:name=".mvvm.view.LoginActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
複製代碼
每一個模塊獨立運行後如圖:
ARouter是阿里巴巴出品的路由框架,能夠實現各組件之間的通訊
編寫一個工具類進行路由跳轉
object NavigationUtils {
/**
* 去往登陸頁面
*/
fun goLoginActivity() {
ARouter.getInstance().build(RouterConstants.LOGIN_ACTIVITY).navigation()
}
/**
* 去往首頁
*/
fun goMainActivity() {
ARouter.getInstance().build(RouterConstants.MAIN_ACTIVITY).navigation()
}
/**
* 去往WebView頁面
* url:加載的網址
* title:加載的標題
*/
const val WEB_URL = "url"
const val WEB_TITLE = "title"
fun goWebActivity(url: String, title: String) {
ARouter.getInstance().build(RouterConstants.WEB_ACTIVITY)
.withString(WEB_URL, url)
.withString(WEB_TITLE, title)
.navigation()
}
}
複製代碼
以MainActivity爲例 1,建立MainActivity
@Route(path = RouterConstants.MAIN_ACTIVITY)
class MainActivity : BaseActivity<ActivityMainBinding>() {
override fun getLayoutId() = R.layout.activity_main
private val mViewModel by lazy {
createVM(MainViewModel::class.java)
}
override fun initView() {
//設置viewModel
mBinding.apply {
vm=mViewModel
}
btn.setOnClickListener {
mViewModel
.getArticle()
.bindDialogOrLifeCycle(this)
.onHttpSubscribeNoToast(this) {
toast("成功")
}
}
btn_login.setOnClickListener {
NavigationUtils.goLoginActivity()
}
btn_mine.setOnClickListener {
NavigationUtils.goMineActivity()
}
}
override fun initData() {
}
}
複製代碼
2,編寫xml文件
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable name="vm" type="com.mou.mvvmmodule.di.mvvm.viewmodel.MainViewModel"/>
</data>
<com.fortunes.commonsdk.view.toolbar.MyToolBarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:public_toolbar_img="false"
app:public_toolbar_title="首頁">
<Button
android:id="@+id/btn"
android:text="請求"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/btn_login"
android:text="去登陸頁"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/btn_mine"
android:text="去我的中心"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<TextView
android:textColor="@color/black"
android:text="@{vm.chapterName}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<TextView
android:textColor="@color/black"
android:text="@{vm.link}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</com.fortunes.commonsdk.view.toolbar.MyToolBarLayout>
</layout>
複製代碼
3,在ActivityModule中提供dagger須要Activity的注入的實例
@Module
abstract class ActivityModule {
@ContributesAndroidInjector
abstract fun contributeMainActivity(): MainActivity
}
複製代碼
4,建立viewModel
class MainViewModel @Inject constructor(private val apiService: ApiService) : BaseViewModel() {
val chapterName = ObservableItemField<String>()
val link = ObservableItemField<String>()
fun getArticle(): Single<BaseBean<ArticleBean>> {
return apiService
.getArticle()
.async()
.doOnSuccess {
chapterName.set(it.data.datas[0].chapterName)
link.set(it.data.datas[0].link)
}
.doOnError {
Timber.d("doOnError")
}
}
複製代碼
}
4,而後在ViewModelModule中提供dagger須要ViewModel的注入的實例
@Module
abstract class ViewModelModule {
@Binds
@IntoMap
@ViewModelKey(MainViewModel::class)
abstract fun bindMainViewModel(viewModel: MainViewModel): ViewModel
}
複製代碼
這樣就基本完成了一個activity的配置,就能夠進行api調用和後續的數據綁定了
具體的sample地址以下: