MVVM+组件化+Arouter实现
MVVM+组件化实现
模块概览
底层模块:common、network、resource
功能模块:player、firebase、pay
界面模块:mobile、tablet、tablet_login
壳模块: app
模块实现
1.公共模块的gradle配置
由于不同模块,可能引用相同的依赖库,那么对于这部分共同的模块则需要提取出来做统一的管理,因此在项目的根目录创建了common.gradle。
//project是根项目,可以简单认为是个对象,对象持有一个名词为ext的成员变量 project.ext { //基本的变量配合和版本控制 compileSdkVersion = 30 buildToolsVersion = "30" minSdkVersion = 21 targetSdkVersion = 30 applicationId = "com.sdmc.xmediatv" versionCode = 4 versionName = "4.7.0" //设置app配置 setAppDefaultConfig = { extension -> //指定为application,代表该模块可以单独调试 extension.apply plugin: 'com.android.application' extension.description "app" //公共的apply 主要是用于三方库 extension.apply plugin: 'kotlin-android' extension.apply plugin: 'kotlin-android-extensions' extension.apply plugin: 'kotlin-kapt' //设置项目的android setAppAndroidConfig extension.android //设置项目的三方库依赖 setDependencies extension.dependencies } //设置lib配置(只可以作为lib,不可单独调试) setLibDefaultConfig = { extension -> //library,代表只是单纯的库,不需要依赖其他模块 extension.apply plugin: 'com.android.library' extension.description "lib" setLibAndroidConfig extension.android setDependencies extension.dependencies } //是否允许module单独调试 isModuleDebug = false //动态改变,用于单模块调试 setAppOrLibDefaultConfig = { extension -> if (project.ext.isModuleDebug) { extension.apply plugin: 'com.android.application' extension.description "app" } else { extension.apply plugin: 'com.android.library' extension.description "lib" } extension.apply plugin: 'kotlin-android' extension.apply plugin: 'kotlin-android-extensions' extension.apply plugin: 'kotlin-kapt' //设置通用Android配置 setAppOrLibAndroidConfig extension.android //设置通用依赖配置 setDependencies extension.dependencies } //设置application 公共的android配置 setAppAndroidConfig = { extension -> //extension 相当于 android 对象 extension.compileSdkVersion project.ext.compileSdkVersion extension.buildToolsVersion project.ext.buildToolsVersion extension.defaultConfig { applicationId project.ext.applicationId minSdkVersion project.ext.minSdkVersion targetSdkVersion project.ext.targetSdkVersion versionCode project.ext.versionCode versionName project.ext.versionName extension.flavorDimensions "versionCode" testInstrumentationRunner "android.support.tablet_test.runner.AndroidJUnitRunner" //ARouter 编译生成路由 kapt { arguments { arg("AROUTER_MODULE_NAME", project.getName()) includeCompileClasspath = true } } } extension.buildFeatures { dataBinding = true // for view binding : // viewBinding = true } } //设置lib 公共的android配置 setLibAndroidConfig = { extension -> //extension 相当于 android 对象 extension.compileSdkVersion project.ext.compileSdkVersion extension.buildToolsVersion project.ext.buildToolsVersion extension.defaultConfig { minSdkVersion project.ext.minSdkVersion targetSdkVersion project.ext.targetSdkVersion versionCode project.ext.versionCode versionName project.ext.versionName testInstrumentationRunner "android.support.tablet_test.runner.AndroidJUnitRunner" //ARouter 编译生成路由 /*kapt { arguments { arg("AROUTER_MODULE_NAME", project.getName()) } }*/ } extension.buildFeatures { dataBinding = true // for view binding : // viewBinding = true } } //设置通用的 android配置(可作为project单独调试) setAppOrLibAndroidConfig = { extension -> //extension 相当于 android 对象 extension.compileSdkVersion project.ext.compileSdkVersion extension.buildToolsVersion project.ext.buildToolsVersion extension.defaultConfig { minSdkVersion project.ext.minSdkVersion targetSdkVersion project.ext.targetSdkVersion versionCode project.ext.versionCode versionName project.ext.versionName extension.flavorDimensions "versionCode" testInstrumentationRunner "android.support.tablet_test.runner.AndroidJUnitRunner" //ARouter 编译生成路由 kapt { arguments { arg("AROUTER_MODULE_NAME", project.getName()) includeCompileClasspath = true } } } extension.buildFeatures { //启用自动绑定view id dataBinding = true } //使用的jdk版本 extension.compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } extension.kotlinOptions { jvmTarget = JavaVersion.VERSION_1_8 } //动态改变清单文件资源指向 extension.sourceSets { main { if (project.ext.isModuleDebug) { manifest.srcFile 'src/main/manifest/AndroidManifest.xml' } else { manifest.srcFile 'src/main/AndroidManifest.xml' } } } } //公用的三方库依赖,慎重引入,主要引入基础库依赖 setDependencies = { extension -> extension.implementation fileTree(dir: 'libs', include: ['*.jar']) extension.kapt 'com.alibaba:arouter-compiler:1.2.2' //ARouter 路由apt插件,用于生成相应代码,每个module都需要 extension.implementation 'com.alibaba:arouter-api:1.5.0' extension.implementation 'com.alibaba:arouter-compiler:1.2.2' extension.implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" extension.implementation 'androidx.core:core-ktx:1.3.1' extension.implementation 'com.google.code.gson:gson:2.8.6' extension.implementation 'androidx.appcompat:appcompat:1.2.0' extension.implementation 'androidx.constraintlayout:constraintlayout:2.0.0' extension.implementation 'androidx.recyclerview:recyclerview:1.0.0' } }
根目录的build.gradle:
buildscript { //统一制定版本 ext.kotlin_version = '1.4.31' ext.google_service_version = '19.7.0' ext.exoplayer_version = '2.13.2' repositories { //为当前的build.gradle设置依赖库中心 google() jcenter() } dependencies { //gradle版本 classpath "com.android.tools.build:gradle:4.0.1" //kotlin依赖 classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" //bintray.com maven的依赖 classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:1.0" //sonatype maven的依赖 classpath "com.github.dcendents:android-maven-gradle-plugin:1.5" //黄油刀 classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.3' //谷歌服务 classpath 'com.google.gms:google-services:4.3.3' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } allprojects { repositories { //jitpack库 maven { url 'http://www.jitpack.io' } //bintray.com维护的Maven仓库 jcenter() //谷歌maven库 maven { url "https://maven.google.com" } google() //阿里云镜像maven库地址 maven { url "http://maven.aliyun.com/nexus/content/repositories/releases" } } } task clean(type: Delete) { delete rootProject.buildDir }
2.壳app模块的实现
//从项目的根目录中找到common.gradle apply from: "${rootProject.rootDir}/common.gradle" //project.ext则是之前强调的common.gradle中的对象,该对象有setAppDefaultConfig project.ext.setAppDefaultConfig project //由于setAppDefaultConfig中的android配置是通用配置,所以还需要针对壳本身新增配置 android { //混淆规则配置 buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } debug { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } packagingOptions { //nanohttpd轻量级数据库所需 exclude 'META-INF/nanohttpd/default-mimetypes.properties' exclude 'META-INF/nanohttpd/mimetypes.properties' //为了解决部分第三方库重复打包了META-INF的问题 exclude 'META-INF/NOTICE' exclude 'META-INF/LICENSE' //解决kotlin问题 exclude("META-INF/*.kotlin_module") } compileOptions { sourceCompatibility = '1.8' targetCompatibility = '1.8' } kotlinOptions { jvmTarget = "1.8" } //在本地loacl.propreties中配置打包所需信息 Properties properties = new Properties() InputStream inputStream = project.rootProject.file('local.properties').newDataInputStream() properties.load(inputStream) def sdkDir = properties.getProperty('key.file') def keyFile = file(sdkDir) def key_keyAlias = properties.getProperty('keyAlias') def key_keyPassword = properties.getProperty('keyPassword') def key_storePassword = properties.getProperty('storePassword') signingConfigs { release { v2SigningEnabled true } debug { storeFile file(keyFile) storePassword key_storePassword keyAlias key_keyAlias keyPassword key_keyPassword } } /*android.applicationVariants.all { variant -> variant.outputs.all { output -> outputFileName = new File("../version/", "XMediaTV_Android_" + defaultConfig.versionName + "." + releaseTime() + "_" + variant.buildType.name + ".apk") } }*/ //android打包提示check release builds false lintOptions { checkReleaseBuilds false abortOnError false } } dependencies { //壳工程所依赖的模块,implementation代表不依赖模块下的三分库 implementation project(path: ':mobile') implementation project(path: ':tablet') //api 代表依赖模块下所有的库 api project(path: ':common') api project(path: ':firebase') } //设置仓库地址,用于辅助三方库依赖文件下载 repositories { mavenCentral() }
3.底层模块的model配置
common中的gradle文件:
common模块本身就是为了通用,那么对于一些常用的库,则需要在该模块做一定的依赖处理,以下对不同模块功能依赖做出了标注
apply plugin: 'com.android.library' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' android { compileSdkVersion 29 buildToolsVersion "29.0.3" defaultConfig { minSdkVersion 21 targetSdkVersion 29 versionCode 1 versionName "1.0.0" //设置预览界面的库支持 vectorDrawables.useSupportLibrary = true testInstrumentationRunner "androidx.tablet_test.runner.AndroidJUnitRunner" //混淆文件指定 consumerProguardFiles "consumer-rules.pro" //配合根目录的gradle.properties,代码中调用BuildConfig.SERVER_ADDRESS动态获取对应值,用于自动打包改服务地址 buildConfigField "String", "SERVER_ADDRESS", "\"${SERVER_ADDRESS}\"" buildConfigField "String", "TENANT_CODE", "\"${TENANT_CODE}\"" buildConfigField "String", "AD_SERVER_ADDRESS", "\"${AD_SERVER_ADDRESS}\"" buildConfigField "String", "PLAYER_SERVER_URI", "\"${PLAYER_SERVER_URI}\"" buildConfigField "Boolean", "FORCE_USE_TABLET", project.properties.get("FORCE_USE_TABLET") } //设置arouter模块名称 kapt { arguments { arg("AROUTER_MODULE_NAME", project.getName()) } } //资源前缀强制命名 resourcePrefix "common_" //混淆模式 buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } //用于换肤,让所有model都可以拥有不同类型的资源目录 sourceSets { main { res.srcDirs = ['src/main/res', 'src/main/res-light', 'src/main/res-news'] } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } dependencies { implementation fileTree(dir: "libs", include: ["*.jar"]) //》》》》kotlin //kotlin核心库配置,对应根目录的build.gradle版本 implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation 'androidx.core:core-ktx:1.3.2' //kotlin协程库 api 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3-native-mt' api 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3-native-mt' //》》》》视图 //android视图库 api 'androidx.appcompat:appcompat:1.2.0' api 'com.google.android.material:material:1.3.0' api 'androidx.constraintlayout:constraintlayout:2.0.1' api 'androidx.recyclerview:recyclerview:1.1.0' //glide图片加载库 api 'com.github.bumptech.glide:glide:4.12.0' //圆角的ImageView库 api 'com.makeramen:roundedimageview:2.3.0' //recycleView的adapter库 api 'com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.4' //下拉刷新库 implementation 'com.lcodecorex:tkrefreshlayout:1.0.7' //头条的自适应库 api 'me.jessyan:autosize:1.2.1' //换肤库 api 'skin.support:skin-support:4.0.5' // skin-support api 'skin.support:skin-support-appcompat:4.0.5' // skin-support 基础控件支持 api 'skin.support:skin-support-design:4.0.5' // skin-support-design material design 控件支持[可选] api 'skin.support:skin-support-cardview:4.0.5' // skin-support-cardview CardView 控件支持[可选] api 'skin.support:skin-support-constraint-layout:4.0.5' // skin-support-constraint-layout ConstraintLayout 控件支持[可选] //本地资源库 api project(path: ':resource') //》》》》网络 //用于retrofit2库依赖 implementation 'com.squareup.retrofit2:converter-gson:2.9.0' implementation 'com.squareup.retrofit2:adapter-rxjava2:2.9.0' //用于retrofit2+kotlin+coroutines的回调库,可以直接加上suspend关键字获取到回调对象 implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' //okhttp3库 implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.2' //gson解析库 implementation 'com.google.code.gson:gson:2.8.6' //》》》》其余基本库 //郭霖的Litepal数据库 api 'org.litepal.guolindev:core:3.2.2' //sharePreference库 api 'com.orhanobut:hawk:2.0.1' //阿里的路由库 kapt 'com.alibaba:arouter-compiler:1.2.2' api 'com.alibaba:arouter-api:1.5.0' //谷歌的deeplink分享 implementation 'com.google.firebase:firebase-dynamic-links:19.1.0' //-------------------------------------------------------------- //》》》》lifecycle库,用于liveData和viewModle,使用与mvvm框架 def lifecycle_version = '2.2.0' // ViewModel and LiveData api "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" // alternatively - just ViewModel // api "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version" api 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0' // For Kotlin use lifecycle-viewmodel-ktx // alternatively - just LiveData // api "androidx.lifecycle:lifecycle-livedata:$lifecycle_version" // 有生命周期感知的协程 api 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.0' }
network中的gradle文件:
该模块为网络请求模块,用到的框架是retrofit2 + kotlin + coroutines,本质是请求耗时网络,通过kotlin使用coroutines的suspend挂起,来达到子线程请求,主线程接收数据的效果。
apply plugin: 'com.android.library' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' android { compileSdkVersion 29 defaultConfig { minSdkVersion 21 targetSdkVersion 29 versionCode 1 versionName "1.0.0" buildConfigField "String", "SERVER_ADDRESS", "\"${SERVER_ADDRESS}\"" buildConfigField "String", "TENANT_CODE", "\"${TENANT_CODE}\"" buildConfigField "String", "AD_SERVER_ADDRESS", "\"${AD_SERVER_ADDRESS}\"" buildConfigField "String", "PLAYER_SERVER_URI", "\"${PLAYER_SERVER_URI}\"" buildConfigField "Boolean", "FORCE_USE_TABLET", project.properties.get("FORCE_USE_TABLET") } resourcePrefix "network_" buildTypes { debug { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } release { minifyEnabled true } } compileOptions { sourceCompatibility = '1.8' targetCompatibility = '1.8' } } dependencies { implementation fileTree(dir: "libs", include: ["*.jar"]) //用于添加retrofit2拦截请求头用的 implementation 'com.squareup.okhttp3:logging-interceptor:4.8.1' //Chuck辅助工具,用于打印各种http请求 debugImplementation 'com.readystatesoftware.chuck:library:1.1.0' releaseImplementation 'com.readystatesoftware.chuck:library-no-op:1.1.0' //-------------------------------------------------------------- api project(path: ':common') implementation files('libs\\XMediaCryptoJar_HKSC.jar') }
resoure的gradle:
该Model主要是用于存放抽取的颜色、多语言、以及与app相关的图标、占位图等
apply plugin: 'com.android.library' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' android { compileSdkVersion 29 buildToolsVersion "29.0.3" defaultConfig { minSdkVersion 21 targetSdkVersion 29 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } sourceSets { main { res.srcDirs = ['src/main/res', 'src/main/res-light', 'src/main/res-news'] } } } dependencies { implementation fileTree(dir: "libs", include: ["*.jar"]) //kotlin implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation 'androidx.core:core-ktx:1.3.2' //单元测试库 testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' }
基本模块配置都是作为library库使用,对于基本模块的内容有要求,就是一定是一项基础并且多模块共用的功能,才值得放在基础模块中。
4.功能模块或界面模块的依赖配置
对于模块依赖,只要弄清楚api和implementation的功能,并且清楚common.gradle,是如何使用的,就能很清晰的使用模块化项目开发。
apply from: "${rootProject.rootDir}/common.gradle" //使用common.gradle中的公用配置 project.ext.setAppOrLibDefaultConfig project android { resourcePrefix "tablet_" buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } dependencies { //这里统一依赖了network,network中由于是api common,common api resource,所以只需要implementation network就行 implementation project(path: ':network') //统一依赖功能model,播放器、Firebase、支付 implementation project(path: ':player') implementation project(path: ':firebase') implementation project(path: ':pay') //依赖了一个界面模块,含各种平板的弹窗界面和登录 implementation project(path: ':tablet_login') //下拉刷新库 implementation 'com.lcodecorex:tkrefreshlayout:1.0.7' //轮播图库 implementation 'com.github.zhpanvip:BannerViewPager:3.1.6' //谷歌支付库 implementation 'com.android.billingclient:billing:2.1.0' //依次是,谷歌消息、DeepLink、广告 implementation 'com.google.firebase:firebase-messaging:20.1.2' implementation 'com.google.firebase:firebase-dynamic-links:19.1.0' implementation "com.google.android.gms:play-services-ads:$google_service_version" }
模块内容
1.common
image-20210331093833739.png
base包:
存放着各种基类,其中主要的是MVVM,所以着重讲述MVVM框架搭建
ViewModel讲解:
首先,要继承ViewModel,导包,viewModel主要功能是,代替UI界面去做耗时操作,所以通过viewModelScope对象调用协程挂起,最终通过liveData绑定数据回调。
api "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" //viewModel+livedata api 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0' //kotlin专用
由于ViewModel是配合Coroutine使用的,所以必需先认识CoroutineScope(协程范围)和withContext(Dispatchers.IO)中的Dispatchers模式,这里先讲Dispatchers的用途和具体场景。
image-20210331100456926.png
//实现LifecycleObserver接口,是为了通过activity的lifecycle.addObserver(LifecycleObserver) 绑定到生命周期中open class BaseViewModel : ViewModel(), LifecycleObserver { //viewModelScope是ViewModel中的成员对象,该对象实现了CoroutineScope接口 //suspend CoroutineScope.() -> Unit, suspend是kotlin线程挂起的标识,实际上去走了await()方法 //CoroutineScope.() -> Unit 代表一个方法体,并且该方法体对象是CoroutineScope接口对象,主要是规范方法体类型,可除去CoroutineScope. fun launchUI(block: suspend CoroutineScope.() -> Unit) = viewModelScope.launch { try { block() } catch (e: Exception) { e.printStackTrace() } } //相比上个方法,主要是多了 withContext(Dispatchers.IO),目的是切换到子线程做耗时操作 fun launchIO(block: suspend CoroutineScope.() -> Unit) = viewModelScope.launch { withContext(Dispatchers.IO) { try { block() } catch (e: Exception) { e.printStackTrace() } } } //errorHandler 是为了统一处理网络异常 fun launchIO( block: suspend CoroutineScope.() -> Unit, errorHandler: (code: Int, message: String) -> Unit?, ) = viewModelScope.launch { withContext(Dispatchers.IO) { try { if (NetWorkUtils.isNetConnect(CommonManager.context)) { block() } else { errorHandler(300, "No internet connection") } } catch (e: Exception) { handlerErrorCode(e, errorHandler) e.printStackTrace() }finally { //.... } } } private fun handlerErrorCode( e: Exception, errorHandler: (code: Int, message: String) -> Unit?, ) { when (e) { is HttpException -> { errorHandler(e.code(), e.message()) } is UnknownHostException -> { errorHandler(404, "Unable to connect to server") } is SocketTimeoutException -> { errorHandler(408, "Socket time out")//访问超时 } is ConnectException -> { errorHandler(404, "Connect exception") } is SocketException -> { errorHandler(500, "Socket exception") } is EOFException -> { errorHandler(500, "EOF exception") //连接意外中断 } is IllegalArgumentException -> { errorHandler(400, "Illegal argument exception")//参数错误 } is SSLException -> { errorHandler(401, "SSL exception")//证书错误 } is NullPointerException -> { errorHandler(600, "Null pointer exception") } else -> { errorHandler(700, "Unknown exception") } } } fun Map<String, Any>.getBody(): RequestBody { return Gson().toJson(this).toRequestBody("application/json".toMediaTypeOrNull()) } data class ErrorHandler(val code: Int, val message: String)}
BaseVMActivity讲解:
//持有俩个泛型,VM和DB,分别是BaseViewModel和ViewDataBinding的实现类abstract class BaseVMActivity<VM : BaseViewModel, DB : ViewDataBinding> : BaseActivity(){ //是否打印生命周期 var openLifecycle: Boolean = false lateinit var viewModel: VM lateinit var dataBinding: DB override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) LogUtil.e(TAG, "onCreate", openLifecycle) //绑定视图,获取到dataBinding dataBinding = DataBindingUtil.setContentView(this, provideContentViewId()) tvTitle = dataBinding.root.findViewById(R.id.title) back = dataBinding.root.findViewById(R.id.back) //实例化ViewModel viewModel = initVM() //让viewModel关联生命周期 lifecycle.addObserver(viewModel) //开始接口监听回调 startObserve() } abstract fun initVM(): VM abstract fun startObserve() override fun onResume() { super.onResume() LogUtil.e(TAG, "onResume", openLifecycle) } override fun onPause() { super.onPause() LogUtil.e(TAG, "onPause", openLifecycle) } override fun onStart() { super.onStart() LogUtil.e(TAG, "onStart", openLifecycle) } override fun onRestart() { super.onRestart() LogUtil.e(TAG, "onRestart", openLifecycle) } override fun onStop() { super.onStop() LogUtil.e(TAG, "onStop", openLifecycle) } override fun onDestroy() { super.onDestroy() LogUtil.e(TAG, "onDestroy", openLifecycle) } //Arouter路由跳转封装, action代表Postcard方法体回调 fun open(path: String, requestCode: Int = 0, action: Postcard.() -> Unit = {}) { val postcard = ARouter.getInstance().build(path) postcard.action() postcard.navigation(this, requestCode) } fun openWithFinish(path: String, action: Postcard.() -> Unit = {}) { open(path, 0, action) finish() }}
router包:
路由本意是,为了解决不同模块之间无法访问各自界面问题
路由指定,首先需要定义路径名,然后在对应的类中新增注解@Route(path = RouterPath.MOBILE_HOME),之后通过Arouter的navigation()方法跳转到不同模块的界面,从而实现跨模块的跳转。
interface RouterPath { companion object { //mobile const val MOBILE_HOME = "/mobile/home_activity" const val MOBILE_SPLASH = "/mobile/splash" const val MOBILE_VOD_DETAIL = "/mobile/vod_detail" const val MOBILE_LIVE = "/mobile/live" const val MOBILE_PAY = "/mobile/pay/activity" const val MOBILE_EVENT = "/mobile/event" //tablet const val TABLET_HOME = "/tablet/home_activity" const val TABLET_VOD_DETAIL = "/tablet/vod_detail" const val TABLET_SPLASH = "/tablet/splash" const val TABLET_EVENT = "/tablet/event" const val PAY_ACTIVITY = "/pay/activity" }}
view包:
adapterItemDecoration是存放recycleview的装饰类ItemDecoration
viewExpand是存放所有视图相关的kotlin方法扩展,例如ImageView的加载图片封装
其余的则为view包中的一些常用的自定义view视图
2.network:
在了解network模块之前,必需对retrofit的使用由清晰的认识,所以接下来就看一个正常的retrofit是如何调用的。
调用库的依赖:
//retrofit核心库,这个在我们的common包中就有依赖 api 'com.squareup.retrofit2:retrofit:2.9.0' //加上这个之后,你可以省略输入流转Gson的步骤 api 'com.squareup.retrofit2:converter-gson:2.9.0' //使用之后,支持接口回调返回Deferred<T>对象,该对象是延迟结果发送的意思,可以使用suspend代替 api 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2'
实现步骤:
//1.定义接口,并申明请求方式和对应的返回结果interface TestApi { //get请求,(https://www.baidu.com/test/{body}) @GET("/test/{body}") fun getTest(@Path("test") body: String): Deferred<Test>}//2.使用retrofit初始化TestApi接口的实现对象val myTestApi by lazy { val retrofit = retrofit2.Retrofit.Builder() .baseUrl("https://www.baidu.com") //添加Gson转换 .addConverterFactory(GsonConverterFactory.create()) //添加Deferred转换 .addCallAdapterFactory(CoroutineCallAdapterFactory()) .build() //创建实现TestApi接口的MyTestApi对象,通过该对象就可以调用getTest()方法去走对应的网络请求 retrofit.create(MyTestApi::class.java)}//3.使用协程实现简洁回调//确保在UI线程中调用,当getTest方法执行后,回调用await()挂起,如果在方法中申明suspend,则可以省略,直接让getTest()返回TestGlobalScope.launch(Dispatchers.Main) { try { //回调UI线程中刷新界面数据 onResponseUI(myTestApi.getTest("test").await()) } catch (e: Exception) { prink(e) }}//4.搭配LiveData的进阶用法interface HistoryApi { //用了post请求,并且加了suspend,直接让接口返回对象本身 @POST(AppConfig.BO_API + "/play/list") suspend fun getPlayHistory(@Body body: RequestBody): WatchHistoryData}class HistoryViewModel : BaseViewModel() { private var historyApi = NetWorkManager.historyApiSingleton //声明并初始化一个LiveData对象 var playHistoryData: MutableLiveData<WatchHistoryData> = MutableLiveData() fun getPlayHistory( page: Int = 0, pageSize: Int = 12, type: String = XMediaConst.CONTENT_TYPE_VOD ) = launchIO { var parameter: HashMap<String, Any> = hashMapOf() parameter["type"] = type parameter["page"] = page parameter["pageSize"] = pageSize //调用方法返回对象 val resultData = historyApi.getPlayHistory(parameter.getBody()) if (resultData.resultCode == 0) //让liveData对象绑定接口返回对象值 playHistoryData.postValue(resultData) }}//在starObserve中做回调监听,playHistoryData是一个liveData类型,当数据改变就会回调historyViewModel.run { //this传的是一个lifecycleOwner对象,通常是activity或fragment持有 playHistoryData.observe(this@VodFragment) { }}
Network包中的组成:
image-20210331135936944.png
api包:存放所有网络请求接口API
interface HistoryApi { //用了post请求,并且加了suspend,直接让接口返回对象本身 @POST(AppConfig.BO_API + "/play/list") suspend fun getPlayHistory(@Body body: RequestBody): WatchHistoryData}
bean包:存放Bean对象
data class WatchHistoryData( val contents: List<Content>, val description: String, val pageCount: Int, val resultCode: Int)
viewModel包:存放ViewModel实现类
NetworkManager: 用于retrofit网络请求头参数统一配置,retrofit的对象初始化,以及初始化viewModel实现类对象
// lazy默认模式就是同步锁,其余模式暂不展开val historyApiSingleton: HistoryApi by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { retrofit.create(HistoryApi::class.java) }val client = OkHttpClient.Builder() //.cache(Cache(file, cacheSize)) .connectTimeout(DEFAULT_TIME, TimeUnit.SECONDS) //连接超时设置 .readTimeout(DEFAULT_TIME, TimeUnit.SECONDS) //读取超时设置 .writeTimeout(WRITE_TIME, TimeUnit.SECONDS) //写入超时设置 .addInterceptor(headInterceptor()) //请求头拦截 .addInterceptor(ChuckInterceptor(context)) //chunk工具类拦截 //.addNetworkInterceptor(responseCacheInterceptor()) //缓存拦截 .addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) //提交和接收数据的拦截器 .build()var retrofit = Retrofit.Builder() .baseUrl(AppConfig.SERVER_ROOT_URI) .addConverterFactory(GsonConverterFactory.create()) //gson解析 .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) //Rxjava .addCallAdapterFactory(CoroutineCallAdapterFactory()) //协程Coroutine .client(client) .build()
3.功能模块和界面模块
功能模块没什么好讲的,就是功能封装,调用方式也很简洁,而界面模块,主要描述一下MVVM的Activity是怎么实现的
//HomeViewModel,即我们之前将的ViewModel实现类, TabletActivityCategoryBinding则是系统通过xml自动生成的,具体请看下述的xmlclass TabletCategoryActivity : BaseVMActivity<HomeViewModel, TabletActivityCategoryBinding>() { //分类内容列表,具体数据需要调网络请求接口获取 private var categoryContentList: MutableList<CategoryContentData.Content> = mutableListOf() private var categoryAdapter: TabletCategoryAdapter? = null companion object { const val CATEGORY_ID = "category_id" const val CATEGORY_TYPE = "category_type" const val CATEGORY_TITLE = "category_title" } //HomeViewModel初始化,BaseVMActivity重写方法1 override fun initVM(): HomeViewModel = HomeViewModel() //指定xml布局id,BaseVMActivity重写方法2 override fun provideContentViewId(): Int = R.layout.tablet_activity_category override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) initRecyclerView() initData() } private fun initData() { //判断跳转前是否携带CATEGORY_ID参数 intent.getStringExtra(CATEGORY_ID)?.let { if (it.isNotEmpty()) { //调用getCategoryContentList()的网络请求接口 viewModel.getCategoryContentList(it) } } //tv_title自动绑定视图 是在app/build.gradle 中开启了buildFeatures { dataBinding = true } intent.getStringExtra(CATEGORY_TITLE)?.let { tv_title.text = it } } //recyclerView的初始化 private fun initRecyclerView() { categoryAdapter = TabletCategoryAdapter() recycler_view.layoutManager = GridLayoutManager(this, 6) recycler_view.addItemDecoration( GridSpacingItemDecoration( 6, 6f.dpInt, false)) recycler_view.adapter = categoryAdapter categoryAdapter?.setOnItemClickListener { _, _, position -> TabletVodDetailsActivity.actionActivity(this, categoryContentList[position].contentId, 0) } nav_back.setOnClickListener { finish() } } //监听viewModel实现类中的LiveData数据,BaseVMActivity重写方法3 override fun startObserve() { viewModel.categoryContentListData.observe(this, { categoryContentData -> //categoryContentData即网络请求结果对象 categoryContentData.contents.let { if (it.isNotEmpty()) { categoryContentList.addAll(it) //刷新数据 categoryAdapter?.setList(it) } } }) }}
?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" xmlns:tools="http://schemas.android.com/tools"> <-- 生成TabletActivityCategoryBinding类的关键 --> <data> </data> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/theme_bg" android:fitsSystemWindows="true"> <ImageView android:id="@+id/nav_back" android:layout_width="32dp" android:layout_height="32dp" android:layout_marginStart="14dp" android:clickable="true" android:focusable="true" android:padding="8dp" android:src="@drawable/nav_back" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/tv_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@color/main_text_color" android:textSize="24sp" app:layout_constraintBottom_toBottomOf="@id/nav_back" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@id/nav_back" /> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginTop="34dp" app:layoutManager="androidx.recyclerview.widget.GridLayoutManager" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/nav_back" app:spanCount="6" tools:listitem="@layout/tablet_item_category_vertical" /> </androidx.constraintlayout.widget.ConstraintLayout></layout>
MVVM模式总结:
作者:孤縌
链接:https://www.jianshu.com/p/452c40b9a11c