当前位置:   article > 正文

MVVM+组件化+Arouter实现

android mvvm arouter

code小生 一个专注大前端领域的技术平台公众号回复Android加入安卓技术群

作者:孤縌
链接:https://www.jianshu.com/p/452c40b9a11c
声明:本文已获孤縌授权发表,转发等请联系原作者授权

MVVM+组件化实现

模块概览

底层模块:common、network、resource

功能模块:player、firebase、pay

界面模块:mobile、tablet、tablet_login

壳模块: app

模块实现

1.公共模块的gradle配置

由于不同模块,可能引用相同的依赖库,那么对于这部分共同的模块则需要提取出来做统一的管理,因此在项目的根目录创建了common.gradle。

  1. //project是根项目,可以简单认为是个对象,对象持有一个名词为ext的成员变量
  2. project.ext {
  3.     
  4.     //基本的变量配合和版本控制
  5.     compileSdkVersion = 30
  6.     buildToolsVersion = "30"
  7.     minSdkVersion = 21
  8.     targetSdkVersion = 30
  9.     applicationId = "com.sdmc.xmediatv"
  10.     versionCode = 4
  11.     versionName = "4.7.0"
  12.     //设置app配置
  13.     setAppDefaultConfig = {
  14.         extension ->
  15.             //指定为application,代表该模块可以单独调试
  16.             extension.apply plugin: 'com.android.application'
  17.             extension.description "app"
  18.             
  19.             //公共的apply 主要是用于三方库
  20.             extension.apply plugin: 'kotlin-android'
  21.             extension.apply plugin: 'kotlin-android-extensions'
  22.             extension.apply plugin: 'kotlin-kapt'
  23.           
  24.             //设置项目的android
  25.             setAppAndroidConfig extension.android
  26.             //设置项目的三方库依赖
  27.             setDependencies extension.dependencies
  28.     }
  29.     //设置lib配置(只可以作为lib,不可单独调试)
  30.     setLibDefaultConfig = {
  31.         extension ->
  32.             //library,代表只是单纯的库,不需要依赖其他模块
  33.             extension.apply plugin: 'com.android.library'
  34.             extension.description "lib"
  35.            
  36.             setLibAndroidConfig extension.android
  37.             setDependencies extension.dependencies
  38.     }
  39.     //是否允许module单独调试
  40.     isModuleDebug = false
  41.     //动态改变,用于单模块调试
  42.     setAppOrLibDefaultConfig = {
  43.         extension ->
  44.             if (project.ext.isModuleDebug) {
  45.                 extension.apply plugin: 'com.android.application'
  46.                 extension.description "app"
  47.             } else {
  48.                 extension.apply plugin: 'com.android.library'
  49.                 extension.description "lib"
  50.             }
  51.             extension.apply plugin: 'kotlin-android'
  52.             extension.apply plugin: 'kotlin-android-extensions'
  53.             extension.apply plugin: 'kotlin-kapt'
  54.             //设置通用Android配置
  55.             setAppOrLibAndroidConfig extension.android
  56.             //设置通用依赖配置
  57.             setDependencies extension.dependencies
  58.     }
  59.     //设置application 公共的android配置
  60.     setAppAndroidConfig = {
  61.         extension -> //extension 相当于 android 对象
  62.             extension.compileSdkVersion project.ext.compileSdkVersion
  63.             extension.buildToolsVersion project.ext.buildToolsVersion
  64.             extension.defaultConfig {
  65.                 applicationId project.ext.applicationId
  66.                 minSdkVersion project.ext.minSdkVersion
  67.                 targetSdkVersion project.ext.targetSdkVersion
  68.                 versionCode project.ext.versionCode
  69.                 versionName project.ext.versionName
  70.                 extension.flavorDimensions "versionCode"
  71.                 testInstrumentationRunner "android.support.tablet_test.runner.AndroidJUnitRunner"
  72.                 //ARouter 编译生成路由
  73.                 kapt {
  74.                     arguments {
  75.                         arg("AROUTER_MODULE_NAME", project.getName())
  76.                         includeCompileClasspath = true
  77.                     }
  78.                 }
  79.             }
  80.             extension.buildFeatures {
  81.                 dataBinding = true
  82.                 // for view binding :
  83.                 // viewBinding = true
  84.             }
  85.     }
  86.     //设置lib 公共的android配置
  87.     setLibAndroidConfig = {
  88.         extension -> //extension 相当于 android 对象
  89.             extension.compileSdkVersion project.ext.compileSdkVersion
  90.             extension.buildToolsVersion project.ext.buildToolsVersion
  91.             extension.defaultConfig {
  92.                 minSdkVersion project.ext.minSdkVersion
  93.                 targetSdkVersion project.ext.targetSdkVersion
  94.                 versionCode project.ext.versionCode
  95.                 versionName project.ext.versionName
  96.                 testInstrumentationRunner "android.support.tablet_test.runner.AndroidJUnitRunner"
  97.                 //ARouter 编译生成路由
  98.                 /*kapt {
  99.                     arguments {
  100.                         arg("AROUTER_MODULE_NAME", project.getName())
  101.                     }
  102.                 }*/
  103.             }
  104.             extension.buildFeatures {
  105.                 dataBinding = true
  106.                 // for view binding :
  107.                 // viewBinding = true
  108.             }
  109.     }
  110.     //设置通用的 android配置(可作为project单独调试)
  111.     setAppOrLibAndroidConfig = {
  112.         extension -> //extension 相当于 android 对象
  113.             extension.compileSdkVersion project.ext.compileSdkVersion
  114.             extension.buildToolsVersion project.ext.buildToolsVersion
  115.             extension.defaultConfig {
  116.                 minSdkVersion project.ext.minSdkVersion
  117.                 targetSdkVersion project.ext.targetSdkVersion
  118.                 versionCode project.ext.versionCode
  119.                 versionName project.ext.versionName
  120.                 extension.flavorDimensions "versionCode"
  121.                 testInstrumentationRunner "android.support.tablet_test.runner.AndroidJUnitRunner"
  122.                 //ARouter 编译生成路由
  123.                 kapt {
  124.                     arguments {
  125.                         arg("AROUTER_MODULE_NAME", project.getName())
  126.                         includeCompileClasspath = true
  127.                     }
  128.                 }
  129.             }
  130.             extension.buildFeatures {
  131.                 //启用自动绑定view id
  132.                 dataBinding = true
  133.             }
  134.         
  135.             //使用的jdk版本
  136.             extension.compileOptions {
  137.                 sourceCompatibility JavaVersion.VERSION_1_8
  138.                 targetCompatibility JavaVersion.VERSION_1_8
  139.             }
  140.             extension.kotlinOptions {
  141.                 jvmTarget = JavaVersion.VERSION_1_8
  142.             }
  143.             //动态改变清单文件资源指向
  144.             extension.sourceSets {
  145.                 main {
  146.                     if (project.ext.isModuleDebug) {
  147.                         manifest.srcFile 'src/main/manifest/AndroidManifest.xml'
  148.                     } else {
  149.                         manifest.srcFile 'src/main/AndroidManifest.xml'
  150.                     }
  151.                 }
  152.             }
  153.     }
  154.     //公用的三方库依赖,慎重引入,主要引入基础库依赖
  155.     setDependencies = {
  156.         extension ->
  157.             extension.implementation fileTree(dir: 'libs', include: ['*.jar'])
  158.             extension.kapt 'com.alibaba:arouter-compiler:1.2.2'
  159.             //ARouter 路由apt插件,用于生成相应代码,每个module都需要
  160.             extension.implementation 'com.alibaba:arouter-api:1.5.0'
  161.             extension.implementation 'com.alibaba:arouter-compiler:1.2.2'
  162.             extension.implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
  163.             extension.implementation 'androidx.core:core-ktx:1.3.1'
  164.             extension.implementation 'com.google.code.gson:gson:2.8.6'
  165.             extension.implementation 'androidx.appcompat:appcompat:1.2.0'
  166.             extension.implementation 'androidx.constraintlayout:constraintlayout:2.0.0'
  167.             extension.implementation 'androidx.recyclerview:recyclerview:1.0.0'
  168.     }
  169. }

