Android 架构 - MVI

手把手教你搭建 Android MVI架构: MVI + kotlin + Flow Android MVI是一种用于构建Android应用程序的架构模式,其核心思想在于实现单向数据流和唯一可信数据源。在MVI架构中,应用程序的状态管理得到简化,并且用户界面与业务逻辑之间的交互更加清晰和规范。UIState。 阅读详情
AI摘要

本文深入解析Android平台上的MVI(Model-View-Intent)架构模式,涵盖其四大核心原则:唯一可信数据源、状态不可变、数据单向流动及事件驱动。重点阐述UiState、UiAction、UiEvent的设计规范,ViewModel在Compose环境下的生命周期管理、Channel与SharedFlow在事件通信中的选型依据,并指出单Activity架构下ViewModel作用域与销毁时机的关键差异。

一、概念

概念基于单向数据流,数据永远在一个环形结构中单向流动,便于追踪测试。用户事件→业务逻辑→更新状态→界面重组。
通信

View→ViewModel:将用户操作以Intent形式通知给ViewModel,监听ViewModel中State的变化会自动更新到UI。

ViewModel→Model:对Intent类型分类判断做对应的逻辑处理,调用Model获取数据。

ViewModel→View:会获取的数据更新到State,State的变化会自动更新View。

  • 模型 Model:业务逻辑(处理数据的逻辑和存取)。
  • 视图 View:渲染UI(展示界面和数据的状态)。
  • 意图 Intent:承载操作(点击、输入、获取列表数据等)。
  • 状态 State:反应数据当前值。

1.1 唯一可信数据源

为了解决 MVVM 中 UI 订阅多个分散的状态(ViewModel中的LiveData/Flow)导致各种数据并行更新或数据相互依赖时,无法清晰掌握整个页面的状态。MVI使用 UiState 将所有状态聚合在一处(通过 data class 实现),UI刷新只依赖这一个数据源。

1.2 状态不可变

状态不应该被直接修改,而是重新赋值为一个包含新数据的副本。对象变更才能触发观察,属性变更才会引发重组。

1.3 数据单向流动

DataBinding  数据模型和视图一方发生变化就会同步到另一方,数据的流动是双向的,这样不便于追踪测试。MVI强调数据的源头只有一个,目的地也只有一个,永远在一个环形结构中单向流动,便于追踪测试。

1.4 事件驱动

MVVM 没有约束 UI 和 ViewModel 的交互方式可随意调用业务方法更新状态,MVI将用户操作统一封装到 Intent 实现了屏蔽(通过密封接口实现),View只能发送已被定义好的事件让 ViewModel 统一处理(事件隔离实现状态可控),通过对事件类型的识别做出相应的业务处理并更新状态,采用 Channel 保证并发安全。

二、单 Activity 架构的问题

Compose 通过 AndroidComposeView 来与 Activity 交互,使用单 Activity 页面跳转都能在 Compose 内部完成。Navigation不仅支持 View 的 单Activity+多Fragment 架构,也支持 Compose 的 单Activity+多Composable 架构。

2.1 ViewModel的销毁

       Compose 中的 viewModel() 函数可以从任何组合项中获取 ViewModel,考虑到函数的生命周期和作用域,应在屏幕级组合函数中获取 ViewModel 实例,也就是被 Activity、Navigation目的地调用的根级组合项。不要直接将 ViewModel 实例传递给子组合项用,而是传递子组合项所需要的数据或函数(即状态提升)。

  • 如果根组合项托管在 Activity 中,不同组合项中调用 viewModel() 获取相同类型的 ViewModel 将会是同一个实例。因为是 单Activity 架构,绑定的作用域是同一个ViewModelStoreOwner,也因此 ViewModel 的生命周期不会随组合项的销毁而回收。
  • 如果根组合项托管在 Navigstion 目的地中,不同组合项中调用 viewModel() 获取相同类型的 ViewModel 是不同的实例。因为作用域被限定在了目的地,ViewModel的生命周期会跟随目的地从返回站中弹出而清除。

2.2 处理生命周期

2.2.1 可组合项的生命周期处理

详见:附带效应(副作用)

对于组合函数的生命周期:onActive 首次挂载到组件树、onCommit 重组刷新、onDispose 从组件树上移除,可以通过附带效应来监听。

