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>
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/l-wU8jQ-veide8oliqjS_g
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。