根目录的build.gradle:

  1. buildscript {
  2.     //统一制定版本
  3.     ext.kotlin_version = '1.4.31'
  4.     ext.google_service_version = '19.7.0'
  5.     ext.exoplayer_version = '2.13.2'
  6.     
  7.     repositories {
  8.         //为当前的build.gradle设置依赖库中心
  9.         google()
  10.         jcenter()
  11.     }
  12.     dependencies {
  13.         //gradle版本
  14.         classpath "com.android.tools.build:gradle:4.0.1"
  15.         //kotlin依赖
  16.         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
  17.         //bintray.com maven的依赖
  18.         classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:1.0"
  19.         //sonatype maven的依赖
  20.         classpath "com.github.dcendents:android-maven-gradle-plugin:1.5"
  21.         //黄油刀
  22.         classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.3'
  23.         //谷歌服务
  24.         classpath 'com.google.gms:google-services:4.3.3'
  25.         // NOTE: Do not place your application dependencies here; they belong
  26.         // in the individual module build.gradle files
  27.     }
  28. }
  29. allprojects {
  30.     repositories {
  31.         //jitpack库
  32.         maven { url 'http://www.jitpack.io' }
  33.         //bintray.com维护的Maven仓库
  34.         jcenter()
  35.         //谷歌maven库
  36.         maven { url "https://maven.google.com" }
  37.         google()
  38.         //阿里云镜像maven库地址
  39.         maven {
  40.             url "http://maven.aliyun.com/nexus/content/repositories/releases"
  41.         }
  42.     }
  43. }
  44. task clean(type: Delete) {
  45.     delete rootProject.buildDir
  46. }
