Mavericks 简介
Mavericks 是 Airbnb 开源的 MVI 架构的插件,它学起来很容易并且功能强大,Github 地址:github.com/airbnb/mave…,Mavericks 建立在 Android Jetpack 和 Kotlin 协程的基础之上,可以将其视为对谷歌标准库的补充。
MVVM 架构的不足
Android 应用架构从 MVC 到 MVP,再发展到 MVVM,MVVM 作为谷歌官方推荐的应用架构,其功能已经足够强大,那为什么还要弄出来一个 MVI 架构呢?
MVVM 架构在 Jetpack MVVM 中已经介绍过了,在使用 Jetpack MVVM 时,为了避免数据在外部被更改,MutableLiveData 持有的数据向外部暴露时需要转换成 immutable 类型的 LiveData,代码如下:
public class WeatherInfoViewModel extends ViewModel {
// 天气信息
private MutableLiveData<WeatherInfo> weatherInfoLiveData;
// 进条度的显示
private MutableLiveData<Boolean> loadingLiveData;
// 转成 LiveData
public LiveData<WeatherInfo> getWeatherInfoLiveData() {
return weatherInfoLiveData;
}
// 转成 LiveData
public LiveData<Boolean> getLoadingLiveData() {
return loadingLiveData;
}
}
但是如果页面逻辑比较复杂,ViewModel 中需要向外暴露的 MutableLiveData 比较多,这些 MutableLiveData 都需要转换成 LiveData 就有点难受了。
MVI 架构简介
MVI(Model-View-Intent)架构是一种单向的数据流架构,MVI 中的 Intent 不是 Activity 中的 Intent,用户的任何操作(Action)都被包装成 Intent,Intent 会触发 State 的改变。这里的 State 代表的是 UI 的状态。State 存储在 Model 中,View 通过订阅 Model 中 State 的变化来实现界面的更新。界面更新后,用户可以继续执行新的操作。
在 MVI 架构中,数据的流向总是从 Intent -> Model -> View。
Mavericks 的使用
1.引入插件。
implementation 'com.airbnb.android:mavericks:2.7.0'
2.在 Application 中初始化该插件。
Mavericks.initialize(this)
3.创建一个数据类,称为 State Class,包含所有你需要更新 UI 的数据,该数据类需要实现 MavericksState 接口。
data class CounterState(val count: Int = 0) : MavericksState
4.创建一个 CounterViewModel,这里是写业务逻辑的地方,一般会在这里更新 State,该类需要继承 MavericksViewModel。这里需要通过 copy() 方法创建一个新的 State Class 实例,然后调用 setState() 方法更新 State,这样就会触发 UI 更新。
class CounterViewModel(initialState: CounterState) : MavericksViewModel<CounterState>(initialState) {
// 更新 State
fun increment() = setState { copy(count = count + 1) }
fun decrement() = setState { copy(count = count - 1) }
}
5.在 Fragment 中绑定 ViewModel 和更新 UI。
class CounterFragment : Fragment(R.layout.counter_fragment), MavericksView {
private val viewModel: CounterViewModel by fragmentViewModel()
private lateinit var binding: CounterFragmentBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = CounterFragmentBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.btnInc.setOnClickListener {
viewModel.increment()
}
binding.btnDec.setOnClickListener {
viewModel.decrement()
}
}
// 当 State 发生改变时该回调会触发。
override fun invalidate() {
withState(viewModel) { state ->
binding.tvCount.text = "Count: ${state.count}"
}
}
}
源码解析
数据类实现了 MavericksState 接口,点进 MavericksState 接口发现是一个空的接口:
interface MavericksState
CounterViewModel 继承自 MavericksViewModel,MavericksViewModel 是一个抽象类,increment() 方法会调用 MavericksViewModel 中的 setState() 方法,该方法代码如下:
abstract class MavericksViewModel<S : MavericksState>(
initialState: S
) {
protected fun setState(reducer: S.() -> S) {
if (config.debugMode) {
...
} else {
stateStore.set(reducer)
}
}
}
可以看到 setState() 是一个高阶函数,函数类型的参数为:S.() -> S。
前面看到 copy(count = count + 1) 这样的代码,就是调用数据类的 copy() 函数创建该数据类的副本并修改其中的 count 参数。
默认 config.debugMode 为 false,所以会执行 stateStore.set(reducer)。
private val stateStore = config.stateStore
val config: MavericksViewModelConfig<S> = configFactory.provideConfig(
this,
initialState
)
stateStore 即 config.stateStore,config 是通过 configFactory.provideConfig() 创建的:
open class MavericksViewModelConfigFactory(...){
internal fun <S : MavericksState> provideConfig(
viewModel: MavericksViewModel<S>,
initialState: S
): MavericksViewModelConfig<S> {
return buildConfig(viewModel, initialState).also { config ->
onConfigProvidedListener.forEach { callback -> callback(viewModel, config) }
}
}
open fun <S : MavericksState> buildConfig(
viewModel: MavericksViewModel<S>,
initialState: S
): MavericksViewModelConfig<S> {
val scope = coroutineScope()
return object : MavericksViewModelConfig<S>(debugMode, CoroutinesStateStore(initialState, scope, storeContextOverride), scope) {
override fun <S : MavericksState> onExecute(viewModel: MavericksViewModel<S>): BlockExecutions {
return BlockExecutions.No
}
}
}
}
provideConfig() 方法又调用了 buildConfig() 方法,返回的是 MavericksViewModelConfig<S> 的实例,MavericksViewModelConfig 代码如下:
abstract class MavericksViewModelConfig<S : Any>(
val debugMode: Boolean,
val stateStore: MavericksStateStore<S>,
val coroutineScope: CoroutineScope
) {
abstract fun <S : MavericksState> onExecute(
viewModel: MavericksViewModel<S>
): BlockExecutions
enum class BlockExecutions {
No,
Completely,
WithLoading
}
}
MavericksViewModelConfig 是一个抽象类,在这里我们看到了 stateStore,它是 MavericksStateStore<S> 类型的:
interface MavericksStateStore<S : Any> {
val state: S
val flow: Flow<S>
fun get(block: (S) -> Unit)
fun set(stateReducer: S.() -> S)
}
MavericksStateStore 是一个接口,从前面构造 MavericksViewModelConfig<S> 的实例的代码可以看出来,实现类为 CoroutinesStateStore
class CoroutinesStateStore<S : MavericksState>(
initialState: S,
private val scope: CoroutineScope,
private val contextOverride: CoroutineContext = EmptyCoroutineContext
) : MavericksStateStore<S> {
private val setStateChannel = Channel<S.() -> S>(capacity = Channel.UNLIMITED)
init {
setupTriggerFlushQueues(scope)
}
private fun setupTriggerFlushQueues(scope: CoroutineScope) {
if (MavericksTestOverrides.FORCE_SYNCHRONOUS_STATE_STORES) return
scope.launch(flushDispatcher + contextOverride) {
while (isActive) {
// 在这里调用了 flushQueuesOnce()
flushQueuesOnce()
}
}
}
override fun set(stateReducer: S.() -> S) {
setStateChannel.trySend(stateReducer)
// MavericksTestOverrides.FORCE_SYNCHRONOUS_STATE_STORES 为 false
if (MavericksTestOverrides.FORCE_SYNCHRONOUS_STATE_STORES) {
flushQueuesOnceBlocking()
}
}
}
这里调用了 setStateChannel 的 trySend() 方法把更新后的 State 发送给消费者。setStateChannel 是通过 Channel<S.() -> S>(capacity = Channel.UNLIMITED) 创建的,Channel 的作用是把数据从一个协程传递给另一个协程,不了解 Channel 的看这里:Kotlin 中的 Channel 。
在 CoroutinesStateStore 的 init{} 代码块中调用了 flushQueuesOnce() 函数:
private val stateSharedFlow = MutableSharedFlow<S>(
replay = 1,
extraBufferCapacity = SubscriberBufferSize,
onBufferOverflow = BufferOverflow.SUSPEND,
).apply { tryEmit(initialState) }
private suspend fun flushQueuesOnce() {
select<Unit> {
setStateChannel.onReceive { reducer ->
val newState = state.reducer()
if (newState != state) {
state = newState
stateSharedFlow.emit(newState)
}
}
withStateChannel.onReceive { block ->
block(state)
}
}
}
这样 flushQueuesOnce() 中 setStateChannel 的 onReceive() 方法就会触发,这里调用了 state.reducer(),拿前面的示例举例,就是调用了 state.copy(count = count + 1), 就是复制了一个 StateClass 的实例并修改了其中的 count 参数。最后调用 stateSharedFlow.emit(newState),stateSharedFlow 是 MutableSharedFlow 的实例。
stateSharedFlow 的收集函数在哪里调用的呢?
我们在 Fragment 中创建 CounterViewModel 的实例的时候会调用 fragmentViewModel(),其代码如下:
inline fun <T, reified VM : MavericksViewModel<S>, reified S : MavericksState> T.fragmentViewModel(
viewModelClass: KClass<VM> = VM::class,
crossinline keyFactory: () -> String = { viewModelClass.java.name }
): MavericksDelegateProvider<T, VM> where T : Fragment, T : MavericksView =
viewModelDelegateProvider(
viewModelClass,
keyFactory,
existingViewModel = false
) { stateFactory ->
MavericksViewModelProvider.get(
viewModelClass = viewModelClass.java,
stateClass = S::class.java,
viewModelContext = FragmentViewModelContext(
activity = requireActivity(),
args = _fragmentArgsProvider(),
fragment = this
),
key = keyFactory(),
initialStateFactory = stateFactory
)
}
这里会调用 viewModelDelegateProvider() 函数,代码如下:
internal inline fun <T, reified VM : MavericksViewModel<S>, reified S : MavericksState> viewModelDelegateProvider(
viewModelClass: KClass<VM>,
crossinline keyFactory: () -> String,
existingViewModel: Boolean,
noinline viewModelProvider: (stateFactory: MavericksStateFactory<VM, S>) -> VM
): MavericksDelegateProvider<T, VM> where T : Fragment, T : MavericksView {
return object : MavericksDelegateProvider<T, VM>() {
override operator fun provideDelegate(
thisRef: T,
property: KProperty<*>
): Lazy<VM> {
return Mavericks.viewModelDelegateFactory.createLazyViewModel(
stateClass = S::class,
fragment = thisRef,
viewModelProperty = property,
viewModelClass = viewModelClass,
keyFactory = { keyFactory() },
existingViewModel = existingViewModel,
viewModelProvider = viewModelProvider
)
}
}
}
这里会调用 DefaultViewModelDelegateFactory 的 createLazyViewModel() 函数:
class DefaultViewModelDelegateFactory : ViewModelDelegateFactory {
override fun <S : MavericksState, T, VM : MavericksViewModel<S>> createLazyViewModel(
fragment: T,
viewModelProperty: KProperty<*>,
viewModelClass: KClass<VM>,
keyFactory: () -> String,
stateClass: KClass<S>,
existingViewModel: Boolean,
viewModelProvider: (stateFactory: MavericksStateFactory<VM, S>) -> VM
): Lazy<VM> where T : Fragment, T : MavericksView {
return lifecycleAwareLazy(fragment) {
viewModelProvider(RealMavericksStateFactory())
.apply { _internal(fragment, action = { fragment.postInvalidate() }) }
}
}
}
这里有个 _internal() 函数:
fun <VM : MavericksViewModel<S>, S : MavericksState> VM._internal(
owner: LifecycleOwner?,
deliveryMode: DeliveryMode = RedeliverOnStart,
action: suspend (S) -> Unit
) = stateFlow.resolveSubscription(owner, deliveryMode, action)
这里调用了 stateFlow 的 resolveSubscription():
internal fun <T : Any> Flow<T>.resolveSubscription(
lifecycleOwner: LifecycleOwner? = null,
deliveryMode: DeliveryMode,
action: suspend (T) -> Unit
): Job {
return if (lifecycleOwner != null) {
collectLatest(lifecycleOwner, lastDeliveredStates, activeSubscriptions, deliveryMode, action)
} else {
(viewModelScope + configFactory.subscriptionCoroutineContextOverride).launch(start = CoroutineStart.UNDISPATCHED) {
// Use yield to ensure flow collect coroutine is dispatched rather than invoked immediately.
// This is necessary when Dispatchers.Main.immediate is used in scope.
// Coroutine is launched with start = CoroutineStart.UNDISPATCHED to perform dispatch only once.
yield()
collectLatest(action)
}
}
}
在这里调用了流的收集函数,然后会触发对应的 MavericksView 的 postInvalidate() 方法:
fun postInvalidate() {
if (pendingInvalidates.add(System.identityHashCode(this@MavericksView))) {
handler.sendMessage(Message.obtain(handler, System.identityHashCode(this@MavericksView), this@MavericksView))
}
}
private val handler = Handler(Looper.getMainLooper()) { message ->
val view = message.obj as MavericksView
pendingInvalidates.remove(System.identityHashCode(view))
if (view.lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) view.invalidate()
true
}
这里通过 handler 发消息最终执行 MavericksView 中的 invalidate() 方法。
withState() 函数代码如下:
fun <A : MavericksViewModel<B>, B : MavericksState, C> withState(viewModel1: A, block: (B) -> C) = block(viewModel1.state)
它也是一个高阶函数,它可以拿到 viewModel1 的状态(viewModel1.state),并执行相应的代码。在前面的示例中就是拿到 CounterState 并执行 binding.tvCount.text = "Count: ${state.count}" 这段代码。