MVVM优势
主要解决的问题:
- 高度解耦
- 解决了生命周期问题导致的内存泄漏
- 解决了mvp中的大量接口
MVVM的分层
- View层就是展示数据的,以及接收到用户的操作传递给viewModel层,通过dataBinding实现数据与view的单向绑定或双向绑定
- Model层最重要的作用就是获取数据。(由于使用了协程所以不需要通过接口回调数据)
- ViewModel 层通过调用model层获取数据,以及业务逻辑的处理。
- MVVM中 viewModel 和MVP中的presenter 的作用类似 ,只不过是通过 databinding 将数据与ui进行了绑定。livedata用来通知数据的更新。
V层(activity+xml布局,主要负责UI的显示和交互)
package com.zhangyu.myjetpack
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.zhangyu.myjetpack.databinding.ActivityMain3Binding
import com.zhangyu.myjetpack.vm.Main3ViewModel
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
private const val TAG = "MainActivity3"
class Main3Activity : AppCompatActivity() {
companion object {
@JvmStatic
fun start(context: Context) {
val starter = Intent(context, Main3Activity::class.java)
context.startActivity(starter)
}
}
private val viewModel by viewModels<Main3ViewModel> { Main3ViewModelFactory() }
private lateinit var binding: ActivityMain3Binding
private var searchJob: Job? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//viewModel的直接创建方式(不推荐)
//viewModel = ViewModelProvider(this,ViewModelProvider.AndroidViewModelFactory(application)).get(Main3ViewModel::class.java)
//viewModel的另一种创建方式(Android Studio Demo中的方式)
//viewModel = ViewModelProvider(this).get(BlankViewModel::class.java)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main3)
binding.lifecycleOwner = this
binding.viewModel = viewModel
searchArticle()
}
fun onClick(view: View) {
when (view) {
binding.ivImg -> {
searchArticle()
}
}
}
private fun searchArticle() {
searchJob?.cancel()
searchJob = lifecycleScope.launch {
viewModel.searchArticle2()
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="viewModel"
type="com.zhangyu.myjetpack.vm.Main3ViewModel" />
</data>
<TextView
android:id="@+id/tv_test"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:onClick="onClick"
android:text="@{viewModel.liveData.results.get(0).url}" />
</layout>
VM层(从M层获取数据,处理业务逻辑)
package com.zhangyu.myjetpack.vm
import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.zhangyu.myjetpack.bean.Article
import com.zhangyu.myjetpack.data.ArticleRepository
import com.zhangyu.myjetpack.data.ArticleRepositoryProvider
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
private const val TAG = "Main3ViewModel"
class Main3ViewModel(private val articleRepository: ArticleRepository) : ViewModel() {
var liveData: MutableLiveData<Article> = MutableLiveData()
//协程,同步方式
suspend fun searchArticle2() {
liveData.value = articleRepository.searchRandomData2()
Log.d(TAG, "searchArticle2: " + liveData.value?.results)
}
//线程,异步方式,通过接口回调数据
fun searchArticle3() {
articleRepository.searchRandomData3().enqueue(object : Callback<Article> {
override fun onFailure(call: Call<Article>, t: Throwable) {
Log.e(TAG, "onFailure: " + t.message)
}
override fun onResponse(call: Call<Article>, response: Response<Article>) {
liveData.value = response.body()
}
})
}
}
class Main3ViewModelFactory : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return Main3ViewModel(ArticleRepositoryProvider.getInstance()) as T
}
}
M层(获取数据)
package com.zhangyu.myjetpack.data
import android.util.Log
import com.zhangyu.myjetpack.api.ArticleService
import com.zhangyu.myjetpack.bean.Article
import com.zhangyu.myjetpack.utils.RetrofitUtil
import retrofit2.Call
private const val TAG = "ArticleRepository"
interface ArticleRepository {
suspend fun searchRandomData2(): Article
fun searchRandomData3(): Call<Article>
}
private class ArticleRepositoryImpl(private val articleService: ArticleService) : ArticleRepository {
//协程+Retrofit,同步方式
override suspend fun searchRandomData2(): Article {
return try {
articleService.getRandomArticle2("福利", "10")
} catch (e: Exception) {
Log.e(TAG, "searchRandomData2: " + e.message)
Article(true, null)
}
}
//线程+Retrofit,异步
override fun searchRandomData3(): Call<Article> {
return articleService.getRandomArticle3("福利", "10")
}
}
object ArticleRepositoryProvider {
fun getInstance(): ArticleRepository {
return ArticleRepositoryImpl(RetrofitUtil.provide(ArticleService::class.java))
}
}
interface ArticleService {
/**
* 数据类型:福利 | Android | iOS | 休息视频 | 拓展资源 | 前端
* 个数: 数字,大于0
*/
//协程+Retrofit,同步方式
@GET("api/random/data/{type}/{size}")
suspend fun getRandomArticle2(@Path("type") type: String?, @Path("size") size: String?): Article
//线程+Retrofit,异步
@GET("api/random/data/{type}/{size}")
fun getRandomArticle3(@Path("type") type: String?, @Path("size") size: String?): Call<Article>
}
package com.zhangyu.myjetpack.bean
data class Article(
val error: Boolean,
val results: List<Result>?
)
data class Result(
val _id: String,
val createdAt: String,
val desc: String,
val publishedAt: String,
val source: String,
val type: String,
val url: String,
val used: Boolean,
val who: String
)
网络库的封装
import android.util.ArrayMap
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
object RetrofitUtil {
const val BASE_URL = "https://gank.io/"
private fun getRetrofit(): Retrofit {
val logger = HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BASIC }
val client = OkHttpClient.Builder()
.addInterceptor(logger)
.build()
return Retrofit.Builder()
.client(client)
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
private val mApis = ArrayMap<String, Any>()
@Suppress("UNCHECKED_CAST")
fun <T> provide(apiInterfaceClass: Class<T>): T {
val api = mApis[apiInterfaceClass.name] as T ?: getRetrofit().create(apiInterfaceClass)
mApis[apiInterfaceClass.name] = api
return api
}
}
Gradle
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 30
buildToolsVersion "30.0.2"
buildFeatures {
dataBinding = true
// for view binding :
viewBinding = true
}
...
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
// work-runtime-ktx 2.1.0 and above now requires Java 8
jvmTarget = "1.8"
// Enable Coroutines and Flow APIs
freeCompilerArgs += "-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi"
freeCompilerArgs += "-Xopt-in=kotlinx.coroutines.FlowPreview"
}
}
dependencies {
...
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.7.2'
//
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0'//lifecycleScope
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'//viewModelScope
implementation 'androidx.activity:activity-ktx:1.1.0'//by viewModels
implementation 'androidx.fragment:fragment-ktx:1.2.5'//by viewModels
}