kotlin版本組件化+mvvm項目架構

主要技術點
  1. Kotlin
  2. MVVM
  3. Databinding
  4. Arouter路由
  5. Dagger依賴注入
  6. Rxjava
  7. Retrofit

MVVM:MVVM設計模式的一套快速開發庫,整合Okhttp+RxJava+Retrofit+Glide等主流模塊,知足平常開發需求。使用該框架能夠快速開發一個高質量、易維護的Android應用。html

ARouter:阿里出的一個用於幫助 Android App 進行組件化改造的框架 —— 支持模塊間的路由、通訊、解耦。java

MVVM + ARouter:MVVM模式 + 組件化方案,前者是設計模式,後者是方案架構,二者並用,相得益彰。有這兩個框架做支撐,事半功倍,可快速開發組件化應用。android

ps:具體的技能點使用這裏就不講解的git

項目總體模塊

如圖:github

  • app 關於app的入口module
  • lib_basemvvm 整個項目的底層mvvm封裝,並不涉及到項目相關的任何業務代碼
  • lib_common 關於項目公用的業務代碼
  • module_login 登陸模塊
  • module_mine 個人模塊

項目MVVM結構

  • core 存放Application類的相關代碼,好比Activity/Fragment生命週期監聽。
  • di 存放dagger注入的相關代碼
    • component 提供須要注入的橋樑
    • module 提供須要注入的實體
      • AppModule 提供App須要的單例類,好比網絡請求/數據庫
      • ActivityModule 提供Activity類
      • FragmentModule 提供Fragment類
      • ViewModelModule 提供ViewModel類
  • mvvm
    • view 主要是activity和fragment
    • viewmodel 全部的viewmodel
    • model這裏主要仍是網絡的請求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

項目gralde文件說明

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'
	}
  }
 }
}
複製代碼

productFlavor配置

公司採用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

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地址以下:

github.com/mouxuefei/M….

相關文章
相關標籤/搜索