【腾讯Bugly干货分享】一步一步实现Android的MVP框架

本文来自于腾讯bugly开发者社区,非经做者赞成,请勿转载,原文地址:http://dev.qq.com/topic/5799d7844bef22a823b3ad44java

内容大纲:react

  1. Android 开发框架的选择
  2. 如何一步步搭建分层框架
  3. 使用 RxJava 来解决主线程发出网络请求的问题
  4. 结语

1、Android开发框架的选择

因为原生 Android 开发应该已是一个基础的 MVC 框架,因此在初始开发的时候并无遇到太多框架上的问题,但是一旦项目规模到了必定的程度,就须要对整个项目的代码结构作一个整体上的规划,最终的目的是使代码可读,维护性好,方便测试。’android

只有项目复杂度到了必定程度才须要使用一些更灵活的框架或者结构,简单来讲,写个 Hello World 并不须要任何第三方的框架git

原生的 MVC 框架遇到大规模的应用,就会变得代码难读,很差维护,没法测试的囧境。所以,Android 开发方面也有不少对应的框架来解决这些问题。github

构建框架的最终目的是加强项目代码的可读性维护性方便测试 ,若是背离了这个初衷,为了使用而使用,最终是得不偿失的数据库

从根本上来说,要解决上述的三个问题,核心思想无非两种:一个是分层 ,一个是模块化 。两个方法最终要实现的就是解耦,分层讲的是纵向层面上的解耦,模块化则是横向上的解耦。下面咱们来详细讨论一下 Android 开发如何实现不一样层面上的解耦。编程

解耦的经常使用方法有两种:分层模块化json

横向的模块化对你们来可能并不陌生,在一个项目创建项目文件夹的时候就会遇到这个问题,一般的作法是将相同功能的模块放到同一个目录下,更复杂的,能够经过插件化来实现功能的分离与加载。api

纵向的分层,不一样的项目可能就有不一样的分法,而且随着项目的复杂度变大,层次可能愈来愈多。缓存

对于经典的 Android MVC 框架来讲,若是只是简单的应用,业务逻辑写到 Activity 下面并没有太多问题,但一旦业务逐渐变得复杂起来,每一个页面之间有不一样的数据交互和业务交流时,activity 的代码就会急剧膨胀,代码就会变得可读性,维护性不好。

因此这里咱们就要介绍 Android 官方推荐的 MVP 框架,看看 MVP 是如何将 Android 项目层层分解。

2、如何一步步搭建分层框架

若是你是个老司机,能够直接参考下面几篇文章(可在 google 搜到):

  1. Android Application Architecture
  2. Android Architecture Blueprints - Github
  3. Google 官方 MVP 示例之 TODO-MVP - 简书
  4. 官方示例1-todo-mvp - github
  5. dev-todo-mvp-rxjava - github

固然若是你以为看官方的示例太麻烦,那么本文会经过最简洁的语言来说解如何经过 MVP 来实现一个合适的业务分层。

对一个经典的 Android MVC 框架项目来说,它的代码结构大概是下面这样(图片来自参考文献)

简单来说,就是 Activity 或者 Fragment 直接与数据层交互,activity 经过 apiProvider 进行网络访问,或者经过 CacheProvider 读取本地缓存,而后在返回或者回调里对 Activity 的界面进行响应刷新。

这样的结构在初期看来没什么问题,甚至能够很快的开发出来一个展现功能,可是业务一旦变得复杂了怎么办?

咱们做一个设想,假如一次数据访问可能须要同时访问 api 和 cache,或者一次数据请求须要请求两次 api。对于 activity 来讲,它既与界面的展现,事件等有关系,又与业务数据层有着直接的关系,无疑 activity 层会极剧膨胀,变得极难阅读和维护。

在这种结构下, activity 同时承担了 view 层和 controller 层的工做,因此咱们须要给 activity 减负

因此,咱们来看看 MVP 是如何作这项工做的(图片来自参考文献)

这是一个比较典型的 MVP 结构图,相比于第一张图,多了两个层,一个是 Presenter 和 DataManager 层。

所谓自古图片留不住,老是代码得人心。下面用代码来讲明这个结构的实现。

首先是 View 层的 Activity,假设有一个最简单的从 Preference 中获取字符串的界面

public class MainActivity extends Activity implements MainView {

    MainPresenter presenter;
    TextView mShowTxt;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mShowTxt = (TextView)findViewById(R.id.text1);
        loadDatas();
    }

    public void loadDatas() {
        presenter = new MainPresenter();
        presenter.addTaskListener(this);
        presenter.getString();
    }

    @Override
    public void onShowString(String str) {
        mShowTxt.setText(str);
    }
}

