前言
各位Android搬砖工程师们,是不是都有过这样的经历:接手一个祖传项目,打开Activity一看------好家伙!几千行代码挤在一个文件里,UI逻辑、网络请求、数据处理像一锅乱炖的面条,改个按钮颜色都要小心翼翼,生怕动了哪根"面条"就引发连锁崩溃?
别慌,这不是你的错,而是架构的锅。今天咱们就来聊聊如何用MVPS架构,把这锅"代码乱炖"改造成"精装别墅",让你的代码从此条理清晰、颜值在线、可维护性爆棚。
一、架构这东西,到底有多重要?
在聊MVPS之前,咱们先统一思想:为啥要搞架构?
想象一下,如果你要盖个茅草屋,随便找几根木头搭搭就行,不用啥图纸;但如果你要盖个别墅,没有详细的设计图试试?承重墙砌错位置、水管和电线打架、楼上厕所正对着楼下厨房------画面太美不敢看。
写代码也是一个道理。小Demo随便写写无所谓,但商业项目动辄几万、几十万行代码,没有清晰的架构,就像盖别墅没有图纸,早晚得塌。
传统的MVC在Android里早就变了味:Activity既当爹(Controller)又当妈(View),还要兼职处理数据逻辑,活活把自己逼成了"全能型选手",结果就是代码臃肿、耦合严重、测试困难。
后来MVP横空出世,把View和Presenter分开,算是解决了一部分问题。但随着项目越来越复杂,MVP也暴露了一些短板:Model层职责不清、数据来源混乱(本地数据库和网络请求打架)、Presenter偶尔还是会被数据处理逻辑拖累...
于是,MVPS闪亮登场!它在MVP的基础上增加了一个"Secret Weapon"------Repository层,从此代码世界迎来了新的和平。
二、MVPS到底是个啥?拆开来给你看
MVPS全称是Model-View-Presenter-Repository,四个字母代表四个核心角色,各司其职、互不干涉,像一支配合默契的乐队。
1. View:颜值担当,只负责"貌美如花"
View层就是咱们看到的UI,包括Activity、Fragment、XML布局等。它的职责特别单纯:
- 展示数据(把Presenter给的数据显示出来)
- 接收用户操作(按钮点击、输入文字等)
- 把用户操作转交给Presenter处理(自己不做任何决策)
打个比方,View就像餐厅里的服务员,只管把菜(数据)端给顾客(用户),把顾客的需求传给后厨(Presenter),至于菜怎么做、用什么材料,它一概不管。
2. Presenter:逻辑担当,"中间商"不好当
Presenter是View和Repository之间的"中间商",也是业务逻辑的核心处理者:
- 接收View传来的用户指令
- 决定调用哪个Repository获取数据
- 对数据进行加工处理(比如格式转换、过滤等)
- 把处理好的数据交给View展示
它就像餐厅里的厨师长,接到服务员(View)的订单后,安排后厨(Repository)备菜,做好后再让服务员端给顾客。但厨师长不亲自洗菜切菜(那是Repository的活),也不直接端菜(那是View的活)。
3. Repository:数据担当,"数据管家"不好惹
Repository是新增的"强力选手",专门负责数据的获取和管理:
- 统一管理数据来源(网络请求、本地数据库、SP存储等)
- 决定从本地取还是从网络取(比如有缓存先读缓存)
- 对数据进行初步处理(比如JSON解析成实体类)
- 给Presenter提供干净的数据
它就像餐厅的采购+仓库管理员,既要负责买菜(网络请求),又要管理仓库(本地存储),确保厨师长(Presenter)要的时候能及时拿出新鲜食材。
4. Model:实体担当,"数据模板"不能乱
Model就是数据模型,比如User、Order这些实体类,用来规范数据的格式。它就像餐厅的菜单,规定了每道菜(数据)必须包含哪些信息(菜名、价格、食材等)。
三、实战演练:用MVPS做个"用户列表"
光说不练假把式,咱们来实现一个简单的用户列表功能:从网络或本地获取用户数据,展示在RecyclerView上。
1. 先定义数据模型(Model)
Model很简单,就是个JavaBean(Kotlin数据类更方便):
kotlin
// User.kt
data class User(
val id: Int,
val name: String,
val age: Int,
val avatar: String // 头像URL
)
就像定义了"用户"必须有id、名字、年龄和头像,一目了然。
2. 设计View层接口
View层要实现什么功能?展示加载中、展示错误、展示用户列表。我们用接口来定义这些行为,方便Presenter调用:
kotlin
// UserListView.kt
interface UserListView {
// 显示加载中
fun showLoading()
// 显示错误信息
fun showError(message: String)
// 显示用户列表
fun showUserList(users: List<User>)
}
然后让Activity实现这个接口:
kotlin
// UserListActivity.kt
class UserListActivity : AppCompatActivity(), UserListView {
private lateinit var presenter: UserListPresenter
private lateinit var recyclerView: RecyclerView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user_list)
// 初始化Presenter
presenter = UserListPresenter(this)
// 初始化RecyclerView(代码省略)
// 加载数据
presenter.loadUsers()
}
override fun showLoading() {
// 显示ProgressDialog或进度条
progressDialog.show()
}
override fun showError(message: String) {
// 隐藏加载,显示错误提示
progressDialog.dismiss()
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
override fun showUserList(users: List<User>) {
// 隐藏加载,更新列表
progressDialog.dismiss()
recyclerView.adapter = UserAdapter(users)
}
}
Activity里只做UI相关的事,逻辑全交给Presenter,清爽!
3. 实现Presenter层
Presenter需要持有View和Repository的引用,处理业务逻辑:
kotlin
// UserListPresenter.kt
class UserListPresenter(private val view: UserListView) {
// 依赖抽象,不依赖具体实现(方便测试)
private val userRepository: UserRepository = UserRepositoryImpl()
// 加载用户列表
fun loadUsers() {
// 先告诉View显示加载中
view.showLoading()
// 调用Repository获取数据(用协程处理异步)
CoroutineScope(Dispatchers.IO).launch {
try {
// 获取数据
val users = userRepository.getUsers()
// 切换到主线程更新UI
withContext(Dispatchers.Main) {
view.showUserList(users)
}
} catch (e: Exception) {
// 出错了,通知View显示错误
withContext(Dispatchers.Main) {
view.showError(e.message ?: "加载失败")
}
}
}
}
}
Presenter就像个指挥官:让View显示加载,让Repository拿数据,拿到数据后再让View更新------自己不碰具体的UI和数据细节,完美!
4. 实现Repository层
Repository要处理数据来源,这里我们模拟网络请求和本地缓存:
kotlin
// UserRepository.kt(接口)
interface UserRepository {
suspend fun getUsers(): List<User>
}
// UserRepositoryImpl.kt(实现类)
class UserRepositoryImpl : UserRepository {
// 模拟本地缓存
private val localDataSource = LocalDataSource()
// 模拟网络数据源
private val remoteDataSource = RemoteDataSource()
override suspend fun getUsers(): List<User> {
// 策略:先查本地缓存,如果有就返回,没有就请求网络
val localUsers = localDataSource.getLocalUsers()
if (localUsers.isNotEmpty()) {
return localUsers
}
// 本地没有,请求网络
val remoteUsers = remoteDataSource.getRemoteUsers()
// 把网络数据缓存到本地
localDataSource.saveUsers(remoteUsers)
return remoteUsers
}
}
// 本地数据源
class LocalDataSource {
private val db = mutableListOf<User>() // 模拟数据库
suspend fun getLocalUsers(): List<User> {
// 模拟从数据库读取
delay(500) // 模拟IO操作耗时
return db
}
suspend fun saveUsers(users: List<User>) {
// 模拟保存到数据库
delay(500)
db.clear()
db.addAll(users)
}
}
// 网络数据源
class RemoteDataSource {
suspend fun getRemoteUsers(): List<User> {
// 模拟网络请求
delay(1000) // 模拟网络延迟
// 模拟服务器返回数据
return listOf(
User(1, "张三", 25, "https://picsum.photos/id/1/200"),
User(2, "李四", 30, "https://picsum.photos/id/2/200"),
User(3, "王五", 28, "https://picsum.photos/id/3/200")
)
}
}
Repository的作用在这里体现得淋漓尽致:它决定了数据从哪来(本地还是网络),并对数据源进行了封装。Presenter根本不用关心数据是从数据库拿的还是从网络抓的,只管要结果就行。
四、MVPS的"超能力":为什么它比MVP更优秀?
-
数据管理更优雅
以前MVP里,数据逻辑可能散落在Presenter或Model里,时间一长就成了"数据垃圾堆"。MVPS用Repository统一管理,数据来源再复杂也不怕。
-
可测试性MAX
各层通过接口通信,测试时可以轻松替换实现类。比如测试Presenter时,用MockRepository返回假数据,不用真的连网络;测试Repository时,也不用管View和Presenter。
-
代码复用率UP
Repository层可以被多个Presenter共用。比如"用户详情"和"用户列表"都需要用户数据,直接复用同一个Repository就行,不用重复写网络请求代码。
-
抗变化能力强
假设产品经理说"用户数据要从A服务器换成B服务器",你只需要改RemoteDataSource的URL,其他层(View、Presenter)完全不用动------这就是解耦的魅力!
五、避坑指南:使用MVPS要注意这些事
-
别让Presenter太胖
虽然Presenter是逻辑核心,但也不能把所有活儿都扔给它。复杂的数据处理(比如数据过滤、转换)可以放在Repository,Presenter只做协调工作。
-
防止内存泄漏
Presenter持有View的引用,如果Activity销毁时Presenter还在执行异步任务,就会导致内存泄漏。解决办法:在Activity的onDestroy()里让Presenter和View解绑。
kotlin// 在Presenter里加个解绑方法 fun detachView() { // 取消所有异步任务 job.cancel() } // 在Activity里调用 override fun onDestroy() { super.onDestroy() presenter.detachView() }
-
别过度设计
小项目或简单功能用MVPS可能有点"杀鸡用牛刀",这时用简化版MVP甚至单Activity模式可能更高效。架构是服务于项目的,不是用来炫技的。
六、总结:从"码农"到"架构师"的一小步
MVPS架构就像给代码装上了"导航系统",让每个类都知道自己该去哪、该做啥,再也不用在"面条代码"里迷路了。
它可能比传统写法多了几个类,但带来的可维护性、可测试性提升是长远的。当项目越来越大,团队越来越多时,你会感谢当初选择了好架构的自己。
最后送大家一句箴言:写代码就像谈恋爱,初期图省事(随便写),后期肯定要吵架(难维护);前期多花点心思(做好架构),后期才能甜甜蜜蜜(轻松迭代)。
祝各位都能写出赏心悦目的代码,早日从"搬砖工程师"升级为"架构师"!