Day2 当前最火爆的APP架构及其实现

点此进入目录:[干货] 十天 教你从创意到上线APP

一、架构设计的目的

通过设计使程序模块化,做到模块内部的高聚合和模块之间的低耦合。这样做的好处是使得程序在开发的过程中,开发人员只需要专注于一点,提高程序开发的效率,并且更容易进行后续的测试以及定位问题。但设计不能违背最初的目的,对于不同量级的工程,具体架构的实现方式必然是不同的,切忌犯为了设计而设计,为了架构而架构的毛病。

下面我们详细介绍下Android中常用的两种架构类型,然后结合实例讲解具体实现流程,最后根据业务场景选取合适我们的架构模式。

举个简单的例子:

一个Android App如果只有几个Java文件,那只需要做点模块和层次的划分就可以,引入框架或者架构反而提高了工作量,降低了生产力;

但如果当前开发的App最终代码量在10W行以上,本地需要进行复杂操作,同时也需要考虑到与其余的Android开发者以及后台开发人员之间的同步配合,那就需要在架构上进行一些思考!

二、MVC设计架构

1、MVC简介

MVC全名是Model View Controller,如图,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。

其中M层处理数据,业务逻辑等;V层处理界面的显示结果;C层起到桥梁的作用,来控制V层和M层通信以此来达到分离视图显示和业务逻辑层。

2、Android中的MVC

视图层(View)

一般采用XML文件进行界面的描述,这些XML可以理解为AndroidApp的View。使用的时候可以非常方便的引入。同时便于后期界面的修改。逻辑中与界面对应的id不变化则代码不用修改,大大增强了代码的可维护性。

控制层(Controller)

Android的控制层的重任通常落在了众多的Activity的肩上。这句话也就暗含了不要在Activity中写代码,要通过Activity交割Model业务逻辑层处理,这样做的另外一个原因是Android中的Actiivity的响应时间是5s,如果耗时的操作放在这里,程序就很容易被回收掉。

模型层(Model)

我们针对业务模型,建立的数据结构和相关的类,就可以理解为AndroidApp的Model,Model是与View无关,而与业务相关的,比如:对数据库的操作、对网络等的操作都应该在Model里面处理,当然对业务计算等操作也是必须放在的该层的。就是应用程序中二进制的数据。

3、MVC框架在Activity中的体现

我们这里以一个按钮点击后进行网络请求然后将请求数据同步更新到界面的Demo为例,向大家展示MVC在Android中具体的应用场景和控制流程:
(1)Controller控制器&View:
public class MainActivity extends ActionBarActivity implements OnWeatherListener, View.OnClickListener {

    private WeatherModel weatherModel;
    private EditText cityNOInput;
    private TextView city;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        weatherModel = new WeatherModelImpl();
        initView();
    }

    // 初始化View
    private void initView() {
        cityNOInput = findView(R.id.et_city_no);
        city = findView(R.id.tv_city);
        findView(R.id.btn_go).setOnClickListener(this);
    }

    // 显示结果
    public void displayResult(Weather weather) {
        WeatherInfo weatherInfo = weather.getWeatherinfo();
        city.setText(weatherInfo.getCity());
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_go:
                weatherModel.getWeather(cityNOInput.getText().toString().trim(), this);
                break;
        }
    }

    @Override
    public void onSuccess(Weather weather) {
        displayResult(weather);
    }

    @Override
    public void onError() {
        Toast.makeText(this, 获取天气信息失败, Toast.LENGTH_SHORT).show();
    }

    private T findView(int id) {
        return (T) findViewById(id);
    }
}

从上面的代码可以看到,Activity持有了WeatherModel模型的对象,当用户有点击Button的时候,Activity作为Controller控制层读取View视图层EditTextView的数据然后向Model模型发起数据请求,也就是调用WeatherModel对象的getWeather()方法。当Model模型处理数据结束后,通过接口OnWeatherListener通知View视图层数据处理,然后View视图层调用displayResult()方法更新UI。至此,整个MVC框架流程就在Activity中体现出来了。

(2)WeatherModelImpl代码实现&Model模型
public interface WeatherModel {
    void getWeather(String cityNumber, OnWeatherListener listener);
}

