ReAndroid-架构设计-MVC

163 阅读7分钟

Android的开发过程,免不了会出现开发人员水平的参差不齐,项目的规范对于小项目的单人开发来讲没有那么得重要(并不意味着不重要),但是对于一些团队项目,如果没有一个好的规范来约束各部分的开发,会导致整个项目变成空中楼阁,在日后总会出现一些问题,所以架构应运而生。

在复习Android架构设计之前,首先先来看看Android原生帮我们做了怎么样的架构设计:Android原生将视图层通过一种类似于html语法的结构从逻辑代码中分离了出来,这种使视图和逻辑分离的架构设计简单地称之为VC架构,这种架构设计可以说这种方式一方面简化了逻辑代码,另一方面方便了开发者对于视图层代码的编写。

一般的入门级代码也是直接通过这种方式写出来的,但是随着项目的逐渐扩大,会发现所有的逻辑操作,包括网络请求、本地化存储、图片加载、缓存等等诸多复杂的逻辑都杂糅在V层,导致Activity或Fragment变得异常臃肿,为了解决这个问题,一种新的架构设计应运而生,这就是MVC架构。

MVC架构由M-V-C三部分组成,其将原本VC架构的C层分割为M和C两部分,其具体含义如下:

  • M:Model-数据层
  • V:View-视图层
  • C:Controller-控制层

一开始看到这个分层模式的时候觉得真的很有道理,试想一下,一个应用是否就是包含三部分:

首先是离用户最近的View视图层,视图层的作用就是将UI进行展示,Android中所编写Xml视图文件和Activity中的部分代码组成了视图层;

然后是提供数据的Model数据层,Android的数据来源基本就两个渠道(不算传感器数据):本地存储和网络数据,获取数据的相关操作都是在Model层中做的;

最后就是Controller控制层,如果需要数据最终展示到视图上,那么就需要有一个中间人把它们关联起来,就有了控制层来处理数据层获得数据,展示到视图层上去,在Android中控制层以Activity为主要控制者。

MVC架构具体组成如下图,用户对View层进行操作,View层将相应的事件传递给Controller层,Controller层完成具体的逻辑之后将结果交给Model层对View层进行刷新。

MVC架构

关于MVC的组成,我们简单地了解了一下,对于大多数Android开发新手来说,最开始学的开发方式可以说已经和MVC架构很相像了,只需要将数据层做进一步分离,就可以得到一个简单的MVC架构,接下来以一个信息展示功能为例,先来看一下常规的VC架构是如何实现的:

接口使用的是github开放的接口来获取 square 的相关信息:

api.github.com/users/squar…

数据获取通过 okhttp 网络框架来获取网络数据:

github.com/square/okht…

大概要实现的页面如下,点击按钮获取到用户的信息(页面布局没什么好展示的):

信息页面大概的布局

前面提到VC架构方式对于逻辑处理都是在Activity或者Fragemnt中去执行的,那么下面来看一下具体的逻辑实现:

public class UserInfoActivity extends AppCompatActivity implements View.OnClickListener {

    private Button mBtnGetInfo;
    private ImageView mIvAvatar;
    private TextView mTvNickname;
    private TextView mTvUserPager;
    private TextView mTvBlog;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_user_info);
        // 绑定控件
        mBtnGetInfo = findViewById(R.id.btn_get_info);
        mIvAvatar = findViewById(R.id.iv_avatar);
        mTvNickname = findViewById(R.id.tv_nickname);
        mTvUserPager = findViewById(R.id.tv_user_page);
        mTvBlog = findViewById(R.id.tv_blog);

        mBtnGetInfo.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        // 网络请求时的状态
        mTvNickname.setText("加载中..");
        mTvUserPager.setText("加载中..");
        mTvBlog.setText("加载中..");

        // 网络请求
        OkHttpClient client = new OkHttpClient();
        final Request request = new Request.Builder()
                .url("https://api.github.com/users/square")
                .build();

        Call call = client.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(@NotNull Call call, @NotNull IOException e) {

            }

            @Override
            public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                // 获取返回数据
                String resp = response.body().string();
                // 解析数据
                Gson gson = new Gson();
                final UserInfoBean bean = gson.fromJson(resp, UserInfoBean.class);
                // UI配置
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Glide.with(UserInfoActivity.this)
                                .load(bean.getAvatar_url())
                                .into(mIvAvatar);
                        mTvNickname.setText("昵称:" + bean.getName());
                        mTvUserPager.setText("个人主页" + bean.getUrl());
                        mTvBlog.setText("博客:" + bean.getBlog());
                    }
                });
            }
        });
    }
}

另外贴出我所使用的数据类,可以不用看,就是根据github开放接口设置的:

public class UserInfoBean {

    private String avatar_url;
    private String name;
    private String url;
    private String blog;

    public String getAvatar_url() {
        return avatar_url;
    }

    public void setAvatar_url(String avatar_url) {
        this.avatar_url = avatar_url;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getBlog() {
        return blog;
    }

    public void setBlog(String blog) {
        this.blog = blog;
    }
}

可以看到所有的逻辑都包含在Activity中,这还只是一个简单的网络调用,假设这个页面很复杂,如果把所有的逻辑处理都放在一个类中一方面会导致这个类十分庞大不利于代码的阅读,另外一方面对于代码的维护成本也会上升,所以渐渐地传统的VC模式越来越不适合复杂的应用场景了,架构就来到了下一阶段MVC架构。

接下来来看看如何将现有的这个VC模式改装成MVC模式:

这个改装过程最直接的一个变化就是数据的获取变成Model层被抽离出来,分析一下现有功能的数据获取是通过网络请求获取到的,因此需要新建一个类来实现数据获取的这部分逻辑:

public class UserInfoModel {

