Android 项目架构设计:从 MVC 、MVP 到 MVVM,基于 Retrofit、LiveData 与 DataBinding 的 Android 架构模式演进实战
Android 项目架构设计实战:从 MVC、MVP 到 MVVM,梳理职责拆分、数据回传与 DataBinding 落地
前言
这篇文章围绕一个完整的 Android 示例工程展开,按工程从 MVC 演进到 MVP、再到 MVVM 的实际推进顺序,逐步梳理三种架构模式在页面职责划分、网络请求回传、界面更新方式上的差异。文章不会把过程重写成一套脱离实现现场的标准答案,而是尽量保留每一步为什么这样改、代码改在什么位置、数据如何在各层之间流转,以及最终界面是如何被更新起来的。
在这一套示例里,GET 请求用于按用户 id 查询昵称,POST 请求用于提交账号密码并返回登录结果。MVC 侧重理解 Controller 如何在 View 与 Model 之间充当桥梁,MVP 重点看 Presenter 如何接管交互流程,MVVM 则进一步把界面状态交给 LiveData 与 DataBinding 驱动,让 View 不再手动维护大部分 UI 更新逻辑。

目录
- Android 项目架构设计实战:从 MVC、MVP 到 MVVM,梳理职责拆分、数据回传与 DataBinding 落地
- 1. 为什么项目需要架构模式
- 2. MVC 架构模式
- 3. MVP 架构模式
- 4. MVVM 架构模式
- 5. 相关代码附录
1. 为什么项目需要架构模式
在 Android 项目里,如果页面、数据请求、结果处理和 UI 更新全部堆进一个 Activity,代码往往会越来越难维护。页面一多、交互一复杂,就很容易出现职责混乱、改一处牵一片、定位问题困难的情况。架构模式的价值,本质上就是先把职责切开,再让各层按照清晰的边界协作。
把这个目标拆开来看,架构模式至少解决了几件事:
- 它能把
UI、业务逻辑和数据访问拆分开,降低直接耦合。 - 它能让逻辑代码更容易单独测试,而不是每次都依赖页面环境。
- 它能让数据流向更清晰,新成员接手时更容易看懂页面是怎么跑起来的。
- 它能给后续扩展留空间,例如增加状态字段、增加新接口、替换页面实现时,不必整页重写。
围绕 Android 开发,最常见的三种模式就是 MVC、MVP 和 MVVM。它们并不是互相割裂的三套知识点,而是一条很清晰的演进链路:先解决“页面和数据分层”的问题,再解决“控制逻辑过重”的问题,最后解决“页面更新依赖手动调用”的问题。后面三章就沿着这条链路,逐步把同一组页面请求示例改造成三种不同架构。
2. MVC 架构模式
2.1 MVC 的职责划分
MVC 是最经典的分层方式之一,它把应用拆成 Model、View 和 Controller 三部分。
Model负责处理数据与业务逻辑,例如网络请求、参数校验、结果回传。View负责展示界面和承接用户输入。Controller负责从View收集输入,再调用Model,最后把结果回传给View。
放到 Android 里,Activity 往往天然同时承担 View 和 Controller 的一部分职责,这也是 MVC 在简单页面里比较容易落地的原因。不过这种便利也是它的限制来源,因为一旦交互变多,Activity 很容易变得臃肿。

