我收藏的Android开发文章Android学习Android应用开发那些事

安卓MVVM开发:基于AAC架构玩安卓客户端(Databindi

2019-11-06  本文已影响0人  showMethe

一、开篇

全篇内容将简单介绍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

上一篇下一篇

猜你喜欢

热点阅读