LaunchedEffect第一次调用Compose函数时执行(首次进入页面)。
DisposableEffect需要重写 onDispose() 函数当页面退出时调用(退出页面时释放资源)。
SideEffectCompose函数每次执行都会调用该方法(每次重组时)。

2.2.2 Activity 的生命周期获取

详见:使用Lifecycle

三、搭建项目

3.1 定义界面状态 UiState

  • 命名采用:页面名称 + UiState。
  • 一般同 ViewModel 写在同一个 .kt 文件中,也可以单独写在一个 .kt 文件中。
  • 采用 data class 因为自带 copy() 功能,非常方便更新部分属性。界面刷新用到的状态全都定义成属性,集中在一起实现唯一可信数据源。属性使用 val 确保不可变,提供默认值方便初始化和减少 null 判断。根据多个状态派生出来的状态,定义在 data class 的类体中。
  • UI 所需要的某些状态若是相互独立,不要定义在同一个数据类中,刷新频率高的那个会造成低的频繁更新。(属性内容无变化会跳过重组也还好,分开更方便阅读)
data class DemoUiState(
    val isLoading: Boolean = false,
    val success: List<DemoBean> = emptyList(),
    val isLogin: Boolean = false,    //是否登录
    val isPremium: Boolean= false    //是不是会员
) {
    val canDownload: Boolean = isLogin && isPremium    //派生状态
}

3.2 定义用户事件 UiAction

  • 命名采用:页面名称 + UiAction。
  • 一般同 ViewModel 写在同一个 .kt 文件中,也可以单独写在一个 .kt 文件中。
  • 采用 sealed interface 除了保证类型受控优化 when 判断,子类不带参数就定义成 data object 类型方便复用(用object是因为创建的实例无状态区别,data object 是它的优化版,不会打印必要信息)。将可能的用户行为全部定义成子类。
sealed interface DemoUiAction {
    //按钮点击
    data class OnClick(val url: String) : DemoUiAction
}

3.3 定义界面事件 UiEvent

如果只有 Toast 和 Dialog 这种简单的需求可省去,在 ViewModel 中直接用 ApplicationContext 弹 Toast,把 isShowDialog 整合进 UiState 中,ViewModel 直接改状态弹窗并通过 UiAction 响应 UI 中的操作。

  • 命名采用:页面名称 + UiEvent。
  • 一般同 ViewModel 写在同一个 .kt 文件中,也可以单独写在一个 .kt 文件中。
sealed interface DemoUiEvent {
    data class ShowDialog(val str: String) : DemoUiEvent
}

3.4 ViewModel(处理用户事件+更新状态+发送界面事件)

  • Compose 向外部(ViewModel)发送用户事件属于副作用,涉及并发安全问题(多核心优化重组,可能执行在非UI线程),因此考虑协程间通信使用 Channel 来实现。
    • 事件只能被消费一次,不能回放造成粘性事件。(排除 StateFlow)
    • 事件必须执行(消费代码若是执行在了生产后,也不能丢弃值)。(排除SharedFlow)
  • ViewModel 向 UI 发送界面事件,像弹 Toast/Dialog 没有订阅者就应该丢弃事件,因此使用事件流 SharedFlow 来实现。
  • 页面数据的初始化在 ViewModel 的 init{} 代码块中调用,因为在 Compose 中用 LaunchedEffects() 会受 Activity 重建影响而再次触发。(视情况而定,如果界面是 Pager 中的子页,Pager会缓存页面所以 LaunchedEffects() 不会跟随Activity重建而再次触发。这样反而不能通过判断 index 在子页显示时才初始化数据)
  • 状态可用 MutableState 或 StateFlow 实现。MutableState 在普通使用下更简洁但仅限Compose,StateFlow 在并发修改时 update() 方法原子更新更简洁。