上面这张图对应的就是 MVC 的基本关系:View 不直接处理数据,Model 不直接操作界面,中间依赖 Controller 进行协调。
2.2 前置代码:先把网络请求和页面骨架搭起来
在进入职责拆分之前,先把这个示例要用到的基础能力准备好。因为页面里要发起 GET 和 POST 请求,所以首先要在清单文件中声明网络权限,否则请求根本没有出网能力。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>
FrameworkModeByJavaProject/app/src/main/AndroidManifest.xml
这里的权限声明很简单,但它决定了后面所有 Retrofit 请求能否正常执行。页面层、数据层怎么拆都建立在这个基础之上。
接着把依赖补齐。示例里需要 Gson 做 JSON 解析,需要 Retrofit 组织接口请求,也需要 LiveData 和 ViewModel 为后面的 MVVM 章节做准备,所以依赖会统一放在 app 模块里。
plugins {
alias(libs.plugins.androidApplication)
}
android {
namespace 'com.ls.frameworkmodebyjavaproject'
compileSdk 34
dataBinding {
enabled = true
}
defaultConfig {
applicationId "com.ls.frameworkmodebyjavaproject"
minSdk 26
targetSdk 34
versionCode 1
versionName "1.0"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation libs.appcompat
implementation libs.material
implementation libs.activity
implementation libs.constraintlayout
implementation 'com.google.code.gson:gson:2.11.0'
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
implementation 'com.squareup.retrofit2:retrofit:2.11.0'
implementation 'com.squareup.retrofit2:converter-gson:2.11.0'
implementation "androidx.lifecycle:lifecycle-livedata:2.6.1"
implementation "androidx.lifecycle:lifecycle-viewmodel:2.6.1"
}
FrameworkModeByJavaProject/app/build.gradle
这一步把后面三种架构都会用到的基础能力先统一准备好。尤其是 Retrofit 与 converter-gson,它们会让 Model 层直接拿到解析后的实体对象,而不必自己手动处理原始字符串。
页面布局先保持简单,核心是两个输入场景:上半部分按用户 id 发起 GET 查询,下半部分输入账号密码发起 POST 登录请求。
<?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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
tools:context=".mvc.MvcActivity">
<TextView
android:id="@+id/tv_get_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="GET请求:"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/et_user_id"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入要查询的用户id"
android:inputType="number"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_get_label" />
<Button
android:id="@+id/btn_get"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="发起GET请求"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/et_user_id" />
<TextView
android:id="@+id/tv_post_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:text="GET请求:"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/btn_get" />
<EditText
android:id="@+id/et_user_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入用户名"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_post_label" />
<EditText
android:id="@+id/et_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入密码"
android:inputType="textPassword"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/et_user_name" />
<Button
android:id="@+id/btn_post"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="发起POST请求"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/et_password" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Retrofit"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
FrameworkModeByJavaProject/app/src/main/res/layout/activity_mvc.xml
这个布局本身并不复杂,但它已经明确了 View 层需要承担的责任:负责展示输入框、按钮和结果区域,采集用户输入,等待上层控制逻辑决定什么时候发请求、什么时候展示结果。
有了页面骨架后,再定义网络接口。这里把所有请求声明集中到 ApiService,后面的 Model 只需要面向接口发请求,不需要关心底层 OkHttp 细节。
public interface ApiService {
@GET("addons/cms/api.user/userInfo")
Call<UserInfoQuery> getUserInfo(
@Query("user_id") String userId,
@Query("type") String type);
@POST("addons/cms/api.login/login")
Call<ResLogin> login(@Body ReqLogin login);
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/reterofit/ApiService.java
这里的两个方法分别对应页面里的两种操作。getUserInfo 通过 @Query 传入查询参数,login 通过 @Body 提交请求体。接口先定义清楚,后续无论是 MVC、MVP 还是 MVVM,数据层都可以直接复用。
为了让 Retrofit 自动把 JSON 映射成 Java 对象,还需要先定义返回实体。用户信息查询接口的数据层次比较深,所以实体类里既要保留根节点字段,也要保留内部的 user、list 等嵌套结构。
public class UserInfoQuery {
private int code;
private String msg;
private String time;
private DataBean data;
public static class DataBean {
private UserBean user;
private ListBean list;
private int archives;
private int comments;
public static class UserBean {
private int id;
private String nickname;
private String bio;
private String avatar;
private String status;
private String url;
}
public static class ListBean {
private int total;
private int per_page;
private int current_page;
private int last_page;
}
}
// 省略 getter、setter 方法
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/bean/UserInfoQuery.java
这个实体类的作用不是单纯“接数据”,而是把返回结构拆成了可以逐级访问的对象层次。后面无论是在 Activity、Presenter 还是 ViewModel 中取昵称,最终都会沿着 getData().getUser().getNickname() 这条链路读取数据。
请求发起前,页面端先把基础交互跑通。在最初的 MVC 写法里,MvcActivity 直接初始化控件、监听按钮点击,再在内部完成网络请求和结果更新。
public class MvcActivity extends AppCompatActivity implements View.OnClickListener {
private EditText etUserId;
private EditText etUserName;
private EditText etPassword;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mvc);
findViewById(R.id.btn_get).setOnClickListener(this);
findViewById(R.id.btn_post).setOnClickListener(this);
etUserId = findViewById(R.id.et_user_id);
etUserName = findViewById(R.id.et_user_name);
etPassword = findViewById(R.id.et_password);
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_get) {
String userId = etUserId.getText().toString().trim();
sendGetRequest(userId);
} else if (v.getId() == R.id.btn_post) {
String account = etUserName.getText().toString().trim();
String password = etPassword.getText().toString().trim();
sendPostRequest(account, password);
}
}
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/mvc/MvcActivity.java
这种写法足够直接,优点是上手快,页面、点击事件、请求调用都能在一个类里顺着看下来。但它也埋下了后面要拆分的问题,因为一旦 sendGetRequest 和 sendPostRequest 里塞进完整请求逻辑,Activity 很快就会同时承担 View 和 Controller 的双重职责。
2.3 MVC 中 View 获取 Model 处理结果
既然 Activity 里既有页面代码,又有网络请求代码,那么第一步自然就是把数据处理逻辑抽到 Model。这里新建 MvcUserModel,专门接管登录请求和查询请求。
public class MvcUserModel {
private static final String TAG = "MvcUserModel";
public MvcUserModel() {
}
protected void sendPostRequest(String account, String password) {
if (account == null || account.isEmpty()) {
Log.i(TAG, "sendPostRequest: 用户名不能为空!");
return;
}
if (password == null || password.isEmpty()) {
Log.i(TAG, "sendPostRequest: 密码不能为空!");
return;
}
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://titok.fzqq.fun/")
.addConverterFactory(GsonConverterFactory.create())
.build();
ApiService apiService = retrofit.create(ApiService.class);
Call<ResLogin> loginCall = apiService.login(new ReqLogin(account, password));
loginCall.enqueue(new Callback<ResLogin>() {
@Override
public void onResponse(Call<ResLogin> call, Response<ResLogin> response) {
ResLogin resLogin = response.body();
if (!response.isSuccessful()) {
Log.i(TAG, "onResponse: 请求失败,message = " + response.message());
return;
}
if (resLogin == null) {
Log.i(TAG, "onResponse: 返回数据为空");
return;
}
if (resLogin.getCode() == 1) {
Log.i(TAG, "onResponse: 登录成功,resLogin = " + resLogin);
} else {
Log.i(TAG, "onResponse: 登录失败,msg = " + resLogin.getMsg());
}
}
@Override
public void onFailure(Call<ResLogin> call, Throwable throwable) {
Log.i(TAG, "onFailure: 网络请求失败");
}
});
}
protected void sendGetRequest(String userId) {
if (userId == null || userId.isEmpty()) {
Log.i(TAG, "sendGetRequest: 查询id不能为空");
return;
}
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://titok.fzqq.fun/")
.addConverterFactory(GsonConverterFactory.create())
.build();
ApiService apiService = retrofit.create(ApiService.class);
Call<UserInfoQuery> queryCall = apiService.getUserInfo(userId, "archives");
queryCall.enqueue(new Callback<UserInfoQuery>() {
@Override
public void onResponse(Call<UserInfoQuery> call, Response<UserInfoQuery> response) {
if (!response.isSuccessful()) {
Log.i(TAG, "onResponse: 请求失败,message = " + response.message());
return;
}
UserInfoQuery body = response.body();
if (body == null || body.getData() == null || body.getData().getUser() == null) {
Log.i(TAG, "onResponse: 返回数据为空");
return;
}
String nickname = body.getData().getUser().getNickname();
Log.i(TAG, "onResponse: body = " + nickname);
}
@Override
public void onFailure(Call<UserInfoQuery> call, Throwable throwable) {
Log.i(TAG, "onFailure: 网络请求失败!");
}
});
}
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/mvc/MvcUserModel.java
这一改动的意义在于,Model 终于开始真正承担“处理数据”的责任了。参数校验、Retrofit 初始化、接口调用、响应判空、日志输出,这些都从页面层移走,页面不再需要自己直接拼请求。
接着在 Controller 里实例化 Model,并把用户输入作为参数传进去。这样 Activity 不再直接处理网络请求,而是专注于从 View 拿输入,再把输入交给 Model。
private MvcUserModel mMvcUsmerModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mvc);
mMvcUsmerModel = new MvcUserModel(this);
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_get) {
String userId = etUserId.getText().toString();
mMvcUsmerModel.sendGetRequest(userId);
} else if (v.getId() == R.id.btn_post) {
String account = etUserName.getText().toString();
String password = etPassword.getText().toString();
mMvcUsmerModel.sendPostRequest(account, password);
}
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/mvc/MvcActivity.java
这里最关键的一点,不是简单地“把方法挪走”,而是明确了层之间的调用方向:View 上的输入先进入 Controller,再由 Controller 决定调用哪个 Model 方法。View 和 Model 本身并不直接通信。
2.4 MVC 中 Model 获取 View 中的数据
当 Model 只负责请求、Controller 只负责发起调用后,新的问题马上出现了:请求结果如何再回到页面?Model 不能直接拿到控件实例更新界面,所以需要通过回调把结果交回 Controller。
先在 MvcUserModel 中定义回调接口和回调对象,让 Model 知道结果该回传给谁。
public class MvcUserModel {
UserModelCallback mCallback;
public MvcUserModel(UserModelCallback callback) {
this.mCallback = callback;
}
protected interface UserModelCallback {
void onUserInfoQuerySuccess(UserInfoQuery userInfo);
void onUserInfoQueryFail(String errorMessage);
void onLoginSuccess(ResLogin login);
void onLoginFail(String errorMessage);
}
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/mvc/MvcUserModel.java
有了回调定义之后,Model 就能在请求成功或失败时,把结果通过统一接口抛回去。这一步相当于给 Model -> Controller 这条返回链路补齐出口。
下面把回调真正接入到数据处理逻辑里。登录请求和查询请求在拿到结果后,不再只写日志,而是直接调用回调方法把结果交给上层。
protected void sendPostRequest(String account, String password) {
if (account == null || account.isEmpty()) {
mCallback.onLoginFail(" 用户名不能为空!");
return;
}
if (password == null || password.isEmpty()) {
mCallback.onLoginFail(" 密码不能为空!");
return;
}
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://titok.fzqq.fun/")
.addConverterFactory(GsonConverterFactory.create())
.build();
ApiService apiService = retrofit.create(ApiService.class);
Call<ResLogin> loginCall = apiService.login(new ReqLogin(account, password));
loginCall.enqueue(new Callback<ResLogin>() {
@Override
public void onResponse(Call<ResLogin> call, Response<ResLogin> response) {
ResLogin resLogin = response.body();
if (response.isSuccessful() && resLogin.getCode() == 1) {
mCallback.onLoginSuccess(resLogin);
} else {
mCallback.onLoginFail(resLogin.getMsg());
}
}
@Override
public void onFailure(Call<ResLogin> call, Throwable throwable) {
mCallback.onLoginFail("网络请求失败");
}
});
}
protected void sendGetRequest(String userId) {
if (userId == null || userId.isEmpty()) {
Log.i(TAG, "sendGetRequest: 查询id不能为空");
return;
}
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://titok.fzqq.fun/")
.addConverterFactory(GsonConverterFactory.create())
.build();
ApiService apiService = retrofit.create(ApiService.class);
Call<UserInfoQuery> queryCall = apiService.getUserInfo(userId, "archives");
queryCall.enqueue(new Callback<UserInfoQuery>() {
@Override
public void onResponse(Call<UserInfoQuery> call, Response<UserInfoQuery> response) {
UserInfoQuery body = response.body();
mCallback.onUserInfoQuerySuccess(body);
}
@Override
public void onFailure(Call<UserInfoQuery> call, Throwable throwable) {
mCallback.onUserInfoQueryFail("网络请求失败!");
}
});
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/mvc/MvcUserModel.java
到这里为止,Model 已经只负责两件事:接收输入、处理结果。至于结果拿来怎么展示,已经不归它管了。
最后让 MvcActivity 实现 MvcUserModel.UserModelCallback,把页面展示逻辑写在回调实现里。这样当 Model 把结果回传回来时,Controller 就可以顺手把它交给 View。
public class MvcActivity extends AppCompatActivity
implements View.OnClickListener, MvcUserModel.UserModelCallback {
private EditText etUserId;
private EditText etUserName;
private EditText etPassword;
private MvcUserModel mMvcUsmerModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mvc);
mMvcUsmerModel = new MvcUserModel(this);
findViewById(R.id.btn_get).setOnClickListener(this);
findViewById(R.id.btn_post).setOnClickListener(this);
etUserId = findViewById(R.id.et_user_id);
etUserName = findViewById(R.id.et_user_name);
etPassword = findViewById(R.id.et_password);
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_get) {
String userId = etUserId.getText().toString();
mMvcUsmerModel.sendGetRequest(userId);
} else if (v.getId() == R.id.btn_post) {
String account = etUserName.getText().toString();
String password = etPassword.getText().toString();
mMvcUsmerModel.sendPostRequest(account, password);
}
}
@Override
public void onUserInfoQuerySuccess(UserInfoQuery userInfo) {
String nickname = userInfo.getData().getUser().getNickname();
Toast.makeText(MvcActivity.this, "查询到:" + nickname, Toast.LENGTH_SHORT).show();
}
@Override
public void onUserInfoQueryFail(String errorMessage) {
Toast.makeText(MvcActivity.this, errorMessage, Toast.LENGTH_SHORT).show();
}
@Override
public void onLoginSuccess(ResLogin login) {
Toast.makeText(MvcActivity.this, "欢迎你," +
login.getData().getUser_id(), Toast.LENGTH_SHORT).show();
}
@Override
public void onLoginFail(String errorMessage) {
Toast.makeText(MvcActivity.this, errorMessage, Toast.LENGTH_SHORT).show();
}
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/mvc/MvcActivity.java
这一版 MVC 已经形成了完整闭环:用户输入从页面进入 Controller,Controller 调用 Model 发起请求,Model 再通过回调把结果还给 Controller,最后由 Controller 更新界面。
2.5 MVC 的工作流程与优缺点
围绕刚才这套代码,MVC 的工作流可以按下面的顺序理解:
- 用户在
View中输入用户id、账号或密码,并点击按钮。 Controller也就是MvcActivity读取输入参数,决定调用sendGetRequest还是sendPostRequest。MvcUserModel完成参数校验、请求发起和结果解析。MvcUserModel通过UserModelCallback把结果交回MvcActivity。MvcActivity根据成功或失败结果,通过Toast更新界面反馈。
先把 Model 抽出来,页面内部原本直接写请求逻辑的部分就被独立成了一个数据处理类:

接着 Activity 只负责把输入参数传给 Model,不再自己手动拼接完整请求流程:

为了让 Model 能把结果回交给页面,还要先声明回调接口和回调对象来源:

在 Controller 实例化 Model 时,把 this 作为回调实现对象传进去,返回链路才算真正打通:

之后 Model 在请求成功或失败的分支里,就可以直接通过回调方法把结果回抛给 Controller:

而 Controller 只需要实现这些回调,把结果转成具体的 UI 展示行为即可:

整个过程中,View 与 Model 并不直接互通,所有数据流转都要经过 Controller:

从优点上看,MVC 至少完成了最基础的职责拆分:页面不再直接承载所有数据逻辑,Model 可以被独立复用,结构比把所有代码堆在一个 Activity 中清晰很多。
但它的局限也非常明显:
Activity仍然很重,因为它同时承担View展示和Controller协调。- 页面一复杂,回调、输入获取、结果展示仍会继续堆到同一个类里。
View与Controller的边界并不彻底清晰,扩展性有限。
也正因为这个问题,下一步才需要把“流程编排”再进一步从 Activity 中抽出去,这就是 MVP 要解决的核心。
3. MVP 架构模式
3.1 为什么要从 MVC 继续演进到 MVP
学完 MVC 之后,最直接的感受就是:虽然 Model 已经抽出来了,但 Activity 依旧同时承担着页面输入采集、结果接收和流程控制三类职责。换句话说,View 与 Controller 并没有真正分开。
MVP 的思路,就是把原本堆在 Activity 里的“流程协调能力”继续抽出去,单独交给 Presenter。这样:
View只负责展示和暴露 UI 操作接口。Model只负责处理数据逻辑。Presenter负责组织请求、接收结果、驱动界面更新。
这也是为什么 MVP 常常比 MVC 更适合交互更复杂的 Android 页面。

