Android 用Jetpack搭建一个轻量级MVVM(一)

2,131 阅读7分钟

Google推出Jetpack 到现在两年多了,18年末开始使用Jetpack来做为app项目架构,总之两个字“真香”

前言

Jetpack 是一套库、工具和指南,可帮助开发者更轻松地编写优质应用。这些组件可帮助您遵循最佳做法、让您摆脱编写样板代码的工作并简化复杂任务,以便您将精力集中放在所需的代码上。

Jetpack 包含与平台 API 解除捆绑的 androidx.* 软件包库。这意味着,它可以提供向后兼容性,且比 Android 平台的更新频率更高,以此确保您始终可以获取最新且最好的 Jetpack 组件版本。 这是官网的介绍

架构图

引用Google的图

JetPack - MVVM 总览

本文的项目架构组成为 mvvm+ okhttp+ retrofit+ RxJava+ LiveData+ dagger+ 协程 这里也不说各个组件的底层原理了,这里介绍的是如何将这些组件组合搭建一个轻量级的app架构 至于RxJava在本文中是多一个使用方式和协程并不冲突 但是有点显得多余,可以自由选择

框架搭建

这里框架的搭建也不说什么模块化,控件化了!文中项目主要针对基类、api、工具集、以及部分功能组件化的封装。

[TOC]

ViewModel

在viewModel中我们要做什么事呢?主要的任务就是网络请求和对返回数据进行处理,在baseViewModel 中只有协程的请求体的封装,在这当中不会有状态的展示StateView,StateView在继承baseViewModel的ViewModel中处理,当然如果说是直接引入代码,可直接在Base中处理

在viewModel中有一个管理协程生命周期的一个类叫做viewModelScope,viewModelScope会减少大量的模块代码,在viewModel的clear()方法中会自动清理取消协程,我们只需要直接引用viewModelScope

private fun launchUi(block: suspend CoroutineScope.() -> Unit) =
    viewModelScope.launch { block() }
复制代码

我们建立起一个网络请求的请求体名为async() 这个方法中我们将传入,网络请求service 以及成功失败的回调接口

 fun <T> async(
        request: suspend CoroutineScope.() -> T,
        success: (T) -> Unit,
        error: suspend CoroutineScope.(BaseResponseThrowable) -> Unit,
        complete: suspend CoroutineScope.() -> Unit = {}
    ) {
        launchUi {
           //这里处理网络请求
        }
    }

复制代码

当传入网络请求service之后需要一个实际请求网络的载体 将载体的方法名为handleRequest()

   private suspend fun <T> handleRequest(
        block: suspend CoroutineScope.() -> T,
        success: suspend CoroutineScope.(T) -> Unit,
        error: suspend CoroutineScope.(BaseResponseThrowable) -> Unit,
        complete: suspend CoroutineScope.() -> Unit
    ) {
        coroutineScope {
            try {
                success(block())
            } catch (e: Throwable) {
                error(ThrowableHandler.handleThrowable(e))
            } finally {
                complete()
            }
        }
    }
复制代码

这个时候我们只要在业务层的viewModel中调用async()方法就可以处理网络请求

   //这是一个网络请求方法
   fun getNews(type: String) {
      async({ repository.getNews(type) }
          , {
              itemNews.clear()
              itemNews.addAll(it.list)
          }, {
              it.printStackTrace()
          }, {
              
          })
  }

复制代码

现在看来网络请求是不是显得一样的简洁。如果在返回数据中是以BaseResponse<T>这种方式做为接受数据,那么增加一个请求数据的过滤

 //请求数据过滤
 private suspend fun <T> executeResponse(
        response: BaseResponse<T>,
        success: suspend CoroutineScope.(T) -> Unit
    ) {
        coroutineScope {
            if (response.isSuccess()) success(response.data())
            else throw BaseResponseThrowable(response.code(), response.msg())
        }
    }
    
复制代码