class DemoViewModel(
    private val repository: DemoRepository
) : ViewModel() {
    //暴露给UI订阅的界面状态
    var uiState by mutableStateOf(DemoUiState())
        private set

    //暴露给UI订阅的界面事件
    private val _uiEvent = MutableSharedFlow<DemoUiEvent>()
    val uiEvent = _uiEvent.asSharedFlow()

    //定义用来通信的Channel
    private val uiAction = Channel<DemoUiAction>()

    //ViewModel初始化时:初始化数据,启动用户事件处理
    init {
        viewModelScope.launch {
            initData()
            handleAction()
        }
    }

    //处理用户事件
    private suspend fun handleAction() {
        uiAction.consumeAsFlow().collect { action ->
            when(action) {
                is DemoUiAction.OnClick -> _uiEvent.emit(DemoUiEvent.ShowToast("点击了按钮"))
            }
        }
    }

    //暴露给UI发送用户事件(比直接在UI中获取Channel发送方便,包装成普通函数更方便外部调用)
    fun dispatchEvent(action: DemoUiAction) {
        viewModelScope.launch {
            uiAction.send(action)
        }
    }

    //(在业务代码里)更新状态和发送界面事件
    private suspend fun initData() {
        //状态设为加载中
        uiState = uiState.copy(isLoading = true)
        runCatching {
            repository.getData()
        }.onSuccess { data ->
            //赋值成功结果,取消加载中
            uiState = uiState.copy(success = it, isLoading = false)
        }.onFailure {
            //取消加载中,并弹Dialog
            uiState = uiState.copy(isLoading = false)
            _uiEvent.emit(DemoUiEvent.ShowDialog(it.message.toString()))
        }
    }

    override fun onCleared() {
        super.onCleared()
        uiAction.close()  //释放资源
    }
}

3.5 UI(响应状态+处理界面事件+发送用户事件)

  • 在屏幕级组合项获取ViewModel。
  • 当 Activity 处于可交互期间(resume)才需要处理一次性事件。
@Composable
fun MainScreen(
    viewModel: DemoViewModel = viewModel()    //普通获取VM
//  viewModel: DemoViewModel = viewModel(factory = DemoViewModelFactory(DemoRepository(DemoDataSource())))    //带参获取VM
) {
    //只在Activity处于可交互时处理事件
    LifecycleResumeEffect(Unit) {
        lifecycleScope.launch {
            viewModel.uiEvent.collect { event ->
                when (event) {
                    is DemoUiEvent.ShowDialog -> {}
                }
            }
        }
        //必须调用,可清理资源
        onPauseOrDispose {}
    }
    //读取状态并处理
    ScreenContent(
        data = viewModel.uiState.success,
        onClick = { viewModel.dispatchAction(DemoAction.OnClick("url")) }
    )
}

@Composable
private fun ScreenContent(
    data: List<DemoBean>,
    onClick: (String) -> Unit
) {
    //将数据设置给子组件
}

四、封装 BaseViewModel

解决 ViewModel 中模板代码过多问题。

  • 不要定义抽象方法 initData() 让子类实现图省事,如果子类实现在其中调用了 Repository 的方法,由于会先调用父类中的 init{} 代码块,此时子类的 Repository 还未初始化完成,initData() 会空指针异常。
abstract class BaseVM<State: UiState, Action: UiAction, Event: UiEvent> : ViewModel() {

    var state by mutableStateOf(initUiState())
        protected set
    private val actionChannel = Channel<Action>()
    protected val _eventSharedFlow = MutableSharedFlow<Event>()    //protected暴露给子VM发射数据
    val eventSharedFlow = _eventSharedFlow.asSharedFlow()    //暴露给UI只能收集

    //属性和init代码块按顺序执行,这里放在状态之后
    init {
        handleAction()
    }

    private suspend fun handleAction() {
        viewModelScope.launch {
            actionChannel.consumeAsFlow().collect {
                onAction(it)
            }
        }
    }

    fun dispatchAction(action: Action) {
        viewModelScope.launch {
            actionChannel.send(action)
        }
    }

    protected abstract fun initUiState(): State
    protected abstract suspend fun onAction(action: Action)

    override fun onCleared() {
        super.onCleared()
        actionChannel.close()
    }

}

interface UiState
interface UiAction
interface UiEvent

子ViewModel中使用:

class DemoVM : BaseVM<DemoState, DemoAction, DemoEvent>() {

    override fun initUiState() = DemoState()

    override suspend fun onAction(action: DemoAction) = when (action) {
        DemoAction.InitData -> initData()
    }

