当前位置: 首页 > news >正文

Android-kotlin MVVM框架搭建+Retrofit二次封装

目录

一,定义

1.1 MVC

1.2 MVP

1.3 MVVM

二,MVVM框架搭建

2.1 Model层

2.2 View层

2.2.1 ViewBinding

2.2.2 ViewModel+LiveData

2.2.3 封装BaceActivity

2.3 ViewModel层

总结

三,Retrofit封装


一,定义

安卓的框架由最初的mvc,到后来的mvp,又到现在的mvvm。

1.1 MVC

Android采用XML文件实现页面布局,通过Java在Activity中开发业务逻辑,这种开发模式实际上已经采用了MVC的思想,分离视图和控制器。MVC模式(Model–view–controller)是软件工程中的一种软件架构模式,把软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller)。

  • 控制器(Controller)- 负责转发请求,对请求进行处理。
  • 视图(View) – 界面设计人员进行图形界面设计。
  • 模型(Model) – 程序员编写程序应有的功能(实现算法等等)、数据库专家进行数据管理和数据库设计(可以实现具体的功能)。

在Android编程中,View对应xml布局文件,Model对应实体模型(网络、数据库、I/O),Controller对应Activity业务逻辑,数据处理和UI处理。如下图所示:

在实际开发过程中,纯粹作为View的各个XML文件功能较弱,Activity基本上都是View和Controller的合体,既要负责视图的显示又要加入控制逻辑,承担的功能很多,导致代码量很大。所有更贴切的目前常规的开发说应该是View-Model模式,大部分都是通过Activity的协调。

1.2 MVP

关于MVP模式,之前的文章已经讲过了,并进行了框架的搭建,https://yuanzhen.blog.csdn.net/article/details/133266373

1.3 MVVM

MVVM是Model-View-ViewModel的简称,它由三个部分组成,也就是 Model、View 和 ViewModel,其中视图模型(ViewModel)其实就是 PM 模式中的展示模型,在 MVVM 中叫做视图模型。从实际效果来看,ViewModel是View的数据模型和Presenter的结合,具体结构如下图所示:

  • Model(模型层)通过网络和本地数据库获取视图层所需数据;
  • View(视图层)采用XML文件进行界面的描述;
  • ViewModel(视图-模型层)负责View和Model之间的通信,以此分离视图和数据。

View和Model之间通过Android Data Binding技术,实现视图和数据的双向绑定;ViewModel持有Model的引用,通过Model的方法请求数据;获取数据后,通过Callback(回调)的方式回到ViewModel中,由于ViewModel与View的双向绑定,使得界面得以实时更新。同时,界面输入的数据变化时,由于双向绑定技术,ViewModel中的数据得以实时更新,提高了数据采集的效率。

MVVM架构将Presenter改名为ViewModel,基本上与MVP模式完全一致,唯一的区别是,它采用双向绑定(data-binding)View的变动,自动反映在 ViewModel,反之亦然,这就导致了我们如果要完整的采用 MVVM 必须熟练的掌握 DataBinding 等基础组建,这就给我们MVVM引入项目带了困难。

二,MVVM框架搭建

2.1 Model层

Model层的主要作用就是通过网络和本地数据库获取视图层所需数据,由于网络框架还没搭建,所以先用数据模拟一下:

测试数据:

{"status":200,"desc":"操作成功","data":"2025-09-26 13:15:03"}

首先创建一个数据类:

data class DateTimeBean(val status :Int ,val desc:String ,val data:String)

然后创建一个接口,用于数据成功和失败的回调:

interface ICallBack<T> {fun onSuccess(result: T)fun onError(e:Throwable)
}

最后创建MainActivity的Model,因为Model层获取数据需要不同的传参,所以Model层就没有必要创建基类或者接口,这里我们直接创建一个MainModel,并模拟数据的获取:

open class MainModel {fun getTimeData(callBack: ICallBack<DateTimeBean>){val data = DateTimeBean(200, "获取成功", "结果获取成功")callBack.onSuccess(data)}}

Model层的创建就完成了。非常简单,如果需要增加一些通用的功能,比如loading等,可以封装基类去实现。

2.2 View层

View层其实就是Activity及其xml,因为要实现mvvm,所以这里我们采用viewbinding+viewmodel+livedata 来实现

