前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一种MVVM风格的Android项目架构浅析

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

作者头像
杨永贞
发布2020-08-04 21:28:16
1.2K0
发布2020-08-04 21:28:16
举报

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

欢迎讨论,刨根留底不是目的,纠结什么MVVM也不是目的,更好的做到逻辑清晰和方便维护,才是大家共同的目的

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018-07-05 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
事件总线
腾讯云事件总线(EventBridge)是一款安全,稳定,高效的云上事件连接器,作为流数据和事件的自动收集、处理、分发管道,通过可视化的配置,实现事件源(例如:Kafka,审计,数据库等)和目标对象(例如:CLS,SCF等)的快速连接,当前 EventBridge 已接入 100+ 云上服务,助力分布式事件驱动架构的快速构建。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档