Android--MVVM组件化 kotlin(1)
前言
MVVM
在经历了MVC和MVP之后,MVVM开发模式应运而生,MVVM的高度解耦和搭配ViewModel、LiveData等JetPack组件使用,减少因生命周期搞出的BUG的同时,也让我们可以更加关注业务代码,减少一些细节的关注,总的来说就是不但省心还能偷懒。
Kotlin
Android开发的亲儿子,使用过JAVA语言的话,上手难度很低,看看语法,一周之内就能掌握,对于本人来说,代码减少了很多,语法糖也很实用,最值得夸赞的是Kotlin的扩展函数和代理,能帮助减少很多模块代码,自带的非空断言能避免一部分BUG产生。(现在不会kotlin,都不好意思说自己会Android)
组件化
把项目功能模块分开解耦,功能模块间一般互不影响,能单独运行,单独开发维护,减少编译速度,提升开发速度。
介绍
本篇介绍一个自己东拼西凑的MVVM组件化基本框架,开发语言使用Kotlin对于业务定制化不高的可以直接copy使用。
相关构成:
buildSrc -- 依赖管理
autosize -- 今日头条屏幕适配方案
gson、fastjson
okhttp、Retrofit、kotlinx-coroutines--Retrofit加kotlin协程做网络请求
coil--kotlin编写的图片加载框架
rxtool-基本工具类
material_dialogs--MD风格弹窗工具类
smartrefresh--刷新加载工具类
BaseRecyclerViewAdapterHelper--RecycleView适配器工具类
koin--Kotlin注解
arouter--阿里巴巴的组件化路由工具
......
感谢以上框架作者做出的奉献!
组件化
kotlin的组件化和java的组件化是有一些细节(坑)要处理的
好的,我们开始,创建项目和组件就不多说了,大家都会,
我的组件分为一下几块,
app(壳工程)、common(工具类)、home(Home模块)、main(主页模块)、other(其他业务模块)
buildSrc
结构
build.gradle.kts
plugins{
`kotlin-dsl`
}
repositories {
gradlePluginPortal()
}
BuildConfig.kt
/**
* 构建版本信息
*/
object BuildConfig {
const val kotlinVersion = "1.4.10"
const val composeVersion = "1.0.0-alpha01"
const val compileSdkVersion = 29
const val buildToolsVersion = "29.0.3"
const val minSdkVersion = 21
const val targetSdkVersion = 29
const val versionCode = 1
const val versionName = "1.0.0"
// true 分开 false集成
const val isComponent = false
}
/**
* 组件化 true合并 false分开
*/
fun buildTime():String {
return Date().time.toString()
}
Deploy.kt
/**
* SDK版本信息
*/
object SdkVersions {
const val androidSupportSdkVersion = "28.0.3"
const val androidxSdkVersion = "1.0.0"
const val dagger2SdkVersion = "2.19"
const val glideSdkVersion = "4.11.0"
const val coilVersion = "1.0.0-rc3"
const val butterknifeSdkVersion = "10.2.0"
const val rxlifecycleSdkVersion = "1.0"
const val rxlifecycle2SdkVersion = "2.2.2"
const val espressoSdkVersion = "3.0.1"
const val canarySdkVersion = "1.5.4"
const val adapterHelperVersion = "3.0.4"
const val rxToolVersion = "2.6.2"
const val koinVersion = "2.2.0-rc-3"
const val refreshVersion = "2.0.1"
const val fuelVersion = "2.3.0"
const val kotlinCoreVersion = "1.3.2"
const val okhttp3Version = "4.9.0"
const val retrofitSdkVersion = "2.9.0"
}
/**
* Koin kotlin 注解库
*/
object Koin {
// Koin for Kotlin
const val koin_core = "org.koin:koin-core:${SdkVersions.koinVersion}"
const val koin_core_ext = "org.koin:koin-core-ext:${SdkVersions.koinVersion}"
const val koin_test = "org.koin:koin-test:${SdkVersions.koinVersion}"
// Koin for Android
const val koin_android = "org.koin:koin-android:${SdkVersions.koinVersion}"
// Koin AndroidX
// Koin AndroidX Scope features
const val koin_scope = "org.koin:koin-androidx-scope:${SdkVersions.koinVersion}"
// Koin AndroidX ViewModel features
const val koin_viewmodel = "org.koin:koin-androidx-viewmodel:${SdkVersions.koinVersion}"
// Koin AndroidX Fragment features
const val koin_fragment = "org.koin:koin-androidx-fragment:${SdkVersions.koinVersion}"
// Koin AndroidX Experimental features
const val koin_ext = "org.koin:koin-androidx-ext:${SdkVersions.koinVersion}"
}
/**
* 网络加载库
* fuel
* retrofit
* okgo
* OkHttp3
*/
object NetWork {
// fuel
const val fuel_android = "com.github.kittinunf.fuel:fuel-android:${SdkVersions.fuelVersion}"
const val fuel_gson = "com.github.kittinunf.fuel:fuel-gson:${SdkVersions.fuelVersion}"
//retrofit
const val retrofit = "com.squareup.retrofit2:retrofit:${SdkVersions.retrofitSdkVersion}"
const val retrofit_converter_gson =
"com.squareup.retrofit2:converter-gson:${SdkVersions.retrofitSdkVersion}"
const val retrofit_adapter_rxjava =
"com.squareup.retrofit2:adapter-rxjava:${SdkVersions.retrofitSdkVersion}"
const val retrofit_adapter_rxjava2 =
"com.squareup.retrofit2:adapter-rxjava2:${SdkVersions.retrofitSdkVersion}"
const val retrofit_url_manager = "me.jessyan:retrofit-url-manager:1.4.0"
// Okgo网络框架
const val okgo = "com.lzy.net:okgo:3.0.4"
const val okrx2 = "com.lzy.net:okrx2:2.0.2"
// OkHttp3
const val okhttp3 = "com.squareup.okhttp3:okhttp:${SdkVersions.okhttp3Version}"
const val okhttp_urlconnection =
"com.squareup.okhttp:okhttp-urlconnection:${SdkVersions.okhttp3Version}"
const val okhttp3_interceptor =
"com.squareup.okhttp3:logging-interceptor:${SdkVersions.okhttp3Version}"
}
BuildConfig和Deploy是可以写在一起的,分开是为了方便区分,这里用BuildCofig编写系统相关变量,Deploy编写所有依赖库
buildSrc就这么多
common
工具类模块
这个模块是整个项目的公共工具模块,所有模块都要引用,比较重要,放在前面
先把一些基类和配置定义好
build.gradle.kts
我这边是用的kotlin配置,arouter配置要注意,公用的第三方库要使用Api关键字
plugins {
id("com.android.library")
kotlin("android")
kotlin("android.extensions")
kotlin("kapt")
}
android {
val javaVersion = JavaVersion.VERSION_1_8
buildToolsVersion(BuildConfig.buildToolsVersion)
compileSdkVersion(BuildConfig.compileSdkVersion)
buildFeatures {
dataBinding = true
}
defaultConfig {
minSdkVersion(BuildConfig.minSdkVersion)
targetSdkVersion(BuildConfig.targetSdkVersion)
versionCode = BuildConfig.versionCode
versionName = BuildConfig.versionName
multiDexEnabled = true
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
compileOptions {
// isCoreLibraryDesugaringEnabled = true
sourceCompatibility = javaVersion
targetCompatibility = javaVersion
}
kotlinOptions {
jvmTarget = javaVersion.toString()
}
}
buildTypes {
getByName("release") {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
}
//ARouter添加
kapt {
arguments {
arg("AROUTER_MODULE_NAME", project.name)
}
}
dependencies {
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
// api(SystemLibs.core_x)
// androidTestApi(SystemLibs.espresso_core_x)
testImplementation(SystemLibs.junit_x)
api(kotlin("stdlib",BuildConfig.kotlinVersion))
// kotlin flow
// api rootProject.ext.dependencies["kotlinx-coroutines"]
api(SystemLibs.multidex)
api(SystemLibs.design_x)
api(SystemLibs.recyclerview_x)
api(SystemLibs.appcompat_x)
api(SystemLibs.cardview_x)
api(SystemLibs.constraint_layout_x)
api(SystemLibs.lifecycle_extensions)
api(SystemLibs.viewmodel_ktx)
api(SystemLibs.core)
api(SystemLibs.coroutines_core)
api(SystemLibs.coroutines_android)
// liveData
// api (SystemLibs.lifecycle_livedata)
//今日头条兼容
api(Utils.autosize)
// gson
api(Utils.gson)
// 网络加载框架
// api(NetWork.fuel_android)
// api(NetWork.fuel_gson)
api(NetWork.okhttp3)
api(NetWork.okhttp3_interceptor)
api(NetWork.retrofit)
api(NetWork.retrofit_converter_gson)
// 阿里巴巴JSON解析类
api(Utils.fastjson)
// 图片解析类 Glide
api(Image.coil)
// api rootProject.ext.dependencies["glide"]
// kapt rootProject.ext.dependencies["glide-compiler"]
//基础工具库
api(Utils.rxtool)
api(Utils.material_dialogs)
api(Utils.material_dialogs_lifecycle)
// api(Utils.loadsir)
// api rootProject.ext.dependencies["rxtool-ui"]
// 刷新
api(SmartRefresh.smartrefresh)
api(SmartRefresh.header_classics)
api(SmartRefresh.footer_classics)
// 万能适配器
api(Utils.adpter_helper)
api(Koin.koin_android)
api(Koin.koin_scope)
api(Koin.koin_viewmodel)
api(Koin.koin_fragment)
// api rootProject.ext.dependencies["koin_ext"]
//
// api rootProject.ext.dependencies["viewmodel-ktx"]
api(Utils.arouter)
kapt(Utils.arouter_compiler)
}
这里放公用的图标,布局,风格,尺寸......
AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.jee.common">
<application>
<uses-library
android:name="org.apache.http.legacy"
android:required="false" />
<meta-data
android:name="design_width_in_dp"
android:value="360" />
<meta-data
android:name="design_height_in_dp"
android:value="640" />
</application>
</manifest>
Base基类我就只贴Application部分了,其他的大家都可以按自己的习惯定制
BaseApplication
open class BaseApplication : MultiDexApplication() {
init {
//设置全局的Header构建器
SmartRefreshLayout.setDefaultRefreshHeaderCreator { context, layout ->
layout.setPrimaryColorsId(R.color.White, R.color.text_color_important) //全局设置主题颜色
ClassicsHeader(context) //.setTimeFormat(new DynamicTimeFormat("更新于 %s"));//指定为经典Header,默认是 贝塞尔雷达Header
}
//设置全局的Footer构建器
SmartRefreshLayout.setDefaultRefreshFooterCreator { context, layout ->
layout.setPrimaryColorsId(R.color.White, R.color.text_color_important)
//指定为经典Footer,默认是 BallPulseFooter
ClassicsFooter(context).setDrawableSize(20f)
}
}
private var isDebugARouter: Boolean = true
override fun onCreate() {
super.onCreate()
if (isDebugARouter) {
ARouter.openLog()
ARouter.openDebug()
}
ARouter.init(this)
RxTool.init(this)
SPreference.setContext(applicationContext)
MultiStatePage.register(EmptyState(), ErrorState(), LoadingState())
// appliation初始化
loadModuleApp()
}
/**
* 加载各个模块的Application,例如:推送和IM等模块都需要有Application,
* 但组件化只能有一个Application,而且为了解耦各个模块不能互相引用,
* 所以只能通过反射方式,把这些module_appliation进行初始化
*/
private fun loadModuleApp() {
for (moduleImpl in IMoudelApplication.MODULE_APP) {
try {
val clazz = Class.forName(moduleImpl)
val obj = clazz.newInstance()
if (obj is IMoudelApplication) {
obj.onCreate(this)
}
} catch (e: ClassNotFoundException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
} catch (e: InstantiationException) {
e.printStackTrace()
}
}
}
override fun onTerminate() {
super.onTerminate()
ARouter.getInstance().destroy()
}
}
IMoudelApplication
这个接口是为了各组件化在Application里初始化自己特有的一些东西,按组件顺序执行
interface IMoudelApplication {
companion object {
/**
* 按顺序加载
*/
val MODULE_APP: Array<String>
get() = arrayOf(
"com.jee.component.App",
"com.jee.main.MainApp",
"com.jee.home.HomeApp",
"com.jee.other.OtherApp"
)
}
fun onCreate(application: Application)
}
PathConstants 路由的地址我是放在common里面的
object PathConstants {
const val HOME_PATH = "/home/homefragment" //首页片段
const val FIND_HOME_PATH = "/find/homefragment" //发现模块首页片段
const val MINE_HOME_PATH = "/mine/homefragment" //我的模块首页片段
const val LOGIN_PATH = "/login/ac" //登录页面
const val FIND_DETAIL_PATH = "/find/detail" //发现详情页面
const val MAIN_ACTIVITY_PATH = "/main/mainactivity" //app壳工程主页
const val HOME_ACTIVITY_PATH = "/home/homeactivity" //Home工程主页
const val MAIN_ACTIVITY_TEST = "/main/test" //app测试
const val SPLASH_ACTIVITY_PATH = "/main/splash" //欢迎页
}
工具类会以及Koin跨组件使用会在后面文章给出
app
这个模块为app壳模块,相对简单
在app模块的清单文件中注册权限,应用的基本配置
App
这个类就是实现IMoudelApplication,以便相关初始化,在这里初始化了Koin,注册所有组件的viewModle
build.gradle.kts
plugins {
id("com.android.application")
kotlin("android")
kotlin("android.extensions")
kotlin("kapt")
}
android {
val javaVersion = JavaVersion.VERSION_1_8
buildToolsVersion(BuildConfig.buildToolsVersion)
compileSdkVersion(BuildConfig.compileSdkVersion)
buildFeatures {
dataBinding = true
}
defaultConfig {
applicationId = "com.jee.component"
minSdkVersion(BuildConfig.minSdkVersion)
targetSdkVersion(BuildConfig.targetSdkVersion)
versionCode = BuildConfig.versionCode
versionName = BuildConfig.versionName
multiDexEnabled = true
//打包时间
resValue("string", "build_time", buildTime())
compileOptions {
// isCoreLibraryDesugaringEnabled = true
sourceCompatibility = javaVersion
targetCompatibility = javaVersion
}
kotlinOptions {
jvmTarget = javaVersion.toString()
}
ndk {
abiFilters.add("armeabi-v7a")
}
}
buildTypes {
getByName("release") {
buildConfigField("boolean", "LEO_DEBUG", "false")
//是否zip对齐
isZipAlignEnabled = true
// isShrinkResources = true
//Proguard
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
getByName("debug") {
//给applicationId添加后缀“.debug”
applicationIdSuffix = ".debug"
//manifestPlaceholders = [app_icon: "@drawable/launch_beta"]
buildConfigField("boolean", "LOG_DEBUG", "true")
isZipAlignEnabled = false
isMinifyEnabled = false
// isShrinkResources = false
isDebuggable = true
}
}
//ARouter添加
kapt {
arguments {
arg("AROUTER_MODULE_NAME", project.name)
}
}
dependencies {
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
kapt(Utils.arouter_compiler)
if (BuildConfig.isComponent) {
implementation(project(":common"))
} else {
implementation(project(":main"))
implementation(project(":home"))
implementation(project(":other"))
}
}
}
public class App : IMoudelApplication {
override fun onCreate(application: Application) {
Log.d(Constants.LOG_TAG,"App---初始化")
// just declare it
// start Koin!
startKoin {
// Android context
androidContext(application.applicationContext)
// modules
modules(CommonModules.theLibModule, MainModules.theLibModule)
}
}
}
LaunchActivity
应用启动类,跳转到main模块
class LaunchActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
intentByRouter(PathConstants.SPLASH_ACTIVITY_PATH)
finish()
}
}
main
主页模块,涉及业务为主页相关
先看看组件化配置
build.gradle.kts
plugins {
if (BuildConfig.isComponent) {
id("com.android.application")
} else {
id("com.android.library")
}
kotlin("android")
kotlin("android.extensions")
kotlin("kapt")
}
android {
val javaVersion = JavaVersion.VERSION_1_8
buildToolsVersion(BuildConfig.buildToolsVersion)
compileSdkVersion(BuildConfig.compileSdkVersion)
compileOptions {
// isCoreLibraryDesugaringEnabled = true
sourceCompatibility = javaVersion
targetCompatibility = javaVersion
}
// composeOptions{
// kotlinCompilerVersion =BuildConfig.kotlinVersion
// kotlinCompilerExtensionVersion =BuildConfig.composeVersion
// }
kotlinOptions {
jvmTarget = javaVersion.toString()
// useIR = true
}
buildFeatures {
dataBinding = true
// compose = true
// viewBinding = true
}
defaultConfig {
minSdkVersion(BuildConfig.minSdkVersion)
targetSdkVersion(BuildConfig.targetSdkVersion)
versionCode = BuildConfig.versionCode
versionName = BuildConfig.versionName
multiDexEnabled = true
}
sourceSets {
getByName("main") {
if (BuildConfig.isComponent) {
manifest.srcFile("src/main/debug/AndroidManifest.xml")
java { exclude("debug/**") }
} else {
manifest.srcFile("src/main/release/AndroidManifest.xml")
}
}
}
}
kapt {
arguments {
arg("AROUTER_MODULE_NAME", project.name)
}
}
dependencies {
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
api(project(":common"))
kapt(Utils.arouter_compiler)
implementation("com.github.gcacace:signature-pad:1.3.1")
// compose
// implementation (SystemLibs.compose_ui)
// implementation (SystemLibs.compose_ui_tooling)
// implementation (SystemLibs.compose_foundation)
// implementation (SystemLibs.compose_material)
// implementation (SystemLibs.compose_material_icons_core)
// implementation (SystemLibs.compose_material_icons_extended)
// implementation (SystemLibs.compose_runtime_livedata)
//
// androidTestImplementation(SystemLibs.compose_ui_test)
}
BuildConfig.isComponent是判断是否组件化,以觉得这个模块是否作为组件
此模块不是组件时就可以单独运行
思考?作为组件的的启动页肯定是由依赖这个组件的模块调起,那么不作为组件应该怎么运行这个模块呢?
因此我们需要两种配置,方便两种模式来调试应用
getByName("main") {
if (BuildConfig.isComponent) {
manifest.srcFile("src/main/debug/AndroidManifest.xml")
java { exclude("debug/**") }
} else {
manifest.srcFile("src/main/release/AndroidManifest.xml")
}
}
在项目类建立两个文件夹,名称随意,能区分就行,这里使用的是debug和release
从截图应该可以看出来 debug是不作为组件时使用的,release是被其他模块依赖时使用的,然后如上述代码配置好就可以了
release/AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.jee.main">
<application android:theme="@style/CommonTheme">
<activity android:name=".SplashActivity"
android:screenOrientation="portrait"/>
<activity android:name=".main.MainActivity"
android:screenOrientation="portrait"/>
</application>
</manifest>
debug/AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.jee.main">
<application
android:name="debug.MainApp"
android:allowBackup="true"
android:icon="@drawable/ic_lanucher"
android:label="@string/mains_app_name"
android:requestLegacyExternalStorage="true"
android:supportsRtl="true"
android:usesCleartextTraffic="true"
android:theme="@style/CommonTheme">
<activity android:name="debug.LaunchActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".SplashActivity"
android:screenOrientation="portrait" />
<activity
android:name=".main.MainActivity"
android:screenOrientation="portrait" />
</application>
</manifest>
debug/MainApp
public class MainApp : BaseApplication() {
override fun onCreate() {
super.onCreate()
// just declare it
// start Koin!
startKoin {
// Android context
androidContext(this@MainApp)
// modules
modules(CommonModules.theLibModule, MainModules.theLibModule)
}
}
}
main/MainApp
public class MainApp : IMoudelApplication {
override fun onCreate(application: Application) {
Log.d(Constants.LOG_TAG,"MainApp---初始化")
}
}
上面是Application相关的东西
SplashActivity
从app模块跳转到这个界面
@Route(path = PathConstants.SPLASH_ACTIVITY_PATH)
class SplashActivity : BaseActivity<SplashActivityBinding>(R.layout.layout_splash) {
override fun initView() {
Handler().postDelayed(Runnable {
intentByRouter(PathConstants.MAIN_ACTIVITY_PATH)
finish()
}, 2000)
}
}
再跳转到主界面,进行业务逻辑处理
组件化相关知识点:
app壳模块--配置基本的应用信息,图标,名称,权限等,启动页跳转到主界面,组件化时依赖其他所有模块,非组件化时只依赖common模块
common工具模块--配置所有公共工具类处,包括网络、权限、弹窗、状态变更、信息存储、基本控件、基本布局等
main主页模块--将主页模块分离出来,再将剩下的细分到其他模块,主要做主页上的相关业务
home模块--一个业务模块,看业务需求定义该模块
other模块--其他业务模块,无法区分的一些业务可以放在这里面
- 分享
- 举报
-
浏览量:2035次2020-08-14 18:38:14
-
浏览量:2054次2020-08-14 18:32:35
-
浏览量:3242次2020-08-14 18:37:03
-
浏览量:3432次2020-08-14 18:36:42
-
浏览量:4607次2021-04-15 14:56:16
-
浏览量:6308次2021-03-29 11:34:27
-
浏览量:4905次2021-04-10 14:11:46
-
浏览量:8515次2017-12-01 16:55:09
-
浏览量:6298次2020-12-20 20:54:26
-
浏览量:8115次2020-12-08 14:36:36
-
浏览量:9871次2020-12-05 14:15:53
-
浏览量:3239次2020-08-14 18:10:33
-
浏览量:619次2023-07-28 10:19:40
-
浏览量:1035次2023-07-26 14:12:28
-
浏览量:4853次2021-06-28 15:59:34
-
浏览量:4647次2021-08-24 15:04:21
-
浏览量:4200次2021-07-02 14:29:37
-
浏览量:4037次2021-07-20 13:58:16
-
浏览量:4497次2021-07-12 13:50:16
-
广告/SPAM
-
恶意灌水
-
违规内容
-
文不对题
-
重复发帖
不吃兔兔
感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~
举报类型
- 内容涉黄/赌/毒
- 内容侵权/抄袭
- 政治相关
- 涉嫌广告
- 侮辱谩骂
- 其他
详细说明