从这张关系图就能看出,View 不再直接持有数据处理过程,而是通过 Presenter 与 Model 协作。Activity 还在,但它更多扮演的是 View 的实现类。
3.2 前置代码:保留页面输入,先抽出 Presenter 入口
MVP 的页面骨架和 MVC 基本一致,区别在于页面中新增了一个 ProgressBar,用于后面演示“请求开始时显示加载、请求结束时隐藏加载”的 UI 控制。
public class MvpActivity extends AppCompatActivity implements View.OnClickListener {
private EditText etUserId;
private EditText etUserName;
private EditText etPassword;
private MvpUserPresenter mPresenter;
private ProgressBar loading;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mvp);
findViewById(R.id.btn_get).setOnClickListener(this);
findViewById(R.id.btn_post).setOnClickListener(this);
etUserId = findViewById(R.id.et_user_id);
etUserName = findViewById(R.id.et_user_name);
etPassword = findViewById(R.id.et_password);
loading = findViewById(R.id.loading);
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_get) {
} else if (v.getId() == R.id.btn_post) {
}
}
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/mvp/MvpActivity.java
这份代码先保留页面输入控件和点击入口,但暂时不直接落请求逻辑,目的是给 Presenter 留出接管空间。
<?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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
tools:context=".mvp.MvpActivity">
<TextView
android:id="@+id/tv_get_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="GET请求:"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/et_user_id"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入要查询的用户id"
android:inputType="number"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_get_label" />
<Button
android:id="@+id/btn_get"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="发起GET请求"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/et_user_id" />
<TextView
android:id="@+id/tv_post_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:text="Post请求:"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/btn_get" />
<EditText
android:id="@+id/et_user_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入用户名"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_post_label" />
<EditText
android:id="@+id/et_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入密码"
android:inputType="textPassword"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/et_user_name" />
<Button
android:id="@+id/btn_post"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="发起POST请求"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/et_password" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Retrofit"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<ProgressBar
android:id="@+id/loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
FrameworkModeByJavaProject/app/src/main/res/layout/activity_mvp.xml
这份布局和 MVC 的主要差异在于 loading 控件。它会成为观察职责拆分是否到位的一个很好的切口,因为“什么时候显示、什么时候隐藏”正是流程编排逻辑,而不应该散落在多个请求回调里。
Model 在 MVP 中依旧负责数据处理,不过返回结果的方式从“成员回调对象”改成了“把回调作为方法参数传入”,这样 Presenter 可以按不同场景组织不同回调。
public class MvpUserModel {
protected void sendPostRequest(String account, String password, UserLoginCallback callback) {
if (account == null || account.isEmpty()) {
callback.onLoginFail(" 用户名不能为空!");
return;
}
if (password == null || password.isEmpty()) {
callback.onLoginFail(" 密码不能为空!");
return;
}
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://titok.fzqq.fun/")
.addConverterFactory(GsonConverterFactory.create())
.build();
ApiService apiService = retrofit.create(ApiService.class);
Call<ResLogin> loginCall = apiService.login(new ReqLogin(account, password));
loginCall.enqueue(new Callback<ResLogin>() {
@Override
public void onResponse(Call<ResLogin> call, Response<ResLogin> response) {
ResLogin resLogin = response.body();
if (response.isSuccessful() && resLogin.getCode() == 1) {
callback.onLoginSuccess(resLogin);
} else {
callback.onLoginFail(resLogin.getMsg());
}
}
@Override
public void onFailure(Call<ResLogin> call, Throwable throwable) {
callback.onLoginFail("网络请求失败");
}
});
}
protected void sendGetRequest(String userId, UserQueryCallback callback) {
if (userId == null || userId.isEmpty()) {
Log.i("MvcUserModel", "sendGetRequest: 查询id不能为空");
return;
}
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://titok.fzqq.fun/")
.addConverterFactory(GsonConverterFactory.create())
.build();
ApiService apiService = retrofit.create(ApiService.class);
Call<UserInfoQuery> queryCall = apiService.getUserInfo(userId, "archives");
queryCall.enqueue(new Callback<UserInfoQuery>() {
@Override
public void onResponse(Call<UserInfoQuery> call, Response<UserInfoQuery> response) {
UserInfoQuery body = response.body();
callback.onUserInfoQuerySuccess(body);
}
@Override
public void onFailure(Call<UserInfoQuery> call, Throwable throwable) {
callback.onUserInfoQueryFail("网络请求失败!");
}
});
}
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/mvp/MvpUserModel.java
Model 仍然保持“只关心数据”的定位,但它不再关心结果到底回给哪个具体类,而是由调用方在发起请求时显式传入回调实现。
3.3 Presenter:接管 View 与 Model 之间的流程编排
Presenter 是 MVP 的核心。它既不直接持有控件,也不直接发起网络实现细节,但它决定了请求什么时候开始、结果回来后应该调用哪些界面方法。
一开始先定义出 Presenter 壳子,并在构造方法里持有 Model。
public class MvpUserPresenter {
private MvpUserModel mModel;
public MvpUserPresenter(MvpUserView view) {
mModel = new MvpUserModel();
}
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/mvp/MvpUserPresenter.java
这个初始版本还没有真正驱动界面,但已经把 Model 的入口固定在 Presenter 中了。接下来页面不再直接调用 Model,而是先调用 Presenter 暴露出来的方法。
在页面层里,先声明 Presenter,再把点击事件转交给它:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mvp);
mPresenter = new MvpUserPresenter(this);
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_get) {
String userId = etUserId.getText().toString().trim();
mPresenter.getUserInfo(userId);
} else if (v.getId() == R.id.btn_post) {
String account = etUserName.getText().toString().trim();
String password = etPassword.getText().toString().trim();
mPresenter.login(account, password);
}
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/mvp/MvpActivity.java
也就是说,View 的职责开始被压缩成“采集输入 + 分发事件”。真正决定下一步走向的,已经不是 Activity,而是 Presenter。
随后在 Presenter 中补齐请求入口方法,让它真正能调起 Model 的两个数据接口。
public void getUserInfo(String userId) {
mView.showLoading();
mModel.sendGetRequest(userId, new MvpUserModel.UserQueryCallback() {
@Override
public void onUserInfoQuerySuccess(UserInfoQuery userInfo) {
}
@Override
public void onUserInfoQueryFail(String errorMessage) {
}
});
}
public void login(String account, String password) {
mView.showLoading();
mModel.sendPostRequest(account, password, new MvpUserModel.UserLoginCallback() {
@Override
public void onLoginSuccess(ResLogin login) {
}
@Override
public void onLoginFail(String errorMessage) {
}
});
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/mvp/MvpUserPresenter.java
这里先把方法骨架搭出来,能清楚看到 Presenter 的位置:它先通知界面显示加载,再调用 Model,然后等待回调结果,最后根据成功或失败决定更新哪些界面。
为了配合这种写法,MvpUserModel 里把查询和登录拆成了两种不同回调接口。这样 Presenter 在处理查询昵称和处理登录结果时,不必被迫实现一大组混合接口。
protected interface UserQueryCallback {
void onUserInfoQuerySuccess(UserInfoQuery userInfo);
void onUserInfoQueryFail(String errorMessage);
}
protected interface UserLoginCallback {
void onLoginSuccess(ResLogin login);
void onLoginFail(String errorMessage);
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/mvp/MvpUserModel.java
这一步其实是在继续做职责细分:不仅层级在拆,连“返回结果的协议”也在按业务场景拆。查询和登录各走各的回调,Presenter 处理起来会更清晰。
Model 的数据处理方法本身也随之修改为“回调入参模式”。这样发起请求时,谁来处理结果完全由调用方决定。
protected void sendPostRequest(String account, String password, UserLoginCallback callback) {
if (account == null || account.isEmpty()) {
callback.onLoginFail(" 用户名不能为空!");
return;
}
if (password == null || password.isEmpty()) {
callback.onLoginFail(" 密码不能为空!");
return;
}
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://titok.fzqq.fun/")
.addConverterFactory(GsonConverterFactory.create())
.build();
ApiService apiService = retrofit.create(ApiService.class);
Call<ResLogin> loginCall = apiService.login(new ReqLogin(account, password));
loginCall.enqueue(new Callback<ResLogin>() {
@Override
public void onResponse(Call<ResLogin> call, Response<ResLogin> response) {
ResLogin resLogin = response.body();
if (response.isSuccessful() && resLogin.getCode() == 1) {
callback.onLoginSuccess(resLogin);
} else {
callback.onLoginFail(resLogin.getMsg());
}
}
@Override
public void onFailure(Call<ResLogin> call, Throwable throwable) {
callback.onLoginFail("网络请求失败");
}
});
}
protected void sendGetRequest(String userId, UserQueryCallback callback) {
if (userId == null || userId.isEmpty()) {
Log.i("MvcUserModel", "sendGetRequest: 查询id不能为空");
return;
}
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://titok.fzqq.fun/")
.addConverterFactory(GsonConverterFactory.create())
.build();
ApiService apiService = retrofit.create(ApiService.class);
Call<UserInfoQuery> queryCall = apiService.getUserInfo(userId, "archives");
queryCall.enqueue(new Callback<UserInfoQuery>() {
@Override
public void onResponse(Call<UserInfoQuery> call, Response<UserInfoQuery> response) {
UserInfoQuery body = response.body();
callback.onUserInfoQuerySuccess(body);
}
@Override
public void onFailure(Call<UserInfoQuery> call, Throwable throwable) {
callback.onUserInfoQueryFail("网络请求失败!");
}
});
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/mvp/MvpUserModel.java
到这里,Presenter 已经不需要像 MVC 那样去实现 Model 的统一接口,它只要在调用时内联提供当前场景需要的回调逻辑即可。
3.4 MVP 处理 View 的交互
前面把 Model -> Presenter 这条数据回传链路打通了,现在还差 Presenter -> View。为此需要先定义一组界面更新能力,让 View 通过接口把自己能做的事情暴露出来。
public interface MvpUserView {
void showUserInfo(UserInfoQuery info);
void onLoginSuccess(ResLogin login);
void showError(String error);
void showLoading();
void hideLoading();
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/mvp/MvpUserView.java
这个接口不是在替 Activity 写代码,而是在声明“页面能做什么”。比如它能展示用户信息,能展示错误,能控制加载动画,但它不再关心这些动作应该在什么时候发生。
对应地,Presenter 中要显式持有这个 View 接口对象:
public class MvpUserPresenter {
private final MvpUserView mView;
private MvpUserModel mModel;
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/mvp/MvpUserPresenter.java
有了这层持有关系,Presenter 才能在请求开始时调用 showLoading,在请求结束时调用 hideLoading,在成功或失败时调用不同的界面反馈方法。
接着让 MvpActivity 实现 MvpUserView,这样它虽然还是页面类,但已经不再承担流程控制职责,而是作为一个“界面能力提供者”存在。
public class MvpActivity extends AppCompatActivity
implements View.OnClickListener, MvpUserView {
private EditText etUserId;
private EditText etUserName;
private EditText etPassword;
private MvpUserPresenter mPresenter;
private ProgressBar loading;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mvp);
mPresenter = new MvpUserPresenter(this);
findViewById(R.id.btn_get).setOnClickListener(this);
findViewById(R.id.btn_post).setOnClickListener(this);
etUserId = findViewById(R.id.et_user_id);
etUserName = findViewById(R.id.et_user_name);
etPassword = findViewById(R.id.et_password);
loading = findViewById(R.id.loading);
}
@Override
public void showUserInfo(UserInfoQuery info) {
String nickname = info.getData().getUser().getNickname();
Toast.makeText(this, "查询到:" + nickname, Toast.LENGTH_SHORT).show();
}
@Override
public void onLoginSuccess(ResLogin login) {
Toast.makeText(this, "欢迎你," +
login.getData().getUser_id(), Toast.LENGTH_SHORT).show();
}
@Override
public void showError(String error) {
Toast.makeText(this, error, Toast.LENGTH_SHORT).show();
}
@Override
public void showLoading() {
loading.setVisibility(View.VISIBLE);
}
@Override
public void hideLoading() {
loading.setVisibility(View.INVISIBLE);
}
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/mvp/MvpActivity.java
可以看到,页面层现在只保留了非常明确的 UI 行为:展示查询结果、展示登录成功提示、展示错误、控制加载条显隐。这些方法都和“如何发请求、何时调用”无关。
最后把 Presenter 补成完整版本,让它统一协调 View 与 Model:
public class MvpUserPresenter {
private final MvpUserView mView;
private MvpUserModel mModel;
public MvpUserPresenter(MvpUserView view) {
mModel = new MvpUserModel();
mView = view;
}
public void getUserInfo(String userId) {
mView.showLoading();
mModel.sendGetRequest(userId, new MvpUserModel.UserQueryCallback() {
@Override
public void onUserInfoQuerySuccess(UserInfoQuery userInfo) {
mView.showUserInfo(userInfo);
mView.hideLoading();
}
@Override
public void onUserInfoQueryFail(String errorMessage) {
mView.showError(errorMessage);
mView.hideLoading();
}
});
}
public void login(String account, String password) {
mView.showLoading();
mModel.sendPostRequest(account, password, new MvpUserModel.UserLoginCallback() {
@Override
public void onLoginSuccess(ResLogin login) {
mView.onLoginSuccess(login);
mView.hideLoading();
}
@Override
public void onLoginFail(String errorMessage) {
mView.showError(errorMessage);
mView.hideLoading();
}
});
}
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/mvp/MvpUserPresenter.java
这一版的职责分工已经非常清楚:
View负责把“能做哪些 UI 更新”暴露出来。Model负责发请求并回传结果。Presenter负责定义整条交互流程,决定先显示加载、后发请求,再根据结果更新页面。
这就是 MVP 相对 MVC 的核心进步,它让页面不再直接承担流程编排。
3.5 MVP 的工作流程与优缺点
结合这一套实现,MVP 的流程可以概括成下面五步:
- 用户在
View中点击按钮。 View把事件交给Presenter。Presenter调用Model执行数据逻辑。Model通过回调把结果返回给Presenter。Presenter再调用View接口更新页面。
这张流程图对应的就是这种交互关系:

从这张图也能看出,View 与 Model 的联系已经被 Presenter 完整接管。页面负责展示,数据层负责处理,中间的协调关系不再堆在 Activity 里。
MVP 的优点很明显:
- 界面更新和业务处理解耦更彻底,页面更容易维护。
Presenter可以独立承接流程逻辑,职责边界更清楚。- 页面层只暴露接口,更适合做单元测试和替换实现。
但它也有新的成本:
View接口、Presenter类、回调接口都会增加代码量。- 页面越多,接口与
Presenter的数量会明显上升。 Presenter仍然依赖View接口,本质上还没有完全摆脱对界面的感知。
因此,如果还想继续降低手动控制 UI 的负担,就需要再往前走一步,让页面更多地“观察状态”而不是“等待方法调用”,这就是 MVVM 的目标。
4. MVVM 架构模式
4.1 为什么要从 MVP 继续演进到 MVVM
MVP 已经把流程协调从 Activity 中抽到了 Presenter,但它仍有一个明显特点:Presenter 需要主动调用 showLoading、hideLoading、showUserInfo 这一类界面方法。也就是说,界面更新虽然被统一编排了,但本质上仍是“命令式调用”。
MVVM 要解决的,就是把这种命令式调用进一步改造成状态驱动。具体做法是:
Model继续专注数据处理。ViewModel接收Model结果,并把结果转换成可观察的 UI 状态。View只观察这些状态,一旦状态变化,就自动刷新界面。
当 LiveData 和 DataBinding 接进来之后,页面层需要手动写的更新代码会明显减少。

这张关系图体现的重点,不再是“谁调用谁的方法”,而是 ViewModel 如何作为中间状态层,把 Model 的结果转成 View 能直接消费的界面数据。
4.2 Model:继续专注数据处理
到了 MVVM,Model 本身的职责其实没有变化,依然负责参数校验、网络请求和结果回调。也正因为职责稳定,前两种架构里的数据处理代码可以非常自然地沿用到这里。
public class MvvmUserModel {
protected void sendPostRequest(String account, String password, UserLoginCallback callback) {
if (account == null || account.isEmpty()) {
callback.onLoginFail(" 用户名不能为空!");
return;
}
if (password == null || password.isEmpty()) {
callback.onLoginFail(" 密码不能为空!");
return;
}
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://titok.fzqq.fun/")
.addConverterFactory(GsonConverterFactory.create())
.build();
ApiService apiService = retrofit.create(ApiService.class);
Call<ResLogin> loginCall = apiService.login(new ReqLogin(account, password));
loginCall.enqueue(new Callback<ResLogin>() {
@Override
public void onResponse(Call<ResLogin> call, Response<ResLogin> response) {
ResLogin resLogin = response.body();
if (response.isSuccessful() && resLogin.getCode() == 1) {
callback.onLoginSuccess(resLogin);
} else {
callback.onLoginFail(resLogin.getMsg());
}
}
@Override
public void onFailure(Call<ResLogin> call, Throwable throwable) {
callback.onLoginFail("网络请求失败");
}
});
}
protected void sendGetRequest(String userId, UserQueryCallback callback) {
if (userId == null || userId.isEmpty()) {
Log.i("MvvmUserModel", "sendGetRequest: 查询id不能为空");
return;
}
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://titok.fzqq.fun/")
.addConverterFactory(GsonConverterFactory.create())
.build();
ApiService apiService = retrofit.create(ApiService.class);
Call<UserInfoQuery> queryCall = apiService.getUserInfo(userId, "archives");
queryCall.enqueue(new Callback<UserInfoQuery>() {
@Override
public void onResponse(Call<UserInfoQuery> call, Response<UserInfoQuery> response) {
UserInfoQuery body = response.body();
callback.onUserInfoQuerySuccess(body);
}
@Override
public void onFailure(Call<UserInfoQuery> call, Throwable throwable) {
callback.onUserInfoQueryFail("网络请求失败!");
}
});
}
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/mvvm/MvvmUserModel.java
这正是 MVVM 值得注意的一点:它不是让所有层都推倒重来,而是在保留数据层定位不变的前提下,把“界面如何更新”这件事从方法调用变成状态观察。
4.3 View:先完成页面初始化,再把事件交给 ViewModel
先看页面层的初始写法。MvvmActivity 仍然负责初始化控件、绑定点击事件和获取用户输入,但接下来这些输入不会再直接送给 Model,而是交给 ViewModel。
public class MvvmActivity extends AppCompatActivity implements View.OnClickListener {
private EditText etUserId;
private EditText etUserName;
private EditText etPassword;
private ProgressBar loading;
private MvvmViewModel mViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mvvm);
binding.setLifecycleOwner(this);
binding.setViewModel(mViewModel);
findViewById(R.id.btn_get).setOnClickListener(this);
findViewById(R.id.btn_post).setOnClickListener(this);
etUserId = findViewById(R.id.et_user_id);
etUserName = findViewById(R.id.et_user_name);
etPassword = findViewById(R.id.et_password);
loading = findViewById(R.id.loading);
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_get) {
String userId = etUserId.getText().toString().trim();
} else if (v.getId() == R.id.btn_post) {
String account = etUserName.getText().toString().trim();
String password = etPassword.getText().toString().trim();
}
}
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/mvvm/MvvmActivity.java
这里最需要关注的是 binding.setLifecycleOwner(this) 和 binding.setViewModel(mViewModel) 这两行,因为它们为后面的数据绑定做了准备。页面不再只是拿控件引用,它还要把自身生命周期和 ViewModel 绑定到布局上。
要让 ViewModel 真正成为中间层,还需要把生命周期依赖补齐:
implementation "androidx.lifecycle:lifecycle-livedata:2.6.1"
implementation "androidx.lifecycle:lifecycle-viewmodel:2.6.1"
FrameworkModeByJavaProject/app/build.gradle
这两个依赖一个负责可观察数据容器,一个负责 ViewModel 生命周期管理。没有它们,后面的状态驱动更新就无从谈起。
4.4 ViewModel:把流程控制改成状态驱动
先创建 MvvmViewModel,让它像 Presenter 一样持有 Model,并暴露查询与登录两个入口方法。这样页面层的点击事件就有了新的接收方。
public class MvvmViewModel extends ViewModel {
private final MvvmUserModel mModel;
public MvvmViewModel() {
mModel = new MvvmUserModel();
}
public void getUserInfo(String userId) {
mModel.sendGetRequest(userId, new MvvmUserModel.UserQueryCallback() {
@Override
public void onUserInfoQuerySuccess(UserInfoQuery userInfo) {
}
@Override
public void onUserInfoQueryFail(String errorMessage) {
}
});
}
public void login(String account, String password) {
mModel.sendPostRequest(account, password, new MvvmUserModel.UserLoginCallback() {
@Override
public void onLoginSuccess(ResLogin login) {
}
@Override
public void onLoginFail(String errorMessage) {
}
});
}
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/mvvm/MvvmViewModel.java
这个阶段的 ViewModel 看起来很像 Presenter,但真正的差别马上就会出现:Presenter 直接调用界面方法,ViewModel 则会把界面变化先转换成状态。
第一个要改成状态的,就是加载中的显示逻辑。把 loading 的显示状态封装成 MutableLiveData<Boolean>,然后在请求开始与结束时修改这个值,而不是直接去操作控件。
public class MvvmViewModel extends ViewModel {
private final MvvmUserModel mModel;
private final MutableLiveData<Boolean> mShowLoading = new MutableLiveData<>();
public MvvmViewModel() {
mModel = new MvvmUserModel();
}
public MutableLiveData<Boolean> getShowLoading() {
return mShowLoading;
}
public void getUserInfo(String userId) {
mShowLoading.setValue(true);
mModel.sendGetRequest(userId, new MvvmUserModel.UserQueryCallback() {
@Override
public void onUserInfoQuerySuccess(UserInfoQuery userInfo) {
mShowLoading.setValue(false);
}
@Override
public void onUserInfoQueryFail(String errorMessage) {
mShowLoading.setValue(false);
}
});
}
public void login(String account, String password) {
mShowLoading.setValue(true);
mModel.sendPostRequest(account, password, new MvvmUserModel.UserLoginCallback() {
@Override
public void onLoginSuccess(ResLogin login) {
mShowLoading.setValue(false);
}
@Override
public void onLoginFail(String errorMessage) {
mShowLoading.setValue(false);
}
});
}
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/mvvm/MvvmViewModel.java
这一步是 MVVM 的关键转折点。界面不再等待“有人来调用 showLoading”,而是改成“观察 mShowLoading 是否变化”。只要这个布尔值变了,界面就知道自己该如何刷新。
对应地,页面层开始观察 ViewModel 状态,而不是在请求回调里手动操作控件:
public class MvvmActivity extends AppCompatActivity implements View.OnClickListener {
private ProgressBar loading;
private MvvmViewModel mViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mvvm);
mViewModel = new ViewModelProvider(this).get(MvvmViewModel.class);
loading = findViewById(R.id.loading);
mViewModel.getShowLoading().observe(this, new Observer<Boolean>() {
@Override
public void onChanged(Boolean show) {
loading.setVisibility(show ? View.VISIBLE : View.INVISIBLE);
}
});
}
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/mvvm/MvvmActivity.java
这里已经能看出 MVVM 的味道了:页面层不再关心请求是成功还是失败,它只关心“当前状态值是什么”。当 mShowLoading 变成 true 时显示进度条,变成 false 时隐藏进度条。
最后把页面点击事件接到 ViewModel 上,让数据流正式跑起来:
@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_get) {
String userId = etUserId.getText().toString().trim();
mViewModel.getUserInfo(userId);
} else if (v.getId() == R.id.btn_post) {
String account = etUserName.getText().toString().trim();
String password = etPassword.getText().toString().trim();
mViewModel.login(account, password);
}
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/mvvm/MvvmActivity.java
这样一来,View 负责发事件,ViewModel 负责改状态,Model 负责处理数据,三层之间的职责关系就开始稳定下来。
4.5 MVVM 中的被观察对象
仅仅观察一个加载状态,还不足以体现 MVVM 的优势。真正关键的是,页面更新时并不需要一整个返回对象,它往往只需要几个能直接驱动 UI 的字段。
因此,MvvmViewModel 会进一步把查询昵称、登录用户 id、错误信息这些界面状态分别拆成独立的可观察对象:
public class MvvmViewModel extends ViewModel {
private final MvvmUserModel mModel;
private final MutableLiveData<Boolean> mShowLoading = new MutableLiveData<>();
private final MutableLiveData<String> mNickName = new MutableLiveData<>();
private final MutableLiveData<Integer> mUserId = new MutableLiveData<>();
private final LiveData<String> mStrUserId = Transformations.map(mUserId, new Function1<Integer, String>() {
@Override
public String invoke(Integer integer) {
String content = "我查询到了用户id:" + integer;
return content;
}
});
private final MutableLiveData<String> mErrorMsg = new MutableLiveData<>();
public LiveData<String> getStrUserId() {
return mStrUserId;
}
public MutableLiveData<String> getErrorMsg() {
return mErrorMsg;
}
public MutableLiveData<Boolean> getShowLoading() {
return mShowLoading;
}
public MutableLiveData<String> getNickName() {
return mNickName;
}
public MutableLiveData<Integer> getUserId() {
return mUserId;
}
public void getUserInfo(String userId) {
mShowLoading.setValue(true);
mModel.sendGetRequest(userId, new MvvmUserModel.UserQueryCallback() {
@Override
public void onUserInfoQuerySuccess(UserInfoQuery userInfo) {
String nickname = userInfo.getData().getUser().getNickname();
mNickName.setValue(nickname);
mShowLoading.setValue(false);
}
@Override
public void onUserInfoQueryFail(String errorMessage) {
mNickName.setValue(null);
mShowLoading.setValue(false);
mErrorMsg.setValue(errorMessage);
}
});
}
public void login(String account, String password) {
mShowLoading.setValue(true);
mModel.sendPostRequest(account, password, new MvvmUserModel.UserLoginCallback() {
@Override
public void onLoginSuccess(ResLogin login) {
int userId = login.getData().getUser_id();
mUserId.setValue(userId);
mShowLoading.setValue(false);
}
@Override
public void onLoginFail(String errorMessage) {
mUserId.setValue(0);
mShowLoading.setValue(false);
mErrorMsg.setValue(errorMessage);
}
});
}
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/mvvm/MvvmViewModel.java
这一版 ViewModel 已经完全体现出 MVVM 的状态驱动思想了:
- 查询成功时,页面只需要昵称,所以写入
mNickName。 - 登录成功时,页面只需要用户
id,所以写入mUserId。 - 错误发生时,页面只需要错误文案,所以写入
mErrorMsg。
页面层则分别观察这些字段,不必再拿整个响应对象自己拆解:
mViewModel.getShowLoading().observe(this, new Observer<Boolean>() {
@Override
public void onChanged(Boolean show) {
loading.setVisibility(show ? View.VISIBLE : View.INVISIBLE);
}
});
mViewModel.getUserId().observe(this, new Observer<Integer>() {
@Override
public void onChanged(Integer userId) {
int i = userId.intValue();
if (i > 0) {
Toast.makeText(MvvmActivity.this,
"欢迎你," + i, Toast.LENGTH_SHORT).show();
}
}
});
mViewModel.getNickName().observe(this, new Observer<String>() {
@Override
public void onChanged(String s) {
Toast.makeText(MvvmActivity.this, "查询到:" + s, Toast.LENGTH_SHORT).show();
}
});
mViewModel.getErrorMsg().observe(this, new Observer<String>() {
@Override
public void onChanged(String error) {
Toast.makeText(MvvmActivity.this, error, Toast.LENGTH_SHORT).show();
}
});
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/mvvm/MvvmActivity.java
这正是 MVVM 相比 MVP 的一个重要优势:页面不需要拿完整结果再自己决定如何处理,而是直接观察那些已经被整理成“可用于更新 UI 的最小状态”的字段。
4.6 在 MVVM 中添加 DataBinding
前面只是把页面更新改成了 LiveData.observe。如果想继续减少样板代码,还可以把布局文件直接接到 ViewModel 上,让部分属性通过数据绑定自动更新。
首先在 android 配置中启用 DataBinding:
android {
namespace 'com.ls.frameworkmodebyjavaproject'
compileSdk 34
dataBinding {
enabled = true
}
}
FrameworkModeByJavaProject/app/build.gradle
启用后,布局文件就可以从普通的根布局切换成 layout 包裹结构,并在 data 节点中声明要绑定的 ViewModel 类型。
<?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="viewModel"
type="com.ls.frameworkmodebyjavaproject.mvvm.MvvmViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
tools:context=".mvvm.MvvmActivity">
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
FrameworkModeByJavaProject/app/src/main/res/layout/activity_mvvm.xml
这一步的意义在于,布局文件不再只是被动展示控件,它开始显式声明“我要绑定哪个 ViewModel”。后面控件属性就可以直接读取 viewModel 暴露出来的状态。
接着在控件上使用 @{} 或 @={} 表达式,把布局属性和 ViewModel 字段直接绑定起来:
<ProgressBar
android:id="@+id/loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{viewModel.showLoading}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_nick_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@={viewModel.nickName}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:text="nickName" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:text="@{viewModel.userId}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/tv_nick_name"
tools:text="userId" />
FrameworkModeByJavaProject/app/src/main/res/layout/activity_mvvm.xml
这里可以看到三个典型绑定场景:
loading直接绑定showLoading。- 昵称文本绑定
nickName。 - 用户
id文本绑定userId。
如果控件需要的类型和 LiveData 当前提供的类型不一致,可以在 ViewModel 内部先做一次类型转换。例如 mUserId 是 Integer,而文本展示通常更适合 String,这时就可以用 Transformations.map 包一层新的可观察字段。
private final MutableLiveData<Integer> mUserId = new MutableLiveData<>();
private final LiveData<String> mStrUserId = Transformations.map(mUserId, new Function1<Integer, String>() {
@Override
public String invoke(Integer integer) {
String content = "我查询到了用户id:" + integer;
return content;
}
});
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/mvvm/MvvmViewModel.java
这样处理后,布局就不需要自己拼字符串,而是直接绑定转换后的字段:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:text="@{viewModel.strUserId}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/tv_nick_name"
tools:text="userId" />
FrameworkModeByJavaProject/app/src/main/res/layout/activity_mvvm.xml
这种写法更符合 MVVM 的核心思路:页面只消费适合展示的数据,不在 View 层再做一次二次加工。
如果继续使用 DataBinding,页面初始化方式也要切换成 DataBindingUtil.setContentView,这样才能拿到自动生成的 ActivityMvvmBinding 对象:
ActivityMvvmBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_mvvm);
mViewModel = new ViewModelProvider(this).get(MvvmViewModel.class);
binding.setLifecycleOwner(this);
binding.setViewModel(mViewModel);
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/mvvm/MvvmActivity.java
这几行代码把布局、生命周期和 ViewModel 真正绑定到了一起。之后只要 LiveData 变化,布局表达式就能自动拿到新值。
4.7 使用 BindingAdapter 处理类型兼容
除了在 ViewModel 中做类型转换,DataBinding 还提供了另一种方式来解决控件属性类型不匹配的问题,也就是 BindingAdapter。

它的思路是:当某个控件属性接收到的数据类型和属性原生要求不一致时,可以由自定义适配方法接管这次赋值过程。
例如下面这个文本控件,直接把 viewModel.userId 绑定到 android:text:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:text="@{viewModel.userId}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/tv_nick_name"
tools:text="userId" />
FrameworkModeByJavaProject/app/src/main/res/layout/activity_mvvm.xml
这个时候,android:text 原本期望的是文本类型,而 viewModel.userId 暴露出来的是 Integer。这时就可以定义一个 BindingAdapter,专门负责把 int 转成控件需要的文本。
public class BindingAdapters {
@BindingAdapter("android:text")
public static void mySetText(TextView textView, int value) {
if (value == 0) {
return;
}
textView.setText("我接收到了一个userid = " + value);
}
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/mvvm/BindingAdapters.java
一旦这个适配器存在,DataBinding 在给 TextView 的 android:text 赋值时,就会先看有没有匹配的适配方法可用。如果类型不兼容但能找到这个方法,就会把值交给 mySetText 处理。
同理,ProgressBar 的 android:visibility 也可以通过 BindingAdapter 来接收布尔值,再由适配器内部把它转换成 View.VISIBLE 或 View.INVISIBLE:
@BindingAdapter("android:visibility")
public static void setVisibility(ProgressBar progressBar, boolean show) {
progressBar.setVisibility(show ? View.VISIBLE : View.INVISIBLE);
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/mvvm/BindingAdapters.java
对应的布局绑定仍然保持简单:
<ProgressBar
android:id="@+id/loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{viewModel.showLoading}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
FrameworkModeByJavaProject/app/src/main/res/layout/activity_mvvm.xml
这样一来,布局依然只负责表达“我要绑定哪个状态”,至于具体怎么把状态转成控件可接受的值,则交给 BindingAdapter 统一处理。
4.8 MVVM 的工作流程与优缺点
到这里,MVVM 的完整工作流已经比较清晰了:
- 用户在
View中操作输入框或按钮。 View把事件交给ViewModel。ViewModel调用Model执行数据处理。Model把结果回传给ViewModel。ViewModel把结果整理成LiveData状态。View通过观察或数据绑定自动响应这些状态变化。
相比 MVP,它最大的变化不是“多了一个类”,而是页面更新模式从“主动调用界面方法”变成了“被动观察状态变化”。这会带来几个很明显的优点:
View与Model的耦合进一步降低,页面只关心状态,不关心请求细节。- 数据驱动 UI,减少了大量手动
setText、setVisibility一类重复更新代码。 ViewModel独立于Activity的具体实现,更利于测试与复用。- 当页面状态越来越多时,拆成多个
LiveData字段也更利于维护。
当然,MVVM 也有自己的成本:
LiveData、DataBinding、BindingAdapter都需要额外学习和调试成本。- 布局一复杂,数据绑定表达式和适配器会让问题定位变得更隐蔽。
- 如果状态建模不合理,
ViewModel同样可能变成新的复杂中心。
但从 Android 官方推荐方向和复杂页面可维护性来看,MVVM 确实更适合作为现代 Android 项目的主流架构方案。
5. 相关代码附录
5.1 app 入口与公共请求定义
整个示例工程先通过入口页把三种架构模式串起来,方便在同一个应用里分别验证 MVC、MVP 和 MVVM 的页面行为。下面这份入口页代码只负责跳转,不承担业务逻辑。
package com.ls.frameworkmodebyjavaproject;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn_mvc).setOnClickListener(view ->{
startActivity(new Intent(this, MvcActivity.class));
});
findViewById(R.id.btn_mvp).setOnClickListener(view ->{
startActivity(new Intent(this, MvpActivity.class));
});
findViewById(R.id.btn_mvvm).setOnClickListener(view ->{
startActivity(new Intent(this, MvvmActivity.class));
});
}
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/MainActivity.java
入口布局同样保持极简,只提供三个按钮,分别进入三种架构示例页。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:padding="16dp"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/btn_mvc"
android:text="MVC架构模式"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/btn_mvp"
android:text="MVP架构模式"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/btn_mvvm"
android:text="MVVM架构模式"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
FrameworkModeByJavaProject/app/src/main/res/layout/activity_main.xml
清单文件中注册了入口页和三种架构页面,同时声明了网络权限,保证所有示例请求都能正常执行。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.FrameworkModeByJavaProject"
tools:targetApi="31">
<activity
android:name=".mvvm.MvvmActivity"
android:exported="false" />
<activity
android:name=".mvp.MvpActivity"
android:exported="false" />
<activity
android:name=".mvc.MvcActivity"
android:exported="false" />
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
FrameworkModeByJavaProject/app/src/main/AndroidManifest.xml
app 模块的构建文件则统一放置网络、Lifecycle 与 DataBinding 依赖,保证三种模式都运行在同一套基础设施上。
plugins {
alias(libs.plugins.androidApplication)
}
android {
namespace 'com.ls.frameworkmodebyjavaproject'
compileSdk 34
dataBinding {
enabled = true
}
defaultConfig {
applicationId "com.ls.frameworkmodebyjavaproject"
minSdk 26
targetSdk 34
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation libs.appcompat
implementation libs.material
implementation libs.activity
implementation libs.constraintlayout
testImplementation libs.junit
androidTestImplementation libs.ext.junit
androidTestImplementation libs.espresso.core
implementation 'com.google.code.gson:gson:2.11.0'
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
implementation 'com.squareup.retrofit2:retrofit:2.11.0'
implementation 'com.squareup.retrofit2:converter-gson:2.11.0'
implementation "androidx.lifecycle:lifecycle-livedata:2.6.1"
implementation "androidx.lifecycle:lifecycle-viewmodel:2.6.1"
}
FrameworkModeByJavaProject/app/build.gradle
请求接口和实体类是三种模式共享的数据基础。ApiService 定义了查询用户与登录两个接口,是 Model 层与远端通信的统一入口。
package com.ls.frameworkmodebyjavaproject.reterofit;
public interface ApiService {
@GET("addons/cms/api.user/userInfo")
Call<UserInfoQuery> getUserInfo(
@Query("user_id") String userId,
@Query("type") String type);
@POST("addons/cms/api.login/login")
Call<ResLogin> login(@Body ReqLogin login);
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/reterofit/ApiService.java
登录请求体只承载账号和密码,是 POST 请求提交参数的简单封装。
package com.ls.frameworkmodebyjavaproject.bean;
public class ReqLogin {
private String account;
private String password;
public ReqLogin(String account, String password) {
this.account = account;
this.password = password;
}
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/bean/ReqLogin.java
登录返回体负责承接 token 与 user_id,是登录成功提示与后续用户状态更新的来源。
package com.ls.frameworkmodebyjavaproject.bean;
public class ResLogin {
private int code;
private String msg;
private String time;
private DataBean data;
public static class DataBean {
private String token;
private int user_id;
// 省略 getter、setter 方法
}
// 省略 getter、setter 方法
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/bean/ResLogin.java
用户查询返回体负责承接昵称等嵌套结构,是后面三个页面展示查询结果时共同依赖的数据模型。
package com.ls.frameworkmodebyjavaproject.bean;
public class UserInfoQuery {
private int code;
private String msg;
private String time;
private DataBean data;
public static class DataBean {
private UserBean user;
private ListBean list;
private int archives;
private int comments;
public static class UserBean {
private int id;
private String nickname;
private String bio;
private String avatar;
private String status;
private String url;
}
public static class ListBean {
private int total;
private int per_page;
private int current_page;
private int last_page;
}
}
// 省略 getter、setter 方法
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/bean/UserInfoQuery.java
5.2 MVC 相关代码
MVC 页面布局负责收集查询与登录输入,是 View 层最直接的承载体。
<?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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
tools:context=".mvc.MvcActivity">
<TextView
android:id="@+id/tv_get_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="GET请求:"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/et_user_id"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入要查询的用户id"
android:inputType="number"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_get_label" />
<Button
android:id="@+id/btn_get"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="发起GET请求"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/et_user_id" />
<TextView
android:id="@+id/tv_post_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:text="GET请求:"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/btn_get" />
<EditText
android:id="@+id/et_user_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入用户名"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_post_label" />
<EditText
android:id="@+id/et_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入密码"
android:inputType="textPassword"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/et_user_name" />
<Button
android:id="@+id/btn_post"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="发起POST请求"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/et_password" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Retrofit"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
FrameworkModeByJavaProject/app/src/main/res/layout/activity_mvc.xml
MvcUserModel 是 MVC 中的数据处理中心,负责请求发起与回调结果转发,它对应正文里“把请求逻辑从 Activity 中抽离到 Model”的关键步骤。
package com.ls.frameworkmodebyjavaproject.mvc;
public class MvcUserModel {
private static final String TAG = "MvcUserModel";
UserModelCallback mCallback;
public MvcUserModel(UserModelCallback callback) {
this.mCallback = callback;
}
protected void sendPostRequest(String account, String password) {
if (account == null || account.isEmpty()) {
Log.i(TAG, "sendPostRequest: 用户名不能为空!");
mCallback.onLoginFail(" 用户名不能为空!");
return;
}
if (password == null || password.isEmpty()) {
Log.i(TAG, "sendPostRequest: 密码不能为空!");
mCallback.onLoginFail(" 密码不能为空!");
return;
}
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://titok.fzqq.fun/")
.addConverterFactory(GsonConverterFactory.create())
.build();
ApiService apiService = retrofit.create(ApiService.class);
Call<ResLogin> loginCall = apiService.login(new ReqLogin(account, password));
loginCall.enqueue(new Callback<ResLogin>() {
@Override
public void onResponse(Call<ResLogin> call, Response<ResLogin> response) {
ResLogin resLogin = response.body();
if (response.isSuccessful() && resLogin.getCode() == 1) {
mCallback.onLoginSuccess(resLogin);
} else {
mCallback.onLoginFail(resLogin.getMsg());
}
}
@Override
public void onFailure(Call<ResLogin> call, Throwable throwable) {
mCallback.onLoginFail("网络请求失败");
}
});
}
protected void sendGetRequest(String userId) {
if (userId == null || userId.isEmpty()) {
Log.i(TAG, "sendGetRequest: 查询id不能为空");
return;
}
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://titok.fzqq.fun/")
.addConverterFactory(GsonConverterFactory.create())
.build();
ApiService apiService = retrofit.create(ApiService.class);
Call<UserInfoQuery> queryCall = apiService.getUserInfo(userId, "archives");
queryCall.enqueue(new Callback<UserInfoQuery>() {
@Override
public void onResponse(Call<UserInfoQuery> call, Response<UserInfoQuery> response) {
UserInfoQuery body = response.body();
String nickname = body.getData().getUser().getNickname();
mCallback.onUserInfoQuerySuccess(body);
Log.i(TAG, "onResponse: body " + nickname);
}
@Override
public void onFailure(Call<UserInfoQuery> call, Throwable throwable) {
mCallback.onUserInfoQueryFail("网络请求失败!");
}
});
}
protected interface UserModelCallback {
void onUserInfoQuerySuccess(UserInfoQuery userInfo);
void onUserInfoQueryFail(String errorMessage);
void onLoginSuccess(ResLogin login);
void onLoginFail(String errorMessage);
}
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/mvc/MvcUserModel.java
MvcActivity 同时承担 View 与 Controller 的职责,这正是 MVC 结构特点最集中的体现,也是后面演进到 MVP 的起点。
package com.ls.frameworkmodebyjavaproject.mvc;
public class MvcActivity extends AppCompatActivity
implements View.OnClickListener, MvcUserModel.UserModelCallback {
private static final String TAG = "MvcActivity";
private EditText etUserId;
private EditText etUserName;
private EditText etPassword;
private MvcUserModel mMvcUsmerModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mvc);
mMvcUsmerModel = new MvcUserModel(this);
findViewById(R.id.btn_get).setOnClickListener(this);
findViewById(R.id.btn_post).setOnClickListener(this);
etUserId = findViewById(R.id.et_user_id);
etUserName = findViewById(R.id.et_user_name);
etPassword = findViewById(R.id.et_password);
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_get) {
String userId = etUserId.getText().toString();
mMvcUsmerModel.sendGetRequest(userId);
} else if (v.getId() == R.id.btn_post) {
String account = etUserName.getText().toString();
String password = etPassword.getText().toString();
mMvcUsmerModel.sendPostRequest(account, password);
}
}
@Override
public void onUserInfoQuerySuccess(UserInfoQuery userInfo) {
String nickname = userInfo.getData().getUser().getNickname();
Toast.makeText(MvcActivity.this, "查询到:" + nickname, Toast.LENGTH_SHORT).show();
}
@Override
public void onUserInfoQueryFail(String errorMessage) {
Toast.makeText(MvcActivity.this, errorMessage, Toast.LENGTH_SHORT).show();
}
@Override
public void onLoginSuccess(ResLogin login) {
Toast.makeText(MvcActivity.this, "欢迎你," +
login.getData().getUser_id(), Toast.LENGTH_SHORT).show();
}
@Override
public void onLoginFail(String errorMessage) {
Toast.makeText(MvcActivity.this, errorMessage, Toast.LENGTH_SHORT).show();
}
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/mvc/MvcActivity.java
5.3 MVP 相关代码
MVP 页面布局在 MVC 基础上增加了 loading 控件,它对应正文里“由 Presenter 统一控制页面状态”的演示场景。
<?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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
tools:context=".mvp.MvpActivity">
<TextView
android:id="@+id/tv_get_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="GET请求:"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/et_user_id"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入要查询的用户id"
android:inputType="number"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_get_label" />
<Button
android:id="@+id/btn_get"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="发起GET请求"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/et_user_id" />
<TextView
android:id="@+id/tv_post_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:text="Post请求:"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/btn_get" />
<EditText
android:id="@+id/et_user_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入用户名"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_post_label" />
<EditText
android:id="@+id/et_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入密码"
android:inputType="textPassword"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/et_user_name" />
<Button
android:id="@+id/btn_post"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="发起POST请求"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/et_password" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Retrofit"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<ProgressBar
android:id="@+id/loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
FrameworkModeByJavaProject/app/src/main/res/layout/activity_mvp.xml
MvpUserView 是页面层暴露给 Presenter 的能力清单,它对应正文里“先定义界面能做什么,再由 Presenter 决定何时调用”的那一步。
package com.ls.frameworkmodebyjavaproject.mvp;
public interface MvpUserView {
void showUserInfo(UserInfoQuery info);
void onLoginSuccess(ResLogin login);
void showError(String error);
void showLoading();
void hideLoading();
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/mvp/MvpUserView.java
MvpUserModel 延续数据处理职责,但改成“回调作为方法参数”的方式,为 Presenter 提供更灵活的流程组织能力。
package com.ls.frameworkmodebyjavaproject.mvp;
public class MvpUserModel {
private static final String TAG = "MvcUserModel";
public MvpUserModel() {
}
protected void sendPostRequest(String account, String password, UserLoginCallback callback) {
if (account == null || account.isEmpty()) {
Log.i(TAG, "sendPostRequest: 用户名不能为空!");
callback.onLoginFail(" 用户名不能为空!");
return;
}
if (password == null || password.isEmpty()) {
Log.i(TAG, "sendPostRequest: 密码不能为空!");
callback.onLoginFail(" 密码不能为空!");
return;
}
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://titok.fzqq.fun/")
.addConverterFactory(GsonConverterFactory.create())
.build();
ApiService apiService = retrofit.create(ApiService.class);
Call<ResLogin> loginCall = apiService.login(new ReqLogin(account, password));
loginCall.enqueue(new Callback<ResLogin>() {
@Override
public void onResponse(Call<ResLogin> call, Response<ResLogin> response) {
ResLogin resLogin = response.body();
if (response.isSuccessful() && resLogin.getCode() == 1) {
callback.onLoginSuccess(resLogin);
} else {
callback.onLoginFail(resLogin.getMsg());
}
}
@Override
public void onFailure(Call<ResLogin> call, Throwable throwable) {
callback.onLoginFail("网络请求失败");
}
});
}
protected void sendGetRequest(String userId, UserQueryCallback callback) {
if (userId == null || userId.isEmpty()) {
Log.i(TAG, "sendGetRequest: 查询id不能为空");
return;
}
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://titok.fzqq.fun/")
.addConverterFactory(GsonConverterFactory.create())
.build();
ApiService apiService = retrofit.create(ApiService.class);
Call<UserInfoQuery> queryCall = apiService.getUserInfo(userId, "archives");
queryCall.enqueue(new Callback<UserInfoQuery>() {
@Override
public void onResponse(Call<UserInfoQuery> call, Response<UserInfoQuery> response) {
UserInfoQuery body = response.body();
String nickname = body.getData().getUser().getNickname();
callback.onUserInfoQuerySuccess(body);
Log.i(TAG, "onResponse: body " + nickname);
}
@Override
public void onFailure(Call<UserInfoQuery> call, Throwable throwable) {
callback.onUserInfoQueryFail("网络请求失败!");
}
});
}
protected interface UserQueryCallback {
void onUserInfoQuerySuccess(UserInfoQuery userInfo);
void onUserInfoQueryFail(String errorMessage);
}
protected interface UserLoginCallback {
void onLoginSuccess(ResLogin login);
void onLoginFail(String errorMessage);
}
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/mvp/MvpUserModel.java
MvpUserPresenter 是 MVP 的流程中枢,它对应正文里“显示加载、发请求、处理回调、更新界面”的整套编排逻辑。
package com.ls.frameworkmodebyjavaproject.mvp;
public class MvpUserPresenter {
private final MvpUserView mView;
private MvpUserModel mModel;
public MvpUserPresenter(MvpUserView view) {
mModel = new MvpUserModel();
mView = view;
}
public void getUserInfo(String userId) {
mView.showLoading();
mModel.sendGetRequest(userId, new MvpUserModel.UserQueryCallback() {
@Override
public void onUserInfoQuerySuccess(UserInfoQuery userInfo) {
mView.showUserInfo(userInfo);
mView.hideLoading();
}
@Override
public void onUserInfoQueryFail(String errorMessage) {
mView.showError(errorMessage);
mView.hideLoading();
}
});
}
public void login(String account, String password) {
mView.showLoading();
mModel.sendPostRequest(account, password, new MvpUserModel.UserLoginCallback() {
@Override
public void onLoginSuccess(ResLogin login) {
mView.onLoginSuccess(login);
mView.hideLoading();
}
@Override
public void onLoginFail(String errorMessage) {
mView.showError(errorMessage);
mView.hideLoading();
}
});
}
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/mvp/MvpUserPresenter.java
MvpActivity 在 MVP 中只作为 View 实现类存在,它负责提供 UI 操作方法,并把点击事件交给 Presenter。
package com.ls.frameworkmodebyjavaproject.mvp;
public class MvpActivity extends AppCompatActivity
implements View.OnClickListener, MvpUserView {
private EditText etUserId;
private EditText etUserName;
private EditText etPassword;
private MvpUserPresenter mPresenter;
private ProgressBar loading;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mvp);
mPresenter = new MvpUserPresenter(this);
findViewById(R.id.btn_get).setOnClickListener(this);
findViewById(R.id.btn_post).setOnClickListener(this);
etUserId = findViewById(R.id.et_user_id);
etUserName = findViewById(R.id.et_user_name);
etPassword = findViewById(R.id.et_password);
loading = findViewById(R.id.loading);
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_get) {
String userId = etUserId.getText().toString().trim();
mPresenter.getUserInfo(userId);
} else if (v.getId() == R.id.btn_post) {
String account = etUserName.getText().toString().trim();
String password = etPassword.getText().toString().trim();
mPresenter.login(account,password);
}
}
@Override
public void showUserInfo(UserInfoQuery info) {
String nickname = info.getData().getUser().getNickname();
Toast.makeText(this, "查询到:" + nickname, Toast.LENGTH_SHORT).show();
}
@Override
public void onLoginSuccess(ResLogin login) {
Toast.makeText(this, "欢迎你," +
login.getData().getUser_id(), Toast.LENGTH_SHORT).show();
}
@Override
public void showError(String error) {
Toast.makeText(this, error, Toast.LENGTH_SHORT).show();
}
@Override
public void showLoading() {
loading.setVisibility(View.VISIBLE);
}
@Override
public void hideLoading() {
loading.setVisibility(View.INVISIBLE);
}
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/mvp/MvpActivity.java
5.4 MVVM 相关代码
MVVM 布局是数据绑定的直接落点,它对应正文里“把 ViewModel 暴露给布局,再让控件直接读取状态”的那部分内容。
<?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="viewModel"
type="com.ls.frameworkmodebyjavaproject.mvvm.MvvmViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
tools:context=".mvvm.MvvmActivity">
<TextView
android:id="@+id/tv_get_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="GET请求:"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/et_user_id"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入要查询的用户id"
android:inputType="number"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_get_label" />
<Button
android:id="@+id/btn_get"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="发起GET请求"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/et_user_id" />
<TextView
android:id="@+id/tv_post_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:text="GET请求:"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/btn_get" />
<EditText
android:id="@+id/et_user_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入用户名"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_post_label" />
<EditText
android:id="@+id/et_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入密码"
android:inputType="textPassword"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/et_user_name" />
<Button
android:id="@+id/btn_post"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="发起POST请求"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/et_password" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Retrofit"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<ProgressBar
android:id="@+id/loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{viewModel.showLoading}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_nick_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@={viewModel.nickName}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:text="nickName" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:text="@{viewModel.userId}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/tv_nick_name"
tools:text="userId" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
FrameworkModeByJavaProject/app/src/main/res/layout/activity_mvvm.xml
MvvmUserModel 继续负责数据请求,它和前两种模式的 Model 共享同样的职责边界,对应正文里“数据层职责稳定不变”的部分。
package com.ls.frameworkmodebyjavaproject.mvvm;
public class MvvmUserModel {
private static final String TAG = "MvvmUserModel";
public MvvmUserModel() {
}
protected void sendPostRequest(String account, String password, UserLoginCallback callback) {
if (account == null || account.isEmpty()) {
Log.i(TAG, "sendPostRequest: 用户名不能为空!");
callback.onLoginFail(" 用户名不能为空!");
return;
}
if (password == null || password.isEmpty()) {
Log.i(TAG, "sendPostRequest: 密码不能为空!");
callback.onLoginFail(" 密码不能为空!");
return;
}
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://titok.fzqq.fun/")
.addConverterFactory(GsonConverterFactory.create())
.build();
ApiService apiService = retrofit.create(ApiService.class);
Call<ResLogin> loginCall = apiService.login(new ReqLogin(account, password));
loginCall.enqueue(new Callback<ResLogin>() {
@Override
public void onResponse(Call<ResLogin> call, Response<ResLogin> response) {
ResLogin resLogin = response.body();
if (response.isSuccessful() && resLogin.getCode() == 1) {
callback.onLoginSuccess(resLogin);
} else {
callback.onLoginFail(resLogin.getMsg());
}
}
@Override
public void onFailure(Call<ResLogin> call, Throwable throwable) {
callback.onLoginFail("网络请求失败");
}
});
}
protected void sendGetRequest(String userId, UserQueryCallback callback) {
if (userId == null || userId.isEmpty()) {
Log.i(TAG, "sendGetRequest: 查询id不能为空");
return;
}
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://titok.fzqq.fun/")
.addConverterFactory(GsonConverterFactory.create())
.build();
ApiService apiService = retrofit.create(ApiService.class);
Call<UserInfoQuery> queryCall = apiService.getUserInfo(userId, "archives");
queryCall.enqueue(new Callback<UserInfoQuery>() {
@Override
public void onResponse(Call<UserInfoQuery> call, Response<UserInfoQuery> response) {
UserInfoQuery body = response.body();
String nickname = body.getData().getUser().getNickname();
callback.onUserInfoQuerySuccess(body);
Log.i(TAG, "onResponse: body " + nickname);
}
@Override
public void onFailure(Call<UserInfoQuery> call, Throwable throwable) {
callback.onUserInfoQueryFail("网络请求失败!");
}
});
}
protected interface UserQueryCallback {
void onUserInfoQuerySuccess(UserInfoQuery userInfo);
void onUserInfoQueryFail(String errorMessage);
}
protected interface UserLoginCallback {
void onLoginSuccess(ResLogin login);
void onLoginFail(String errorMessage);
}
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/mvvm/MvvmUserModel.java
MvvmViewModel 是 MVVM 的核心,它把网络结果拆成可观察状态,并负责类型转换、错误透出与加载状态控制,对应正文里“从流程控制转向状态驱动”的整章内容。
package com.ls.frameworkmodebyjavaproject.mvvm;
public class MvvmViewModel extends ViewModel {
private final MvvmUserModel mModel;
private final MutableLiveData<Boolean> mShowLoading = new MutableLiveData<>();
private final MutableLiveData<String> mNickName = new MutableLiveData<>();
private final MutableLiveData<Integer> mUserId = new MutableLiveData<>();
private final LiveData<String> mStrUserId = Transformations.map(mUserId, new Function1<Integer, String>() {
@Override
public String invoke(Integer integer) {
String content = "我查询到了用户id:" + integer;
return content;
}
});
private final MutableLiveData<String> mErrorMsg = new MutableLiveData<>();
public MvvmViewModel() {
mModel = new MvvmUserModel();
}
public LiveData<String> getStrUserId() {
return mStrUserId;
}
public MutableLiveData<String> getErrorMsg() {
return mErrorMsg;
}
public MutableLiveData<Boolean> getShowLoading() {
return mShowLoading;
}
public MutableLiveData<String> getNickName() {
return mNickName;
}
public MutableLiveData<Integer> getUserId() {
return mUserId;
}
public void getUserInfo(String userId) {
mShowLoading.setValue(true);
mModel.sendGetRequest(userId, new MvvmUserModel.UserQueryCallback() {
@Override
public void onUserInfoQuerySuccess(UserInfoQuery userInfo) {
String nickname = userInfo.getData().getUser().getNickname();
mNickName.setValue(nickname);
mShowLoading.setValue(false);
}
@Override
public void onUserInfoQueryFail(String errorMessage) {
mNickName.setValue(null);
mShowLoading.setValue(false);
mErrorMsg.setValue(errorMessage);
}
});
}
public void login(String account, String password) {
mShowLoading.setValue(true);
mModel.sendPostRequest(account, password, new MvvmUserModel.UserLoginCallback() {
@Override
public void onLoginSuccess(ResLogin login) {
int userId = login.getData().getUser_id();
mUserId.setValue(userId);
mShowLoading.setValue(false);
}
@Override
public void onLoginFail(String errorMessage) {
mUserId.setValue(0);
mShowLoading.setValue(false);
mErrorMsg.setValue(errorMessage);
}
});
}
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/mvvm/MvvmViewModel.java
BindingAdapters 负责处理布局属性与状态类型不一致的场景,它对应正文里“用 BindingAdapter 兜住文本和显隐属性转换”的那一步。
package com.ls.frameworkmodebyjavaproject.mvvm;
public class BindingAdapters {
@BindingAdapter("android:text")
public static void mySetText(TextView textView, int value) {
if (value == 0) {
return;
}
textView.setText("我接收到了一个userid = " + value);
}
@BindingAdapter("android:visibility")
public static void setVisibility(ProgressBar progressBar, boolean show) {
progressBar.setVisibility(show ? View.VISIBLE : View.INVISIBLE);
}
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/mvvm/BindingAdapters.java
MvvmActivity 最终只负责初始化绑定、把用户操作交给 ViewModel,以及在仍保留观察者写法的地方响应状态变化,它对应正文里“页面成为状态消费者”的最终落点。
package com.ls.frameworkmodebyjavaproject.mvvm;
public class MvvmActivity extends AppCompatActivity implements View.OnClickListener {
private EditText etUserId;
private EditText etUserName;
private EditText etPassword;
private ProgressBar loading;
private MvvmViewModel mViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMvvmBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_mvvm);
mViewModel = new ViewModelProvider(this).get(MvvmViewModel.class);
binding.setLifecycleOwner(this);
binding.setViewModel(mViewModel);
findViewById(R.id.btn_get).setOnClickListener(this);
findViewById(R.id.btn_post).setOnClickListener(this);
etUserId = findViewById(R.id.et_user_id);
etUserName = findViewById(R.id.et_user_name);
etPassword = findViewById(R.id.et_password);
loading = findViewById(R.id.loading);
mViewModel.getShowLoading().observe(this, new Observer<Boolean>() {
@Override
public void onChanged(Boolean show) {
loading.setVisibility(show ? View.VISIBLE : View.INVISIBLE);
}
});
mViewModel.getUserId().observe(this, new Observer<Integer>() {
@Override
public void onChanged(Integer userId) {
int i = userId.intValue();
if (i > 0) {
Toast.makeText(MvvmActivity.this,
"欢迎你," + i, Toast.LENGTH_SHORT).show();
}
}
});
mViewModel.getNickName().observe(this, new Observer<String>() {
@Override
public void onChanged(String s) {
Toast.makeText(MvvmActivity.this, "查询到:" + s, Toast.LENGTH_SHORT).show();
}
});
mViewModel.getErrorMsg().observe(this, new Observer<String>() {
@Override
public void onChanged(String error) {
Toast.makeText(MvvmActivity.this, error, Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_get) {
String userId = etUserId.getText().toString().trim();
mViewModel.getUserInfo(userId);
} else if (v.getId() == R.id.btn_post) {
String account = etUserName.getText().toString().trim();
String password = etPassword.getText().toString().trim();
mViewModel.login(account, password);
}
}
}
FrameworkModeByJavaProject/app/src/main/java/com/ls/frameworkmodebyjavaproject/mvvm/MvvmActivity.java
更多推荐


所有评论(0)