首页 > 技术知识 > 正文

前言

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(其他业务模块) Android–MVVM组件化 kotlin(1) buildSrc 结构 Android–MVVM组件化 kotlin(1)1 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 工具类模块 这个模块是整个项目的公共工具模块,所有模块都要引用,比较重要,放在前面 先把一些基类和配置定义好 Android–MVVM组件化 kotlin(1)2 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) }
<

这里放公用的图标,布局,风格,尺寸…… Android–MVVM组件化 kotlin(1)3 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>

Android–MVVM组件化 kotlin(1)4 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模块的清单文件中注册权限,应用的基本配置 Android–MVVM组件化 kotlin(1)5 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 主页模块,涉及业务为主页相关 Android–MVVM组件化 kotlin(1)6 先看看组件化配置 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模块–其他业务模块,无法区分的一些业务可以放在这里面

猜你喜欢