    override suspend fun initData() {
        state = state.copy(name = "")
        _eventSharedFlow.emit(DemoEvent.ShowToast(""))
    }

}
data class DemoState(
    val name: String = ""
) : UiState

sealed interface DemoAction : UiAction {
    object InitData: DemoAction
}

sealed interface DemoEvent : UiEvent {
    data class ShowToast(val str: String) : DemoEvent
}

UI中使用:

@Composable
fun DemoScreen( vm: DemoVM = viewModel()) {
    LaunchedEffect(Unit) {
        vm.eventSharedFlow.collect { event ->
            when (event) {
                is DemoEvent.ShowToast -> TODO()
            }
        }
    }
}
浅谈Android MVI架构---大自然的搬运工~~~ 浅谈 Android MVI架构 阅读详情

相关推荐

Android MVI 架构学习

这些年来,Android 上发展了多种主流架构,从最开始的MVC,到Clean和MVP,再到现在最火热的MVVM,可以说架构发展一直很卷,这不,MVVM还没有用个几年呢,MVI就出来了。要说 Android 架构卷,实则不然,上面说的这些架构其实根本不是来自 Android 的,而是源自于Web,即大前端, Web 由于其自身特性(还不算完全成熟,业务多且杂,热部署等),版本迭代速度巨快,技术的更新迭代也因此很快,上面这些架构最早就是在前端所应用和发展出来的, 而移动端则是直接抄来,跟着Web的步伐前进。.

RikkaTheWorld 3688

Android应用开发架构之MVI:原理、流程与实战指南

在现代的Android应用开发中,构建可维护、可扩展且具备响应式特性的架构变得越来越重要。在本文中,我们将介绍一种新颖的架构模式:MVI(Model-View-Intent)。我们将详细讲解MVI架构的原理和流程,并通过具体的代码示例进行实战演示。同时,我们还会分享一些在实际工作中的经验和解决的问题。此外,我们将与MVVM和MVP架构进行对比,探讨MVI架构的优点和如何弥补其缺点。MVI架构通过响应式数据流和单向数据流的特性,提供了一种可维护、可测试且具备响应式特性的架构模式。

大模型训练 3978

Android 架构MVI、MVVM、MVC、MVP

简单了解一下MVI、MVC、MVP和MVVM。

csdn_aiyang的博客 2395

Android应用架构的未来:深入理解MVI模式及其优势

MVI架构模式是基于函数式编程思想的,它强调了数据的不变性和单向流动。在MVI模型(Model):负责管理应用的状态数据,并对外部事件做出响应。视图(View):负责显示界面,并将用户的操作转化为意图(Intent)。意图(Intent):代表用户的行为,如点击按钮、输入文本等,它们被发送到模型层以改变应用的状态。数据流方向MVI:单向数据流,从Model到View。MVVM:双向数据绑定,View和ViewModel之间自动同步。Presenter作为中间层,View和Model。

Androiddddd的博客 2817

android MVI架构

kotlin复制代码。

2301_77184740的博客 1522

Android MVI架构模式详解

MVI 通过强制单向数据流和不可变状态,解决了 Android 开发中常见的状态同步问题。结合 Kotlin 协程和 Jetpack 组件,可构建高可维护性、可测试性的应用,特别适合复杂交互场景。MVI(Model-View-Intent)是一种。

JMY_2016的博客 1312

android mvi架构_Android上的MVI体系结构入门

android mvi架构 介绍 (Introduction) Like MVC, MVP or MVVM, MVI is an architectural design pattern that helps us better organize our code to create robust and maintainable applications. It is in the same f...

weixin_26739079的博客 4073

Android架构重构为MVI脚本

MVI架构重构

weixin_43994638的博客 590

探索未来安卓开发新境界:基于Kotlin的MVI清洁架构应用

探索未来安卓开发新境界:基于Kotlin的MVI清洁架构应用 在日新月异的移动开发领域,一款设计精良、性能卓越的应用框架往往能成为开发者手中的利剑。今天,我们要介绍的是一款名为Android Kotlin MVI Clean Architecture APP的开源项目,它以其先进的技术和清晰的设计理念,为安卓开发者们开辟了一条高效开发之路。 项目介绍 Android Kotlin MVI Clea...

gitblog_00224的博客 921

Android中MVI架构详解