同时我们可以增加请求时loading状态的控制,这里就不具体阐述,可在代码中查看,代码均有注释 除开协程,rxjava是我们最常用的请求方式,而在rxjava中主要注意的就是内存泄漏问题,现有比较有名的管理rxjava内存的库有RxLifecycleAutoDispose 这里使用AutoDispose管理在0.8.0版本之后是针对Androidx的 如果不是androidx 要用之前的版本。在activity和fragment中可以直接使用,在Androidx中activity和fragment本身是实现了lifecycle的

 Observable.interval(1, TimeUnit.SECONDS)
        .doOnDispose {
          Log.i(TAG, "Disposing subscription from onResume() with untilEvent ON_DESTROY")
        }
        .autoDisposable(AndroidLifecycleScopeProvider.from(this, Lifecycle.Event.ON_DESTROY))//OnDestory时自动解绑
        .subscribeBy { num -> Log.i(TAG, "Started in onResume(), running until in onDestroy(): $num") }
复制代码

但是viewModel中并不能这么引用,viewmodel的生命周期和activity的生命周期是有区别的,这种情况下我们应该怎么使用呢?总不能在activity中传入lifecycle到viewmodel中吧?这个时候我们就需要实现LifecycleScopeProvider

open class BaseLifeViewModel (application: Application) : AndroidViewModel(application),
    LifecycleScopeProvider<ViewEvent> {
    private val lifecycleEvents = BehaviorSubject.createDefault(ViewEvent.CREATED)

    override fun lifecycle(): Observable<ViewEvent> {
        return lifecycleEvents.hide()
    }

    override fun correspondingEvents(): CorrespondingEventsFunction<ViewEvent> {
        return CORRESPONDING_EVENTS
    }

    /**
     * Emit the [ViewModelEvent.CLEARED] event to
     * dispose off any subscriptions in the ViewModel.
     * 在nCleared() 中进行解绑 
     */
    override fun onCleared() {
        lifecycleEvents.onNext(ViewEvent.DESTROY)
        super.onCleared()
    }

    override fun peekLifecycle(): ViewEvent {
        return lifecycleEvents.value as ViewEvent
    }

    companion object {
        var CORRESPONDING_EVENTS: CorrespondingEventsFunction<ViewEvent> = CorrespondingEventsFunction { event ->
            when (event) {
                ViewEvent.CREATED -> ViewEvent.DESTROY
                else -> throw LifecycleEndedException(
                    "Cannot bind to ViewModel lifecycle after onCleared.")
            }
        }
    }
    fun <T> auto(provider: ScopeProvider): AutoDisposeConverter<T> {
        return AutoDispose.autoDisposable(provider)
    }
}
复制代码

