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

目录

一,定义

[1.1 MVC](#1.1 MVC)

[1.2 MVP](#1.2 MVP)

[1.3 MVVM](#1.3 MVVM)

二,MVVM框架搭建

[2.1 Model层](#2.1 Model层)

[2.2 View层](#2.2 View层)

[2.2.1 ViewBinding](#2.2.1 ViewBinding)

[2.2.2 ViewModel+LiveData](#2.2.2 ViewModel+LiveData)

[2.2.3 封装BaceActivity](#2.2.3 封装BaceActivity)

[2.3 ViewModel层](#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层的主要作用就是通过网络和本地数据库获取视图层所需数据,由于网络框架还没搭建,所以先用数据模拟一下:

测试数据:

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

首先创建一个数据类:

Kotlin 复制代码
data class DateTimeBean(val status :Int ,val desc:String ,val data:String)

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

Kotlin 复制代码
interface ICallBack<T> {

    fun onSuccess(result: T)

    fun onError(e:Throwable)
}

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

Kotlin 复制代码
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的使用:

bash 复制代码
buildFeatures {
    dataBinding = true
    viewBinding = true
}

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

XML 复制代码
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/txt_test"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />

</RelativeLayout>

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

Kotlin 复制代码
class TestActivity :AppCompatActivity() {

    var binding :ActivityTestBinding? =null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityTestBinding.inflate(layoutInflater)
        val view = binding?.root
        setContentView(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中添加依赖:

bash 复制代码
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1"

创建TestViewModel:

Kotlin 复制代码
class TestViewModel: ViewModel() {

    val count:MutableLiveData<String> by lazy { getTest() }

    fun getTest():MutableLiveData<String> {
      return MutableLiveData<String>()
    }

}

在TestActivity中,使用viewModel:

Kotlin 复制代码
class TestActivity :AppCompatActivity() {

    var binding :ActivityTestBinding? =null

    protected lateinit var viewModel:TestViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityTestBinding.inflate(layoutInflater)
        val view = binding?.root
        setContentView(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。

Kotlin 复制代码
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:

Kotlin 复制代码
abstract class BaseActivity<K :ViewModel,W :ViewBinding> :AppCompatActivity() {

    private var factory: ViewModelProvider? = null
    protected lateinit var viewModel: K
    protected lateinit var binding: W
    protected  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.actualTypeArguments
            if (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.actualTypeArguments
            try {
                //取第二个,也就是我们的viewbinding
                val aClass: Class<*> = arguments[1] as Class<*>
                val method = aClass.getDeclaredMethod("inflate", LayoutInflater::class.java)
                binding = method.invoke(aClass, layoutInflater) as W
                setContentView(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() {}

    //初始化view
    abstract fun initView()
}

这样MainActivity就可以简化为:

Kotlin 复制代码
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类:

Kotlin 复制代码
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中使用:

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

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

Kotlin 复制代码
@Keep
data class BaseResponseString(val status :Int ,val desc:String ,val data: String)

然后创建一个Request接口:

Kotlin 复制代码
interface Request {

    @GET
    fun get(@Url url:String): Call<BaseResponseBody>

    @GET
    fun getString(@Url url:String): Call<BaseResponseString>

    @GET
    fun get(@Url url:String, @QueryMap map:Map<String, Object> ):Call<BaseResponseBody>

    @POST
    fun post(@Url url:String):Call<BaseResponseBody>

    @POST
    fun post(@Url url:String, @Body map: Map<String, Object>):Call<BaseResponseBody>

    @POST
    fun post(@Url url:String, @Body body:List<Object>):Call<BaseResponseBody>
}

还有之前创建的ICallBack:

Kotlin 复制代码
interface ICallBack<T> {

    fun onSuccess(result: T)

    fun onError(e:Throwable)
}

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

Kotlin 复制代码
class RetrofitManager private constructor() {

    companion object {
        private const val BASE_URL = "http://192.168.31.87/"

        @Volatile private var instance: RetrofitManager? = null

        fun 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中使用:

Kotlin 复制代码
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:

Kotlin 复制代码
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:

Kotlin 复制代码
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 复制代码
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/txt_second"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="30sp"
        android:text="第二页"
        android:textColor="#00ff22"/>

</RelativeLayout>

运行效果如下:

相关推荐
2501_916007472 小时前
Java界面开发工具有哪些?常用Java GUI开发工具推荐、实战经验与对比分享
android·java·开发语言·ios·小程序·uni-app·iphone
铭哥的编程日记3 小时前
《Linux 基础 IO 完全指南:从文件描述符到缓冲区》
android·linux·运维
come112344 小时前
Go 语言中的结构体
android·开发语言·golang
武陵悭臾5 小时前
安卓应用开发学习:应用ViewPager2翻页视图实现页面水平切换
android·学习·viewpager2·deepseek·翻页视图
tingting01195 小时前
mysql 8.4.2 备份脚本
android·数据库·mysql
William_cl6 小时前
【连载1】《假装自己是个小白 —— 重新认识 MySQL》实践指南
android·mysql·oracle
脚踏实地,坚持不懈!6 小时前
Android,jetpack compose实现俄罗斯方块,简单案例♦️
android
一直向钱7 小时前
android 字符串工具类(兼容 Android 16+ / API 16,无报错版)
android