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

您所在的位置:网站首页 mvvm框架搭建 Android 用Jetpack搭建一个轻量级MVVM(一)

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

#Android 用Jetpack搭建一个轻量级MVVM(一)| 来源: 网络整理| 查看: 265

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 async( request: suspend CoroutineScope.() -> T, success: (T) -> Unit, error: suspend CoroutineScope.(BaseResponseThrowable) -> Unit, complete: suspend CoroutineScope.() -> Unit = {} ) { launchUi { //这里处理网络请求 } } 复制代码 复制代码

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

private suspend fun 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这种方式做为接受数据,那么增加一个请求数据的过滤

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

同时我们可以增加请求时loading状态的控制,这里就不具体阐述,可在代码中查看,代码均有注释 除开协程,rxjava是我们最常用的请求方式,而在rxjava中主要注意的就是内存泄漏问题,现有比较有名的管理rxjava内存的库有RxLifecycle和AutoDispose 这里使用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 { private val lifecycleEvents = BehaviorSubject.createDefault(ViewEvent.CREATED) override fun lifecycle(): Observable { return lifecycleEvents.hide() } override fun correspondingEvents(): CorrespondingEventsFunction { 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 = CorrespondingEventsFunction { event -> when (event) { ViewEvent.CREATED -> ViewEvent.DESTROY else -> throw LifecycleEndedException( "Cannot bind to ViewModel lifecycle after onCleared.") } } } fun auto(provider: ScopeProvider): AutoDisposeConverter { return AutoDispose.autoDisposable(provider) } } 复制代码 复制代码

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

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

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

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

具体的可见代码 [TOC]

Activity与Fragment

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

abstract class CommonBaseActivity:AppCompatActivity(){ lateinit var binding: VB override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) binding = DataBindingUtil.setContentView(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(supportActionBar).setTitle(title) } override fun setTitle(title: Int) { Objects.requireNonNull(supportActionBar).setTitle(getString(title)) } override fun onSupportNavigateUp(): Boolean { onBackPressed() return true } } 复制代码 复制代码

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

这个类主要用于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( //这里使用ObservableList,在init代码块中ListChangedCallback的方法一一对应,这样的话可以充分利用RecycleView的特性,单个数据改变的刷新 var itemData: ObservableList, var layoutId: Int, var dataId: Int ) : RecyclerView.Adapter() { private lateinit var bing:Vb override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): BaseDataBingViewHolder { bing = DataBindingUtil.inflate( LayoutInflater.from(viewGroup.context), layoutId, viewGroup, false ) return BaseDataBingViewHolder(bing) } override fun onBindViewHolder(viewHolder: BaseDataBingViewHolder, i: Int) { viewHolder.binding.setVariable(dataId,itemData[i]) bindViewHolder(viewHolder,i,itemData[i]) } protected open fun bindViewHolder( @NonNull viewHolder: BaseDataBingViewHolder, 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) { itemData = newItemData notifyDataSetChanged() } init { itemData.addOnListChangedCallback(object : ObservableList.OnListChangedCallback() { override fun onChanged(observableList: ObservableList) { notifyDataSetChanged() } override fun onItemRangeChanged( observableList: ObservableList, i: Int, i1: Int ) { notifyItemRangeChanged(i, i1) } override fun onItemRangeInserted( observableList: ObservableList, i: Int, i1: Int ) { notifyItemRangeInserted(i, i1) } override fun onItemRangeMoved( observableList: ObservableList, i: Int, i1: Int, i2: Int ) { if (i2 == 1) { notifyItemMoved(i, i1) } else { notifyDataSetChanged() } } override fun onItemRangeRemoved( observableList: ObservableList, i: Int, i1: Int ) { notifyItemRangeRemoved(i, i1) } }) } } 复制代码 复制代码 public class BaseDataBingViewHolder extends RecyclerView.ViewHolder { public VB binding; public BaseDataBingViewHolder(VB binding) { super(binding.getRoot()); this.binding = binding; } } 复制代码 复制代码

用法

public class NewAdapter extends BaseRecyclerViewAdapter { public NewAdapter(@NotNull ObservableList itemData, int layoutId, int brId) { super(itemData, layoutId, brId); } @Override protected void bindViewHolder(@NonNull @NotNull BaseDataBingViewHolder 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 void addItem(RecyclerView recyclerView, ObservableList it) { BaseRecyclerViewAdapter adapter = (BaseRecyclerViewAdapter) recyclerView.getAdapter(); if (adapter != null) { adapter.onSetItem(it); } } 复制代码 复制代码

在xml中

复制代码 复制代码

[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 } 复制代码 复制代码

接下来就是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 = 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() { @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


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3