public class WeatherModelImpl implements WeatherModel {
    @Override
    public void getWeather(String cityNumber, final OnWeatherListener listener) {
        // 数据层操作
        VolleyRequest.newInstance().newGsonRequest(http://www.weather.com.cn/data/sk/ + cityNumber + .html, Weather.class, new Response.Listener<weather>() {
            @Override
            public void onResponse(Weather weather) {
                if (weather != null) {
                    listener.onSuccess(weather);
                } else {
                    listener.onError();
                }
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                listener.onError();
            }
        });
    }
}

通过以上代码看出,这里设计了一个WeatherModel模型接口,然后实现了接口WeatherModelImpl类,Controller控制器Activity调用WeatherModelImpl类中的方法发起网络请求,然后通过实现OnWeatherListener接口来获得网络请求的结果通知View视图层更新UI。至此,Activity就将View视图显示和Model模型数据处理隔离开了。Activity担当Contronller完成了Model和View之间的协调作用。

至于这里为什么不直接设计成类里面的一个getWeather()方法直接请求网络数据呢?我们考虑下面这种情况:现在代码中的网络请求是使用Volley框架来实现的,如果哪天老板非要你使用Afinal框架实现网络请求,问题要怎么解决呢?难道是修改getWeather()方法的实现吗?这样当然不行,因为这样修改不仅破坏了以前的代码而且还不利于维护。考虑到以后代码的扩展和维护性,我们选择设计接口的方式来解决这个问题:我们实现另外一个WeatherModelWithAfinalImpl类,继承自WeatherModel重写里面的方法,这样不仅保留了以前的WeatherModelImpl类请求网络方式,还增加了WeatherModelWithAfinalImpl类的请求方式,这样Activity调用代码无需要任何修改。

三、MVP设计架构

1、MVP的诞生

在App开发过程中经常出现的问题就是某一部分的代码量过大,虽然做了模块划分和接口隔离,但也很难完全避免,而且从实践中看出,这更多的出现在UI部分,也就是Activity里。不过Activity内容过多的原因也很好解释,因为Activity本身需要担负与用户之间的操作交互、界面的展示,所以它不是单纯的Controller或View,这就造成了Activity的臃肿。所以为了解决这个问题,让我们引入MVP框架。

2、MVC的缺点

正如刚才所说,在Android开发中Activity并不是一个标准的MVC模式中的Controller,它的首要职责是加载应用的布局和初始化用户界面,并接受和处理来自用户的操作请求,进而作出响应。随着界面及其逻辑的复杂度不断提升,Activity类的职责不断增加,以致变得庞大臃肿,这便是MVC的缺点所在。

3、什么是MVP?

MVP是从MVC框架演变过来的,当然也就与MVC有一定的相似性:Controller/Presenter负责逻辑的处理,Model提供数据,View负责显示。


MVP框架由3部分组成:View负责显示;Presenter负责逻辑处理;Model提供数据。在MVP模式里通常包含3个要素(加上View interface是4个):

  • View:负责绘制UI元素、与用户进行交互(在Android中体现为Activity);
  • Model:负责存储、检索、操纵数据(有时也实现一个Model interface用来降低耦合);
  • Presenter:作为View与Model交互的中间纽带,处理与用户交互的负责逻辑;
  • View interface:需要View实现的接口,View通过View interface与Presenter进行交互,以此降低耦合并且方便进行单元测试;
Tips:View interface的必要性

回想一下你在开发Android应用时是如何对代码逻辑进行单元测试的?是否每次都要将应用部署到Android模拟器或真机上,然后通过模拟用户操作进行测试?然而由于Android平台的特性,每次部署都耗费了大量的时间,这直接导致开发效率的降低。而在MVP模式中,处理复杂逻辑的Presenter是通过interface与View(Activity)进行交互的,这说明我们可以通过自定义类实现这个interface来模拟Activity的行为对Presenter进行单元测试,省去了大量的部署及测试的时间。

4、MVC → MVP

当我们将Activity复杂的逻辑处理移至另外的一个类(Presenter)中时,Activity其实就是MVP模式中的View,它负责UI元素的初始化,建立UI元素与Presenter的关联(Listener之类),同时自己也会处理一些简单的逻辑(复杂的逻辑交由Presenter处理)。

MVP的Presenter是框架的控制者,承担了大量的逻辑操作,而MVC的Controller更多时候承担一种转发的作用。因此在App中引入MVP的原因,是为了将此前在Activty中包含的大量逻辑操作放到控制层中,避免Activity的臃肿。

