code小生 一个专注大前端领域的技术平台公众号回复Android
加入安卓技术群
作者:泡芙先生
链接:https://juejin.im/post/5f09ac336fb9a07e93303162
声明:本文已获泡芙先生
授权发表,转发等请联系原作者授权
1. 背景
之前公司项目用的一直是MVP框架,我个人也在几个月前基于鸿神 WanAndroid API开发了一款MVP版的App,使用MVP的过程最深的感受是开发效率极低,往往写一大堆接口,可复用的屈指可数。年初了解了Jetpack模式下的MVVM,在LiveData、ViewModel、DataBinDing的加持下实现了单向依赖和数据绑定,代码量大幅度减少,根据Jetpack的特性项目稳定性也提升了不少。
为了更深入的理解Jetpack中各个组件,在前段时间基于Jetpack MVVM又实现了一版WanAndroid。相比上一版的MVP增加了夜间模式和音乐播放器,播放器界面仿照网易云音乐。App中也大量的使用属性动画让界面简约而不简陋。先上图look一波
![](https://i-blog.csdnimg.cn/blog_migrate/aaaff2a4cdf14b4fa7ccadcc4c3fadea.png)
关于播放器当前只支持读取本地音乐,如果想体验可以事先下载几首歌
先附上https://github.com/zskingking/Jetpack-Mvvm
2. 应用技术
基础框架选用MVVM,选用的Jetpack组件包括Lifecycle、ViewModel、LiveData、DataBinDing、Navigation、Room。
项目基于Navigation由单Activity多Fragment实现,使用这种模式给我最直观的感受就是快,比如点击搜索进入搜索界面的衔接动画,在多Activity之间是不可能这么连贯的。
整个项目全部使用Kotlin语言,广泛应用了协程编写了大量的扩展函数。
关于每个模块的职责我是这样定义的:
Model
对应项目中Repository,做数据请求以及业务逻辑。很多人将业务逻辑编写到VM层,但我个人认为写在Model层更为合适,因为数据和业务逻辑本身就是息息相关,拿到数据及时处理业务逻辑,最后通过ViewModel注入的LiveData将数据发送给View层。在该层我也对协程做了封装,以及统一捕获处理错误信息。
代码大概张这样:
/**
* 错误方法
*/
typealias Error = suspend (e: ApiException) -> Unit
/**
* des 基础数据层
* @date 2020/5/18
* @author zs
*
* @param coroutineScope 注入viewModel的coroutineScope用于协程管理
* @param errorLiveData 业务出错或者爆发异常,由errorLiveData通知视图层去处理
*/
open class BaseRepository(
private val coroutineScope: CoroutineScope,
private val errorLiveData: MutableLiveData<ApiException>
) {
/**
* 对协程进行封装,统一处理错误信息
*
* @param block 执行中
* @param success 执行成功
*/
protected fun <T> launch(
block: suspend () -> T
, success: suspend (T) -> Unit
, error:Error? = null): Job {
return coroutineScope.launch {
runCatching {
withContext(Dispatchers.IO) {
block()
}
}.onSuccess {
success(it)
}.onFailure {
it.printStackTrace()
getApiException(it).apply {
error?.invoke(this)
toast(errorMessage)
//统一响应错误信息
errorLiveData.value = this
}
}
}
}
/**
* 捕获异常信息
*/
private fun getApiException(e: Throwable): ApiException {
...
...
}
}
ViewModel
基于Jetpack中的ViewModel进行封装(友情提示:Jetpack ViewModel和MVVM ViewModel没有半毛钱关系,切勿将两个概念混淆)。在项目中VM层职责很简单,通过内部通过LiveData做数据存储,以及结合DataBinding做数据绑定。
View
尽量只做UI渲染。与MVP中不同,View是通过DataBinding与数据进行绑定,Activity或Fragment非常轻盈只专注于生命周期的管理,数据渲染基本全部由DataBinding+BindAdapter实现。
关于MVVM模版类的封装可至package com.zs.base_library.base(包名)下查看。
网络层
关于网络层继续使用OkHttp Retrofit,并对Retrofit多ApiService以及多域名进行了封装。配合Repository中封装的协程使用美得不能再美。
数据库
项目中历史记录是在本地数据库进行维护的,关于数据库使用了Jetpack中的Room。
主题切换
Android原生提供的夜间切换好像又API版本限制,所以就没有用。我个人在本地维护了两套主题,可动态切换。当前包含白天、夜间两套主题
3. 关于注释
去年在我的Leader强行督促下养成了写注释的规习惯,我个人对写注释的要求也越来越高。
项目中运用了大量的设计模式,每用到一种设计模式我都会结合当时场景进行解释,比如播放器中某个接口,我会这样写注释:
/**
* des 所有的具体Player必须实现该接口,目的是为了让PlayManager不依赖任何
* 具体的音频播放实现,原因大概有两点
*
* 1.PlayManager包含业务信息,Player不应该与业务信息进行耦合,否则每次改动都会对业务造成影响
*
* 2.符合开闭原则,如果需要对Player进行替换势必会牵连到PlayManager中的业务,因而造成不必要的麻烦
* 如果基于IPlayer接口编程,扩展出一个Player即可,正所谓对扩展开放、修改关闭
*
* @author zs
* @data 2020-06-23
*/
interface IPlayer {
....
....
}
/**
* des 音频管理
* 通过单例模式实现,托管音频状态与信息,并且作为唯一的可信源
* 通过观察者模式统一对状态进行分发
* 实则是一个代理,将目标对象Player与调用者隔离,并且在内部实现了对观察者的注册与通知
* @author zs
* @data 2020/6/25
*/
class PlayerManager private constructor() : IPlayerStatus {
....
....
}
关于播放器的设计我觉得还是有些地方值得和大家分享,后面我会单独写一篇文章进行分析。
写在最后
此项目中你很难看到不明不白的代码。Jetpack和Kotlin是大势所趋,既然拒绝不了那何不开心的拥抱。功能目前已完成90%,代码也在持续优化,欢迎大家关注、下载源代码,让我们共同学习、共同进步。
再次附上https://github.com/zskingking/Jetpack-Mvvm,如果觉得对你有帮助麻烦给个支持!
相关阅读
1 Jetpack Hilt 依赖注入框架上手指南
2 JetPack + 单 Activity 的一些思路
3 Android Jetpack 最佳开发姿势
4 6. Jetpack---Paging你知道怎样上拉加载吗?
5 2019 年终总结,245+篇,已分类整理
如果你有写博客的好习惯
欢迎投稿
赞+在看,小生感恩❤️