赞
踩
code小生 一个专注大前端领域的技术平台公众号回复Android
加入安卓技术群
作者:孤縌
链接:https://www.jianshu.com/p/452c40b9a11c
声明:本文已获孤縌
授权发表,转发等请联系原作者授权
MVVM+组件化实现
底层模块:common、network、resource
功能模块:player、firebase、pay
界面模块:mobile、tablet、tablet_login
壳模块: app
由于不同模块,可能引用相同的依赖库,那么对于这部分共同的模块则需要提取出来做统一的管理,因此在项目的根目录创建了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
- }
- //从项目的根目录中找到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()
- }
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库使用,对于基本模块的内容有要求,就是一定是一项基础并且多模块共用的功能,才值得放在基础模块中。
对于模块依赖,只要弄清楚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"
- }
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的用途和具体场景。
- //实现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视图
在了解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包中的组成:
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()
功能模块没什么好讲的,就是功能封装,调用方式也很简洁,而界面模块,主要描述一下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>
Android路由框架ARouter的集成、基本使用以及踩坑全过程
我是code小生
,喜欢可以随手点个在看
、转发给你的朋友,谢谢~
-
- 如果你有写博客的好习惯
- 欢迎投稿
点个“在看”小生感恩❤️
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。