在baseviewModel中继承BaseLifeViewModel,在业务viewModel中使用

    fun getRxNews(type: String) {
        repository.getRxNews(type)
            .`as`(auto(this))
            .subscribes({
                //请求结果
            },{
                //返回异常
            })
复制代码

为了使用方便使用以及自定义异常,利用扩展函数将AutoDisposeConverter增加了一个使用函数subscribes

fun <T> SingleSubscribeProxy<T>.subscribes(onSuccess: (T) -> Unit,
                                            onError: (BaseResponseThrowable)->Unit) {
    ObjectHelper.requireNonNull(onSuccess, "onSuccess is null")
    ObjectHelper.requireNonNull(onError, "onError is null")
    val observer: RequestObserver<T> = RequestObserver(onSuccess, onError)
    subscribe(observer)
}
复制代码

具体的可见代码 [TOC]

Activity与Fragment

baseActivity和fragment里面的内容很简单只有一个toolbar的设置以及dagger的注入

abstract class CommonBaseActivity<VB: ViewDataBinding>:AppCompatActivity(){
    lateinit var binding: VB
    override fun onCreate(savedInstanceState: Bundle?) {
        AndroidInjection.inject(this)
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView<VB>(this, getLayout())
        initView()
    }
    @LayoutRes
    protected abstract fun getLayout(): Int

    protected abstract fun initView()

    //设置toolbar
    fun setSupportToolBar(toolBar: Toolbar) {
        setSupportActionBar(toolBar)
        val actionBar = supportActionBar
        if (actionBar != null) {
            actionBar.setDisplayHomeAsUpEnabled(true)
            actionBar.setDisplayShowHomeEnabled(true)
            actionBar.setHomeButtonEnabled(true)
        }
    }

    fun setTitle(title: String) {
        Objects.requireNonNull<ActionBar>(supportActionBar).setTitle(title)
    }

    override fun setTitle(title: Int) {
        Objects.requireNonNull<ActionBar>(supportActionBar).setTitle(getString(title))
    }

    override fun onSupportNavigateUp(): Boolean {
        onBackPressed()
        return true
    }
}
复制代码

dagger的使用这里就不多说了,在开发中我们主要关注ActivityBindingModuleFragmentBindingModule

这个类主要用于activity和fragment的注入,详细可看代码

@Module
abstract class ActivityBindingModule {
    @ActivityScoped
    @ContributesAndroidInjector
    abstract fun mainActivity(): MainActivity
    
    @FragmentScoped
    @ContributesAndroidInjector
    abstract fun newFragment(): NewFragment
}
复制代码

AppModule类中主要做网络api的初始化操作

@Module
public abstract class AppModule {
    @Provides
    @Singleton
    static Retrofit providerRetrofit() {
        return Net.INSTANCE.getRetrofit(UriConfig.INSTANCE.getBASE_URL(),6000L);
    }
    @Provides
    @Singleton
    static BaseApiService providerBaseApi() {
        return providerRetrofit().create(BaseApiService.class);
    }
}
复制代码
object Net {
    private var retrofit: Retrofit? = null
    private var okHttpClient: OkHttpClient? = null
    private var timeOut = 6000L
    fun getRetrofit(baseUrl: String, time: Long = 6000L): Retrofit {
        timeOut = time
        if (null == retrofit) {
            if (null == okHttpClient) {
                okHttpClient = getOkHttpClient()
            }
            //Retrofit2后使用build设计模式
            retrofit = Retrofit.Builder()
                //设置服务器路径
                .baseUrl("$baseUrl/")
                //添加转化库,默认是Gson  DecodeConverterFactory DecodeConverterFactory
                //                    .addConverterFactory(DecodeConverterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                //添加回调库,采用RxJava
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                //设置使用okhttp网络请求
                .client(okHttpClient!!)
                .build()
            return retrofit!!
        }
        return retrofit!!
    }

    private fun getOkHttpClient(): OkHttpClient {
        val loggingInterceptor = HttpLoggingInterceptor()
        if (LogUtils.isDebug) {
            loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
        }
        val headerInterceptor = Interceptor { chain ->
            val builder = chain.request().newBuilder()
            //请求头携token
            builder.addHeader("Authorization", "")
            chain.proceed(builder.build())
        }
        return OkHttpClient.Builder()
            .connectTimeout(timeOut, TimeUnit.SECONDS)
            .addInterceptor(loggingInterceptor)
            .addInterceptor(headerInterceptor)
            .writeTimeout(timeOut, TimeUnit.SECONDS)
            .readTimeout(timeOut, TimeUnit.SECONDS)
            .build()
    }
}
复制代码

[TOC]

RecycleView

在项目中用刀的列表最多的应该就是RecycleView了,针对RecycleView的封装的开源库已经有很多了,可满足各个场景的取消,这里针对RecycleView做简单封装,达到方便使用的效果以及适用于大多数普遍场景的需要,

abstract class BaseRecyclerViewAdapter<T,Vb:ViewDataBinding>(
    //这里使用ObservableList,在init代码块中ListChangedCallback的方法一一对应,这样的话可以充分利用RecycleView的特性,单个数据改变的刷新
    var itemData: ObservableList<T>,
    var layoutId: Int,
    var dataId: Int 
) : RecyclerView.Adapter<BaseDataBingViewHolder<Vb>>() {

    private lateinit var bing:Vb
    override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): BaseDataBingViewHolder<Vb> {
        bing = DataBindingUtil.inflate<Vb>(
            LayoutInflater.from(viewGroup.context),
            layoutId,
            viewGroup,
            false
        )
        return BaseDataBingViewHolder(bing)
    }

    override fun onBindViewHolder(viewHolder: BaseDataBingViewHolder<Vb>, i: Int) {
        viewHolder.binding.setVariable(dataId,itemData[i])
        bindViewHolder(viewHolder,i,itemData[i])

    }

    protected open fun bindViewHolder(
        @NonNull viewHolder: BaseDataBingViewHolder<Vb>,
        position: Int,
        t: T
    ) {
    }

    override fun getItemCount(): Int {
        return if (itemData == null) 0 else itemData.size
    }

    fun getItemLayout(itemData: T): Int {
        return layoutId
    }

    fun onSetItem(newItemData: ObservableList<T>) {
        itemData = newItemData
        notifyDataSetChanged()
    }

    init {
        itemData.addOnListChangedCallback(object : ObservableList.OnListChangedCallback<ObservableList<T>>() {
            override fun onChanged(observableList: ObservableList<T>) {
                notifyDataSetChanged()
            }
            override fun onItemRangeChanged(
                observableList: ObservableList<T>,
                i: Int,
                i1: Int
            ) {
                notifyItemRangeChanged(i, i1)
            }

            override fun onItemRangeInserted(
                observableList: ObservableList<T>,
                i: Int,
                i1: Int
            ) {
                notifyItemRangeInserted(i, i1)
            }

            override fun onItemRangeMoved(
                observableList: ObservableList<T>,
                i: Int,
                i1: Int,
                i2: Int
            ) {
                if (i2 == 1) {
                    notifyItemMoved(i, i1)
                } else {
                    notifyDataSetChanged()
                }
            }

            override fun onItemRangeRemoved(
                observableList: ObservableList<T>,
                i: Int,
                i1: Int
            ) {
                notifyItemRangeRemoved(i, i1)
            }
        })
    }
}
复制代码
public class BaseDataBingViewHolder<VB extends ViewDataBinding> extends RecyclerView.ViewHolder {
    public VB binding;
    public BaseDataBingViewHolder(VB binding) {
        super(binding.getRoot());
        this.binding = binding;
    }
}
复制代码

