Mavericks 的源码解析

57 阅读2分钟

Mavericks 简介

MavericksAirbnb 开源的 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 的变化来实现界面的更新。界面更新后,用户可以继续执行新的操作。

image.png 在 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}" 这段代码。