2.壳app模块的实现
  1. //从项目的根目录中找到common.gradle
  2. apply from: "${rootProject.rootDir}/common.gradle"
  3. //project.ext则是之前强调的common.gradle中的对象,该对象有setAppDefaultConfig
  4. project.ext.setAppDefaultConfig project
  5. //由于setAppDefaultConfig中的android配置是通用配置,所以还需要针对壳本身新增配置
  6. android {
  7.     //混淆规则配置
  8.     buildTypes {
  9.         release {
  10.             minifyEnabled true
  11.             proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
  12.         }
  13.         debug {
  14.             minifyEnabled false
  15.             proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
  16.         }
  17.     }
  18.     packagingOptions {
  19.         //nanohttpd轻量级数据库所需
  20.         exclude 'META-INF/nanohttpd/default-mimetypes.properties'
  21.         exclude 'META-INF/nanohttpd/mimetypes.properties'
  22.         //为了解决部分第三方库重复打包了META-INF的问题
  23.         exclude 'META-INF/NOTICE'
  24.         exclude 'META-INF/LICENSE'
  25.         //解决kotlin问题
  26.         exclude("META-INF/*.kotlin_module")
  27.     }
  28.     compileOptions {
  29.         sourceCompatibility = '1.8'
  30.         targetCompatibility = '1.8'
  31.     }
  32.     kotlinOptions {
  33.         jvmTarget = "1.8"
  34.     }
  35.     //在本地loacl.propreties中配置打包所需信息
  36.     Properties properties = new Properties()
  37.     InputStream inputStream = project.rootProject.file('local.properties').newDataInputStream()
  38.     properties.load(inputStream)
  39.     def sdkDir = properties.getProperty('key.file')
  40.     def keyFile = file(sdkDir)
  41.     def key_keyAlias = properties.getProperty('keyAlias')
  42.     def key_keyPassword = properties.getProperty('keyPassword')
  43.     def key_storePassword = properties.getProperty('storePassword')
  44.     signingConfigs {
  45.         release {
  46.             v2SigningEnabled true
  47.         }
  48.         debug {
  49.             storeFile file(keyFile)
  50.             storePassword key_storePassword
  51.             keyAlias key_keyAlias
  52.             keyPassword key_keyPassword
  53.         }
  54.     }
  55.     /*android.applicationVariants.all {
  56.         variant ->
  57.             variant.outputs.all {
  58.                 output ->
  59.                     outputFileName = new File("../version/", "XMediaTV_Android_" +
  60.                             defaultConfig.versionName + "." + releaseTime() + "_" + variant.buildType.name + ".apk")
  61.             }
  62.     }*/
  63.     //android打包提示check release builds false
  64.     lintOptions {
  65.         checkReleaseBuilds false
  66.         abortOnError false
  67.     }
  68. }
  69. dependencies {
  70.     //壳工程所依赖的模块,implementation代表不依赖模块下的三分库
  71.     implementation project(path: ':mobile')
  72.     implementation project(path: ':tablet')
  73.     //api 代表依赖模块下所有的库
  74.     api project(path: ':common')
  75.     api project(path: ':firebase')
  76. }
  77. //设置仓库地址,用于辅助三方库依赖文件下载
  78. repositories {
  79.     mavenCentral()
  80. }
3.底层模块的model配置

common中的gradle文件:

common模块本身就是为了通用,那么对于一些常用的库,则需要在该模块做一定的依赖处理,以下对不同模块功能依赖做出了标注

  1. apply plugin: 'com.android.library'
  2. apply plugin: 'kotlin-android'
  3. apply plugin: 'kotlin-android-extensions'
  4. apply plugin: 'kotlin-kapt'
  5. android {
  6.     compileSdkVersion 29
  7.     buildToolsVersion "29.0.3"
  8.     defaultConfig {
  9.         minSdkVersion 21
  10.         targetSdkVersion 29
  11.         versionCode 1
  12.         versionName "1.0.0"
  13.         //设置预览界面的库支持
  14.         vectorDrawables.useSupportLibrary = true
  15.         testInstrumentationRunner "androidx.tablet_test.runner.AndroidJUnitRunner"
  16.         //混淆文件指定
  17.         consumerProguardFiles "consumer-rules.pro"
  18.         //配合根目录的gradle.properties,代码中调用BuildConfig.SERVER_ADDRESS动态获取对应值,用于自动打包改服务地址
  19.         buildConfigField "String""SERVER_ADDRESS""\"${SERVER_ADDRESS}\""
  20.         buildConfigField "String""TENANT_CODE""\"${TENANT_CODE}\""
  21.         buildConfigField "String""AD_SERVER_ADDRESS""\"${AD_SERVER_ADDRESS}\""
  22.         buildConfigField "String""PLAYER_SERVER_URI""\"${PLAYER_SERVER_URI}\""
  23.         buildConfigField "Boolean""FORCE_USE_TABLET", project.properties.get("FORCE_USE_TABLET")
  24.     }
  25.     //设置arouter模块名称
  26.     kapt {
  27.         arguments {
  28.             arg("AROUTER_MODULE_NAME", project.getName())
  29.         }
  30.     }
  31.     //资源前缀强制命名
  32.     resourcePrefix "common_"
  33.     //混淆模式
  34.     buildTypes {
  35.         release {
  36.             minifyEnabled false
  37.             proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
  38.         }
  39.     }
  40.     //用于换肤,让所有model都可以拥有不同类型的资源目录
  41.     sourceSets {
  42.         main {
  43.             res.srcDirs = ['src/main/res',
  44.                            'src/main/res-light',
  45.                            'src/main/res-news']
  46.         }
  47.     }
  48.     compileOptions {
  49.         sourceCompatibility JavaVersion.VERSION_1_8
  50.         targetCompatibility JavaVersion.VERSION_1_8
  51.     }
  52. }
  53. dependencies {
  54.     implementation fileTree(dir: "libs", include: ["*.jar"])
  55.     //》》》》kotlin
  56.     //kotlin核心库配置,对应根目录的build.gradle版本
  57.     implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
  58.     implementation 'androidx.core:core-ktx:1.3.2'
  59.     //kotlin协程库
  60.     api 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3-native-mt'
  61.     api 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3-native-mt'
  62.     //》》》》视图
  63.     //android视图库
  64.     api 'androidx.appcompat:appcompat:1.2.0'
  65.     api 'com.google.android.material:material:1.3.0'
  66.     api 'androidx.constraintlayout:constraintlayout:2.0.1'
  67.     api 'androidx.recyclerview:recyclerview:1.1.0'
  68.     //glide图片加载库
  69.     api 'com.github.bumptech.glide:glide:4.12.0'
  70.     //圆角的ImageView库
  71.     api 'com.makeramen:roundedimageview:2.3.0'
  72.     //recycleView的adapter库
  73.     api 'com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.4'
  74.     //下拉刷新库
  75.     implementation 'com.lcodecorex:tkrefreshlayout:1.0.7'
  76.     //头条的自适应库
  77.     api 'me.jessyan:autosize:1.2.1'
  78.     //换肤库
  79.     api 'skin.support:skin-support:4.0.5'                   // skin-support
  80.     api 'skin.support:skin-support-appcompat:4.0.5'         // skin-support 基础控件支持
  81.     api 'skin.support:skin-support-design:4.0.5'
  82.     // skin-support-design material design 控件支持[可选]
  83.     api 'skin.support:skin-support-cardview:4.0.5'
  84.     // skin-support-cardview CardView 控件支持[可选]
  85.     api 'skin.support:skin-support-constraint-layout:4.0.5'
  86.     // skin-support-constraint-layout ConstraintLayout 控件支持[可选]
  87.     //本地资源库
  88.     api project(path: ':resource')
  89.     //》》》》网络
  90.     //用于retrofit2库依赖
  91.     implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
  92.     implementation 'com.squareup.retrofit2:adapter-rxjava2:2.9.0'
  93.     //用于retrofit2+kotlin+coroutines的回调库,可以直接加上suspend关键字获取到回调对象
  94.     implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2'
  95.     //okhttp3库
  96.     implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.2'
  97.     //gson解析库
  98.     implementation 'com.google.code.gson:gson:2.8.6'
  99.     //》》》》其余基本库
  100.     //郭霖的Litepal数据库
  101.     api 'org.litepal.guolindev:core:3.2.2'
  102.     //sharePreference库
  103.     api 'com.orhanobut:hawk:2.0.1'
  104.     //阿里的路由库
  105.     kapt 'com.alibaba:arouter-compiler:1.2.2'
  106.     api 'com.alibaba:arouter-api:1.5.0'
  107.     //谷歌的deeplink分享
  108.     implementation 'com.google.firebase:firebase-dynamic-links:19.1.0'
  109.     //--------------------------------------------------------------
  110.     //》》》》lifecycle库,用于liveData和viewModle,使用与mvvm框架
  111.     def lifecycle_version = '2.2.0'
  112.     // ViewModel and LiveData
  113.     api "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
  114.     // alternatively - just ViewModel
  115. //    api "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"
  116.     api 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0'
  117.     // For Kotlin use lifecycle-viewmodel-ktx
  118.     // alternatively - just LiveData
  119. //    api "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
  120.     // 有生命周期感知的协程
  121.     api 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.0'
  122. }