因此我们可以发现MVP的优点如下:
  • 模型与视图完全分离,我们可以修改视图而不影响模型;
  • 可以更高效地使用模型,因为所有的交互都发生在一个地方 —— Presenter内部;
  • 我们可以将一个Presenter用于多个视图,而不需要改变Presenter的逻辑。这个特性非常的有用,因为视图的变化总是比模型的变化频繁;
  • 如果我们把逻辑放在Presenter中,那么我们就可以脱离用户接口来测试这些逻辑(单元测试)。

具体到Android App中,一般可以将App根据程序的结构进行纵向划分,根据MVP可以将App分别为模型层(M)、UI层(V)和逻辑层(P)。UI层一般包括Activity、Fragment等直接和UI相关的类,UI层的Activity在启动之后实例化相应的Presenter,App的控制权后移,由UI转移到Presenter,两者之间的通信通过BroadCast、Handler或者接口完成,只传递事件和结果。

举个简单的例子,UI层通知逻辑层(Presenter)用户点击了一个Button,逻辑层(Presenter)自己决定应该用什么行为进行响应,该找哪个模型(Model)去做这件事,最后逻辑层(Presenter)将完成的结果更新到UI层。

MVP的变种:Passive View

MVP的变种有很多,其中使用最广泛的是Passive View模式,即被动视图。在这种模式下,View和Model之间不能直接交互,而是通过Presenter与Model打交道。Presenter接受View的UI请求并完成简单的UI处理逻辑并调用Model进行业务处理,然后调用View将相应的结果反映出来。View直接依赖Presenter,但是Presenter间接依赖View,它直接依赖的是View实现的接口。

相对于View的被动,Presenter就是主动的一方:
  • Presenter是整个MVP体系的控制中心,而不是单纯的处理View请求的人;
  • View仅仅是用户交互请求的汇报者,对于响应用户交互相关的逻辑和流程,View不参与决策,真正的决策者是Presenter;
  • View向Presenter发送用户交互请求应该采用这样的口吻:“我现在将用户交互请求发送给你,你看着办,需要我的时候我会协助你”,不应该是这样:“我现在处理用户交互请求了,我知道该怎么办,但是我需要你的支持,因为实现业务逻辑的Model只信任你”;
  • 对于绑定到View上的数据,不应该是View从Presenter上“拉”回来的,应该是Presenter主动“推”给View的;
  • View尽可能不维护数据状态,因为其本身仅仅实现单纯的、独立的UI操作;
  • Presenter才是整个体系的协调者,它根据处理用于交互的逻辑给View和Model安排工作。

5、MVP架构存在的问题与解决办法

  • 加入模板方法(Template Method)
    转移逻辑操作之后可能部分较为复杂的Activity内代码量还是不少,于是需要在分层的基础上再加入模板方法(Template Method)。
    具体做法是在Activity内部分层,其中最顶层为BaseActivity,不做具体显示而是提供一些基础样式:Dialog、ActionBar在内的内容等。展现给用户的Activity继承BaseActivity,重写BaseActivity预留的方法。如有必要再进行二次继承,App中Activity之间的继承次数最多不超过3次。
    “爱阅”中采用的即是这样的方法,加入模板方法不仅省去了很多重复的逻辑工作量,也更方便不同子视图层的个性化定制;

  • Model内部分层
    模型层中的整体代码量是最大的,一般由大量的Package组成,针对这部分需要做的就是在程序设计的过程中,做好模块的划分,进行接口隔离,在内部进行分层。

  • 强化Presenter
    强化Presenter的作用,将所有逻辑操作都放在Presenter内也容易造成Presenter内的代码量过大,对于这点有一个方法是在UI层和Presenter之间设置中介者Mediator,将例如数据校验、组装在内的轻量级逻辑操作放在Mediator中;在Presenter和Model之间使用代理Proxy;通过上述两者分担一部分Presenter的逻辑操作,但整体框架的控制权还是在Presenter手中。Mediator和Proxy不是必须的,只在Presenter负担过大时才建议使用。

四 、MVP代码实例

接下来我们看看MVP在Android开发中是怎么应用的!
(1)建立Bean
public class UserBean {
     private String mFirstName;
     private String mLastName;
     public UserBean(String firstName, String lastName) {
            this. mFirstName = firstName;
            this. mLastName = lastName;
     }
     public String getFirstName() {
            return mFirstName;
     }
     public String getLastName() {
            return mLastName;
     }
}
(2)建立Model
public interface UserModel {
     void setID(int id);

     void setFirstName(String firstName);

     void setLastName(String lastName);

