Android 开发架构
文章目录
- MVC
- MVP
- MVVM
- 总结
- Clean Archtechure(MVVM + Clean Architecture)
- Domain 层(核心逻辑,不依赖 Android)
- 1)Entity:业务对象
- 2)Repository 接口(抽象数据访问)
- 3)UseCase(业务动作)
- Data 层(具体实现 Repository)
- Presentation 层(UI)
- ViewModel
- UI
MVC
- M:Model,数据模块,提供数据。(它不仅是一个 entity,也是包含获取数据的)
- V:View,UI 模块,视图层。
- C:Controller,控制模块,相当于 Activity。
它的特点是,Activity 同时控制 View 和 Model,所有的逻辑都在 Activity 中,太臃肿,不利于测试与维护。仅仅适合简单的 Demo。
// Model:数据类
public class User {private String name;public User(String name) { this.name = name; }public String getName() { return name; }public void setName(String name) { this.name = name; }
}// View + Controller:Activity同时控制UI与逻辑
public class MainActivity extends AppCompatActivity {private EditText etName;private TextView tvResult;private Button btnSave;private User user; // Model@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);etName = findViewById(R.id.etName);tvResult = findViewById(R.id.tvResult);btnSave = findViewById(R.id.btnSave);user = new User("Default");btnSave.setOnClickListener(v -> {// Controller逻辑直接写在ActivityString newName = etName.getText().toString();user.setName(newName);tvResult.setText("Hello, " + user.getName());});}
}
MVP
- P:Presenter,演示层,连接 View 和 Model,处理业务逻辑,更新 View。
这一层主要出现的目的就是,让 Activity 中只提供对 UI 的操作方式;让 Model 提供对数据的操作的逻辑;Presenter 调用上面两者,实现具体的业务逻辑。(最终调用 Presenter 的地方还是在 Activity 中,要喂给它 ViewImpl,但逻辑是在 Presenter 中做的)
这么做的好处就是不用在 Activity 里实现逻辑了,而是放在 Presenter 中,解耦。而且 Presenter 可以进行单元测试。(单元测试要求:在普通 JVM 环境运行、不依赖 Android Framework。但 Activity,需要系统 Context、有 UI 生命周期、依赖 View 树等,没法写单元测试)
// 定义契约接口(约定View与Presenter的职责)
interface UserContract {interface View {void showUserName(String name);}interface Presenter {void onSaveButtonClicked(String input);}
}
// Model
class UserModel {private String name = "Default";public void setName(String name) { this.name = name; }public String getName() { return name; }
}
// Presenter(中间层)
class UserPresenter implements UserContract.Presenter {private final UserContract.View view;private final UserModel model;public UserPresenter(UserContract.View view) {this.view = view;this.model = new UserModel();}@Overridepublic void onSaveButtonClicked(String input) {model.setName(input);view.showUserName("Hello, " + model.getName());}
}
// Activity只负责展示,不写逻辑
public class MainActivity extends AppCompatActivity implements UserContract.View {private EditText etName;private TextView tvResult;private Button btnSave;private UserPresenter presenter;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);etName = findViewById(R.id.etName);tvResult = findViewById(R.id.tvResult);btnSave = findViewById(R.id.btnSave);presenter = new UserPresenter(this);btnSave.setOnClickListener(v ->presenter.onSaveButtonClicked(etName.getText().toString()));}@Overridepublic void showUserName(String name) {tvResult.setText(name);}
}
MVVM
- VM:ViewModel,连接 View 与 Model,通过 LiveData(还可以别的方式)让数据自动驱动 UI 更新
可以看到,上面 MVP 会有大量的 View 接口,Presenter 会大量回调 View 接口。
比起 MVP,MVVM 利用 LiveData 中 observe() 的特性,不再需要 View 接口,不在需要手动回调,把对 UI 的操作又放回了 Activity 中,Activity 中可以自动观察数据变化,然后执行相应 UI 操作,减少了回调接口的这种方式。
// Model
public class User {public MutableLiveData<String> name = new MutableLiveData<>("Default");
}
// ViewModel
public class UserViewModel extends ViewModel {private final User user = new User();public LiveData<String> getName() { return user.name; }public void updateName(String newName) {user.name.setValue(newName);}
}
// Activity(观察 LiveData)
public class MainActivity extends AppCompatActivity {private EditText etName;private TextView tvResult;private Button btnSave;private UserViewModel viewModel;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);etName = findViewById(R.id.etName);tvResult = findViewById(R.id.tvResult);btnSave = findViewById(R.id.btnSave);viewModel = new ViewModelProvider(this).get(UserViewModel.class);// View 自动响应数据变化viewModel.getName().observe(this, name -> {tvResult.setText("Hello, " + name);});btnSave.setOnClickListener(v ->viewModel.updateName(etName.getText().toString()));}
}
总结
- MVC:对 View、Model 的操作都放在了Activity 中(Controller + View),逻辑严重耦合。
- MVP:Activity 只负责 UI,提供 ViewImpl(View 相应的操作) 给 Presenter,在 Presenter 中完成对 View 和 Model 的操作逻辑。也就是比如 Activity 触发一个点击事件,Activity 直接调用 Presenter 的接口,剩下都不用管了。
- MVVM:为避免太多 View 接口及其回调,利用 LiveData 动态监听的方式,对 UI 的操作又回到了 Activity 中。当 ViewModel 中相应的值发生变化,Activity 会监听到,然后做相应的 UI 更新。比起 MVP,也就是 Presenter 不用在处理回调了,只需处理相应的业务逻辑。
Clean Archtechure(MVVM + Clean Architecture)
以上是最传统的 Android 架构方式,在我们公司中又引入了 Clean Archtechure 的概念。对于 Android,看向右下这条线就可以了,这里的 Presenter 是 MVP 中的写法,而我下面的例子是基于 MVVM 的。

