Android 基于Jetpack的MVVM架构入门指南
Posted xiaoqiang_0719
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 基于Jetpack的MVVM架构入门指南相关的知识,希望对你有一定的参考价值。
android MVVM架构入门指南
目录
架构组件简介
Android 架构组件是一系列的组件库,可以帮助我们设计稳健、可测试且易维护的应用,主要和常用的组件包括:View Binding 、Data Binding、Lifecycle、LiveData、ViewModel
如上Android架构图,有四部分主要组件,每个组件都有其责任
-
Activity 和Fragment代表的View层,不处理业务逻辑只显示视图、处理用户交互、观察数据并展示
-
ViewModel+LiveData层,ViewModel向数据仓库Repository中请求数据,然后通过LiveData发送到View层
-
Repository层,它不是一个Android组件,而是一个普通的类,它负责从所有可用的源来获取数据
-
数据层,提供数据源的一层,主要是网络请求框架、Room数据库等
View Binding
简介
主要作用是为了替代 findViewById 在启动View Binding后,系统会为该模块下的每个xml生成一个绑定类,这个类的类名是以xml布局文件名去掉下划线后,单词首字母大写加上Binding命名的。
例如:activity_main.xml ---> ActivityMainBinding
View Binding 的优点
-
空安全:View Binding会创建对视图的直接引用,因此不存在因视图 ID 无效而引发空指针异常的风险。(我们手动findeViewById()时容易传入无效的id)
-
类型安全:每个绑定类中的字段均具有与它们在 XML 文件中引用的视图相匹配的类型。这意味着不存在发生类转换异常的风险。 val button:Button=findViewById<RecyclerView>(R.id.recyclerview)
详细介绍请参见Google官方文档: https://developer.android.google.cn/topic/libraries/view-binding
用法
View Binding 按模块启用,在app模块下的build.gradle添加如下配置
android
...
viewBinding
enabled = true
或者使用buildFeatures方式但需要满足如下条件:
1、Android Studio 版本 : 最低 4.1
2、Gradle 版本 : 最低版本 6.6.1
3、Gradle 插件版本配置 : 最低版本 4.1.0
android
...
buildFeatures
viewBinding true
如果想忽略某个布局文件,可将 tools:viewBindingIgnore="true" 属性添加到相应布局文件的根视图中
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:viewBindingIgnore="true"
tools:context=".MainActivity">
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
...
</androidx.constraintlayout.widget.ConstraintLayout>
1. Activity中使用View Binding
-
调用生成的绑定类中包含的静态 inflate() 方法。此操作会创建该绑定类的实例以供 Activity 使用
-
通过调用 getRoot() 方法获取对根视图的引用
-
调用 setContentView()方法将根视图传入
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle)
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
binding.name.text ="Wilfried"
binding.name.setOnClickListener
2. Fragment中使用View Binding
Fragment中使用View Binding的步骤根Activity类似,主要区分在于是在哪个方法中进行绑定操作onCreateView、onViewCreated
onCreateView
lateinit var binding: FragmentMainBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View?
binding = FragmentMainBinding.inflate(inflater, container, false)
return binding.root
onViewCreated
lateinit var binding: FragmentMainBinding
override fun onViewCreated(view: View, savedInstanceState: Bundle?)
super.onViewCreated(view, savedInstanceState)
binding = FragmentMainBinding.bind(view)
3. ListAdapter中使用View Binding
class MyListAdapter : ListAdapter<Word, MyListAdapter.WordViewHolder>(WORDS_COMPARATOR)
class WordViewHolder(private val viewBinding: RecyclerviewItemBinding) :
RecyclerView.ViewHolder(viewBinding.root)
fun bind(text: Word?)
viewBinding.textView.text = text?.word
companion object
fun create(parent: ViewGroup): WordViewHolder
val bind = RecyclerviewItemBinding
.bind(LayoutInflater.from(parent.context)
.inflate(R.layout.recyclerview_item, parent, false)
)
return WordViewHolder(bind)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WordViewHolder
return WordViewHolder.create(parent)
override fun onBindViewHolder(holder: WordViewHolder, position: Int)
holder.bind(getItem(position))
companion object
private val WORDS_COMPARATOR = object : DiffUtil.ItemCallback<Word>()
override fun areItemsTheSame(oldItem: Word, newItem: Word): Boolean
return oldItem === newItem
override fun areContentsTheSame(oldItem: Word, newItem: Word): Boolean
return oldItem.word == newItem.word
ViewModel
简介
ViewModel 主要是用来存储和管理与UI相关的数据的,将一个Activity或Fragment组件相关的数据逻辑提取出来,并能适配UI组件的生命周期,如当屏幕旋转Activity重建后,ViewModel中的数据依然有效 ViewModel一般都是会配合LiveData使用
ViewModel 的生命周期
ViewModel 对象存在的时间范围是获取 ViewModel 时传递给 ViewModelProvider 的 Lifecycle(Activity、Fragment、所有实现LifecycleOwner的生命周期对象)
我们在首次调用 Activity 对象的 onCreate() 方法时请求 ViewModel。系统有可能会在 Activity 的整个生命周期内多次调用 onCreate(),但是ViewModel只要一个,比如在旋转设备屏幕时重走生命周期,此时依然可以拿到ViewModel中最新的数据。
由上图可以看出,ViewModel 存在的时间范围是从首次请求 ViewModel 直到 Activity 完成并销毁。
详细介绍请参见Google官方文档: https://developer.android.google.cn/topic/libraries/architecture/viewmodel
用法
定义ViewModel
class MyViewModel : ViewModel()
private val users: MutableLiveData<List<User>> by lazy
MutableLiveData<List<User>>().also
loadUsers()
fun getUsers(): LiveData<List<User>>
return users
private fun loadUsers()
// Do an asynchronous operation to fetch users.
...
users.value = usersInfoList
Activity 中使用ViewModel
1. 默认实现方式
class MyActivity : AppCompatActivity()
override fun onCreate(savedInstanceState: Bundle?)
val viewModel = ViewModelProviders
.of(this).get(MainViewModel::class.java)
model.getUsers().observe(this, Observer<List<User>> users ->
// update UI
)
2. 通过activity-ktx扩展库
class MyActivity : AppCompatActivity()
override fun onCreate(savedInstanceState: Bundle?)
// Use the 'by viewModels()' Kotlin property delegate
// from the activity-ktx artifact
val model: MyViewModel by viewModels()
model.getUsers().observe(this, Observer<List<User>> users ->
// update UI
)
3. 过activity-ktx扩展库,自定义 ViewModelProvider.Factory(用于ViewModel需要传参数的情况)
-
MainActivity
class MyActivity : AppCompatActivity()
override fun onCreate(savedInstanceState: Bundle?)
private val wordViewModel: WordViewModel by viewModels
WordViewModelFactory((application as WordsApplication).repository)
model.getUsers().observe(this, Observer<List<User>> users ->
// update UI
)
-
WordViewModel
class WordViewModel(private val repository: WordRepository) : ViewModel()
val allWords: LiveData<List<Word>> = repository.allWords.asLiveData()
fun insert(word: Word) = viewModelScope.launch
repository.insert(word)
class WordViewModelFactory(private val repository: WordRepository) : ViewModelProvider.Factory
override fun <T : ViewModel> create(modelClass: Class<T>): T
if (modelClass.isAssignableFrom(WordViewModel::class.java))
@Suppress("UNCHECKED_CAST")
return WordViewModel(repository) as T
throw IllegalArgumentException("Unknown ViewModel class")
LiveData
简介
LiveData 是一种可观察的数据存储器类。与常规的可观察类不同,LiveData 具有生命周期感知能力,意指它遵循其他应用组件(如 Activity、Fragment 或 Service)的生命周期。这种感知能力可确保 LiveData 仅更新处于活跃生命周期状态的应用组件观察者。
LiveData 的优势
-
观察者会绑定到 Lifecycle 对象(所有实现了所有实现LifecycleOwner的生命周期对象),并在其关联的生命周期遭到销毁后进行自我清理。
-
如果生命周期变为非活跃状态,它会在再次变为活跃状态时接收最新的数据。例如,曾经在后台的 Activity 会在返回前台后立即接收最新的数据。
-
如果由于配置更改(如设备旋转)而重新创建了 Activity 或 Fragment,它会立即接收最新的可用数据。
LiveData的子类
MutableLiveData
实现接口LiveData,并提供两个方法 setValue(T) 和 postValue(T) 用于将数据分发出去
setValue(T) 只能在主线程中调用,postValue(T) 可以在任何线程中调用。
MediatorLiveData
继承自MutableLiveData,它可以作为中间人的角色监听其他LiveData,通过addSource进行注册LiveData,当数据更新时通过MediatorLiveData倒一手再进行处理。提供如下两个方法,
addSource(LiveData< S> source, Observer<? super S> onChanged)方法
removeSource(LiveData< S> toRemote)方法
案例:如果我们只需要liveData1发出的10个值,并将其合并到liveDataMerger中。收到10个值之后,我们就停止监听liveData1并将其删除。
liveDataMerger.addSource(liveData1, new Observer()
private int count = 1;
@Override public void onChanged(@Nullable Integer s)
count++;
liveDataMerger.setValue(s);
if (count > 10)
liveDataMerger.removeSource(liveData1);
);
Transformations
//被监听数据
private val count = MutableLiveData<Int>(1)
private val mapCount: LiveData<Int> = Transformations.map(count)
it + 1
//与map()的区别是必须返回一个 MutableLiveData 对象
private val switchMapCount: LiveData<Int> = Transformations.switchMap(count)
MutableLiveData<Int>(it!! + 1)
详细介绍请参见Google官方文档: https://developer.android.google.cn/topic/libraries/architecture/livedata
用法
-
创建 LiveData 的实例以存储某种类型的数据。这通常在 ViewModel 类中完成。
class MyViewModel : ViewModel()
// Create a LiveData with a String
val livedata: MutableLiveData<String> = MutableLiveData<String>()
-
在界面控制器(如 Activity 或 Fragment)中创建 Observer 对象。
-
使用 observe() 方法将 Observer 对象附加到 LiveData 对象。
myViewModel.livedata.observe(this, Observer
)
-
可以使用 observeForever(Observer) 方法在没有关联的 LifecycleOwner 对象的情况下注册一个观察者。在这种情况下,观察者会被视为始终处于活跃状态,可以通过调用
removeObserver(Observer)
方法来移除这些观察者。
myViewModel.livedata.observeForever
5.更新LiveData:LiveData 是一个抽象类,MutableLiveData 类将公开 setValue(T) 和 postValue(T) 方法,修改存储在 LiveData 对象中的值,则必须使用这些方法。
Data Binding
简介
Data Binding 简单的解释就是,之前我们需要通过id获取到控件,然后通过控件设置数据,现在有了Data Binding之后 我们可以直接在布局文件中直接绑定数据,它的侧重点是在绑定数据。
注意:在许多情况下, Data Binding可简化实现,提高性能,提供与数据绑定相同的好处。如果使用数据绑定的主要目的是取代 findViewById() 调用,请考虑改用 View Binding。
详细介绍请参见Google官方文档: https://developer.android.google.cn/topic/libraries/data-binding
用法
-
使用 Data Binding ,首先需要在 app moudle 下的 build.gradle 中添加:
android
...
dataBinding
enabled = true
...
-
创建一个实体类,例:Info 类
class Info(var name: String = "Wilfried",
var job: String = "Android Engineer",
var company: String = "noboAuto")
3.在这个Activity的xml(activity_my_info.xml)中的根布局下,通过Alt+Enter快捷键创建databinding的布局,同时导入 Info 类
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="myInfo"
type="com.noboauto.example.Info" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.slideshow.SlideshowFragment">
<TextView
android:id="@+id/tv_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@myInfo.name"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
-
此时编译器会自动根据这个布局生成相应的绑定类,这里会生成一个 ActivityMyInfoBinding 的类,然后在对应的Activity获取Data Binding并绑定数据
class MyInfoActivity : AppCompatActivity()
override fun onCreate(savedInstanceState: Bundle?)
super.onCreate(savedInstanceState)
val bindingBinding : ActivityMyInfoBinding = DataBindingUtil.setContentView(this, R.layout.activity_my_info)
bindingBinding.myInfo = Info()//已实现默认参数
双向绑定
单向绑定:字段变化-->View自动更新
双向绑定:字段变化-->view自动更新、用户操作view变化-->字段同步变化(EditText)
为什么要使用双向绑定?
因为我们使用DataBinding一般数据源是ViewModel, 我们可以根据ViewModel的LiveData来赋值给EditText,但是我们从界面操作了EditText无法更新对应的LiveData,也就是EditText的数据已经由用户操作更新了,可是此时ViewModel里的LiveData数据还是原先的数据,如果此时我们要获取数据,那到LiveDate的值就是旧值,所以我们需要双向绑定,当界面更新ViewModel的数据同步更新
如何使用双向绑定?
1、自定义view必须要定义了对关键值的监听,比如CheckBox的checked属性,Android有定义接口来回调这个值的状态,这个时候就可以将navigationViewModel中定义的实现了OnCheckedChangeListener的接口作为参数传入到onCheckedChanged,所以当View有状态的变化就会通过ViewModel注册的监听将状态同步到LiveData,完成View和LiveData状态同步了
android:onCheckedChanged="@navigationViewModel.rememberMeChanged"
2、当然因为是系统控件我们直接使用 android:checked="@=navigationViewModel.booleanLiveData"也就搞定了 双向绑定的形式也就是上面的“@= ”,他可以接收属性的数据更改并同时监听用户更新
3、也可以使用可观察的数据对象,将布局变量设置为 Observable(通常为 BaseObservable)的实现,并使用 @Bindable 注释
4、注册监听,当addTextChangedListener发生变化,会通知属性调用InverseBindingAdapter注解方法
<EditText
android:id="@+id/ed_info2"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_marginTop="20dp"
app:bindingName='@=bannerViewModel.userDefined'
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="@+id/ed_info"
tools:ignore="Autofill,LabelFor,TextFields" />
@BindingAdapter("bindingNameAttrChanged")
@JvmStatic
fun setBindingListener(edit: EditText, listener: InverseBindingListener?)
Log.i(TAG, "setBindingListener..")
var txt = ""
edit.addTextChangedListener(object : TextWatcher
override fun afterTextChanged(p0: Editable?)
if (txt != p0.toString())
// 会通知属性调用InverseBindingAdapter注解方法
listener?.onChange()
txt = p0.toString()
edit.setSelection(txt.length)
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int)
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int)
)
InverseBindingAdapter注解,提供view的最新值,赋值给数据层
@InverseBindingAdapter(attribute = "bindingName")
@JvmStatic
fun getBindingName(edt: EditText): String
Log.i(TAG, "getBindingName..$edt.text")
return edt.text.toString()
数据层发生变化,触发单向绑定,改变view的值
@BindingAdapter("bindingName")
@JvmStatic
fun setBindingName(edt: EditText, txt: String)
Log.i(TAG, "setBindingName..$txt")
edt.setText(txt)
注意:在afterTextChanged的时候判断了txt != p0.toString(),如果不判断会无限循环,所以只有值发生改变的时候才去触发
Lifecycles
简介
生命周期感知型组件可执行操作来响应另一个组件(如 Activity 和 Fragment)的生命周期状态的变化
在生命周期拥有者LifecycleOwner与 生命周期的观察者LifecycleObserver之间快速方便的建立一种联系。在生命周期拥有者的生命周期变化时,观察者会收到对应的通知。
简单的说就是用来监听Activity与Fragment的生命周期变化
主要类
lifecycleRegister
lifecycle的唯一子类,在生命周期拥有者的生命周期发生变化时触发自身状态和相关观察者的订阅回调逻辑。
lifecycleOwner
该接口的实现类可以提供lifecycleRegister的实例,主要实现类就是AppCompatActivity和Fragment。 (android.app.Activity.Activity默认不实现lifecycleOwner接口,所以推荐使用AppCompatActivity)
lifecycleObserver
该接口的实现类表示为关注生命周期事件的观察者。
详细介绍请参见Google官方文档: https://developer.android.google.cn/topic/libraries/architecture/lifecycle
用法
生命周期拥有者 LifecycleOwner
-
AppCompatActivity的实现原理是在的父类ComponentActivity中实现了LifecycleOwner,并创建和提供了LifecycleRegistry
private final LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
@Override
public Lifecycle getLifecycle()
return mLifecycleRegistry;
在ComponentActivity的子类ComponentActivity中实现了事件的分发
@Override
protected void onCreate(@Nullable Bundle savedInstanceState)
...
mFragmentLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
枚举中的方法跟Activity生命周期一致,注意ON_ANY只要生命周期变化就会调用此方法
public enum Event
/**
* Constant for onCreate event of the @link LifecycleOwner.
*/
ON_CREATE,
/**
* Constant for onStart event of the @link LifecycleOwner.
*/
ON_START,
/**
* Constant for onResume event of the @link LifecycleOwner.
*/
ON_RESUME,
/**
* Constant for onPause event of the @link LifecycleOwner.
*/
ON_PAUSE,
/**
* Constant for onStop event of the @link LifecycleOwner.
*/
ON_STOP,
/**
* Constant for onDestroy event of the @link LifecycleOwner.
*/
ON_DESTROY,
/**
* An @link Event Event constant that can be used to match all events.
*/
ON_ANY
-
自定义LifecycleOwner
实现LifecycleOwner接口,重写getLifecycle方法返回Lifecycle的唯一子类LifecycleRegistry
markState和handleLifecycleEvent都可以将生命周期状态分发出去,但是markState已经是过时的方法,建议使用handleLifecycleEvent
class MyActivity : Activity(), LifecycleOwner
private lateinit var lifecycleRegistry: LifecycleRegistry
override fun onCreate(savedInstanceState: Bundle?)
super.onCreate(savedInstanceState)
lifecycleRegistry = LifecycleRegistry(this)
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
lifecycle.addObserver(MyInfoObserver())
lifecycle.addObserver(MyInfoObserver2())
override fun onStart()
super.onStart()
lifecycleRegistry.markState(Lifecycle.State.STARTED)
override fun onResume()
super.onResume()
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
override fun getLifecycle(): Lifecycle
return lifecycleRegistry
生命周期的观察者 LifecycleObserver
两种方式可以实现生命周期的观察
-
实现LifecycleObserver接口,通过OnLifecycleEvent注解来回调各个生命周期状态
class MyInfoObserver : LifecycleObserver
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
fun onMyInfoCreate()
Log.i("wilfried", "onCreate")
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onMyInfoStart()
Log.i("wilfried", "onStart")
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun onMyInfoResume()
Log.i("wilfried", "onResume")
-
实现LifecycleEventObserver接口,通过回调方法onStateChanged来实现状态监听(本身也是实现了LifecycleObserver)
public interface LifecycleEventObserver extends LifecycleObserver
void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event);
class MyInfoObserver2 : LifecycleEventObserver
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event)
Log.i("wilfried", "LifecycleOwner==$source Event==$event.name ")
最终打印结果如下:
2021-05-27 20:37:48.820 28878-28878/com.example.android.roomwordssample I/wilfried: onCreate
2021-05-27 20:37:48.821 28878-28878/com.example.android.roomwordssample I/wilfried: LifecycleOwner==com.example.android.roomwordssample.MyActivity@c1f76a Event==ON_CREATE
2021-05-27 20:37:48.839 28878-28878/com.example.android.roomwordssample I/wilfried: onStart
2021-05-27 20:37:48.839 28878-28878/com.example.android.roomwordssample I/wilfried: LifecycleOwner==com.example.android.roomwordssample.MyActivity@c1f76a Event==ON_START
2021-05-27 20:37:48.841 28878-28878/com.example.android.roomwordssample I/wilfried: onResume
2021-05-27 20:37:48.841 28878-28878/com.example.android.roomwordssample I/wilfried: LifecycleOwner==com.example.android.roomwordssample.MyActivity@c1f76a Event==ON_RESUME
生命周期感知型组件的最佳做法
-
使界面控制器(Activity 和 Fragment)尽可能保持精简。它们不应试图获取自己的数据,而应使用
ViewModel
执行此操作,并观察LiveData
对象以将更改体现到视图中。 -
设法编写数据驱动型界面,对于此类界面,界面控制器的责任是随着数据更改而更新视图,或者将用户操作通知给
ViewModel
。 -
将数据逻辑放在
ViewModel
类中。ViewModel
应充当界面控制器与应用其余部分之间的连接器。不过要注意,ViewModel
不负责获取数据(例如,从网络获取)。但是,ViewModel
应调用相应的组件来获取数据,然后将结果提供给界面控制器。 -
使用数据绑定在视图与界面控制器之间维持干净的接口。这样一来,您可以使视图更具声明性,并尽量减少需要在 Activity 和 Fragment 中编写的更新代码。如果您更愿意使用 Java 编程语言执行此操作,请使用诸如 Butter Knife 之类的库,以避免样板代码并实现更好的抽象化。
-
如果界面很复杂,不妨考虑创建 presenter 类来处理界面的修改。这可能是一项艰巨的任务,但这样做可使界面组件更易于测试。
-
避免在
ViewModel
中引用View
或Activity
上下文。如果ViewModel
存在的时间比 Activity 更长(在配置更改的情况下),Activity 将泄漏并且不会获得垃圾回收器的妥善处置。 -
使用 Kotlin 协程管理长时间运行的任务和其他可以异步运行的操作。
实现了MVVM架构的Demo
以上是关于Android 基于Jetpack的MVVM架构入门指南的主要内容,如果未能解决你的问题,请参考以下文章
Android Jetpack架构组件——什么是Jetpack?