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>

运行效果如下:

相关推荐
阿巴斯甜2 天前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker2 天前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq95272 天前
Andorid Google 登录接入文档
android
黄林晴2 天前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab2 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿2 天前
Android MediaPlayer 笔记
android
Jony_2 天前
Android 启动优化方案
android
阿巴斯甜2 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇2 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_3 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android