Android 架构的演进是一个不断解耦、提升可维护性和可测试性的过程,其核心围绕 UI 与逻辑分离、状态管理优化展开。以下结合技术演进路径和典型案例,解析从早期 MVC 到现代 MVVM 的架构变迁:
一、早期 MVC:代码混杂与 "全能 Activity"
架构特点
- 模型(Model) :负责数据存储(如 SQLite)和业务逻辑
- 视图(View) :XML 布局文件定义 UI
- 控制器(Controller) :Activity 同时承担视图渲染和逻辑处理
java
public class MainActivity extends AppCompatActivity {
private int count = 0;
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.mvc_text);
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
count++;
textView.setText("点击了" + count + "次");
}
});
}
}
- 紧密耦合:Activity 同时处理网络请求、数据库操作和 UI 更新
- 状态丢失:屏幕旋转导致数据重置
- 测试困难:依赖 Android 框架难以进行单元测试
二、经典 MVC:引入独立控制器
架构改进
- Model:数据实体和业务逻辑
- View:Activity 作为视图层
- Controller:独立的逻辑处理类
java
public class MainActivity extends AppCompatActivity implements CounterView{
private CounterController controller;
private TextView countText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
countText = findViewById(R.id.count_text);
Button button = findViewById(R.id.increment_btn);
CounterModel model = new CounterModel();
controller = new CounterController(model);
controller.attachView(this);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
controller.increment();
}
});
}
@Override
public void updateView(int count) {
countText.setText(String.valueOf(count));
}
}
java
public class CounterController {
private CounterModel model;
private CounterView view;
public CounterController(CounterModel model) {
this.model = model;
}
public void attachView(CounterView view){
this.view = view;
updateView();
}
public void increment() {
model.increment();
updateView();
}
private void updateView() {
if(view != null){
view.updateView(model.getCount());
}
}
}
java
public class CounterModel {
private int count = 0;
public int getCount() {
return count;
}
public void increment() {
count++;
}
}
csharp
public interface CounterView {
void updateView(int count);
}
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center">
<TextView
android:id="@+id/count_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
android:textSize="30sp" />
<Button
android:id="@+id/increment_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Increment" />
</LinearLayout>
三、MVP:通过接口彻底解耦
架构核心
- Model:数据层(网络 / 数据库)
- View:UI 接口(Activity/Fragment 实现)
- Presenter:逻辑核心,持有 Model 和 View 接口引用
java
public class LoginActivity extends AppCompatActivity implements LoginContract.View {
private EditText etUsername, etPassword;
private Button btnLogin;
private LoginPresenter loginPresenter;
private ProgressBar progressBar;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
initView();
setupPresenter();
}
private void initView() {
etUsername = findViewById(R.id.et_username);
etPassword = findViewById(R.id.et_password);
btnLogin = findViewById(R.id.btn_login);
progressBar = findViewById(R.id.progress_bar);
btnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
User user = getUserInput();
loginPresenter.login(user);
}
});
}
private void setupPresenter() {
loginPresenter = new LoginPresenter(this);
}
@Override
public void showLoading() {
progressBar.setVisibility(View.VISIBLE);
btnLogin.setEnabled(false);
}
@Override
public void hideLoading() {
progressBar.setVisibility(View.INVISIBLE);
btnLogin.setEnabled(true);
}
@Override
public void onLoginSuccess(User user) {
Toast.makeText(this, "欢迎 " + user.getUsername(), Toast.LENGTH_SHORT).show();
// 跳转到主页面,传递用户信息
Intent intent = new Intent(this, MainActivity.class);
intent.putExtra("username", user.getUsername());
startActivity(intent);
finish();
}
@Override
public void onLoginFailed(String error) {
Toast.makeText(this, error, Toast.LENGTH_SHORT).show();
}
@Override
public User getUserInput() {
String username = etUsername.getText().toString().trim();
String password = etPassword.getText().toString();
return new User(username, password);
}
}
java
public class LoginPresenter implements LoginContract.Presenter{
private LoginContract.View view;
public LoginPresenter(LoginContract.View view) {
this.view = view;
}
@Override
public void login(User user) {
if(!user.isValid()){
view.onLoginFailed("用户名和密码不能为空");
return;
}
view.showLoading();
// 模拟网络请求
new android.os.Handler().postDelayed(() -> {
view.hideLoading();
// 验证登录凭据
if (user.validateCredentials()) {
view.onLoginSuccess(user);
} else {
view.onLoginFailed("用户名或密码错误");
}
}, 1500);
}
}
java
public interface LoginContract {
interface View {
void showLoading();
void hideLoading();
void onLoginSuccess(User user);
void onLoginFailed(String error);
User getUserInput();
}
interface Presenter {
void login(User user);
}
}
java
public class User {
private String username;
private String password;
public User(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
// 验证用户信息
public boolean isValid() {
return username != null && !username.isEmpty() &&
password != null && !password.isEmpty();
}
// 验证登录凭据
public boolean validateCredentials() {
return "admin".equals(username) && "123456".equals(password);
}
}
ini
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="24dp"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="用户登录"
android:textSize="24sp"
android:textStyle="bold"
android:layout_marginBottom="32dp" />
<EditText
android:id="@+id/et_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="用户名"
android:layout_marginBottom="16dp" />
<EditText
android:id="@+id/et_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="密码"
android:inputType="textPassword"
android:layout_marginBottom="24dp" />
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone" />
<Button
android:id="@+id/btn_login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="登录" />
</LinearLayout>
四、MVVM:数据驱动的响应式架构
架构革新
- Model:数据层(Repository 模式)
- View:XML 布局 + DataBinding
- ViewModel:持有 LiveData,与 View 双向绑定
java
public class LoginActivity extends AppCompatActivity {
private EditText etUsername, etPassword;
private Button btnLogin;
private ProgressBar progressBar;
private LoginViewModel loginViewModel;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
initView();
setupViewModel();
setupObservers();
}
private void initView() {
etUsername = findViewById(R.id.et_username);
etPassword = findViewById(R.id.et_password);
btnLogin = findViewById(R.id.btn_login);
progressBar = findViewById(R.id.progress_bar);
btnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
loginViewModel.login(new User(etUsername.getText().toString(), etPassword.getText().toString()));
}
});
}
private void setupViewModel() {
loginViewModel = new ViewModelProvider(this).get(LoginViewModel.class);
}
private void setupObservers() {
loginViewModel.getIsloading().observe(this,isLoading -> {
if(isLoading ){
progressBar.setVisibility(View.VISIBLE);
btnLogin.setEnabled(false);
} else {
progressBar.setVisibility(View.GONE);
btnLogin.setEnabled(true);
}
});
loginViewModel.getLoginResult().observe(this,loginResult -> {
if(loginResult){
// 登录成功
Toast.makeText(this, "登录成功", Toast.LENGTH_SHORT).show();
startActivity(new Intent(this, MainActivity.class));
finish();
}
});
loginViewModel.getErrorMessage().observe(this,errorMessage -> {
if(errorMessage != null){
Toast.makeText(this, errorMessage, Toast.LENGTH_SHORT).show();
}
});
}
}
java
public class LoginViewModel extends ViewModel {
private LoginRepository loginRepository;
private MutableLiveData<Boolean> isloading = new MutableLiveData<>();
private MutableLiveData<Boolean> loginResult = new MutableLiveData<>();
private MutableLiveData<String> errorMessage = new MutableLiveData<>();
public LoginViewModel(){
loginRepository = new LoginRepository();
isloading.setValue(false);
}
public void login(User user){
if(!user.isValid()){
errorMessage.setValue("用户名或密码不能为空");
return;
}
isloading.setValue(true);
loginRepository.login(user).observeForever(new Observer<Boolean>() {
@Override
public void onChanged(Boolean success) {
isloading.setValue(false);
if(success){
loginResult.setValue(true);
} else {
errorMessage.setValue("用户名或密码错误");
}
loginRepository.login(user).removeObserver(this);
}
});
}
public LiveData<Boolean> getIsloading() {
return isloading;
}
public LiveData<Boolean> getLoginResult() {
return loginResult;
}
public LiveData<String> getErrorMessage() {
return errorMessage;
}
}
java
public class LoginRepository {
public LiveData<Boolean> login(User user) {
MutableLiveData<Boolean> result = new MutableLiveData<>();
// 模拟网络请求
new Handler().postDelayed(() -> {
// 简单验证逻辑
boolean success = "admin".equals(user.getUsername()) &&
"123456".equals(user.getPassword());
result.setValue(success);
}, 1500);
return result;
}
}
java
public class User {
private String username;
private String password;
public User(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public boolean isValid() {
return username != null && !username.isEmpty() &&
password != null && !password.isEmpty();
}
}
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="24dp"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="用户登录"
android:textSize="24sp"
android:textStyle="bold"
android:layout_marginBottom="32dp" />
<EditText
android:id="@+id/et_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="用户名"
android:layout_marginBottom="16dp" />
<EditText
android:id="@+id/et_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="密码"
android:inputType="textPassword"
android:layout_marginBottom="24dp" />
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone" />
<Button
android:id="@+id/btn_login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="登录" />
</LinearLayout>
-
Model (User) :
- 封装用户数据
- 提供数据验证方法
-
Repository:
- 数据获取层,负责从网络、数据库等获取数据
- 返回 LiveData 供 ViewModel 观察
-
ViewModel:
- 管理 UI 相关的数据
- 通过 LiveData 暴露状态给 View
- 处理业务逻辑,但不直接引用 View
-
View (Activity) :
- 只负责 UI 显示和用户交互
- 观察 ViewModel 的 LiveData 并更新 UI
- 不包含任何业务逻辑
MVVM 优势:
-
数据驱动 UI:
- UI 自动响应数据变化,无需手动更新
- 通过 LiveData 实现数据绑定
-
更好的生命周期管理:
- ViewModel 独立于 Activity 生命周期
- 屏幕旋转等配置变化时数据不会丢失
-
更清晰的职责分离:
- View 只关心显示,ViewModel 只关心数据,Model 只关心数据结构
-
易于测试:
- ViewModel 可以独立于 Android 组件进行测试
- Repository 可以模拟数据进行测试
-
减少内存泄漏:
- LiveData 具有生命周期感知能力,自动管理观察者