     int getID();

     UserBean load(int id);// 通过id读取user信息,返回一个UserBean
}
(3)Presenter控制器

建立presenter(通过iView和iModel接口操作Model和view),Activity可以把所有逻辑给Presenter处理,这样Java逻辑就从手机的Activity中分离出来。

public class UserPresenter {
     private IUserView mUserView;
     private IUserModel mUserModel;

     public UserPresenter(IUserView view) {
            mUserView = view;
            mUserModel = new UserModel();
     }

     public void saveUser(int id, String firstName, String lastName) {
            mUserModel.setID(id);
            mUserModel.setFirstName(firstName);
            mUserModel.setLastName(lastName);
     }

     public void loadUser(int id) {
           UserBean user = mUserModel.load(id);
            mUserView.setFirstName(user.getFirstName()); // 通过调用IUserView的方法来更新显示
            mUserView.setLastName(user.getLastName());
     }
}
(4)View视图

建立view(更新ui中的view状态),这里列出需要操作当前view的方法,也是接口

public interface IUserView {
     int getID();

     String getFristName();

     String getLastName();

     void setFirstName(String firstName);

     void setLastName(String lastName);
}
(5)MainActivity
public class MainActivity extends Activity implements OnClickListener, IUserView {

     UserPresenter presenter;
     EditText id, first, last;

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

           findViewById(R.id. save).setOnClickListener( this);
           findViewById(R.id. load).setOnClickListener( this);
           id = (EditText) findViewById(R.id. id);
           first = (EditText) findViewById(R.id. first);
           last = (EditText) findViewById(R.id. last);

           presenter = new UserPresenter( this);
     }

     @Override
     public void onClick(View v) {
            switch (v.getId()) {
            case R.id. save:
                 presenter.saveUser(getID(), getFristName(), getLastName());
                 break;
            case R.id. load:
                 presenter.loadUser(getID());
                 break;
            default:
                 break;
           }
     }

     @Override
     public int getID() {
            return new Integer( id.getText().toString());
     }

     @Override
     public String getFristName() {
            return first.getText().toString();
     }

     @Override
     public String getLastName() {
            return last.getText().toString();
     }

     @Override
     public void setFirstName(String firstName) {
            first.setText(firstName);
     }

     @Override
     public void setLastName(String lastName) {
            last.setText(lastName);
     }
}

因此,Activity及从MVC中的Controller中解放出来了,这会Activity主要做显示View的作用和用户交互。每个Activity可以根据自己显示View的不同实现View视图接口IUserView。

五、“爱阅”中的架构设计

上面介绍了Android应用中目前最火热的两种架构模式,那么“爱阅”中采用的架构是什么呢?是MVC和MVP共存的架构模式!大家可能有些奇怪,难道MVC和MVP是可以共存的吗?那是当然的了,只是我们在具体的业务场景中要根据不同的情况做出恰当的选择就好。

比如在“爱阅”的整个架构当中,MainActivity扮演了整个APP主要的业务角色,在APP运行的过程中,无论是侧滑菜单的打开、搜索界面的呈现、收藏界面的展现等等,MainActivity都属于常驻内存的角色,所以对于MainActivity中的一些基本功能而言采用MVC模式最为合适,比如:左右两侧菜单栏的初始化、主界面ViewPager的更新和显示等。而在每个Fragment当中,选择MVP架构最为合适,因为涉及到了许多相似的界面设计(比如主页面中的页面点选和滑动等),这样不仅减少了界面重复逻辑的代码编写,而且把相同的业务逻辑处理抽取到Presenter中进行处理后,更容易实现不同界面的个性化定制并减少模块之间的耦合。

另外,在MainActivity的设计中,对于一些长连接的逻辑处理同样采用了MVP的架构设计。比如:对数据库和网络获取的原始数据解析完成后,我采用了EventBus对事件进行分发并在MainActivity中进行捕获,这样就实现了高度的业务逻辑解耦,即前面所说的 —— 数据不是从MainActivity中拉取,而是把数据推给MainActivity;MainActivity只关心要什么、接受什么,而不关心具体是怎么获得的;MainActivity不是整个APP的业务逻辑承担者,而仅仅轻量化的实现与用户的UI交互功能。

这样,我们就根据具体的业务场景做出了合理的架构选择和设计,在下一篇的课程当中,我们就根据此架构框架进行主页面的开发工作,See You!

联系方式:

简书:WillFlow
GitHub:爱阅

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容