一种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层留出的获取数据的接口用模拟数据实现了,先供测试。最后,由业务人员再把这些接口用真实获取数据的方法给替换掉。。。


相关文章
|
1月前
|
设计模式 前端开发 测试技术
Flutter 项目架构技术指南
探讨Flutter项目代码组织架构的关键方面和建议。了解设计原则SOLID、Clean Architecture,以及架构模式MVC、MVP、MVVM,如何有机结合使用,打造优秀的应用架构。
Flutter 项目架构技术指南
|
1月前
|
数据库 Android开发 开发者
构建高性能微服务架构:从理论到实践构建高效Android应用:探究Kotlin协程的优势
【2月更文挑战第16天】 在当今快速迭代和竞争激烈的软件市场中,微服务架构以其灵活性、可扩展性和独立部署能力而受到企业的青睐。本文将深入探讨如何构建一个高性能的微服务系统,涵盖从理论基础到具体实现的各个方面。我们将重点讨论服务拆分策略、通信机制、数据一致性以及性能优化等关键主题,为读者提供一个清晰、实用的指南,以便在复杂多变的业务环境中构建和维护健壮的微服务体系结构。 【2月更文挑战第16天】 在移动开发领域,性能优化和流畅的用户体验是至关重要的。随着技术的不断进步,Kotlin作为一种现代编程语言,在Android开发中被广泛采用,尤其是其协程特性为异步编程带来了革命性的改进。本文旨在深入
241 5
|
3月前
|
机器学习/深度学习 搜索推荐 算法
深度学习推荐系统架构、Sparrow RecSys项目及深度学习基础知识
深度学习推荐系统架构、Sparrow RecSys项目及深度学习基础知识
|
4月前
|
设计模式 前端开发 Java
KnowStreaming系列教程第二篇——项目整体架构分析
KnowStreaming系列教程第二篇——项目整体架构分析
42 0
|
1月前
|
SpringCloudAlibaba Java 持续交付
【构建一套Spring Cloud项目的大概步骤】&【Springcloud Alibaba微服务分布式架构学习资料】
【构建一套Spring Cloud项目的大概步骤】&【Springcloud Alibaba微服务分布式架构学习资料】
156 0
|
4天前
|
传感器 Java Android开发
Android HAL深入探索(1): 架构概述
Android HAL深入探索(1): 架构概述
22 1
|
4月前
|
前端开发 JavaScript 数据库
Flask狼书笔记 | 09_图片社交网站 - 大型项目的架构与需求(2)
9.8 收藏图片 前面已经学习过如何使用关联表来表示多对多关系,缺点是只能表示关系,不能存储数据(如我还想记录下收藏图片的时间戳)。这种情况下,我们可以使用关联模型来表示多对多关系。 在关联模型中,我们将Photo模型与User模型的多对多关系,分离成了User模型和Collect模型的一对多关系,和Photo模型与Collect模型的一对多关系。
70 0
|
15天前
|
存储 数据库 Android开发
构建高效安卓应用:采用Jetpack架构组件优化用户体验
【4月更文挑战第12天】 在当今快速发展的数字时代,Android 应用程序的流畅性与响应速度对用户满意度至关重要。为提高应用性能并降低维护成本,开发者需寻求先进的技术解决方案。本文将探讨如何利用 Android Jetpack 中的架构组件 — 如 LiveData、ViewModel 和 Room — 来构建高质量的安卓应用。通过具体实施案例分析,我们将展示这些组件如何协同工作以实现数据持久化、界面与逻辑分离,以及确保数据的即时更新,从而优化用户体验并提升应用的可维护性和可测试性。
|
26天前
|
移动开发 前端开发 数据管理
构建高效Android应用:采用MVVM架构与LiveData的全面指南
在移动开发领域,构建一个既快速又可靠的应用对于开发者来说至关重要。随着Android Jetpack组件的推出,MVVM(Model-View-ViewModel)架构和LiveData已成为实现响应式、可测试且易于维护应用的首选解决方案。本文将深入探讨如何在Android应用中实施MVVM模式,以及如何利用LiveData来优化UI组件的数据更新流程,确保用户界面与业务逻辑之间的高度解耦和流畅交互。
18 4
|
1月前
|
消息中间件 并行计算 网络协议
探秘高效Linux C/C++项目架构:让进程、线程和通信方式助力你的代码飞跃
探秘高效Linux C/C++项目架构:让进程、线程和通信方式助力你的代码飞跃
34 0

热门文章

最新文章