用法

public class NewAdapter extends BaseRecyclerViewAdapter<NewResponses.T1348647853363Bean,ItemNewBinding> {

    public NewAdapter(@NotNull ObservableList<NewResponses.T1348647853363Bean> itemData, int layoutId, int brId) {
        super(itemData, layoutId, brId);
    }

    @Override
    protected void bindViewHolder(@NonNull @NotNull BaseDataBingViewHolder<ItemNewBinding> viewHolder, int position, NewResponses.T1348647853363Bean t1348647853363Bean) {
        super.bindViewHolder(viewHolder, position, t1348647853363Bean);
        viewHolder.binding.title.setText(getItemData().get(position).getTitle());
        viewHolder.binding.source.setText(getItemData().get(position).getSource());
        GlideApp.loadImage(getItemData().get(position).getImgsrc(), viewHolder.binding.image);
    }
}
复制代码

在activity或者fragment中

private val adapter by lazy {
        NewAdapter(viewModel.itemNews, R.layout.item_new, 0)
    }
复制代码

在使用中为了更加方便,利用dataBinding来自定以xml属性,这里就要用到@BindingAdapter

    @BindingAdapter({"itmes"})
    public static <T> void addItem(RecyclerView recyclerView, ObservableList<T> it) {
        BaseRecyclerViewAdapter adapter = (BaseRecyclerViewAdapter) recyclerView.getAdapter();
        if (adapter != null) {
            adapter.onSetItem(it);
        }
    }
复制代码

在xml中

  <androidx.recyclerview.widget.RecyclerView
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         app:itmes="@{viewModel.itemNews}"
         android:id="@+id/recycle_view"/>
复制代码

[TOC]

Kotlin 扩展函数工具集

用Kotlin开发 必定不能缺少的就是扩展函数,api的扩展会极大的方便开发,相信很小伙伴已经体会过了,对于扩展函数简单说哈作用,扩展函数就是将对象自定义一系列对象本身不具备的方法或者api对外使用,比如Toast提示在四大组件中我们使用toast提示是用Toast.makeText(context.getApplicationContext(), msg, 0); 获取自定义的ToastUtils,那么如何将activity以及fragment中扩展呢,直接上代码

fun Activity.toast(msg: String?) {
    Toast.makeText(context.getApplicationContext(), msg, 0);
}
复制代码

在activity中我们就可以直接this.toast()或者taost() 来调用 当然还有TextView设置drawableLeft 我们也可以写成扩展函数