Activity 里面包含了几个文件,一个是 View 层的对外接口 MainView,一个是P层 Presenter

首先对外接口 MainView 文件

public interface MainView {
    void onShowString(String json);
}

由于这个界面比较简单,只须要在界面上显示一个字符串,因此只有一个接口 onShowString,再看P层代码

public class MainPresenter {

    MainView mainView;
    TaskManager taskData;

    public MainPresenter() {
        this.taskData = new TaskManager(new TaskDataSourceImpl());
    }

    public MainPresenter test() {
        this.taskData = new TaskManager(new TaskDataSourceTestImpl());
        return this;
    }

    public MainPresenter addTaskListener(MainView viewListener) {
        this.mainView = viewListener;
        return this;
    }

    public void getString() {
        String str = taskData.getTaskName();
        mainView.onShowString(str);
    }

}

能够看到 Presenter 层是链接 Model 层和 View 层的中间层,所以持有 View 层的接口和 Model 层的接口。这里就能够看到 MVP 框架的威力了,经过接口的形式将 View 层和 Model 层彻底隔离开来。

接口的做用相似给层与层之间制定的一种通讯协议,两个不一样的层级相互交流,只要遵照这些协议便可,并不须要知道具体的实现是怎样

看到这里,有人可能就要问,这跟直接调用有什么区别,为何要大费周章的给 view 层和 Model 层各设置一个接口呢?具体缘由,咱们看看 Model 层的实现类就知道了。

下面这个文件是 DataManager.java,对应的是图中的 DataManager 模块

/**
 * 从数据层获取的数据,在这里进行拼装和组合
 */
public class TaskManager {
    TaskDataSource dataSource;

    public TaskManager(TaskDataSource dataSource) {
        this.dataSource = dataSource;
    }

    public String getShowContent() {
        //Todo what you want do on the original data
        return dataSource.getStringFromRemote() + dataSource.getStringFromCache();
    }
}

TaskDataSource.java 文件

/**
 * data 层接口定义
 */
public interface TaskDataSource {
    String getStringFromRemote();
    String getStringFromCache();
}

TaskDataSourceImpl.java 文件

public class TaskDataSourceImpl implements TaskDataSource {
    @Override
    public String getStringFromRemote() {
        return "Hello ";
    }

    @Override
    public String getStringFromCache() {
        return "World";
    }
}

TaskDataSourceTestImpl.java 文件

public class TaskDataSourceTestImpl implements TaskDataSource {
    @Override
    public String getStringFromRemote() {
        return "Hello ";
    }

    @Override
    public String getStringFromCache() {
        return " world Test ";
    }
}

从上面几个文件来看, TaskDataSource.java 做为数据层对外的接口, TaskDataSourceImpl.java 是数据层,直接负责数据获取,不管是从api得到,仍是从本地数据库读取数据,本质上都是IO操做。 TaskManager 是做为业务层,对获取到的数据进行拼装,而后交给调用层。

这里咱们来看看分层的做用

首先来说业务层 TaskManager,业务层的上层是 View 层,下层是 Data 层。在这个类里,只有一个 Data 层的接口,因此业务层是不关心数据是如何取得,只须要经过接口得到数据以后,对原始的数据进行组合和拼装。由于彻底与其上层和下层分离,因此咱们在测试的时候,能够彻底独立的是去测试业务层的逻辑。

TaskManager 中的 construct 方法的参数是数据层接口,这意味着咱们能够给业务层注入不一样的数据层实现。 正式线上发布的时候注入 TaskDataSourceImpl 这个实现,在测试业务层逻辑的时候,注入 TaskDataSourceTestImpl.java 实现。

这也正是使用接口来处理每一个层级互相通讯的好处,能够根据使用场景的不用,使用不一样的实现

到如今为止一个基于 MVP 简单框架就搭建完成了,但其实还遗留了一个比较大的问题。

Android 规定,主线程是没法直接进行网络请求,会抛出 NetworkOnMainThreadException 异常

咱们回到 Presenter 层,看看这里的调用。由于 presenter 层并不知道业务层以及数据层究竟是从网络获取数据,仍是从本地获取数据(符合层级间相互透明的原则),由于每次调用均可能存在触发这个问题。而且咱们知道,即便是从本地获取数据,一次简单的IO访问也要消耗10MS左右。所以多而复杂的IO可能会直接引起页面的卡顿。

理想的状况下,全部的数据请求都应当在线程中完成,主线程只负责页面渲染的工做

固然,Android 自己提供一些方案,好比下面这种:

public void getString() {
    final Handler mainHandler = new Handler(Looper.getMainLooper());
    new Thread(){
        @Override
        public void run() {
            super.run();
            final String str = taskData.getShowContent();
            mainHandler.post(new Runnable() {
                @Override
                public void run() {
                    mainView.onShowString(str);
                }
            });
        }
    }.start();
}

经过新建子线程进行IO读写获取数据,而后经过主线程的 Looper 将结果经过传回主线程进行渲染和展现。

但每一个调用都这样写,首先是新建线程会增长额外的成功,其次就是代码看起来很难读,缩进太多。

好在有了 RxJava ,能够比较方便的解决这个问题。

3、使用RxJava来解决主线程发出网络请求的问题

RxJava 是一个天生用来作异步的工具,相比 AsyncTask, Handler 等,它的优势就是简洁,无比的简洁。

在 Android 中使用 RxJava 须要加入下面两个依赖

compile 'io.reactivex:rxjava:1.0.14' 
compile 'io.reactivex:rxandroid:1.0.1'

这里咱们直接介绍如何使用 RxJava 解决这个问题,直接在 presenter 中修改调用方法 getString

public class MainPresenter {

    MainView mainView;
    TaskManager taskData;

    public MainPresenter() {
        this.taskData = new TaskManager(new TaskDataSourceImpl());
    }

    public MainPresenter test() {
        this.taskData = new TaskManager(new TaskDataSourceTestImpl());
        return this;
    }

    public MainPresenter addTaskListener(MainView viewListener) {
        this.mainView = viewListener;
        return this;
    }

    public void getString() {
        Func1 dataAction = new Func1<String,String>() {
                @Override
                public String call(String param) {
                    return  taskData.getTaskName();
                }
            }    
        Action1 viewAction = new Action1<String>() {
                @Override
                public void call( String str) {
                    mainView.onShowString(str);
                }
            };        
        Observable.just("")
            .observeOn(Schedulers.io())
            .map(dataAction)
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(view);

    }

}

简单说明一下,与业务数据层的交互被定义到 Action1 里,而后交由 rxJava,指定 Schedulers.io() 获取到的线程来执行。Shedulers.io() 是专门用来进行IO访问的线程,而且线程会重复利用,不须要额外的线程管理。而数据返回到 View 层的操做是在 Action1 中彻底,由 rxJava 交由 AndroidSchedulers.mainThread() 指定的UI主线程来执行。

从代码量上来说,似比上一种方式要更多了,但实际上,当业务复杂度成倍增长的时候,RxJava 能够采用这种链式编程方式随意的增长调用和返回,而实现方式要比前面的方法灵活得多,简洁得多。

具体的内容就不在这里讲了,你们能够看参考下面的文章(可在 google 搜到):

  1. 给 Android 开发者的 RxJava 详解
  2. RxJava 与 Retrofit 结合的最佳实践
  3. RxJava使用场景小结
  4. How To Use RxJava

RxJava 的使用场景远不止这些,在上面第三篇文章提到了如下几种使用场景:

  1. 取数据先检查缓存的场景
  2. 须要等到多个接口并发取完数据,再更新
  3. 一个接口的请求依赖另外一个API请求返回的数据
  4. 界面按钮须要防止连续点击的状况
  5. 响应式的界面
  6. 复杂的数据变换

4、结语

至此为止,经过 MVP+RxJava 的组合,咱们已经构建出一个比较灵活的 Android 项目框架,总共分红了四部分:View 层,Presenter 层,Model 业务层,Data 数据持久化层。这个框架的优势大概有如下几点:

  • 每层各自独立,经过接口通讯
  • 实现与接口分离,不一样场景(正式,测试)能够挂载不一样的实现,方便测试和开发写假数据
  • 全部的业务逻辑都在非UI线程中进行,最大限度减小IO操做对UI的影响
  • 使用 RxJava 能够将复杂的调用进行链式组合,解决多重回调嵌套问题

固然,这种方式可能还存在着各类各样的问题,欢迎同窗们提出建议

更多精彩内容欢迎关注bugly的微信公众帐号:

腾讯 Bugly是一款专为移动开发者打造的质量监控工具,帮助开发者快速,便捷的定位线上应用崩溃的状况以及解决方案。智能合并功能帮助开发同窗把天天上报的数千条 Crash 根据根因合并分类,每日日报会列出影响用户数最多的崩溃,精准定位功能帮助开发同窗定位到出问题的代码行,实时上报能够在发布后快速的了解应用的质量状况,适配最新的 iOS, Android 官方操做系统,鹅厂的工程师都在使用,快来加入咱们吧!

相关文章
相关标签/搜索