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

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

 

发布了122 篇原创文章 · 获赞 162 · 访问量 37万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 成长之路 设计师: Amelia_0503

分享到微信朋友圈

×

扫一扫,手机浏览