MVVM+组件化+Arouter实现

作者: 孤縌 | 来源:发表于2021-03-31 16:50 被阅读0次

    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()返回Test
    GlobalScope.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自动生成的,具体请看下述的xml
    class 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模式总结:


    image-20210331151444720.png

    相关文章

      网友评论

        本文标题:MVVM+组件化+Arouter实现

        本文链接:https://www.haomeiwen.com/subject/bfcahltx.html