network中的gradle文件:

该模块为网络请求模块,用到的框架是retrofit2 + kotlin + coroutines,本质是请求耗时网络,通过kotlin使用coroutines的suspend挂起,来达到子线程请求,主线程接收数据的效果。

  1. apply plugin: 'com.android.library'
  2. apply plugin: 'kotlin-android'
  3. apply plugin: 'kotlin-android-extensions'
  4. apply plugin: 'kotlin-kapt'
  5. android {
  6.     compileSdkVersion 29
  7.     defaultConfig {
  8.         minSdkVersion 21
  9.         targetSdkVersion 29
  10.         versionCode 1
  11.         versionName "1.0.0"
  12.         buildConfigField "String""SERVER_ADDRESS""\"${SERVER_ADDRESS}\""
  13.         buildConfigField "String""TENANT_CODE""\"${TENANT_CODE}\""
  14.         buildConfigField "String""AD_SERVER_ADDRESS""\"${AD_SERVER_ADDRESS}\""
  15.         buildConfigField "String""PLAYER_SERVER_URI""\"${PLAYER_SERVER_URI}\""
  16.         buildConfigField "Boolean""FORCE_USE_TABLET", project.properties.get("FORCE_USE_TABLET")
  17.     }
  18.     resourcePrefix "network_"
  19.     buildTypes {
  20.         debug {
  21.             minifyEnabled true
  22.             proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
  23.         }
  24.         release {
  25.             minifyEnabled true
  26.         }
  27.     }
  28.     compileOptions {
  29.         sourceCompatibility = '1.8'
  30.         targetCompatibility = '1.8'
  31.     }
  32. }
  33. dependencies {
  34.     implementation fileTree(dir: "libs", include: ["*.jar"])
  35.     //用于添加retrofit2拦截请求头用的
  36.     implementation 'com.squareup.okhttp3:logging-interceptor:4.8.1'
  37.     //Chuck辅助工具,用于打印各种http请求
  38.     debugImplementation 'com.readystatesoftware.chuck:library:1.1.0'
  39.     releaseImplementation 'com.readystatesoftware.chuck:library-no-op:1.1.0'
  40.     //--------------------------------------------------------------
  41.     api project(path: ':common')
  42.     implementation files('libs\\XMediaCryptoJar_HKSC.jar')
  43. }

resoure的gradle:

该Model主要是用于存放抽取的颜色、多语言、以及与app相关的图标、占位图等

  1. apply plugin: 'com.android.library'
  2. apply plugin: 'kotlin-android'
  3. apply plugin: 'kotlin-android-extensions'
  4. android {
  5.     compileSdkVersion 29
  6.     buildToolsVersion "29.0.3"
  7.     defaultConfig {
  8.         minSdkVersion 21
  9.         targetSdkVersion 29
  10.         versionCode 1
  11.         versionName "1.0"
  12.         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
  13.         consumerProguardFiles "consumer-rules.pro"
  14.     }
  15.     buildTypes {
  16.         release {
  17.             minifyEnabled false
  18.             proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
  19.         }
  20.     }
  21.     sourceSets {
  22.         main {
  23.             res.srcDirs = ['src/main/res',
  24.                            'src/main/res-light',
  25.                            'src/main/res-news']
  26.         }
  27.     }
  28. }
  29. dependencies {
  30.     implementation fileTree(dir: "libs", include: ["*.jar"])
  31.     //kotlin
  32.     implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
  33.     implementation 'androidx.core:core-ktx:1.3.2'
  34.     //单元测试库
  35.     testImplementation 'junit:junit:4.13.2'
  36.     androidTestImplementation 'androidx.test.ext:junit:1.1.2'
  37.     androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
  38. }