MVI (Model-View-Intent) 是一种单向数据流的架构模式,它源于响应式编程思想。在 MVI 中:1、Model: 代表的是UI 状态,它包含了UI状态的所有数据,比如加载中的状态,加载成功的状态,空数据状态,错误状态。object Loading : ToDoState() //加载中的状态data class Success(val tasks: List<ToDoModel>) : ToDoState() //任务加载成功。

路宇的博客 501

告别理论纠结!用ADS和Sigrity玩转电源完整性:从PDN阻抗优化到芯片供电仿真全流程

本文详细介绍了如何利用ADS和Sigrity工具进行电源完整性(PI)分析,从PDN阻抗优化到芯片供电仿真的全流程。通过频域、直流和时域分析,工程师可以精确评估电源网络性能,优化设计以避免电压波动和系统失效。文章还提供了ADS和Sigrity PowerDC的实战操作指南,帮助读者掌握现代PI设计的核心技术。

weixin_33749242的博客 310

android MVI架构梳理

MVI架构模式为Android开发提供了清晰的状态管理方案,其核心是单向数据流:用户操作转为Intent→ViewModel处理→更新Model→刷新UI。实现上结合ViewModel管理业务逻辑,通过协程实现高效异步处理,并配合StateFlow/SharedFlow进行状态管理。协程的轻量级特性和结构化并发机制有效解决了线程管理和内存泄漏问题,而Room数据库可配合流实现数据监听。这种架构使代码更易维护,状态变化可预测,适合复杂应用开发。

a611510的博客 830

Hadoop实战:用MapReduce分析YouTube视频数据集(附完整代码)

本文提供了一个完整的Hadoop MapReduce实战教程,指导读者使用MapReduce框架分析YouTube视频数据集,统计不同视频类别的数量分布。教程涵盖从数据获取、预处理、HDFS操作到Java代码编写、作业提交及结果可视化的全流程,并附有完整代码,帮助开发者深入理解分布式计算思想。

lilh34434的博客 200

Android Jetpack系列之MVI架构