    public void getDataSource(final UserInfoCallback callback) {
        // 网络请求
        OkHttpClient client = new OkHttpClient();
        final Request request = new Request.Builder()
                .url("https://api.github.com/users/square")
                .build();

        Call call = client.newCall(request);

        call.enqueue(new Callback() {
            @Override
            public void onFailure(@NotNull Call call, @NotNull IOException e) {
                // 通过接口返回失败结果
                callback.getDataSourceFail();
            }

            @Override
            public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                // 获取返回数据
                String resp = response.body().string();
                // 解析数据
                Gson gson = new Gson();
                final UserInfoBean bean = gson.fromJson(resp, UserInfoBean.class);
                // 通过接口通知View层进行UI的更新
                callback.getDataSourceSuccess(bean);
            }
        });
    }
}

因为一般的网络请求是异步,我们无法知道什么时候才会获取到数据,因此需要通过接口去通知View层更新UI:

public interface UserInfoCallback {

    void getDataSourceFail();

    void getDataSourceSuccess(UserInfoBean dataSource);
}

因为将数据获取的部分抽离了出来,所以需要对Activity中Controller层部分做一个修改:

public class UserInfoActivity extends AppCompatActivity implements View.OnClickListener {

    ···
    private UserInfoModel mUserInfoModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_user_info);
        ···
        
        mUserInfoModel = new UserInfoModel();
    }

    @Override
    public void onClick(View v) {
        ···

        // 替换原有网络请求的逻辑
        mUserInfoModel.getDataSource(new UserInfoCallback() {
            @Override
            public void getDataSourceFail() {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        mTvNickname.setText("获取数据失败");
                        mTvUserPager.setText("获取数据失败");
                        mTvBlog.setText("获取数据失败");
                    }
                });
            }

            @Override
            public void getDataSourceSuccess(final UserInfoBean dataSource) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Glide.with(UserInfoActivity.this)
                                .load(dataSource.getAvatar_url())
                                .into(mIvAvatar);
                        mTvNickname.setText("昵称:" + dataSource.getName());
                        mTvUserPager.setText("个人主页" + dataSource.getUrl());
                        mTvBlog.setText("博客:" + dataSource.getBlog());
                    }
                });
            }
        });
    }
}

通过接口,View层在获取到了数据之后对UI进行了刷新,这样就实现了一个简单的MVC架构,将Model层抽离了出来。

当然这么实现还不是很优雅,毕竟这个接口这个东西不是所有的人都觉的好用,也可以使用Handler的形式来实现UI的刷新,将已有功能简单地做一下改进:

public class UserInfoActivity extends AppCompatActivity implements View.OnClickListener {

    ···

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_user_info);
        ···
    }

    // 创建一个handler对象用于处理逻辑
    private Handler mHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(@NonNull Message msg) {
            switch (msg.what) {
                case CODE_REQUEST_SUCCESS:
                    UserInfoBean dataSource = (UserInfoBean) msg.obj;
                    Glide.with(UserInfoActivity.this)
                            .load(dataSource.getAvatar_url())
                            .into(mIvAvatar);
                    mTvNickname.setText("昵称:" + dataSource.getName());
                    mTvUserPager.setText("个人主页" + dataSource.getUrl());
                    mTvBlog.setText("博客:" + dataSource.getBlog());
                    break;
                case CODE_REQUEST_FAILURE:
                    mTvNickname.setText("获取数据失败");
                    mTvUserPager.setText("获取数据失败");
                    mTvBlog.setText("获取数据失败");
                    break;
            }
            return false;
        }
    });

    @Override
    public void onClick(View v) {
        ···
        // 传递handler
        mUserInfoModel.getDataSource(mHandler);
    }
}

简单地修改一下通过handler来实现View层的UI刷新就显得更加优雅了。

一般地还会有一种方式会将Controller再做一个分离,通过接口的方式来实现相对于VC两层在同一个类中的更为分离的方式,具体要看情况,如果说被称之为Controller层的逻辑比较复杂,那么建议将其做一个分离方便进行控制和后期的代码维护,一般的写在同一个Activity/Fragemnt中就可以了。

关于MVC架构的优缺点:

优点:

  • 结构清晰,职责划分清晰
  • 降低耦合
  • 有利于组件重用

缺点:

  • 其实我们上述的示例,已经是经过优化的 MVC 结构了,一般来说,Activity / Fragment 会承担 View 和 Controller 两个角色,就会导致 Activity / Fragment 中代码较多
  • Model 直接操作 View,View 的修改会导致 Controller 和 Model 都进行改动
  • 增加了代码结构的复杂性

MVC架构确实在原有的开发架构上有了进一步的改进,但是也正因为它的一些缺点导致MVC架构其实并不适用多人开发,同时对于组件的复用也并不友好。

反过来思考一下,怎么对MVC改进来解决多人开发难和组件不好复用的问题呢?这就引出了MVP的架构设计。