基本模块配置都是作为library库使用,对于基本模块的内容有要求,就是一定是一项基础并且多模块共用的功能,才值得放在基础模块中。

4.功能模块或界面模块的依赖配置

对于模块依赖,只要弄清楚api和implementation的功能,并且清楚common.gradle,是如何使用的,就能很清晰的使用模块化项目开发。

  1. apply from: "${rootProject.rootDir}/common.gradle"
  2. //使用common.gradle中的公用配置
  3. project.ext.setAppOrLibDefaultConfig project
  4. android {
  5.     resourcePrefix "tablet_"
  6.     buildTypes {
  7.         release {
  8.             minifyEnabled false
  9.             proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
  10.         }
  11.     }
  12. }
  13. dependencies {
  14.     //这里统一依赖了network,network中由于是api common,common api resource,所以只需要implementation network就行
  15.     implementation project(path: ':network')
  16.     //统一依赖功能model,播放器、Firebase、支付
  17.     implementation project(path: ':player')
  18.     implementation project(path: ':firebase')
  19.     implementation project(path: ':pay')
  20.     //依赖了一个界面模块,含各种平板的弹窗界面和登录
  21.     implementation project(path: ':tablet_login')
  22.     //下拉刷新库
  23.     implementation 'com.lcodecorex:tkrefreshlayout:1.0.7'
  24.     //轮播图库
  25.     implementation 'com.github.zhpanvip:BannerViewPager:3.1.6'
  26.     //谷歌支付库
  27.     implementation 'com.android.billingclient:billing:2.1.0'
  28.     //依次是,谷歌消息、DeepLink、广告
  29.     implementation 'com.google.firebase:firebase-messaging:20.1.2'
  30.     implementation 'com.google.firebase:firebase-dynamic-links:19.1.0'
  31.     implementation "com.google.android.gms:play-services-ads:$google_service_version"
  32. }

模块内容

1.common

base包:

存放着各种基类,其中主要的是MVVM,所以着重讲述MVVM框架搭建

ViewModel讲解:

首先,要继承ViewModel,导包,viewModel主要功能是,代替UI界面去做耗时操作,所以通过viewModelScope对象调用协程挂起,最终通过liveData绑定数据回调。

  1. api "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" //viewModel+livedata
  2. api 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0' //kotlin专用
  3. 由于ViewModel是配合Coroutine使用的,所以必需先认识CoroutineScope(协程范围)和withContext(Dispatchers.IO)中的Dispatchers模式,这里先讲Dispatchers的用途和具体场景。
  1. //实现LifecycleObserver接口,是为了通过activity的lifecycle.addObserver(LifecycleObserver) 绑定到生命周期中
  2. open class BaseViewModel : ViewModel(), LifecycleObserver {
  3.     
  4.     //viewModelScope是ViewModel中的成员对象,该对象实现了CoroutineScope接口
  5.     //suspend CoroutineScope.() -> Unit, suspend是kotlin线程挂起的标识,实际上去走了await()方法 
  6.     //CoroutineScope.() -> Unit 代表一个方法体,并且该方法体对象是CoroutineScope接口对象,主要是规范方法体类型,可除去CoroutineScope.
  7.     fun launchUI(block: suspend CoroutineScope.() -> Unit) = viewModelScope.launch {
  8.         try {
  9.             block()
  10.         } catch (e: Exception) {
  11.             e.printStackTrace()
  12.         }
  13.     }
  14.     
  15.     //相比上个方法,主要是多了 withContext(Dispatchers.IO),目的是切换到子线程做耗时操作
  16.     fun launchIO(block: suspend CoroutineScope.() -> Unit) = viewModelScope.launch {
  17.         withContext(Dispatchers.IO) {
  18.             try {
  19.                 block()
  20.             } catch (e: Exception) {
  21.                 e.printStackTrace()
  22.             }
  23.         }
  24.     }
  25.     
  26.     //errorHandler 是为了统一处理网络异常
  27.     fun launchIO(
  28.         block: suspend CoroutineScope.() -> Unit,
  29.         errorHandler: (code: Int, message: String) -> Unit?,
  30.     ) = viewModelScope.launch {
  31.         withContext(Dispatchers.IO) {
  32.             try {
  33.                 if (NetWorkUtils.isNetConnect(CommonManager.context)) {
  34.                     block()
  35.                 } else {
  36.                     errorHandler(300"No internet connection")
  37.                 }
  38.             } catch (e: Exception) {
  39.                 handlerErrorCode(e, errorHandler)
  40.                 e.printStackTrace()
  41.             }finally {
  42.                 //....
  43.             }
  44.         }
  45.     }
  46.     private fun handlerErrorCode(
  47.         e: Exception,
  48.         errorHandler: (code: Int, message: String) -> Unit?,
  49.     ) {
  50.         when (e) {
  51.             is HttpException -> {
  52.                 errorHandler(e.code(), e.message())
  53.             }
  54.             is UnknownHostException -> {
  55.                 errorHandler(404"Unable to connect to server")
  56.             }
  57.             is SocketTimeoutException -> {
  58.                 errorHandler(408"Socket time out")//访问超时
  59.             }
  60.             is ConnectException -> {
  61.                 errorHandler(404"Connect exception")
  62.             }
  63.             is SocketException -> {
  64.                 errorHandler(500"Socket exception")
  65.             }
  66.             is EOFException -> {
  67.                 errorHandler(500"EOF exception"//连接意外中断
  68.             }
  69.             is IllegalArgumentException -> {
  70.                 errorHandler(400"Illegal argument exception")//参数错误
  71.             }
  72.             is SSLException -> {
  73.                 errorHandler(401"SSL exception")//证书错误
  74.             }
  75.             is NullPointerException -> {
  76.                 errorHandler(600"Null pointer exception")
  77.             }
  78.             else -> {
  79.                 errorHandler(700"Unknown exception")
  80.             }
  81.         }
  82.     }
  83.     fun Map<String, Any>.getBody(): RequestBody {
  84.         return Gson().toJson(this).toRequestBody("application/json".toMediaTypeOrNull())
  85.     }
  86.     data class ErrorHandler(val code: Int, val message: String)
  87. }

BaseVMActivity讲解:

  1. //持有俩个泛型,VM和DB,分别是BaseViewModel和ViewDataBinding的实现类
  2. abstract class BaseVMActivity<VM : BaseViewModel, DB : ViewDataBinding> : BaseActivity(){
  3.     
  4.     //是否打印生命周期
  5.     var openLifecycle: Boolean = false
  6.     lateinit var viewModel: VM
  7.     lateinit var dataBinding: DB
  8.     
  9.     override fun onCreate(savedInstanceState: Bundle?) {
  10.         super.onCreate(savedInstanceState)
  11.         LogUtil.e(TAG, "onCreate", openLifecycle)
  12.         //绑定视图,获取到dataBinding
  13.         dataBinding = DataBindingUtil.setContentView(this, provideContentViewId())
  14.         tvTitle = dataBinding.root.findViewById(R.id.title)
  15.         back = dataBinding.root.findViewById(R.id.back)
  16.         //实例化ViewModel
  17.         viewModel = initVM()
  18.         //让viewModel关联生命周期
  19.         lifecycle.addObserver(viewModel)
  20.         //开始接口监听回调
  21.         startObserve()
  22.     }
  23.     abstract fun initVM(): VM
  24.     abstract fun startObserve()
  25.     override fun onResume() {
  26.         super.onResume()
  27.         LogUtil.e(TAG, "onResume", openLifecycle)
  28.     }
  29.     override fun onPause() {
  30.         super.onPause()
  31.         LogUtil.e(TAG, "onPause", openLifecycle)
  32.     }
  33.     override fun onStart() {
  34.         super.onStart()
  35.         LogUtil.e(TAG, "onStart", openLifecycle)
  36.     }
  37.     override fun onRestart() {
  38.         super.onRestart()
  39.         LogUtil.e(TAG, "onRestart", openLifecycle)
  40.     }
  41.     override fun onStop() {
  42.         super.onStop()
  43.         LogUtil.e(TAG, "onStop", openLifecycle)
  44.     }
  45.     override fun onDestroy() {
  46.         super.onDestroy()
  47.         LogUtil.e(TAG, "onDestroy", openLifecycle)
  48.     }
  49.     
  50.     //Arouter路由跳转封装, action代表Postcard方法体回调
  51.     fun open(path: String, requestCode: Int = 0, action: Postcard.() -> Unit = {}) {
  52.         val postcard = ARouter.getInstance().build(path)
  53.         postcard.action()
  54.         postcard.navigation(this, requestCode)
  55.     }
  56.     
  57.     fun openWithFinish(path: String, action: Postcard.() -> Unit = {}) {
  58.         open(path, 0, action)
  59.         finish()
  60.     }
  61. }

router包:

路由本意是,为了解决不同模块之间无法访问各自界面问题

路由指定,首先需要定义路径名,然后在对应的类中新增注解@Route(path = RouterPath.MOBILE_HOME),之后通过Arouter的navigation()方法跳转到不同模块的界面,从而实现跨模块的跳转。

  1. interface RouterPath {
  2.     companion object {
  3.         //mobile
  4.         const val MOBILE_HOME = "/mobile/home_activity"
  5.         const val MOBILE_SPLASH = "/mobile/splash"
  6.         const val MOBILE_VOD_DETAIL = "/mobile/vod_detail"
  7.         const val MOBILE_LIVE = "/mobile/live"
  8.         const val MOBILE_PAY = "/mobile/pay/activity"
  9.         const val MOBILE_EVENT = "/mobile/event"
  10.         //tablet
  11.         const val TABLET_HOME = "/tablet/home_activity"
  12.         const val TABLET_VOD_DETAIL = "/tablet/vod_detail"
  13.         const val TABLET_SPLASH = "/tablet/splash"
  14.         const val TABLET_EVENT = "/tablet/event"
  15.         const val PAY_ACTIVITY = "/pay/activity"
  16.     }
  17. }

view包:

adapterItemDecoration是存放recycleview的装饰类ItemDecoration

viewExpand是存放所有视图相关的kotlin方法扩展,例如ImageView的加载图片封装

其余的则为view包中的一些常用的自定义view视图

2.network:

在了解network模块之前,必需对retrofit的使用由清晰的认识,所以接下来就看一个正常的retrofit是如何调用的。

调用库的依赖:

  1. //retrofit核心库,这个在我们的common包中就有依赖
  2. api 'com.squareup.retrofit2:retrofit:2.9.0'
  3. //加上这个之后,你可以省略输入流转Gson的步骤
  4. api 'com.squareup.retrofit2:converter-gson:2.9.0'
  5. //使用之后,支持接口回调返回Deferred<T>对象,该对象是延迟结果发送的意思,可以使用suspend代替
  6. api 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2'

实现步骤:

  1. //1.定义接口,并申明请求方式和对应的返回结果
  2. interface TestApi {
  3.     //get请求,(https://www.baidu.com/test/{body})
  4.     @GET("/test/{body}")
  5.     fun getTest(@Path("test") body: String): Deferred<Test>
  6. }
  7. //2.使用retrofit初始化TestApi接口的实现对象
  8. val myTestApi by lazy {
  9.     val retrofit = retrofit2.Retrofit.Builder()
  10.             .baseUrl("https://www.baidu.com")
  11.             //添加Gson转换
  12.             .addConverterFactory(GsonConverterFactory.create())
  13.             //添加Deferred转换
  14.             .addCallAdapterFactory(CoroutineCallAdapterFactory())
  15.             .build()
  16.     
  17.     //创建实现TestApi接口的MyTestApi对象,通过该对象就可以调用getTest()方法去走对应的网络请求
  18.     retrofit.create(MyTestApi::class.java)
  19. }
  20. //3.使用协程实现简洁回调
  21. //确保在UI线程中调用,当getTest方法执行后,回调用await()挂起,如果在方法中申明suspend,则可以省略,直接让getTest()返回Test
  22. GlobalScope.launch(Dispatchers.Main) {
  23.     try {
  24.         //回调UI线程中刷新界面数据
  25.         onResponseUI(myTestApi.getTest("test").await())
  26.     } catch (e: Exception) {
  27.         prink(e)
  28.     }
  29. }
  30. //4.搭配LiveData的进阶用法
  31. interface HistoryApi {
  32.     //用了post请求,并且加了suspend,直接让接口返回对象本身
  33.     @POST(AppConfig.BO_API + "/play/list")
  34.     suspend fun getPlayHistory(@Body body: RequestBody): WatchHistoryData
  35. }
  36. class HistoryViewModel : BaseViewModel() {
  37.     private var historyApi = NetWorkManager.historyApiSingleton
  38.     //声明并初始化一个LiveData对象
  39.     var playHistoryData: MutableLiveData<WatchHistoryData> = MutableLiveData()
  40.     fun getPlayHistory(
  41.         page: Int = 0, pageSize: Int = 12type: String = XMediaConst.CONTENT_TYPE_VOD
  42.     ) = launchIO {
  43.         var parameter: HashMap<String, Any> = hashMapOf()
  44.         parameter["type"] = type
  45.         parameter["page"] = page
  46.         parameter["pageSize"] = pageSize
  47.         //调用方法返回对象
  48.         val resultData = historyApi.getPlayHistory(parameter.getBody())
  49.         if (resultData.resultCode == 0)
  50.             //让liveData对象绑定接口返回对象值
  51.             playHistoryData.postValue(resultData)
  52.     }
  53. }
  54. //在starObserve中做回调监听,playHistoryData是一个liveData类型,当数据改变就会回调
  55. historyViewModel.run {
  56.     //this传的是一个lifecycleOwner对象,通常是activity或fragment持有
  57.     playHistoryData.observe(this@VodFragment) {
  58.     }
  59. }

Network包中的组成:

api包:存放所有网络请求接口API

  1. interface HistoryApi {
  2.     //用了post请求,并且加了suspend,直接让接口返回对象本身
  3.     @POST(AppConfig.BO_API + "/play/list")
  4.     suspend fun getPlayHistory(@Body body: RequestBody): WatchHistoryData
  5. }

bean包:存放Bean对象

  1. data class WatchHistoryData(
  2.     val contents: List<Content>,
  3.     val description: String,
  4.     val pageCount: Int,
  5.     val resultCode: Int
  6. )

viewModel包:存放ViewModel实现类

NetworkManager: 用于retrofit网络请求头参数统一配置,retrofit的对象初始化,以及初始化viewModel实现类对象

  1. // lazy默认模式就是同步锁,其余模式暂不展开
  2. val historyApiSingleton: HistoryApi by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
  3.             retrofit.create(HistoryApi::class.java)
  4.         }
  5. val client = OkHttpClient.Builder()
  6.             //.cache(Cache(file, cacheSize))
  7.             .connectTimeout(DEFAULT_TIME, TimeUnit.SECONDS) //连接超时设置
  8.             .readTimeout(DEFAULT_TIME, TimeUnit.SECONDS) //读取超时设置
  9.             .writeTimeout(WRITE_TIME, TimeUnit.SECONDS) //写入超时设置
  10.             .addInterceptor(headInterceptor()) //请求头拦截
  11.             .addInterceptor(ChuckInterceptor(context)) //chunk工具类拦截
  12.             //.addNetworkInterceptor(responseCacheInterceptor()) //缓存拦截
  13.             .addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) //提交和接收数据的拦截器
  14.             .build()
  15. var retrofit = Retrofit.Builder()
  16.             .baseUrl(AppConfig.SERVER_ROOT_URI)
  17.             .addConverterFactory(GsonConverterFactory.create()) //gson解析
  18.             .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) //Rxjava
  19.             .addCallAdapterFactory(CoroutineCallAdapterFactory()) //协程Coroutine
  20.             .client(client)
  21.             .build()
