一种MVVM风格的Android项目架构浅析

简介: 一种MVVM风格的Android项目架构浅析

前几天接触公司一Android项目,刚看代码时,不知道这么多层级的代码都是干嘛的,看着有点儿懵。只有清楚了结构和流程,才能够在浩瀚的代码里游刃有余。


先不管局部是什么,从全局上去看才能把一件事情看清楚。从宏观把握,由整体到局部,这是一种哲学和做事的方法论。就好比盲人摸象,即便再摸也不知道他摸的是一头大象。即使不是盲人,把一常人眼贴上去去摸,也未必分得清那就是大象。只有把他带远点儿从远处看,才看清了,哦,那是一头大象。


古人有句诗:“不识庐山真面目,只缘身在此山中。”,一样的道理,只有从全局把事情理清了,才能思路清晰的把一件事情看清楚。如果一下扎进某个点儿去看,往往容易一叶障目,不见森林。


比如linux操作系统代码,几千万行也有了,如果一头扎进去,盲目的看,敢说几年也看不清头绪。假如从整体去把握,了解整体结构,各个模块的作用和功能,有针对的去看,才能理清思路。相信那些精通linux内核的,不是把源码看够个遍了,而是对结构,对功能有更深入的了解。


以下为按照此方法论对一项目结构做一分析。由于零零散散的业余时间看了点儿,有哪里不对的地方请指正。


何为MVVM?懂web开发的都知道有一种风格叫MVC ,模型,视图,控制器。一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。MVC被独特的发展起来用于映射传统的输入、处理和输出功能在一个逻辑的图形化用户界面的结构中。


它强制性的使应用程序的输入、处理和输出分开。使用MVC应用程序被分成三个核心部件:模型、视图、控制器。它们各自处理自己的任务。最典型的MVC就是JSP + servlet + javabean的模式。


MVVM是Model-View-ViewModel的简写。它本质上就是MVC 的改进版。MVVM 就是将其中的View 的状态和行为抽象化,让我们将视图 UI 和业务逻辑分开。当然这些事 ViewModel 已经帮我们做了,它可以取出 Model 的数据同时帮忙处理 View 中由于需要展示内容而涉及的业务逻辑。


View绑定到ViewModel,然后执行一些命令在向它请求一个动作。而反过来,ViewModel跟Model通讯,告诉它更新来响应UI。这样便使得为应用构建UI非常的容易。往一个应用程序上贴一个界面越容易,外观设计师就越容易使用Blend来创建一个漂亮的界面。同时,当UI和功能越来越松耦合的时候,功能的可测试性就越来越强。


搞懂一件事情前不妨先问个为什么,为什么要MVVM?


只有这样才有搞下去的动力。


我们基于MVC开发完第一版本,然后企业需要迭代2.0版本,并且UI界面变化比较大,业务变动较小,怎么办呢?


当2.0的所有东西都已经评审过后。这个时候,新建布局,然后开始按照新的效果图,进行UI布局。然后还要新建Activity、Fragment把相关逻辑和数据填充到新的View上。


如果业务逻辑比较复杂,需要从Activity、Fragment中提取上个版本的所有逻辑,这个时候自己可能就要晕倒了,因为一个复杂的业务,一个Activity几千行代码也是很常见的。千辛万苦做完提取完,可能还会出现很多bug。


这个时候MVVM就闪亮登场了。


可重用性。你可以把一些视图逻辑放在一个ViewModel里面,让很多view重用这段视图逻辑。

在Android中,布局里可以进行一个视图逻辑,并且Model发生变化,View也随着发生变化。

低耦合。以前Activity、Fragment中需要把数据填充到View,还要进行一些视图逻辑。现在这些都可在布局中完成(具体代码请看后面)


甚至都不需要再Activity、Fragment去findViewById。这时候Activity、Fragment只需要做好的逻辑处理就可以了。


接下来分析下这套代码结构,看看它怎么就是MVVM。


先看下它都用了哪些库,涉及哪些知识。看app目录下的build.gradle文件中的包引用以及Adnroidmanifest文件,