2.2.1 ViewBinding

关于viewbinding下面我们来讲解下它的使用

首先在app的build.gradle中添加viewbinding的使用:

buildFeatures {dataBinding = trueviewBinding = true
}

我们创建一个名为 activity_test 的xml文件:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><TextViewandroid:id="@+id/txt_test"android:layout_width="wrap_content"android:layout_height="wrap_content"/></RelativeLayout>

此时会通过apt生成ActivityTestBinding,然后在TestActivity中使用:

class TestActivity :AppCompatActivity() {var binding :ActivityTestBinding? =nulloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)binding = ActivityTestBinding.inflate(layoutInflater)val view = binding?.rootsetContentView(view)binding?.txtTest?.setText("viewbinding")}
}

这里所有在xml中定义的view都可以直接通过binding.id获取到。比如id为txt_test可以通过binding.txtTest获取到。

这样就不用通过findViewById去获取id了。

2.2.2 ViewModel+LiveData

ViewModel+LiveData前面的文章讲过:https://yuanzhen.blog.csdn.net/article/details/134795587

下面来讲一下kotlin中的使用:

首先在app的build.gradle中添加依赖:

implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1"

创建TestViewModel:

class TestViewModel: ViewModel() {val count:MutableLiveData<String> by lazy { getTest() }fun getTest():MutableLiveData<String> {return MutableLiveData<String>()}}

在TestActivity中,使用viewModel:

class TestActivity :AppCompatActivity() {var binding :ActivityTestBinding? =nullprotected lateinit var viewModel:TestViewModeloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)binding = ActivityTestBinding.inflate(layoutInflater)val view = binding?.rootsetContentView(view)var observer:Observer<String> = Observer {binding?.txtTest?.setText(it)}viewModel =ViewModelProvider(this).get(TestViewModel::class.java)viewModel.count.observe(this,observer)binding?.txtTest?.setOnClickListener{viewModel.count.postValue("测试测试")}}
}

运行效果:点击后显示

2.2.3 封装BaceActivity

上面的使用中,每次创建一个activity都要写一堆的代码去获取viewbinding和viewModel,所以我们可以封装一个baseactivity 来去获取这些事情。

首先创建一个ViewModelFactory用来创建ViewModel。

class ViewModelFactory : ViewModelProvider.Factory {override fun <T : ViewModel> create(modelClass: Class<T>): T {return try {modelClass.newInstance()} catch (e: IllegalAccessException) {Log.e(ViewModelFactory::class.java.simpleName, e.toString())throw IllegalArgumentException("unexpected model class $modelClass", e)} catch (e: InstantiationException) {Log.e(ViewModelFactory::class.java.simpleName, e.toString())throw IllegalArgumentException("unexpected model class $modelClass", e)}}
}

然后创建一个BaseActivity 来创建ViewModel和ViewBinding:

abstract class BaseActivity<K :ViewModel,W :ViewBinding> :AppCompatActivity() {private var factory: ViewModelProvider? = nullprotected lateinit var viewModel: Kprotected lateinit var binding: Wprotected  val vMFactory: ViewModelProvider.Factory =ViewModelFactory()override fun onCreate(savedInstanceState: Bundle?) {initWindow()super.onCreate(savedInstanceState)bindView()createViewModel()initView()initClick()}private fun createViewModel() {val superclass = javaClass.genericSuperclass!!if (superclass is ParameterizedType) {val arguments = superclass.actualTypeArgumentsif (arguments.isNotEmpty()) {try {val aClass = arguments[0] as Class<K>if (factory == null) {factory = ViewModelProvider(this, vMFactory!!)}viewModel = factory!![aClass]} catch (e: Exception) {Log.e(BaseActivity::class.java.simpleName, e.toString())e.printStackTrace()}}}}private fun bindView() {//获得带有泛型的父类val superclass = javaClass.genericSuperclass!!// ParameterizedType参数化类型,即泛型if (superclass is ParameterizedType) {//泛型数组val arguments = superclass.actualTypeArgumentstry {//取第二个,也就是我们的viewbindingval aClass: Class<*> = arguments[1] as Class<*>val method = aClass.getDeclaredMethod("inflate", LayoutInflater::class.java)binding = method.invoke(aClass, layoutInflater) as WsetContentView(binding.root)} catch (e: NoSuchMethodException) {Log.e(BaseActivity::class.java.simpleName, e.toString())e.printStackTrace()} catch (e: IllegalAccessException) {Log.e(BaseActivity::class.java.simpleName, e.toString())e.printStackTrace()} catch (e: InvocationTargetException) {Log.e(BaseActivity::class.java.simpleName, e.toString())e.printStackTrace()}}}//初始化窗口可重写此方法open fun initWindow() {}//点击事件设置,可重写此方法open fun initClick() {}//初始化viewabstract fun initView()
}

这样MainActivity就可以简化为:

class MainActivity : BaseActivity<MyViewModel, ActivityMainBinding>() {override fun initView() {viewModel.myViewModel.observe(this) {binding.txtContent.setText(it.data)}binding.txtContent.setOnClickListener {}}
}

2.3 ViewModel层

下面就来创建ViewModel层,有特殊需求的也可以封装一个基类,这里没有涉及到具体项目,所以就不创建基类了,直接创建一个MainModel类:

open class MyViewModel : ViewModel() {val myViewModel : MutableLiveData<DateTimeBean> by lazy { getTimeLiveData() }val model: MainModel by lazy { initModel() }fun initModel(): MainModel {return MainModel()}fun getTimeLiveData():MutableLiveData<DateTimeBean>{return MutableLiveData<DateTimeBean>()}fun getTime(){model.getTimeData(object : ICallBack<DateTimeBean> {override fun onSuccess(result: DateTimeBean) {myViewModel.postValue(result)}override fun onError(e: Throwable) {}})}}

总结

三层都封装完之后,在MainActivity中使用:

class MainActivity : BaseActivity<MyViewModel, ActivityMainBinding>() {override fun initView() {viewModel.myViewModel.observe(this) {binding.txtContent.setText(it.data)}binding.txtContent.setOnClickListener {viewModel.getTime()}}
}

运行效果如下:

三,Retrofit封装

关于retrofit的使用及其源码,之前的文章已经讲过了https://yuanzhen.blog.csdn.net/article/details/145372493

首先我们创建一个基本的数据类:

@Keep
data class BaseResponseString(val status :Int ,val desc:String ,val data: String)

然后创建一个Request接口:

interface Request {@GETfun get(@Url url:String): Call<BaseResponseBody>@GETfun getString(@Url url:String): Call<BaseResponseString>@GETfun get(@Url url:String, @QueryMap map:Map<String, Object> ):Call<BaseResponseBody>@POSTfun post(@Url url:String):Call<BaseResponseBody>@POSTfun post(@Url url:String, @Body map: Map<String, Object>):Call<BaseResponseBody>@POSTfun post(@Url url:String, @Body body:List<Object>):Call<BaseResponseBody>
}

还有之前创建的ICallBack:

interface ICallBack<T> {fun onSuccess(result: T)fun onError(e:Throwable)
}

最后去创建一个单例的RetrofitManager,用来创建Retrofit ,请求get,post等。 这里只封装下get:

class RetrofitManager private constructor() {companion object {private const val BASE_URL = "http://192.168.31.87/"@Volatile private var instance: RetrofitManager? = nullfun getInstance(): RetrofitManager {return instance ?: synchronized(this) {instance ?: RetrofitManager().also { instance = it }}}}val retrofit:Retrofit by lazy { initRetrofit() }fun initRetrofit():Retrofit{val loggingInterceptor = HttpLoggingInterceptor { message -> //打印retrofit日志Log.i("RetrofitLog", "retrofitBack = $message")}loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY)//设置打印等级val client = OkHttpClient.Builder().addInterceptor(loggingInterceptor).connectTimeout(20, TimeUnit.SECONDS)//连接超时事件.readTimeout(20, TimeUnit.SECONDS).writeTimeout(20, TimeUnit.SECONDS).build()val retrofit =Retrofit.Builder().baseUrl(BASE_URL).client(client).addConverterFactory(GsonConverterFactory.create()).build()//配置属性return retrofit}fun <T> get(url:String,callBack:ICallBack<T>){val request =retrofit.create(Request::class.java)request.get(BASE_URL+url).enqueue(object :Callback<BaseResponseBody>{override fun onResponse(call: Call<BaseResponseBody>,response: Response<BaseResponseBody>) {callBack.onSuccess(response.body()!!.data as T)}override fun onFailure(call: Call<BaseResponseBody>, t: Throwable) {callBack.onError(t)}})}fun <T> getString(url:String,callBack:ICallBack<T>){val request =retrofit.create(Request::class.java)request.getString(BASE_URL+url).enqueue(object :Callback<BaseResponseString>{override fun onResponse(call: Call<BaseResponseString>,response: Response<BaseResponseString>) {callBack.onSuccess(response.body()!!.data as T)}override fun onFailure(call: Call<BaseResponseString>, t: Throwable) {callBack.onError(t)}})}
}

在model中使用:

class SecondModel {fun getSecondData(callBack: ICallBack<String>){RetrofitManager.getInstance().getString("passport/web-rbac/logins/currentTime",object:ICallBack<String>{override fun onSuccess(result: String) {callBack.onSuccess(result)}override fun onError(e: Throwable) {callBack.onError(e)}})}
}

创建ViewModel:

class SecondViewModel : ViewModel()  {val secondViewModel : MutableLiveData<String> by lazy { getSecondLiveData() }val secondModel: SecondModel by lazy { initModel() }fun initModel(): SecondModel {return SecondModel()}fun getSecondLiveData():MutableLiveData<String>{return MutableLiveData<String>()}fun getSecondData(){viewModelScope.launch (Dispatchers.IO){secondModel.getSecondData(object : ICallBack<String> {override fun onSuccess(result: String) {secondViewModel.postValue(result)}override fun onError(e: Throwable) {secondViewModel.postValue(e.message)}})}}}

创建Activity:

class SecondActivity: BaseActivity<SecondViewModel, ActivitySecondBinding>() {override fun initView() {viewModel.secondViewModel.observe(this) {binding.txtSecond.setText(it)}binding.txtSecond.setOnClickListener {viewModel.getSecondData()}}
}

创建activity_second.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><TextViewandroid:id="@+id/txt_second"android:layout_width="wrap_content"android:layout_height="wrap_content"android:textSize="30sp"android:text="第二页"android:textColor="#00ff22"/></RelativeLayout>

运行效果如下:

http://www.dtcms.com/a/418246.html

相关文章:

  • QML学习笔记(十八)QML的信号处理器的Connections写法
  • Spring Cloud Gateway 实战:全局过滤器日志统计与 Prometheus + Grafana 接口耗时监控
  • CTFHub RCE通关笔记7:命令注入 过滤cat(9种渗透方法)
  • Kotlin Value Class 全面解析:类型安全与零开销封装
  • 【Android】kotlin.flow简介
  • 如何在电脑上编辑三星联系人
  • Java开发环境搭建之 9.使用Docker Compose 安装部署RabbitMQ
  • 智能家居:从设备互联到智慧感知的技术演进
  • 做网站是个什么行业网站设计示例
  • D018 vue+django 旅游图谱推荐问答系统|neo4j数据库|智能问答
  • 11. Jmeter性能与优化
  • 水脉织城・文脉映画:泰州城市旅游宣传片的专业化叙事路径
  • QT文件解析与乱码问题
  • 医疗编程AI技能树与培训技能树报告(国内外一流大学医疗AI相关专业分析2025版,下)
  • seo网站快速排名企业域名怎么填写
  • 谈谈数学和式的理解和应用
  • 【Linux指令 (一)】Linux 命令行入门:从零开始理解Linux系统理论核心概念与基础指令
  • 网站建设案例赏析网站制作比较好的制作公司
  • 线上JVM问题定位常用命令
  • 通过配置 GitLab 自动触发项目自动化构建与部署
  • 【qml-12】Quick3D实现机器人鼠标拖拽转换视角(无限角度)与滚轮缩放
  • h5实现一个吸附在键盘上的工具栏
  • 解决 sqlplus / as sysdba 登录缓慢问题
  • Mysql DBA学习笔记(主从复制)
  • 网站开发的交付文档企业策划是什么意思
  • 个人网站主机选择上海品牌全案设计公司
  • 布谷娱乐直播系统源码开发实用功能:技术驱动更迭的创新体验
  • ArcGIS JSAPI 高级教程 - 高亮效果优化之开启使用多高亮样式
  • 元宇宙的搜索引擎:如何在虚拟世界中查找信息
  • Unity-AB包