在之前介绍MVVM的文章中,介绍了常用的架构及其对MVVM的封装使用,其中MVVMRepository提供数据,ViewModel中发送数据,UI层使用的LiveData订阅数据,当有数据变化时会主动通知UI层进行刷新。有兴趣的可以去看一下:1、Android Jetpack系列之MVVM使用及封装2、Android Jetpack系列之MVVM使用及封装(续)那么MVI又是什么呢?看了一些关于MVI的文章,大家都称MVI是(Model-View-Intent),其中Intent称为意图(注意这里的。

小马快跑的博客 3680

嵌入式SD/TF卡通用协议-SDIO协议

为了驱动SD卡,首先的第一步就是观察SD卡引脚接线情况,通常我们在画原理图时,都是使用的SD卡卡槽与MCU进行接线。SD卡可以使用SDIO协议和SPI协议进行通信,这里我们主要介绍SDIO协议。下图中的TF卡只是容量小一点的SD卡。以F1C100S为例,接线图如下所示:我们发现上图中的TF卡使用的协议为SDIO协议,主要有4根数据线,1根命令线,1根时钟线组合而成。为了驱动这个SD/TF卡我们就需要去了解SDIO协议的时序。

qq_1033275663的博客 3052

勒索病毒c语言源代码,勒索病毒原理解析

一、编程思路什么是勒索病毒:遍历你电脑上所有文件,并且用加密算法加密,然后把加密秘钥发送到自己邮箱里,弹出窗口。工具:操作系统API,加密算法思路:1.程序设计 思路规划清楚1.1 加密单个文件1.2 遍历文件系统并加密文件2.工程实现: 编程3.测试上线 :、测试欢迎加入学习群【892643663】,获取全套免费C/C++企业实战级课程资源(素材+源码+视频)和编译大礼包二、代码解析//文件遍...

weixin_35355756的博客 6603

Android MVI架构初探

学更好的别人,做更好的自己。——《微卡智享》本文长度为3786字,预计阅读8分钟前言做Android开发的应该都听到过Android的架构,什么MVC,MVP,MVVM,所有的架构来说也没有什么完美之说。当时在还记得最初刚接触Android时,是因为要做一个PDA的盘点机,也是因为有目标和方向,所以从头开始自学的Android并完成了这个程序,当时的目的是完成,所以根本就...

Vaccae的博客 1043

信创改造实战:SpringBoot项目迁移到宝兰德BES 9.5.5的完整避坑指南(附依赖与web.xml配置)

本文详细介绍了将SpringBoot项目迁移到宝兰德BES 9.5.5的完整流程,包括依赖改造、web.xml配置降级等关键步骤。通过实战案例和避坑指南,帮助开发者高效完成国产中间件适配,确保项目平稳运行。特别针对Tomcat依赖排除和BES适配器选择提供了专业建议。

weixin_30689307的博客 742

基于 kotlin版本的 Android的MVI架构

MVI即Model-View-Intent,它受Cycle.js前端框架的启发,提倡一种单向数据流的设计思想,非常适合数据驱动型的UI展示项目:Model: 与其他MVVM中的Model不同的是,MVI的Model主要指UI状态(State)。当前界面展示的内容无非就是UI状态的一个快照:例如数据加载过程、控件位置等都是一种UI状态View: 与其他MVX中的View一致,可能是一个Activity、Fragment或者任意UI承载单元。

qq_42795723的博客 1957

【大模型应用开发-大模型知识】(二)大模型分类和常见大模型

目前包含 Claude 和 Claude-Instant 两种模型可供选择,其中 Claude Instant 的延迟更低,性能略差,价格比完全体的 Claude-v1 要便宜,两个模型的上下文窗口都是 9000 个token(约 5000 个单词,或 15 页)它的目标是“更安全”、“危害更小”的人工智能。Google 还开发了多种 PaLM 的改进版本。更强大的基础模型: ChatGLM3-6B 的基础模型 ChatGLM3-6B-Base 采用了更多样的训练数据、更充分的训练步数和更合理的训练策略。

1万+

深度强化学习调参技巧:以DQN、DDPG、TD3、PPO、SAC等算法为例

深度强化学习调参技巧:以D3QN、DDPG、TD3、PPO、SAC算法为例

丨汀、的博客 3445

打卡学习Android 架构—MVI 架构的理解

intent指的是用户意图,在代码层面上来说他其实就是个枚举类,在kotlin中可以通过sealed关键字来生成封闭类,这个关键字生成的封闭类在when语句中可以不用写elseobject GetBanners : UserIntent() //定义了一个用户获取Banner的意图 }

m0_70748458的博客 1058

Qt Installer Framework 简单使用

qt installer framework 简单使用

qq_42815643的博客 2576

深度解析 Android MVI 架构原理

MVI 架构主要由三个核心组件构成:Model(模型)、View(视图)和 Intent(意图)。下面我们分别对这三个组件进行详细介绍。Intent 层主要负责定义和分类不同类型的用户操作。在前面的示例中,我们使用 Kotlin 的密封类AppIntent来定义不同类型的 Intent:kotlin// 定义一个密封类来表示不同类型的 Intent// 表示加载数据的 Intent// 表示刷新数据的 Intent密封类的好处是可以确保所有可能的 Intent 类型都在这个类中定义,并且在when。

Android开发领域创作者 1046

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

Android干净架构MVI模板使用指南

此项目是基于Android干净架构原则的一个示例实现,特别之处在于其采用了Model-View-Intent (MVI) 设计模式,替代了传统的MVP(Model-View-Presenter)模式。项目利用了Android Architecture Components库中的ViewModel组件,以及Room数据库来处理缓存。该开源工程旨在提供一个开箱即用的框架,帮助开发者理解和实施干净架构,...

gitblog_00353的博客 1198

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

拥抱单向数据流:Android 开发中的 MVI 架构详解

MVI架构提供了一种构建响应式应用的强大方法,它将用户意图、应用状态和界面展示分离开来,使得应用逻辑变得清晰且容易管理。虽然它可能会带来更陡峭的学习曲线和更多的样板代码,但其带来的可测试性、可预测性以及更好的状态管理能力是不可否认的。如果你打算构建一个需要严格状态管理和具有复杂用户交互的应用,MVI架构可能是一个值得考虑的选择。如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。

star_nwe的博客 2742

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

上一篇: Framework - SystemServer
下一篇: Framework - AMS
Jomurphys
博客等级 码龄9年 1768粉丝 261原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值