先上代碼MVVM
aar/source
一般來說組件化項(xiàng)目中都會(huì)做aar和源碼切換,開發(fā)同學(xué)正在進(jìn)行的業(yè)務(wù)module需依賴源碼衣陶,其它的不相干的模塊依賴遠(yuǎn)程aar说莫。大概會(huì)先定義一個(gè)全局變量做aar/source切換的開關(guān)互例,然后在app中進(jìn)行依賴粗井。
module.gradle
ext {
// source/aar
isBusinessDev = false
biz = [
business: "com.xxx.xxx:xxx:1.0.0",
]
}
app下build.gradle
dependencies {
...
if (rootProject.ext.isBusinessDev) {
implementation project(path: ':business')
} else {
implementation rootProject.ext.biz.business
}
}
沒啥毛病枝哄,問題是隨著業(yè)務(wù)迭代module逐漸變多愈犹,需要不停的往app中添加這樣if else
的依賴控制代碼键科,倒也不是if else不好,很多的if else就有點(diǎn)難受受了漩怎。思考一下項(xiàng)目中第三方依賴是怎么偷懶的勋颖。
config.gradle
中定義好依賴庫版本號(hào)、依賴路徑
versions = [
kotlin : '1.5.20',
coroutine : '1.5.2',
androidx_core : '1.3.2',
appcompat : '1.2.0',
lifecycle : '2.3.1',
work_manager : '2.5.0',
room : '2.2.5',
constraintlayout : '2.0.4',
recyclerview : '1.1.0',
material : '1.3.0',
]
common = [
kotlin : "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$versions.kotlin",
coroutine : "org.jetbrains.kotlinx:kotlinx-coroutines-android:$versions.coroutine",
androidx_core : "androidx.core:core-ktx:$versions.androidx_core",
appcompat : "androidx.appcompat:appcompat:$versions.appcompat",
viewmodel : "androidx.lifecycle:lifecycle-viewmodel-ktx:$versions.lifecycle",
livedata : "androidx.lifecycle:lifecycle-livedata-ktx:$versions.lifecycle",
lifecycle : "androidx.lifecycle:lifecycle-runtime-ktx:$versions.lifecycle",
constraintlayout : "androidx.constraintlayout:constraintlayout:$versions.constraintlayout",
recyclerview : "androidx.recyclerview:recyclerview:$versions.recyclerview",
material : "com.google.android.material:material:$versions.material",
]
然后在管理依賴的module中一個(gè)循環(huán)搞定
dependencies {
rootProject.ext.common.each { k, v -> api v }
}
arr/source切換也搞個(gè)循環(huán)好了
ext {
// source/aar
biz = [
business: [false, "com.xxx.xxx:xxx:1.0.0"],
]
// module
modules = [
business: project(':business'),
]
}
app中修改依賴方式
dependencies {
...
biz.each { entry ->
if (entry.value[0]) {
implementation entry.value[1]
} else {
implementation modules.(entry.key)
}
}
}
application/library
為了方便調(diào)試勋锤,很多時(shí)候我們希望業(yè)務(wù)module能單獨(dú)run起來饭玲,讓業(yè)務(wù)module獨(dú)立運(yùn)行∪矗可以給業(yè)務(wù)module加一個(gè)開關(guān)茄厘,然后讓業(yè)務(wù)module用這個(gè)開關(guān)控制application/library的切換。當(dāng)然此種情況下谈宛,我們希望app也能獨(dú)立運(yùn)行次哈,如此一來,業(yè)務(wù)module作為application獨(dú)立運(yùn)行時(shí)吆录,app需剔除該業(yè)務(wù)module的依賴窑滞。
修改module.gradle
ext {
// source/aar
biz = [
business: [false, "com.xxx.xxx:xxx:1.0.0"],
]
// library/application
isBusinessModule = true
// module
modules = [
business: [isBusinessModule, project(':business')],
]
}
app下build.gradle再加個(gè)判斷,module作為applicaiton時(shí)不進(jìn)行依賴恢筝。
dependencies {
biz.each { entry ->
if (entry.value[0]) {
implementation entry.value[1]
} else {
if (modules.(entry.key)[0]) {
implementation modules.(entry.key)[1]
}
}
}
}
業(yè)務(wù)moduel下的build.gradle用module.gradle中定義好的變量isBusinessModule
控制application/library切換哀卫。
def isModule = rootProject.ext.isBusinessModule
if (isModule) {
apply plugin: 'com.android.library'
} else {
apply plugin: 'com.android.application'
}
別忘了application需配置applicationId,manifest需指定啟動(dòng)項(xiàng)和Application撬槽。
android {
resourcePrefix "business_"
defaultConfig {
...
if (!isModule) {
applicationId "com.xxx.xxx.xxx"
}
}
sourceSets {
main {
if (isModule) {
manifest.srcFile 'src/main/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/manifest/AndroidManifest.xml'
}
}
}
}
反射初始化子類對(duì)象
之前看有的同學(xué)搞了些騷操作此改,在基類初始化ViewBinding
。想來也是在父類中拿到泛型類型侄柔,然后反射初始化带斑。ViewBinding生成的類格式是固定的鼓寺,直接匹配類名,反射實(shí)例化然后調(diào)用ViewBinding.inflate()
方法返回ViewBinding
實(shí)例勋磕。
abstract class BaseSimpleActivity<VB : ViewBinding> : BaseActivity() {
protected val binding by lazy {
createViewBinding()
}
open fun createViewBinding() = reflectViewBinding() as VB
@Suppress("UNCHECKED_CAST")
private fun reflectViewBinding(): VB? {
val types = (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments
types.forEach {
if (it.toString().endsWith("Binding") && it is Class<*>) {
val method = it.getDeclaredMethod("inflate", LayoutInflater::class.java)
return method.invoke(it, layoutInflater) as VB
}
}
return null
}
}
當(dāng)然為了防止意外狀況或者是性能問題妈候,createViewBinding()
方法默認(rèn)實(shí)現(xiàn)為反射,加個(gè)open
修飾讓子類可以重寫自己提供ViewBinding
對(duì)象挂滓。
嗯苦银,這樣很香啊~子類拿著binding
直接用就好了。等等ViewModel
是不是也可以這么搞呢赶站,當(dāng)然可以幔虏,搞一搞。
abstract class BaseVMActivity<VM : BaseViewModel<R>, R : BaseRepository, VB : ViewBinding> :
BaseSimpleActivity<VB>() {
protected val viewModel: VM by lazy {
createViewModel()
}
open fun createViewModel() = reflectViewModel()
@Suppress("UNCHECKED_CAST")
private fun reflectViewModel(): VM {
val types = (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments
return ViewModelProvider(this)[types[0] as Class<ViewModel>] as VM
}
}
ViewModel的實(shí)現(xiàn)類命名可能并非以ViewModel結(jié)尾贝椿,這里直接取第一個(gè)泛型類型types[0]
想括。同樣的createViewModel()
默認(rèn)實(shí)現(xiàn)為反射,加個(gè)open讓子類可以重寫烙博。
網(wǎng)絡(luò)請(qǐng)求綁定進(jìn)度對(duì)話框
在網(wǎng)絡(luò)請(qǐng)求開始的時(shí)候彈一個(gè)菊花圈瑟蜈,結(jié)束/失敗的時(shí)候關(guān)閉。因?yàn)橛玫絽f(xié)程viewModelScope
渣窜,就把launch
方法又封裝了一下铺根。
internal typealias NetLaunch<T> = suspend CoroutineScope.() -> BaseResponse<T>
val statusLiveData: MutableLiveData<CoroutineState> by lazy {
MutableLiveData<CoroutineState>()
}
fun <T> start(
refresh: Boolean = true,
block: NetLaunch<T>,
): LaunchHandler<T> {
val launchHandler = LaunchHandler<T>()
viewModelScope.launch {
try {
if (refresh) {
statusLiveData.value = CoroutineState.REFRESH
} else {
statusLiveData.value = CoroutineState.START
}
val result = block()
statusLiveData.value = CoroutineState.FINISH
launchHandler.successListener?.invoke(
LaunchResult.Success(result)
)
} catch (e: Exception) {
statusLiveData.value = CoroutineState.ERROR
if (launchHandler.errorListener == null) {
errorLiveData.value = e
} else launchHandler.errorListener?.invoke(LaunchResult.Error(e))
}
}
return launchHandler
}
先忽略其它代碼,主要看statusLiveData
乔宿,將協(xié)程狀態(tài)發(fā)送到Activity基類BaseVMActivity位迂,在基類中進(jìn)行處理。
private fun initViewModelActions() {
viewModel.statusLiveData.observe(this, { status ->
status?.run {
when (this) {
CoroutineState.START -> {
//START
}
CoroutineState.REFRESH -> {
//REFRESH
ProgressDialog.showProgress(this@BaseVMActivity)
}
CoroutineState.FINISH -> {
//FINISH
ProgressDialog.dismissProgress()
}
CoroutineState.ERROR -> {
//ERROR
ProgressDialog.dismissProgress()
}
}
}
})
//默認(rèn)錯(cuò)誤處理
viewModel.errorLiveData.observe(this, {
ToastUtils.showShort(it.message)
})
}
不管用沒用到協(xié)程详瑞,思路是一致的掂林。網(wǎng)絡(luò)請(qǐng)求入口包裹一層,將狀態(tài)發(fā)送到頁面基類坝橡,在基類統(tǒng)一處理泻帮。
網(wǎng)絡(luò)請(qǐng)求API設(shè)計(jì)
現(xiàn)在都是MVVM了,那就在BaseViewModel里面統(tǒng)一驳庭。寫kotlin還是要有kotlin的風(fēng)格刑顺,搞一些lambda氯窍。
BaseViewModel
val statusLiveData: MutableLiveData<CoroutineState> by lazy {
MutableLiveData<CoroutineState>()
}
val errorLiveData: MutableLiveData<Exception> by lazy {
MutableLiveData<Exception>()
}
fun <T> start(
refresh: Boolean = true,
block: NetLaunch<T>,
): LaunchHandler<T> {
val launchHandler = LaunchHandler<T>()
viewModelScope.launch {
try {
if (refresh) {
statusLiveData.value = CoroutineState.REFRESH
} else {
statusLiveData.value = CoroutineState.START
}
val result = block()
statusLiveData.value = CoroutineState.FINISH
launchHandler.successListener?.invoke(
LaunchResult.Success(result)
)
} catch (e: Exception) {
statusLiveData.value = CoroutineState.ERROR
if (launchHandler.errorListener == null) {
errorLiveData.value = e
} else launchHandler.errorListener?.invoke(LaunchResult.Error(e))
}
}
return launchHandler
}
LaunchHandler
class LaunchHandler<T> {
var successListener: HandlerSuccess<T>? = null
var errorListener: HandlerError? = null
}
infix fun <T> LaunchHandler<T>.resumeWithSuccess(handler: HandlerSuccess<T>) = this.apply {
successListener = handler
}
infix fun <T> LaunchHandler<T>.resumeWithError(handler: HandlerError) = this.apply {
errorListener = handler
}
不傳錯(cuò)誤回調(diào)函數(shù)的情況下饲常,將錯(cuò)誤狀態(tài)errorLiveData
發(fā)送給Activity基類Toast處理,當(dāng)然這里也可以按照狀態(tài)碼做對(duì)應(yīng)的Toast狼讨,最終調(diào)用處就很舒服了:MainViewModel
val contentLiveData by lazy {
MutableLiveData<String>()
}
fun getChapters() {
start {
repository.getChapters()
} resumeWithSuccess {
contentLiveData.value = GsonUtils.instance.toJson(it.response.data)
} resumeWithError {
ToastUtils.showShort(it.error.message)
}
}
resumeWithSuccess贝淤、resumeWithError
方法是中綴函數(shù),調(diào)用時(shí)可以省略一個(gè)點(diǎn)政供。
repository.getChapters()
class MainRepository : BaseRepository() {
private val service = ApiServiceUtil.getApiService<MainApiService>()
suspend fun getChapters(): BaseResponse<List<Chapters>> {
return withContext(Dispatchers.IO) {
service.getChapters()
}
}
}
interface MainApiService : BaseService {
@GET("/wxarticle/chapters/json")
suspend fun getChapters(): BaseResponse<List<Chapters>>
}
沒啥好說的播聪,retrofit接口定義suspend方法朽基,repository中withContext(Dispatchers.IO)
切換到IO線程。