首语
- Android项目开发中,尤其大型的项目中,模块内部的高聚合和模块间的低耦合显得很是重要。为此我们需要选择一种框架模式,Android通常使用到的有MVC,MVP和MVVM。通过框架模式设计的项目能够极大提高开发效率,提高项目的可维护性和可扩展性,同时在模块测试和Bug处理上也有很大便利。
需求(查询用户账号信息)
- 用户输入账号,点击按钮可进行查询账号信息,如果查询数据成功,则将数据展示在页面上;如果查询失败,则在页面上提示获取数据失败。
不使用框架
- Activity主要实现的功能有获取用户输入的信息、展示获取信息成功页面、展示获取信息失败页面、查询用户数据和业务逻辑。
private String getUserInput(){//获取用户输入的信息
return etinput.getText().toString().trim();
}
private void showSuccessPage(Account account){//展示获取信息成功页面
tvinformation.setText("用户账号:"+account.getName()+"用户等级:"+account.getLevel());
}
private void showFailedPage(){//展示获取信息失败页面
tvinformation.setText("获取数据失败");
}
private void getAccountData(String accountName, MCallBack callBack){//查询用户数据
Random random = new Random();
boolean isSuccess = random.nextBoolean();
if (isSuccess){
Account account = new Account();
account.setName(accountName);
account.setLevel(100);
callBack.onSuccess(account);
}else {
callBack.onFailed();
}
}
@Override
public void onClick(View view) {
String userInput = getUserInput();
getAccountData(userInput, new MCallBack() {
@Override
public void onSuccess(Account account) {
showSuccessPage(account);
}
@Override
public void onFailed() {
showFailedPage();
}
});
}
public interface MCallBack {
void onSuccess(Account account);
void onFailed();
}
//Account Bean类
private String name;//姓名
private int level;//等级
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
}
MVC
- MVC的全名是Model-View-Controller,是模型(Model)-视图(View)-控制器(Controller)的缩写。Model层负责数据处理(网络请求,SQL等),View层负责Layout、View控件。Controller层负责Activity,Fragment。
需求分析
- Controller层:业务逻辑处理,获取用户输入,展示成功页面,展示失败页面。
- Model层:查询账号数据。
- View层:layout布局。
实现
- 1.将数据的获取与界面的展示分离 。
- 2.解决各层之间通信问题(Activity通知Model获取数据,Model通知Activity更新页面)。
//Model层
public void getAccountData(String accountName, MCallBack callBack){
Random random = new Random();
boolean isSuccess = random.nextBoolean();
if (isSuccess){
Account account = new Account();
account.setName(accountName);
account.setLevel(100);
callBack.onSuccess(account);
}else {
callBack.onFailed();
}
}
//Controller层
private String getUserInput(){
return etinput.getText().toString().trim();
}
private void showSuccessPage(Account account){
tvinformation.setText("用户账号:"+account.getName()+"用户等级:"+account.getLevel());
}
private void showFailedPage(){
tvinformation.setText("获取数据失败");
}
@Override
public void onClick(View view) {
mMvcModel.getAccountData(getUserInput(), new MCallBack() {
@Override
public void onSuccess(Account account) {
showSuccessPage(account);
}
@Override
public void onFailed() {
showFailedPage();
}
});
}
MVC优缺点
- 优点:一定程度上实现了Model与View的分离,降低了代码的耦合性。
- 缺点:Controller与View难以完全解耦,并且随着项目复杂度的提升,Controller将越来越臃肿。
MVP
- MVP的全名是Model-View-Presenter,是模型(Model)-视图(View)-中间层(Presenter)的缩写。Model层负责数据处理(网络请求、SQL等) ,View层负责用户交互(Activity,Fragment),Presenter层作为View层与Model层中间的纽带,负责处理与用户交互的主要业务逻辑。
需求分析
- Model层:查询账号数据。
- View层:获取用户输入、展示成功界面、展示失败界面。
- Presenter层:业务逻辑处理。
实现
- 1.Activity负责提供View层面的功能(采用实现接口的方式)
- 2.Model层负责提供数据方面的功能。
- Model层与View层不再直接通信,通过Presenter来实现。
//Model层负责提供数据方面的功能
public void getAccountData(String accountName, MCallBack callBack){
Random random = new Random();
boolean isSuccess = random.nextBoolean();
if (isSuccess){
Account account = new Account();
account.setName(accountName);
account.setLevel(100);
callBack.onSuccess(account);
}else {
callBack.onFailed();
}
}
//采用实现接口的方式实现View层面的功能
public interface MVPView {
String getUserInput();
void showSuccessPage(Account account);
void showFailedPage();
}
//中间层Presenter
private MVPView mMVPView;
private MVPModel mMVPModel;
public MVPPresenter(MVPView mMVPView) {
this.mMVPView = mMVPView;
mMVPModel = new MVPModel();
}
public void getData(String accountName){
mMVPModel.getAccountData(accountName, new MCallBack() {
@Override
public void onSuccess(Account account) {
mMVPView.showSuccessPage(account);
}
@Override
public void onFailed() {
mMVPView.showFailedPage();
}
});
}
//Activity中Model与View不再直接通信,通过Presenter来实现。继承View接口层
@Override
public void onClick(View view) {
mMVPPresenter.getData(getUserInput());
}
@Override
public String getUserInput() {
return etinput.getText().toString().trim();
}
@Override
public void showSuccessPage(Account account) {
tvinformation.setText("用户账号:"+account.getName()+"用户等级:"+account.getLevel());
}
@Override
public void showFailedPage() {
tvinformation.setText("获取数据失败");
}
MVP优缺点
- 优点:解决了MVC中Controller与View过度耦合的的缺点,职责划分明显,更加易于维护。
- 缺点:接口数量多,项目复杂度提高,随着项目复杂度的提高,Presenter将越来越臃肿。
建议
-
1.接口规范化(封装父类接口以减少接口的使用量)
-
2.使用MVP插件自动生成MVP的代码
-
3.对于一些简单的页面,可以选择不使用框架。
-
4.根据项目复杂度,部分模块可以选择不使用接口。
MVC与MVP的区别
- 1.Model与View不再直接进行通信,而是通过中间层Presenter来实现。
- 2.Activity的功能被简化,不再充当控制器,主要负责View层面的工作。
PS(常见MVP插件)
- MVPHelper
GitHub地址:https://github.com/githubwing/MVPHelper - MVPPlugin
Github地址:https://github.com/yugai/MVPPlugin
MVVM
- MVVM的全名是Model-View-ViewModel,是模型(Model)-视图(View)-数据视图管理器(ViewModel)的缩写。Model层负责数据处理,View层负责Activity,Fragment。VIewModel层负责调用Model,拿到数据更新自身,而View与ViewModel双向绑定,所以View会自动更新。MVVM在MVP的基础上实现了数据视图的绑定(DataBinding),当数据变化时,视图会自动更新;反之,视图发生变化时,数据也会自动更新。
DataBinding
- DataBinding是Google官方发布的一个实现数据绑定的框架(实现数据与视图的双向绑定),它会使我们告别繁琐的findViewById,同时也减少了接口数量。DataBinding可以帮助我们在安卓中更好的实现MVVM模式。
- 官方文档:https://developer.android.google.cn/jetpack/androidx/releases/databinding?hl=zh_cn
使用步骤
1.启用DataBinding
//编辑app目录下的build.gradle文件,添加下面的内容。
android {
...
dataBinding {
enabled = true
}
}
2.修改布局文件为DataBinding布局
- 选中布局文件根布局(如LinearLayout),按alt+enter,点击Convert to data binding layout,
布局文件转化为DataBinding布局。它最外层是layout,里面包含data和我们的原始布局。 - 在data中我们可以声明对象,然后我们就可以在布局文件中使用对象。@={}表示双向绑定(具体见代码)
<?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="account"
type="com.yhj.framedemo.bean.Account" />
<variable
name="activity"
type="android.app.Activity" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".databinding.DemoActivity">
<TextView
android:id="@+id/tv_level"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="80dp"
android:text="@{account.name+'|'+account.level}" />
<Button
android:id="@+id/bt_level"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="80dp"
android:onClick="@{activity.onClick()}"
android:text="等级+1" />
</LinearLayout>
</layout>
- 生成DataBinding布局以后,setContentView(view)也要发生改变,使用DataBindingUtil.setContentView,同时使用binding省略了findViewById。
ActivityMvvmBinding binding =
DataBindingUtil.setContentView(this, R.layout.activity_mvvm);
binding.etInput.setText("省略findViewById()");
3.数据绑定
- 让Bean类Account继承BaseObservable,为了使数据发生变化视图自动更新,在getLevel()上加注解Binable,在setLevel()中notifyPropertyChanged(BR.level)。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_demo);
account = new Account();
account.setName("Tom");
account.setLevel(100);
binding.setAccount(account);
binding.setActivity(this);
}
public void onClick(View view){
Toast.makeText(this, "点击了!", Toast.LENGTH_SHORT).show();
int level = account.getLevel();
account.setLevel(level+1);
//binding.setAccount(account);
}
public class Account extends BaseObservable {
private String name;//姓名
private int level;//等级
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Bindable
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
notifyPropertyChanged(BR.level);
}
}
需求分析
- Model层:查询账号数据。
- View层:获取用户输入,展示成功界面,展示失败界面。
- ViewModel层:业务逻辑处理,数据更新。
实现
- 提供View,ViewModel以及Model三层。
- 将布局修改为DataBinding布局
- View与ViewModel之间通过DataBinding进行通信。
- 获取数据展示在界面上。
//首先是DataBinding布局
<?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.yhj.framedemo.mvvm.MVVMViewModel"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".mvvm.MVVMActivity">
<EditText
android:hint="请输入要查询的账号"
android:text="@={viewModel.userInput}"
android:layout_marginTop="20dp"
android:id="@+id/et_input"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:layout_gravity="center"
android:layout_marginTop="80dp"
android:id="@+id/bt_get"
android:onClick="@{viewModel.getData}"
android:text="获取账号信息"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:layout_gravity="center"
android:layout_marginTop="80dp"
android:id="@+id/tv_information"
android:text="@{viewModel.result}"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</layout>
//Model层数据处理
public void getAccountData(String accountName, MCallBack callBack){
Random random = new Random();
boolean isSuccess = random.nextBoolean();
if (isSuccess){
Account account = new Account();
account.setName(accountName);
account.setLevel(100);
callBack.onSuccess(account);
}else {
callBack.onFailed();
}
}
//ViewModel层
public class MVVMViewModel extends BaseObservable {
private String result;
private String userInput;
@Bindable
public String getUserInput() {
return userInput;
}
public void setUserInput(String userInput) {
this.userInput = userInput;
notifyPropertyChanged(BR.userInput);
}
private final MVVMModel mvvmModel;
private MVVMModel mvvmModel1;
private ActivityMvvmBinding binding;
@Bindable
public String getResult() {
return result;
}
public void setResult(String result) {
this.result = result;
notifyPropertyChanged(BR.result);
}
//一般需要传入Application对象,方便在ViewModel中使用Application
//比如SharedPreference
public MVVMViewModel(Application application) {
mvvmModel = new MVVMModel();
}
public void getData(View view) {
mvvmModel.getAccountData(userInput, new MCallBack() {
@Override
public void onSuccess(Account account) {
String info = account.getName() + "|" + account.getLevel();
setResult(info);
}
@Override
public void onFailed() {
setResult("获取失败!!!");
}
});
}
}
//View层Activity
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMvvmBinding binding =
DataBindingUtil.setContentView(this, R.layout.activity_mvvm);
MVVMViewModel mvvmViewModel = new MVVMViewModel(getApplication());
binding.setViewModel(mvvmViewModel);
}
- View可以向ViewModel传递信息,ViewModel改变数据通知View自动更新,但是有些功能我们要在Activity中实现(比如点击按钮获取应用权限),ViewModel如何通知View呢?要实现这个功能,我们可以让ViewModel执有Activity的引用,或者借用EventBus。对于MVVM模式来说,我们可以使用LiveData+ViewModel来实现。
LiveData
- LiveData是一个可以被观察的数据持有者,它可以通过添加观察者的方式来让其他组件观察他的变更。LiveData遵从应用程序的生命周期(如果LiveData的观察者已经是销毁状态,LiveData就不会通知该观察者)。
- 官方文档:https://developer.android.google.cn/reference/android/arch/lifecycle/LiveData?hl=zh-cn
MVVM优缺点
- 优点:实现了数据与视图的双向绑定,极大的简化代码。
- 缺点:bug难以调试,并且DataBinding目前还存在一些编译问题。
总结
- Android开发中,一直在用MVC,甚至可以说没有使用,一些大型项目随着不断的迭代,出现了许多问题,推出了MVP,再到现在主推的MVVM。架构演进代码质量也更高,但学习难度也增大。
- Android开发语言,一直在用Java,如今又推出了Kotlin语言,已经成为Android官方支持开发语言。以及跨平台开发方案Flutter使用的Dart语言,想想哇,Android之路漫漫啊,还有许多东西要学习使用。需要一直提升自己,做未来的T型人才。
- 项目源码:FrameDemo