对它用到库和知识点先做到心里有数。比如里面用到了


io.reactivex.rxjava2:rxandroid:2.0.2


compile('com.squareup.okhttp3:logging-interceptor:3.7.0')


compile('com.squareup.retrofit2:retrofit:2.4.0')


android {
    dataBinding {
        enabled = true
    }


大致从这几个就看出,使用了Andoid自带的databing技术,使用了很火的异步框架RxJava,使用了网络库retrofit等等


请看以下代码结构:



它咋就是MVVM的风格呢? 从MainActivity中,看不到findID和 控件事件响应的方法以及界面更新的方法。


在哪实现界面的操作和更新呢?这期中是怎样的一种逻辑?


接着看MainActivity中的OnCreate方法,


  @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        setContentView(R.layout.activity_main);
        writeLinuxParams();
    }


看不出来什么,但是往上层看,因为它是继承自自定义的BaseActivity,那就往BaseActivity找,


public class MainActivity extends BaseActivity implements MainView,IDecoderAcquirer


果然,在这里看到了


@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    context = this;
    if (getLayResId() > 0) {
        setContentView(getLayResId());
    }
    initView();
    registerRxbus();
    initData();
    initEvent();
}


而initView里,进行了Databinding.


@Override
protected void initView() {
    super.initView();
    binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
    contentlayout=binding.contentlayout;
    faceswiping=binding.includeMainQrcode.faceswiping;


接着浏览各个文件夹,大致翻看了下,


activity文件夹,放置各个activity


adpter文件夹,各种适配器类,因为有些控件 listView之类的需要传参为adpter


base文件夹,放一些基础类,供其他类继承,如BaseActivity,BaseView之类的。一般的MainAcity不直接继承系统的Activity,


而是多继承一层,如自己定义的BaseActivity,有好处的。更为灵活和便于一些控制和全局之类的操作。


Bean文件夹,放置一些可以服用的Bean。如MVVM上就需要一些Bean和界面layout上的一一对应。



Bean下面的MVVM就是和界面绑定相关的字段定义。bussiness是和业务相关的一些Bean.


controller文件夹,这个应该是跟控制相关的,放置到这里面了。


dilaog文件夹,用到的各种对话框界面。


setting文件夹,跟设置相关的一些界面(Acticvity)


service文件夹,后台服务线程的一些业务操作。


wige,文件夹,一些自定义或第三方控件


那么跟MVVM相关的,在结构上是如何体现的呢?


涉及以下几个文件夹,mode文件夹,viewmode文件夹,bean下的MVVM文件夹。Ilistenner文件夹。


他们之间的关系是什么样的?如何实现MVVM的?


翻开看代码,


public class MainViewModel implements MainListener, View.OnClickListener, VitualRequestResultListener {
    private String TAG = "MainViewModel";
    public AuxScreenController asController;
    private SysParManager sysParManager;


MainViewModel继承了MainLister接口,做了哪些事情呢?


/**
 * 支付成功后界面刷新
 */
public void refreshPaySucScreen(String name, String outID, long balance, long mainWalletFare, long subWalletFare, long consumeMoney, long mngfare) {
    if (Constant.modelType == 2) {
        inputMoney = opfareFinal;
    }
    mainSimpleBean.cardinfo_name.set(name);
    mainSimpleBean.cardinfo_outid.set(outID);
    mainSimpleBean.cardinfo_pay_state.set("消费明细");
    mainSimpleBean.cardinfo_rl_jiaoyi.set(true);


从中可以大致了解到,MainViewModel这个类,负责把需要显示的内容 传递给 View(layout里绑定的控件,界面显示),且实现了View。OnClieckListener接口,负责接收界面响应。


而项目中的Ilistener文件夹,里面定义了一些接口如MainLister接口提供给MainViewModel继承。这样就相当于 解耦了一层。作为一个桥梁,中间层。把MVVM 分割为 M +V +(桥梁)+VM


V层完成界面绑定,VM层继承了Ilistener,实现了更新界面的接口,M层类里面组合使用了这些接口,把响应的数据传过去。


往下看,


要显示的数据从哪里来呢?又是怎么来的呢?


接下来看model文件夹。模型层。


public class MainModelImpl implements MainModel {
    private String TAG = "MainModelImpl";
    private static String setTime = "";
    private static String setDay = "";
    private static String setWeek = "";
    private boolean timecount = true;
    private StartTime startTime;
    private MainListener listener;
    private WelcomeTimeBean welcomeTimeBean = new WelcomeTimeBean();
    private Observable<String> badRequestObservable;    //收到BAD_REQUUEST报文
    private Observable<WSResponseData> setTimeObservable;
    private Observable<String> netChangeObserver;


从他里面看到了RxBus的身影。且该类组合使用了MainListener的方法,


里面注册了RxBus的消息接收响应。收到订阅的事件后,调用MainListener接口中的方法,去把数据填进去,最终实现了界面上更新的效果。


@Override
    public void initRxbus(ArrayMap<Object, Observable> observables) {
        badRequestObservable = RxBus.get().register(CommonConstant.NOTIFY_REQUEST_FAILED, String.class);
        observables.put(CommonConstant.NOTIFY_REQUEST_FAILED,badRequestObservable);
        badRequestObservable
                .subscribe(new Consumer<String>() {
                    @Override
                    public void accept(String arg0) {
                        if (arg0 != null && !arg0.trim().equals("")) {
                            Log.d(TAG, "收到BAD_REQUEST SEQ=" + com.newcapec.commontools.GsonUtil.GsonString(arg0));
//                            CardConstant.isEnableTheSweep = true;
                            listener.badRequest(arg0);
                            listener.initPayStatus();
                        }
                    }
                });
        setTimeObservable = RxBus.get().register(CommonConstant.NOTIFY_TIME, WSResponseData.class);//获取系统时间
        observables.put(CommonConstant.NOTIFY_TIME,setTimeObservable);
        setTimeObservable.observeOn(AndroidSchedulers.mainThread(), false, 100).subscribe(new Consumer<WSResponseData>() {
            @Override
            public void accept(WSResponseData result) {
                Log.i(TAG, "===校验时间===");
                if (result != null) {


由此可以理出,何为MVVM ? 即 M (model)+ V(视图)  + VM (ViewModel)


从上述工程结构上看,model文件夹即充当了M (model)的角色。里面注册了RxBus,获取数据并对收到的事件消息进行响应。调用VM(ViewModel)中的接口方法,完成对界面数据的更新操作。


viewModel文件夹,充当了VM (ViewModel)层


V层呢?这个应该是在 Bean文件夹的MVVM文件夹中定义的Bean,以及在BaseActivity中完成的DataBanding充当了View层。


至此,MVVM 各个层已经介绍完了。


总结下就是 V层完成界面绑定,VM层继承了Ilistener,实现了更新界面的接口,且负责和界面交互的业务逻辑,M层类里面组合使用了这些接口,注册了RxBus事件总线,把数据源和响应的数据传过去。


理清了各个文件夹的功能和MVVM的结构,代码看起来就清楚多了。


拿以上结构举例,如果界面发生了很大变化,有哪些组件是可以复用的?只需改下跟界面绑定的Bean以及Bean和界面的绑定,


model和viewmodel基本是可以复用的。


大致就这些了,不过发现项目里分层也不是很清晰。


以上仅是该工程的分析,并不一定就是完整意义上的MVVM,关于MVVM,不同人有不同的理解。


总而言之,言而总之,谁能把业务和界面分清楚了,做到逻辑清晰,条理清晰,方便复用,方便维护就是最好的。。


知乎上有这个问题的大讨论,https://www.zhihu.com/question/30976423


我觉得虽然业务复杂多变,但是界面可能更复杂多变。界面耦合在业务里,会给业务功能的复用带来很大的麻烦。


既然要努力的把界面和业务逻辑分开,那么,把业务放在model层里,里面不涉及任何界面更新的东西。且留出供viewmodel层调用获取数据的接口 。而viewmodel层也留出 供model业务层涉及显示需求的接口。让model 层可以调用他来做到更新界面。


做个假设,如果界面更改了,增了些控件,减了几个Button,你有哪些地方要改的?


如果换了个项目,业务差不多,但界面无一丝相似之处,你有哪些要改的?


如果,viewmodel层不涉及任何业务,model层不涉及任何界面。那么,需要改动的地方有:Activity和相应的layout,以及layout对应绑定的Bean。还有viewmodel层的负责跟界面交互的地方。业务model层可以全部照搬过来,


model层留出供viewmodel层获取数据的接口,viewmodel层留出供model层调用的显示。两者相互留出彼此使用的接口。


这样,如果调试界面的人员和业务人员分工,那么,只需根据需要,把 model层留出的获取数据的接口用模拟数据实现了,先供测试。最后,由业务人员再把这些接口用真实获取数据的方法给替换掉。。。


相关文章
|
4天前
|
XML 前端开发 Android开发
Kotlin教程笔记(80) - MVVM架构设计
Kotlin教程笔记(80) - MVVM架构设计
|
7天前
|
存储 前端开发 数据可视化
在实际项目中,如何选择使用 Flux 架构或传统的 MVC 架构
在实际项目中选择使用Flux架构或传统MVC架构时,需考虑项目复杂度、团队熟悉度和性能需求。Flux适合大型、高并发应用,MVC则适用于中小型、逻辑简单的项目。
|
6天前
|
前端开发 JavaScript 测试技术
android做中大型项目完美的架构模式是什么?是MVVM吗?如果不是,是什么?
在 Android 开发中,选择合适的架构模式对于构建中大型项目至关重要。常见的架构模式有 MVVM、MVP、MVI、Clean Architecture 和 Flux/Redux。每种模式都有其优缺点和适用场景,例如 MVVM 适用于复杂 UI 状态和频繁更新,而 Clean Architecture 适合大型项目和多平台开发。选择合适的架构应考虑项目需求、团队熟悉度和可维护性。
30 5
|
6天前
|
安全 搜索推荐 Android开发
深入探索Android与iOS的系统架构差异
【10月更文挑战第29天】 在当今的智能手机市场中,Android和iOS无疑是两大主流操作系统。本文旨在深入探讨这两个系统的架构差异,从底层的操作系统设计到用户界面的呈现,以及它们如何影响了开发者和用户的体验。通过对比分析,我们可以更清晰地理解这两种平台的优势与局限,为开发者在选择开发平台时提供有价值的参考,同时也为用户选择设备提供一定的指导。
22 2
|
10天前
|
存储 Dart 前端开发
flutter鸿蒙版本mvvm架构思想原理
在Flutter中实现MVVM架构,旨在将UI与业务逻辑分离,提升代码可维护性和可读性。本文介绍了MVVM的整体架构,包括Model、View和ViewModel的职责,以及各文件的详细实现。通过`main.dart`、`CounterViewModel.dart`、`MyHomePage.dart`和`Model.dart`的具体代码,展示了如何使用Provider进行状态管理,实现数据绑定和响应式设计。MVVM架构的分离关注点、数据绑定和可维护性特点,使得开发更加高效和整洁。
145 3
|
16天前
|
前端开发 JavaScript 测试技术
Android适合构建中大型项目的架构模式全面对比
Android适合构建中大型项目的架构模式全面对比
33 2
|
17天前
|
存储 前端开发 测试技术
Android kotlin MVVM 架构简单示例入门
Android kotlin MVVM 架构简单示例入门
22 1
|
7天前
|
前端开发 Java 测试技术
android MVP契约类架构模式与MVVM架构模式,哪种架构模式更好?
android MVP契约类架构模式与MVVM架构模式,哪种架构模式更好?
13 0
|
缓存 安全 开发工具
Android 解决bug:Android studio 运行、编译项目时导致电脑死机
Android 解决bug:Android studio 运行、编译项目时导致电脑死机
894 0
|
Android开发
Android Studio在android Emulator中运行的项目黑屏
Android Studio在android Emulator中运行的项目黑屏
748 0
Android Studio在android Emulator中运行的项目黑屏
下一篇
无影云桌面