安卓MVVM开发:基于AAC架构玩安卓客户端(Databindi
一、开篇
全篇内容将简单介绍AAC的在我个人开发上的应用, AAC即Android Architecture Components,一个处理UI的生命周期与数据的持久化的架构。
核心使用:LiveData、ViewModel、Databinding 、Lifecycles
辅助:Room、WorkManager 、Glide等等常用框架
全篇大部分内容都围绕Kotlin,这门Google强推的语言上,加上协程这个工具的出现,使得Kotlin对于网络请求,异步操作变得更加方便。所以网络部分我已经不再使用Retrofit2 + Rxjava2了。
首先、本客户端借助玩安卓的API实现网络功能,非常感谢鸿洋大神玩安卓,给我们提供了分享知识和技术的地方
玩安卓:https://www.wanandroid.com/
接着本客户端使用了来自github的第三方库为:
1、live-event-bus github地址:https://github.com/JeremyLiao/LiveEventBus 一款很优秀的消息总线。
2、Activity返回侧滑动画SlideBack github地址:https://github.com/ParfoisMeng/SlideBack 项目中下载了源码并进行了部分修改。
非常感谢所有作者
二、玩安卓部分预览图
image image三、图片墙部分预览图:
本部分采用Room实现基本登录注册,点赞,下单(假功能,仅效果显示)的操作
image四、从网络开始讲起
在使用的API方法上加上suspend
@FormUrlEncoded
@POST("user/login")
suspend fun login(@Field("username") username:String,@Field("password") password:String) : Response<JsonResult<Auth>>
处理请求的逻辑封装了另一个类CallResult,只显示关键部分
fun hold(result: suspend () -> Response<JsonResult<T>>): CallResult<T> {
var response: Response<JsonResult<T>>?
var netJob: Job? = null
owner?.apply {
netJob = lifecycleScope.launchWhenStarted {
__________ 处理loading状态 ————————————————
response = withContext(Dispatchers.IO) {
withTimeoutOrNull(10000){//超时处理
result.invoke() //网络请求
}
}
if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
withContext(Dispatchers.Main) {
__________ 处理超时和返回结果的逻辑————————————————
}
} else {
netJob?.cancel()
}
}
}
return this
}
使用时候如下:
fun login(username:String,password:String,call:MutableLiveData<Result<Auth>>){
CallResult<Auth>(owner)
.loading {
__________ 处理读取————————————————
}.success { result, message ->
__________ 处理成功————————————————
call.value = result
}.error { result, code, message ->
__________ 处理错误————————————————
call.value = result
}.outTime {
__________ 处理超时————————————————
call.value = it
}.hold {
api.login(username, password)//登录
}
}
五、架构细讲
由于着重在AAC上,所以采用了MVVM的设计模式,利用DataBinding的优势,把数据和交互的简单操作,都能在xml上完成
以下拉加载和读取更多为例子
xml如下
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/refresh"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
onRefresh="@{Home::loadRefresh}"
refreshing="@{Home.refreshing}"
>
<showmethe.github.core.widget.common.AutoRecyclerView
android:id="@+id/rv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:requiresFadingEdge="vertical"
android:fadingEdge="vertical"
android:fadingEdgeLength="@dimen/px20dp"
loadMore="@{Home::loadMore}"
/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
请勿直接复制,省略databinding的set操作和初始化
class HomeFragment : LazyFragment<FragmentHomeBinding, MainViewModel>() {
val refreshing = MutableLiveData<Boolean>() //不能private
private val pagerNumber = MutableLiveData<Int>()
override fun observerUI() {
pagerNumber.observe(this, Observer {
it?.apply {
router.toTarget("getHomeArticle",this) //这个待会讲,利用注解调用viewModel中的方法
}
})
}
//___________省略无关代码_________
fun loadMore(){//不能private
pagerNumber.value = pagerNumber.value!! + 1
}
fun loadRefresh(){
pagerNumber.value = 0
}
}
}
对应的BindingAdapter拓展方法,利用了Kotlint的特性
@BindingAdapter("loadMore")
fun AutoRecyclerView.loadMore(loadingMore: (()->Unit)?){
setOnLoadMoreListener {
loadingMore?.invoke()
}
}
@BindingAdapter("onRefresh")
fun SwipeRefreshLayout.onRefresh(onRefreshListener: SwipeRefreshLayout.OnRefreshListener?){
setOnRefreshListener(onRefreshListener)
}
@BindingAdapter("refreshing")
fun SwipeRefreshLayout.refreshing(newValue: Boolean) {
if(isRefreshing != newValue)
isRefreshing = newValue
}
5.1 VMRouter的使用
在上面代码中有一段router.toTarget("getHomeArticle",this)的代码,是用来调用ViewModel中的方法,ViewModel中使用注解VMPath 和一个在该ViewModel中唯一的路径,如下:
/**
* 登录
*/
@VMPath("login")
fun login(username:String,password:String){
repository.login(username, password, auth)
}
通过反射拿到对应方法,并缓存起来。其实这里是因为存在反射,其实是会有性能的问题,但是这个消耗很低。
5.2 RecyclerViewAdapter的封装
既然使用了Databinding,那不可不提ObservableArrayList,这个类可以让你对数组的变化进行刷新处理,不在需要每次新数据都自己调用Adapter的刷新,减少漏调时候出现的奇怪问题。实现OnListChangedCallback即可,
/**
* The callback that is called by ObservableList when the list has changed.
*/
abstract class OnListChangedCallback<T extends ObservableList> {
/**
* Called whenever a change of unknown type has occurred, such as the entire list being
* set to new values.
*
* @param sender The changing list.
*/
public abstract void onChanged(T sender);
/**
* Called whenever one or more items in the list have changed.
* @param sender The changing list.
* @param positionStart The starting index that has changed.
* @param itemCount The number of items that have changed.
*/
public abstract void onItemRangeChanged(T sender, int positionStart, int itemCount);
/**
* Called whenever items have been inserted into the list.
* @param sender The changing list.
* @param positionStart The insertion index
* @param itemCount The number of items that have been inserted
*/
public abstract void onItemRangeInserted(T sender, int positionStart, int itemCount);
/**
* Called whenever items in the list have been moved.
* @param sender The changing list.
* @param fromPosition The position from which the items were moved
* @param toPosition The destination position of the items
* @param itemCount The number of items moved
*/
public abstract void onItemRangeMoved(T sender, int fromPosition, int toPosition,
int itemCount);
/**
* Called whenever items in the list have been deleted.
* @param sender The changing list.
* @param positionStart The starting index of the deleted items.
* @param itemCount The number of items removed.
*/
public abstract void onItemRangeRemoved(T sender, int positionStart, int itemCount);
}
再结合Databinding的特性,不再需要ButterKnife和findviewbyId找到view中对应的id,而且利用数据绑定,而且数据绑定使得有时候连Id也不再需要书写了,当然有些场景还是需要的,代码更加的简洁和方便了,但是也有人觉得这样不好维护,但我觉得数据绑定减少了不少代码的书写,再加上kotlin更加简洁了。
项目github地址:https://github.com/ShowMeThe/WanAndroid