生命不必每时每刻都要冲刺,低沉时就当是放一个悠长假期。 - 北川悦吏子/编剧
GitHub地址:ProjectPatternStudy
基本Android项目都采用MVC、MVP、MVVM架构,个人认为软件架构没有绝对的优劣之分,大家都各有利弊。
- 如果页面比较单一,采用MVC也未尝不可;
- 如果需要稳定性高,解耦性强就可以选用MVP,使M层与V层分离,结构更清晰;
- 如果想尝鲜(其实已经有段时间了),少写接口,高效,也可以使用MVVM;
阮一峰《MVC,MVP 和 MVVM 的图示》总结的非常简练,这里相当于扩展了一下,对于不太懂的人可能会用处更大。
MVP-databinding:是使用MVP架构,但是布局使用databinding设置值,也是行之有效的一种,也可以满足你的需求。
MVC
Model-View-Controller,最常见的软件架构之一。
- 视图(View):用户界面。
- 控制器(Controller):业务逻辑
- 模型(Model):数据保存
如Avtivity
里的一个点击事件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| * 将业务逻辑封装在Model里, 但C(Activity)层可以和Model直接交互, 交互完后根据显示结果来调整V层(如 显示数据) */ EssayModel essayModel = new EssayModel(LoadDataActivity.this); essayModel.getEssay(3, new EssayModel.OnEssayListener() { @Override public void onSuccess(List<Essay> list) { * 直接使用list,得到List的逻辑都放在mode层 */ if (list != null && list.get(0) != null) { tvViewUpdata.setText("MVC 更新数据: " + list.get(0).getTitle()); } } @Override public void onError() { } });
|
如果一个页面比较简单,只有简单的几个操作,也不会经常去改可以使用此方式;如果页面逻辑比较复杂,接口请求都有好几个,那么不建议使用MVC,因为代码会全部堆积在一个Activity里面,会显得非常之冗余。
MVP
MVP 模式将 Controller 改名为 Presenter,同时改变了通信方向。
通过P层将Model层与View层解耦,同时P与V、P与M可以相互通信。
下面举个登录的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
| public class UserLoginActivity extends AppCompatActivity implements IUserLoginView { @BindView(R.id.et_username) EditText etUsername; @BindView(R.id.et_password) EditText etPassword; @BindView(R.id.bt_login) Button btLogin; @BindView(R.id.bt_clear) Button btClear; @BindView(R.id.progress) ProgressBar progress; @BindView(R.id.activity_user_login) RelativeLayout activityUserLogin; private UserLoginPresenter userLoginPresenter = new UserLoginPresenter(this); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_user_login); ButterKnife.bind(this); setTitle("用户登录(MVP)"); initListener(); } private void initListener() { btLogin.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { userLoginPresenter.login(); } }); btClear.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { userLoginPresenter.clear(); } }); } @Override public String getUserName() { return etUsername.getText().toString().trim(); } @Override public String getPassword() { return etPassword.getText().toString().trim(); } @Override public void clearUserName() { etUsername.setText(""); } @Override public void clearPassword() { etPassword.setText(""); } @Override public void showLoading() { progress.setVisibility(View.VISIBLE); } @Override public void hindLoading() { progress.setVisibility(View.GONE); } @Override public void toMainActivity() { Toast.makeText(this, "login success , to MainActivity!", Toast.LENGTH_SHORT).show(); } @Override public void showFailedError() { Toast.makeText(this, "login failed!", Toast.LENGTH_SHORT).show(); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| * Created by jingbin on 2016/11/3. * Presenter是用作Model和View之间交互的桥梁,那么应该有什么方法呢? * 其实也是主要看该功能有什么操作,比如本例,两个操作:login和clear。 */ public class UserLoginPresenter { private IUserLoginView iUserLoginView; private UserBiz mUserBiz; private Handler mHandler = new Handler(); public UserLoginPresenter(IUserLoginView iUserLoginView) { this.iUserLoginView = iUserLoginView; this.mUserBiz = new UserBiz(); } public void login() { iUserLoginView.showLoading(); mUserBiz.login(iUserLoginView.getUserName(), iUserLoginView.getPassword(), new OnLoginListener() { @Override public void loginSuccess(User user) { mHandler.post(new Runnable() { @Override public void run() { iUserLoginView.toMainActivity(); iUserLoginView.hindLoading(); } }); } @Override public void loginFailed() { mHandler.post(new Runnable() { @Override public void run() { iUserLoginView.hindLoading(); iUserLoginView.showFailedError(); } }); } }); } public void clear() { iUserLoginView.clearUserName(); iUserLoginView.clearPassword(); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public interface IUserLoginView { // login说明了要有用户名、密码,那么对应两个方法: String getUserName(); String getPassword(); void clearUserName(); void clearPassword(); // 再者login是个耗时操作,我们需要给用户一个友好的提示,一般就是操作ProgressBar,所以再两个: void showLoading(); void hindLoading(); // login当然存在登录成功与失败的处理,我们主要看成功我们是跳转Activity,而失败可能是去给个提醒: void toMainActivity(); void showFailedError(); }
|
用户点击登录,触发点击事件,然后通过P层userLoginPresenter
,调用登录的方法login()
,方法里面会通过Model层mUserBiz.login()
去做一些数据请求操作的处理,然后得到相应的数据返回。这里看到Model层的数据处理操作放在P层里,是不与V层直接交互的。
然后M层得到数据后回调,P层根据相应的数据,显示不同的UI,如toMainActivity
,showFailedError
等,这样V层只会出现一些基本的显示逻辑的处理。
MVVM
MVVM 模式将 Presenter 改名为 ViewModel,基本上与 MVP 模式完全一致。
唯一的区别是,它采用双向绑定(data-binding):View的变动,自动反映在 ViewModel,反之亦然。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| * 简单的MVVM + data-binding案例: * 以点击一下按钮然后年龄会+2 为例 * * @author jingbin */ public class ChangeAgeActivity extends AppCompatActivity { private ChangeAgeViewModel viewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActivityChangeAgeBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_change_age); setTitle("MVVM + data-binding"); viewModel = ViewModelProviders.of(this).get(ChangeAgeViewModel.class); binding.setViewModel(viewModel); binding.setButtonname("年龄+2"); viewModel.desc.observe(this, new Observer<String>() { @Override public void onChanged(@Nullable String desc) { Log.e("desc", desc); } }); binding.btAge.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { viewModel.change(); } }); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| * @author jingbin */ public class ChangeAgeViewModel extends AndroidViewModel { final MutableLiveData<String> desc = new MutableLiveData<>(); public final ObservableField<String> age = new ObservableField<>(); public ChangeAgeViewModel(@NonNull Application application) { super(application); age.set(String.valueOf(23)); } void change() { String value = age.get(); if (!TextUtils.isEmpty(value)) { Integer integer = Integer.valueOf(value); age.set(String.valueOf(integer + 2)); desc.setValue("年龄改变:" + age.get()); } } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| <?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <data> <variable name="viewModel" type="com.example.jingbin.projectstru.mvvm.ChangeAgeViewModel" /> <variable name="buttonname" type="String" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="25dp"> <TextView android:id="@+id/tv_mvvm" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="年龄" android:textColor="@color/colorPrimary" /> <TextView android:id="@+id/tv_age" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:layout_marginBottom="10dp" android:text="@{viewModel.age}" /> <Button android:id="@+id/bt_age" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{buttonname}" /> </LinearLayout> </layout>
|
可以看出,MVVM比MVP少了对应View的接口文件,这样更简洁了,而且,改变ViewModel里的值,则xml
文件对应的值也会对应改变。如果通过手动setText(),则ViewModel
里的值也会得到改变。通过这一层关系,我们可以通过数据去操控View
里的显示,所以才可以去除掉对应View的接口文件。
MVP-databinding
基本实现了MVC,MVP,MVVM后,我发现它们各自有各自的优缺点。
MVC:简单,单一页面可以实现。但是不利于复杂页面。
MVP:解耦,结构清晰。但文件较多,每一个页面基本要新建P层和V层的文件,同时还会有findViewById操作。
MVVM:解耦,结构相对清晰,文件相对MVP较少。但如果页面显示比较复杂,需要通过多个值去控制页面的显示,或者页面一个值的显示 要通过多种逻辑去处理得到结果,个人感觉还是不太适用。(其中的ViewModel与对应宿主的生命周期相同,从而内存泄漏问题比MVP处理较好这里先不做讨论)
MVP-databinding:
处理方式与MVP相同,只是使用了databinding的优势,databinding节省了类似findViewById和数据绑定的时间,从此代码里就没有findViewById和ButterKnife之类的代码了,而且也不会有通过多个值去控制页面的显示
这样不好操作的情况了。当然文件还是会多一些。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| * MVP + data-binding * * @author jingbin */ public class MvpDataBindingActivity extends AppCompatActivity implements ChangeAgeView { private ActivityMvpDataBindingBinding binding; private ChangeAgePresenter presenter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = DataBindingUtil.setContentView(this, R.layout.activity_mvp_data_binding); setTitle("MVP + data-binding"); presenter = new ChangeAgePresenter(this); binding.setButtonname("年龄+2"); binding.btAge.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { presenter.changeAge(binding.getUser()); } }); } @Override public void showContentView(UserBean user) { binding.setUser(user); } @Override protected void onDestroy() { super.onDestroy(); presenter.clear(); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| * @author jingbin * @date 2019/02/26 */ public class ChangeAgePresenter { private ChangeAgeView changeInterface; private UserModel userModel; public ChangeAgePresenter(ChangeAgeView changeInterface) { this.changeInterface = changeInterface; changeInterface.showContentView(new UserBean("小白", 23)); } * 改变年龄 */ public void changeAge(UserBean myUser) { if (userModel == null) { userModel = new UserModel(); } userModel.changeAge(myUser, 2, new UserModel.ChangeInterface() { @Override public void success(UserBean user) { changeInterface.showContentView(user); } }); } public void clear() { userModel = null; } }
|
参考资料
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| projectstru ├─ MainActivity.java ├─ mvc │ ├─ LoadDataActivity.java │ ├─ bean │ │ └─ Essay.java │ └─ model │ ├─ EssayModel.java │ └─ MainModel.java ├─ mvp │ ├─ UserLoginActivity.java │ ├─ bean │ │ └─ User.java │ ├─ model │ │ ├─ IUserBiz.java │ │ ├─ OnLoginListener.java │ │ └─ UserBiz.java │ ├─ presenter │ │ └─ UserLoginPresenter.java │ └─ view │ └─ IUserLoginView.java ├─ mvpdatabindind │ ├─ MvpDataBindingActivity.java │ ├─ bean │ │ └─ UserBean.java │ ├─ model │ │ └─ UserModel.java │ ├─ presenter │ │ └─ ChangeAgePresenter.java │ └─ view │ └─ ChangeAgeView.java └─ mvvm ├─ ChangeAgeActivity.java └─ ChangeAgeViewModel.java
|
End
对应项目:ProjectPatternStudy 😁
此文仅个人总结,如有不当之处,请留言告知。