文章共2301个字,预计阅读需约13分钟
一个引入了优质开源库的框架
retrofit2
网络请求库
glide
图片加载库
BaseRecyclerViewAdapterHelper
adapter库
permissionsdispatcher-ktx
配合kotlin使用的权限请求库
mmkv
使用腾讯的mmkv替代SharePreference的高性能key-value组件
smartRefresh
上拉加载下拉刷新库
shimmerlayout
*度高,方便使用的骨骼加载
liveEvent
消息总线,基于LiveData,具有生命周期感知能力
UnPeek-LiveData
解决LiveData数据倒灌disklrucache
硬盘缓存
QMUI
module的方式引入QMUI,因为QMUI有很多功能,所以单独抽取出一些经常用的功能
你可以轻松做这些
一目了然的项目结构
方便编写,方便维护
类很少
写一个界面你需要创建三个文件,xml布局,activity/fragment,viewmodel,没有其他的了。
LiveEventBus
消息总线,相比于evnetBus和RxBus,具有生命周期感知,而且依赖度小,因为他是依赖于google,而RxBus是依赖于RxJava
//发送消息
LiveEventBus.get(Constant.DEL_ADDRESS_EVENT).post("")
//接受消息
LiveEventBus.get(Constant.EVENT)
.observe(this, Observer {
})
网络请求代码简约
viewmodel两行代码即可完成基本网络调用并把数据通过liveData回传给界面。demo中举例了三种请求情况,单个实体,接收null,分页。分页请求与不分页的结构相差不大,代码不需要变动太大。
单个实体
val singEntityLiveData = StateLiveData<User>()
fun singEntity() = launchUI(
response = { mApi.singEntity().data!! },
liveData = singEntityLiveData
)
接收null
接收null时,StateLiveData要写死Any?,?是kotlin允许为null。设置allowNull为true,否则livedata不会回传
val mayNullLiveData = StateLiveData<Any?>().allowNull(true)
fun mayNull() = launchUI(
response = { mApi.mayNull().data },
liveData = mayNullLiveData
)
分页
val pageEntityLiveData = StateLiveData<Page<PageUser>>()
fun pageUser(pageNum: Int) = launchUIPage(
pageNum,
response = { mApi.pageEntity(pageNum).data!! },
liveData = pageEntityLiveData
)
页面缓存(离线缓存)
需求:
-
网络请求与读取缓存必须是同时执行,不能同步执行。
-
如果网络请求比读取缓存快,那么缓存的数据不能覆盖网络请求的数据。
-
如果没有网,读取缓存,接口提示网络错误。
-
每次的新数据会覆盖之前缓存的数据。
fun cacheData() = launchUIPageCache(
cache = {
CacheManager.getInstance().getCacheList(
Constant.CACHE,
CacheEntity::class.java
)
},
response = { mApi.cacheEntity().data!! },
liveData = cacheEntityLiveData
)
轻松实现监听接口状态
before 请求之前,response 请求结果,error 错误结果,complete 接口请求后(成功/失败)执行,设置 retry 错误重试(可以单独设置或全局设置最大次数和可自定义设置条件或无条件)
fun singEntity() {
launchUI(
response = { mApi.singEntity().data!! },
liveData = singEntityLiveData,
error = { e, code ->
//错误逻辑处理
},
retry = { e, code ->
//重试逻辑
RetryUtil(3).retryToException(e is SocketTimeoutException) {
singEntity()
}
},
complete = {
//接口执行完毕
}
)
}
使用StateLiveData
界面对接口各种状态的处理,complete(),finishMore(),setPageData()均为kt拓展方法
/**
* @param state 状态类
* @param before 接口请求前
* @param complete 接口请求后(成功/失败)执行
* @param hasMore 是否有下一页,做分页
* @param stateLayout 状态页面
*/
fun apiState(
state: State,
before: (() -> Unit)? = null,
complete: (() -> Unit)? = null,
error: ((code: Int) -> Unit)? = null,
hasMore: ((page: Page<*>) -> Unit)? = null,
stateLayout: ((layout: Int) -> Unit)? = null
) {
when (state.stateCode) {
Constant.BEFORE -> before?.invoke()
Constant.COMPLETE -> complete?.invoke()
Constant.ERROR -> error?.invoke(state.errorCode)
Constant.HAS_MORE -> hasMore?.invoke(state.page)
Constant.STATE_LAYOUT -> stateLayout?.invoke(state.stateLayout)
}
}
mModel.run {
/**
* 分页回调
*/
pageEntityLiveData.observeInActivity(this@NetWorkActivity) { setData(it.list) }
pageEntityLiveData.state.observeInActivity(this@NetWorkActivity) { state ->
apiState(state,
complete = { refresh.complete() },
stateLayout = { mAdapter.setEmptyView(it) },
hasMore = { refresh.finishMore(it) }
)
}
/**
* 单个实体回调
*/
singEntityLiveData.observeInActivity(this@NetWorkActivity) {
}
singEntityLiveData.state.observeInActivity(this@NetWorkActivity) {
apiState(
it,
before = { showLoading() },
complete = { dismissLoading() })
}
/**
* 可以为null时回调
*/
mayNullLiveData.observeInActivity(this@NetWorkActivity) {
}
mayNullLiveData.state.observeInActivity(this@NetWorkActivity) {
apiState(
it,
before = { showLoading() },
complete = { dismissLoading() })
}
/**
* 缓存方式回调
*/
cacheEntityLiveData.observeInActivity(this@NetWorkActivity) {
//这里会执行两次回调数据,缓存数据+网络数据
}
cacheEntityLiveData.state.observeInActivity(this@NetWorkActivity) {
apiState(
it,
before = { showLoading() },
complete = { dismissLoading() })
}
//初始调用接口
singEntity()
mayNull()
cache()
}
自定义很多kotlin拓展方法(更多方法请见demo)
比如你想加载一个图片
imageView.loadImage("url")
倒计时
直接在activity/fragment
界面调用lifecycleCountdown
方法,5个参数:时间,间隔,开始监听,进度监听,结束监听,自动感知生命周期,不需要担心页面关闭后倒计时还在跑。
lifecycleCountdown(10, 1,
start = {},
schedule = {},
completion = {})
重写方法
重写某个接口发现要重写所有但却只需要一两个?
viewpager2.setOnPageChangeListener {
}
editText.setOnTextChanged { s, start, before, count ->
}
点击事件
view.onClick{}
设置文字的删除线
textView.centerLine()
轻松实现骨骼加载
骨骼加载与smartRefresh同时使用可能会遇到上拉加载会置顶的问题,需要调整骨骼加载hide的时机
recyclerView.initSkeletonRecyclerView(adapter)
下载文件功能
fileDownloader(context, downloadUrl,
completed = {},
pending = {},
progress = { soFarBytes, totalBytes ->
})
《用户协议》与《隐私政策》显示并点击很麻烦?
textView.protocol("...《用户协议》和《隐私政策》...") {
when(it){
0 -> WebViewActivity.start(context,Constant.USER_PROTOCOL)
1 -> WebViewActivity.start(context,Constant.PRIVACY_PROTOCOL)
}
}
为什么要自己写框架
菜鸟级
那是我刚入行,不知道大家是不是同款安卓。
那时的我并无框架概念,通常项目都是直接引入的库。所以所有的代码都在Activity,也没有进行很好的封装。那造成一个问题就是,简单的功能要写好多重复的代码。
刚入行,即使知道问题所在,但是很多问题需要花精力去解决。
熟练工
就这样过了差不多快一年,开始尝试模仿mvc,mvp,mvvm模式。但是写出来的东西总觉得不是自己想要的(过度的追求框架的标准,设计模式,思想)。
我常常也会逛一逛技术帖子,github,找到了一些优秀的框架,如MVPArms,Jetpack-MVVM-Best-Practice…
就这样,开始一点点的理解他们的框架以及逻辑,思想并进行实战项目,比如这个MVPArms,我用了两年。
进阶
随着工龄和经验的增加,慢慢开始有了属于自己的代码风格和方法论。
例如MVPArms,这个框架确实很优秀,但是总归是别人写的,不可能100%的符合自己,当时框架中使用的是dagger2和rxjava,它们倆都太庞大了,rxjava我也只是使用过几个操作符。
其实我倒认为功能单一的App是未来的发展趋势,尽管国内都是一些超级App(功能聚合)。
轻量的App也比较符合我的个人习惯,我一般都是下载lite版的App,不管是某东还是其他。App虽然功能单一,简单。从美学角度,简单并不意味着简陋,每一个轻量应用的设计都值得我们推敲。
想明白这些之后,我发现我需要的是一个轻量框架,加上这些年我开发的项目都是中小型的,轻量可能更加适合我。
未来
在我尝试写各种框架的过程中,也遇到过很多问题,如同上面说的,刚开始我过度追求框架的标准,设计模式,思想,导致写出来的框架不是有问题,就是怪怪的,当然,这些问题都是归咎于我自己的技术问题,没有对框架真正的深入理解,没有理解底层原理。
后来我自我反省,总想着这些也不是办法,不能为了框架而写框架,所以还是一点点的来,从简陋到简约。未来的我还是要不断的学习,要符合Google的框架思想,尝试写出适合更大项目开发的框架。目前的计划是,jetpack compose发布稳定版后,会第一时间更新到我的框架中,如果公司愿意,也想对项目进行实战。
一直都觉得安卓要有自己的风格,而不是遵从iOS的风格,或者两端长得一摸一样,5.0之前安卓确实挺丑的,5.0之后加入了Material Design感觉很不一样,当时以为主流的软件都会向MD风格靠齐,结果现在已经过去这么多年了,主流的软件还是很少看到MD风格。当然不是说安卓就一定要遵从标准或MD,只是我个人更喜欢而已,我觉得推特,电报,google家的一些APP,体验很好,在我手机上其他的软件可能会卡,但是他们不会卡,而且交互很棒,我的框架也加入了一些MD的界面,未来还会加入更多。
计划
- 1.不断的修善框架,尽量减少不可预期的报错。
- 2.尝试加入新的技术让框架更健壮,但不丢失轻量,简约。
- 3.加入更多Material Design样式,交互(完善中)
- 4.尝试使用jetpack compose重构布局(等待稳定版)
- 5.更加需要优化代码
- 6…
总结
我是有开源精神的一个人,无奈自身技术不强,所以很多时候不好意思分享自己学习到的技术。刚开始做这行的时候,那时候啥也不懂,学到了啥就写个博客,写个demo,当时也帮助到了一些人,反而是经验越足就越不敢写博客了。
github地址:JetPackMVVMLight