总结梳理下 Android 架构设计的几种模式
为什么我们的项目需要使用架构模式?个人理解使用架构模式是为了使我们的代码模块化,达到模块内部高内聚和模块间低耦合的目的,同时开发人员只需要关注相应的业务逻辑点,提高开发效率。当然,使用架构模式不一定是减少了代码量,相反还可能增加代码量;但是它帮你简化了逻辑,提高了扩展性和兼容性。针对不同的项目规模,合理采用或不采用架构模式都是值得考虑的。
MVC MVC(Model-View-Controller)
Model: 模型层。主要是实体类,数据库,网络等存在的层面,Model 将新的数据发送到 View 层,用户得到数据响应。
View: 视图层。一般以 xml 为代表的视图界面,它显示来自 Model 层的数据,同时将用户的点击操作等事件传送到 Controller 层。
Controller 控制层。一般以 Activity/Fragment 为代表,它连接 Model 层和 View 层;它收到 View 层发送过来的事件请求,然后从 Model 层获取数据,并展示给 View 层。
优点 多视图可共用同一模型,重用性高;模块彼此独立清晰,耦合低。
缺点 增加了实现复杂性;视图与控制层联系过于紧密,没有完全解耦;视图层无法组件化,复用比较困难。
MVP Model(Model View Presenter),Model: 逻辑模型,View: 视图模型,Presenter: 接口。
MVP 是 MVC 的升级版,让 Model 层和 View 层完全解耦,代码清晰。
MVP 中的 P,是 Presenter 的含义,和 MVC 比较类似,都是将用户对 View 的操作交付给 Presenter,Presenter 会执行相应的逻辑,在这过程中会操作 Model,当 Model 执行完业务逻辑之后,同样是通过观察者模式把自己的结果传递出去,不过不是告诉 View,而是告诉 Presenter,Presenter 得到消息后,通过 View 的接口更新页面。
这里详细代码可查看源码:AndroidArchitDemo
MVP 代码演示
如图所示,代码结构分为三个模块,每个模块内封装了对应的基类。实际使用时,每个页面都会有一个对应的合约(Contract) 类,里面包含该页面的 View 接口和 Presenter 接口。
比如 userdetail 功能页面,里面就有对应的 合约接口类,页面 UI 类以及 Presenter 实现类。这样做结构会比较清晰,当然每新建一个页面都有对应的合约及 Presenter 类,也是比较麻烦。
优点 Model 层和 View 层完成接耦;View 层可以组件化,便于单元测试。
缺点 接口类增多,实现复杂;View 层和 Presenter 层通过接口交互,一旦 View 层某个 UI 元素修改,需要修改接口,则所有接口事件均需要修改
MVVM
Model: 模型层,负责数据实现和逻辑处理,类似 MVP。
View: 视图层,类似 MVP
ViewModel:创建关联,将 Model 层和 View 层绑定起来,实现双向绑定。
也就是说,MVVM 把 View 和 Model 的同步逻辑自动化了。以前 Presenter 负责的 View 和 Model 同步不再手动地进行操作,而是交由框架所提供的 Binder 进行负责。只需要告诉 Binder,View 显示的数据对应的是 Model 哪一部分即可。
Android 官方推出的 MVVM 的 DataBinding,便是一个双向绑定的库。
MVVM 代码演示 启用:build.gradle 中 开启
1 2 3 4 5 6 7 android { ... dataBinding { enabled true } }
Model 层定义相关的 bean 类以及数据获取逻辑,这块没什么特别之处,这里不特殊说明。
View 层即界面布局 xml 和 Activity 中引用
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 <?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"> <data> <variable name="viewModel" type="com.bashellwang.androidarchitdemo.mvvm.viewmodel.SchoolViewModel" /> </data> <android.support.constraint.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_marginTop="50dp" android:id="@+id/tv_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{viewModel.mSchool.toString()}" android:textColor="#000000" android:textSize="16sp" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/tv_desc" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{viewModel.mSchool.description}" android:textColor="#000000" android:layout_marginTop="20dp" android:textSize="16sp" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@+id/tv_name" /> <Button android:id="@+id/btn_get" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="随机查询学校" android:onClick="@{viewModel.onItemClick}" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@+id/tv_desc" /> </android.support.constraint.ConstraintLayout> </layout>
1 2 3 4 5 6 7 8 9 10 public class MvvmDemoActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActivityMvvmDemoBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_mvvm_demo); SchoolViewModel viewModel = new SchoolViewModel(binding); } }
可以看到 View 层只处理 UI 相关的逻辑。
ViewModel 层处理业务相关逻辑,这里继承 BaseObservable,当获取到新数据后调用 notifyChange 即可。因为数据和 UI 已经绑定了,所以调用后相应的控件会自动更新 UI。
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 public class SchoolViewModel extends BaseObservable { private static final String TAG = "SchoolViewModel"; public School mSchool; private ActivityMvvmDemoBinding mBinding; private SchoolModelDao mSchoolDao; public SchoolViewModel(ActivityMvvmDemoBinding demoBinding) { this.mBinding = demoBinding; // 把自己和 binding 绑定,实现双向绑定 mBinding.setViewModel(this); this.mSchoolDao = new SchoolModelDao(); init(); } // 初始化数据展示 public void init() { mSchool = new School(5, "aa", "aaa", 300); } public void onItemClick(View view) { mSchoolDao.getSchoolById(new Random().nextInt(10), new Callback<School, String>() { @Override public void onSuccess(School school) { Log.e(TAG, "获取新school 成功:" + school.toString()); updateData(school); } @Override public void onFail(String s) { } }); } private void updateData(School school) { mSchool = school; notifyChange(); } }
优点 提高可维护性。解决了 MVP 大量的手动 View 和 Model 同步的问题,提供双向绑定机制。提高了代码的可维护性。
简化测试。因为同步逻辑是交由 Binder 做的,View 跟着 Model 同时变更,所以只需要保证 Model 的正确性,View 就正确。大大减少了对 View 同步更新的测试。
缺点 过于简单的图形界面不适用,或说牛刀杀鸡。
对于大型的图形应用程序,视图状态较多,ViewModel 的构建和维护的成本都会比较高。
数据绑定的声明是指令式地写在 View 的模版当中的,这些内容是没办法去打断点 debug 的。
代码下载 本文相关 demo 源码可见:AndroidArchitDemo
参考资料
googlesamples/android-architecture
todo-mvp
从0到1搭建MVP框架
深入了解MV**模式
一步步带你精通MVP
Android 架构设计 — 关于 MVVM 模式的探讨
Android MVP架构从入门到精通-真枪实弹