3.功能模块和界面模块

功能模块没什么好讲的,就是功能封装,调用方式也很简洁,而界面模块,主要描述一下MVVM的Activity是怎么实现的

  1. //HomeViewModel,即我们之前将的ViewModel实现类, TabletActivityCategoryBinding则是系统通过xml自动生成的,具体请看下述的xml
  2. class TabletCategoryActivity : BaseVMActivity<HomeViewModel, TabletActivityCategoryBinding>() {
  3.     //分类内容列表,具体数据需要调网络请求接口获取
  4.     private var categoryContentList: MutableList<CategoryContentData.Content> = mutableListOf()
  5.     private var categoryAdapter: TabletCategoryAdapter? = null
  6.     companion object {
  7.         const val CATEGORY_ID = "category_id"
  8.         const val CATEGORY_TYPE = "category_type"
  9.         const val CATEGORY_TITLE = "category_title"
  10.     }
  11.     //HomeViewModel初始化,BaseVMActivity重写方法1
  12.     override fun initVM(): HomeViewModel = HomeViewModel()
  13.     //指定xml布局id,BaseVMActivity重写方法2
  14.     override fun provideContentViewId(): Int = R.layout.tablet_activity_category
  15.     override fun onCreate(savedInstanceState: Bundle?) {
  16.         super.onCreate(savedInstanceState)
  17.         initRecyclerView()
  18.         initData()
  19.     }
  20.     private fun initData() {
  21.         //判断跳转前是否携带CATEGORY_ID参数
  22.         intent.getStringExtra(CATEGORY_ID)?.let {
  23.             if (it.isNotEmpty()) {
  24.                 //调用getCategoryContentList()的网络请求接口
  25.                 viewModel.getCategoryContentList(it)
  26.             }
  27.         }
  28.         //tv_title自动绑定视图 是在app/build.gradle 中开启了buildFeatures { dataBinding = true }
  29.         intent.getStringExtra(CATEGORY_TITLE)?.let {
  30.             tv_title.text = it
  31.         }
  32.     }
  33.     
  34.     //recyclerView的初始化
  35.     private fun initRecyclerView() {
  36.         categoryAdapter = TabletCategoryAdapter()
  37.         recycler_view.layoutManager = GridLayoutManager(this, 6)
  38.         recycler_view.addItemDecoration(
  39.             GridSpacingItemDecoration(
  40.                 66f.dpInt, false))
  41.         recycler_view.adapter = categoryAdapter
  42.         categoryAdapter?.setOnItemClickListener { _, _, position ->
  43.             TabletVodDetailsActivity.actionActivity(this,
  44.                 categoryContentList[position].contentId,
  45.                 0)
  46.         }
  47.         nav_back.setOnClickListener { finish() }
  48.     }
  49.     
  50.     //监听viewModel实现类中的LiveData数据,BaseVMActivity重写方法3
  51.     override fun startObserve() {
  52.         viewModel.categoryContentListData.observe(this, { categoryContentData ->
  53.             //categoryContentData即网络请求结果对象                                            
  54.             categoryContentData.contents.let {
  55.                 if (it.isNotEmpty()) {
  56.                     categoryContentList.addAll(it)
  57.                     //刷新数据
  58.                     categoryAdapter?.setList(it)
  59.                 }
  60.             }
  61.         })
  62.     }
  63. }
  1. ?xml version="1.0" encoding="utf-8"?>
  2. <layout xmlns:android="http://schemas.android.com/apk/res/android"
  3.     xmlns:app="http://schemas.android.com/apk/res-auto"
  4.     xmlns:tools="http://schemas.android.com/tools">
  5.     <-- 生成TabletActivityCategoryBinding类的关键 -->
  6.     <data>
  7.     </data>
  8.     <androidx.constraintlayout.widget.ConstraintLayout
  9.         android:layout_width="match_parent"
  10.         android:layout_height="match_parent"
  11.         android:background="@drawable/theme_bg"
  12.         android:fitsSystemWindows="true">
  13.         <ImageView
  14.             android:id="@+id/nav_back"
  15.             android:layout_width="32dp"
  16.             android:layout_height="32dp"
  17.             android:layout_marginStart="14dp"
  18.             android:clickable="true"
  19.             android:focusable="true"
  20.             android:padding="8dp"
  21.             android:src="@drawable/nav_back"
  22.             app:layout_constraintStart_toStartOf="parent"
  23.             app:layout_constraintTop_toTopOf="parent" />
  24.         <TextView
  25.             android:id="@+id/tv_title"
  26.             android:layout_width="wrap_content"
  27.             android:layout_height="wrap_content"
  28.             android:textColor="@color/main_text_color"
  29.             android:textSize="24sp"
  30.             app:layout_constraintBottom_toBottomOf="@id/nav_back"
  31.             app:layout_constraintEnd_toEndOf="parent"
  32.             app:layout_constraintStart_toStartOf="parent"
  33.             app:layout_constraintTop_toTopOf="@id/nav_back" />
  34.         <androidx.recyclerview.widget.RecyclerView
  35.             android:id="@+id/recycler_view"
  36.             android:layout_width="match_parent"
  37.             android:layout_height="match_parent"
  38.             android:layout_marginTop="34dp"
  39.             app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
  40.             app:layout_constraintEnd_toEndOf="parent"
  41.             app:layout_constraintStart_toStartOf="parent"
  42.             app:layout_constraintTop_toBottomOf="@id/nav_back"
  43.             app:spanCount="6"
  44.             tools:listitem="@layout/tablet_item_category_vertical" />
  45.     </androidx.constraintlayout.widget.ConstraintLayout>
  46. </layout>

MVVM模式总结:

相关推荐

Android Jetpack 架构开发组件化应用实战

Android 组件化工程结构以及项目具体实施方案

一篇文章搞懂 Android 组件化

Android 彻底组件化—如何使用 Arouter

Android路由框架ARouter的集成、基本使用以及踩坑全过程

我是code小生,喜欢可以随手点个在看、转发给你的朋友,谢谢~

  1. 如果你有写博客的好习惯
  2. 欢迎投稿
点个“在看”小生感恩❤️
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/701468
推荐阅读
相关标签
  

闽ICP备14008679号