fun TextView.setImageLeft(imageId: Int) {
    val drawable =
        resources.getDrawable(imageId)
    drawable.setBounds(0, 0, drawable.minimumWidth, drawable.minimumHeight)
    setCompoundDrawables(drawable, null, null, null)
}
复制代码

调用的时候 textview.setImageLeft(R.mipmap.ic_back_close) 这样看起来不是就像是textview自带这个方法设置,又比如我们在EditText中如何没有任何输入我们将button禁止点击

fun EditText.watcher(textView: TextView) {
    this.addTextChangedListener(object : TextWatcher {
        override fun afterTextChanged(p0: Editable?) {

        }

        override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {

        }

        override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
            textView.isEnabled = p0?.length != 0
        }

    })
}
复制代码

调用的时候 edittext.watcher(textview) 这样是不是很方便呢?看起来就像是系统api,当然我们还可以自定以业务相关的api,更多的使用可以查看Base.kt这个类

到这里已经将ui层以及viewModel 和网络api有个基本的封装了。接下来我们模拟业务进行网络api请求然后到ui显示

[TOC]

实战

这里以网易的新闻api作为接口 首先在apiservice中申明请求

interface BaseApiService {

    @GET("/nc/article/headline/{id}/0-10.html")
    suspend fun getNews(@Path("id") id : String?): NewResponses

    @GET("/nc/article/headline/{id}/0-10.html")
    fun getRxNews(@Path("id") id : String?): Single<NewResponses>
}
复制代码

接下来就是Repository

class UserRepository @Inject internal constructor(private val apiService: BaseApiService) {
    /**
     * 协程请求
     */
    suspend fun getNews(type: String): NewResponses = apiService.getNews(type)
    /**
    *rxjava 请求
    */
    fun getRxNews(type: String)=apiService.getRxNews(type).async()

}
复制代码

然后到viewModel

class NewViewModel @Inject constructor(application: Application) : BaseViewModel(application) {
    @Inject
    lateinit var repository: UserRepository
    var itemNews: ObservableList<NewResponses.T1348647853363Bean> = ObservableArrayList()

    //直接获取结果的
    fun getNews(type: String) {
        async({ repository.getNews(type) }
            , {
                itemNews.clear()
                itemNews.addAll(it.list)
            }, {
                it.printStackTrace()
            }, {

            })
    }

    //带loading的
    fun getNewsLoading() {
        async({ repository.getNews("") }
            , {
                //返回结果
            }
            , true, {}, {})
    }
      fun getRxNews(type: String) {
        repository.getRxNews(type)
            .`as`(auto(this))
            .subscribes({

            },{

            })
}
复制代码

在fragment中

@FragmentScoped
class NewFragment : CommonBaseFragment<FragmentNewBinding>() {

    @Inject
    lateinit var viewModel: NewViewModel
    private val adapter by lazy {
        NewAdapter(viewModel.itemNews, R.layout.item_new, 0)
    }

    fun newInstance(type: String): NewFragment {
        val args = Bundle()
        args.putString("type", type)
        val fragment = NewFragment()
        fragment.arguments = args
        return fragment
    }

    override fun getLayout(): Int {
        return R.layout.fragment_new
    }

    override fun initView() {
        val type = arguments!!.getString("type", "")
        viewModel.getNews(type)
        binding.viewModel = viewModel
        binding.recycleView.layoutManager = LinearLayoutManager(activity)
        binding.recycleView.adapter = adapter
    }

    override fun loadData() {

    }
}
复制代码

到此一个业务请求完结

[TOC]

总结

到了这里文章基本上算完了,至于jetpack中的其他组件如room等,根据项目实际业务引入。文章粗略的介绍了搭建过程,如果你觉得对你有帮助可下载源码看看,如果你觉得不足以及错误之处,欢迎留言指出,这个开发框架的搭建是一个很轻量级的,其本质也是搭建一个轻量级的,Android发展到现在,出现很模式 mvc、mvp、mvvm等,可根据实际需求选择,没必要钟情于某一个模式,毕竟没有更好的,只有更适合的。后期会上传java版本,以及组件化开发的版本

github demo