Clean Architecture 最根本的规则是:源代码依赖关系必须只指向内层,而不能指向外层。内层代码不应该知道任何关于外层代码的事情。
在 Android 开发中,Clean Architecture 通常被具体化为三个主要层次:
- Domain Layer(领域层)
- Entity:业务对象,不依赖 Android。
- Repository 接口:只定义数据获取规则,不关心数据来源。
- UseCase:封装业务动作,UI 只调用 UseCase。
- Data Layer(数据层)
- 实现 Repository 接口,访问实际数据源。可以随时替换数据来源,而 Domain 或者 Presentation 不需要任何修改。
- Presentation Layer(表现层)
- ViewModel,调用 UseCase,不直接访问底层数据源。
- Fragment / Activity,根据 ViewModel 的回调,进行 UI 变更。
下面介绍一个例子,目录结构:
clean_arch_example
│
├── domain
│ ├── entity
│ │ └── LoginResult.java
│ ├── repository
│ │ └── UserRepository.java
│ └── usecase
│ └── LoginUseCase.java
│
├── data
│ └── UserRepositoryImpl.java
│
└── presentation├── LoginViewModel.java└── LoginFragment.java
Domain 层(核心逻辑,不依赖 Android)
1)Entity:业务对象
public class LoginResult {private final boolean success;private final String message;public LoginResult(boolean success, String message) {this.success = success;this.message = message;}public boolean isSuccess() { return success; }public String getMessage() { return message; }
}
2)Repository 接口(抽象数据访问)
在 Domain 层里只定义规则,不关心数据从哪里来。
public interface UserRepository {LoginResult login(String username, String password);
}
3)UseCase(业务动作)
UseCase 是业务逻辑聚合点,Presentation 永远只和 UseCase 交互。
比如执行登录逻辑
public class LoginUseCase {private final UserRepository repository;public LoginUseCase(UserRepository repository) {this.repository = repository;}public LoginResult execute(String username, String password) {if (username.isEmpty() || password.isEmpty()) {return new LoginResult(false, "用户名或密码不能为空");}return repository.login(username, password);}
}
Data 层(具体实现 Repository)
Data 层可以换成,Retrofit 网络请求、数据库、文件等,但 Domain 与 Presentation 层都不需要改。而且也更方便 Mock 测试。
这里模拟登录数据来自“后台服务器”。
public class UserRepositoryImpl implements UserRepository {@Overridepublic LoginResult login(String username, String password) {// 模拟网络请求if ("admin".equals(username) && "123456".equals(password)) {return new LoginResult(true, "登录成功!");}return new LoginResult(false, "用户名或密码错误");}
}
Presentation 层(UI)
ViewModel
P 层使用 ViewModel 的方式,这也是为什么我在标题后面加了“(MVVM + Clean Architecture)”。当然也可以使用 MVP 中 Presenter 呢种方式。
public class LoginViewModel extends ViewModel {private final MutableLiveData<LoginResult> loginResultLiveData = new MutableLiveData<>();private final LoginUseCase loginUseCase;public LoginViewModel() {// Data层UserRepository repository = new UserRepositoryImpl();// Domain层UseCasethis.loginUseCase = new LoginUseCase(repository);}public LiveData<LoginResult> getLoginResult() {return loginResultLiveData;}public void onLoginButtonClicked(String username, String password) {LoginResult result = loginUseCase.execute(username, password);loginResultLiveData.postValue(result);}
}
UI
public class LoginFragment extends Fragment {private EditText editUsername;private EditText editPassword;private Button btnLogin;private TextView tvResult;private LoginViewModel viewModel;@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {View view = inflater.inflate(R.layout.fragment_login, container, false);editUsername = view.findViewById(R.id.edit_username);editPassword = view.findViewById(R.id.edit_password);btnLogin = view.findViewById(R.id.btn_login);tvResult = view.findViewById(R.id.tv_result);setupViewModel();setupObserver();setupListener();return view;}private void setupViewModel() {viewModel = new ViewModelProvider(this).get(LoginViewModel.class);}private void setupObserver() {viewModel.getLoginResult().observe(getViewLifecycleOwner(), result -> {if (result != null) {tvResult.setText(result.getMessage());}});}private void setupListener() {btnLogin.setOnClickListener(v -> {String username = editUsername.getText().toString().trim();String password = editPassword.getText().toString().trim();viewModel.login(username, password);});}
}
