内容大纲:

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

一、Android开发框架的选择

由于原生 Android 开发应该已经是一个基础的 MVC 框架,所以在初始开发的时候并没有遇到太多框架上的问题,可是一旦项目规模到了一定的程度,就需要对整个项目的代码结构做一个总体上的规划,最终的目的是使代码可读,维护性好,方便测试。’

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

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

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

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

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

横向的模块化对大家来可能并不陌生,在一个项目建立项目文件夹的时候就会遇到这个问题,通常的做法是将相同功能的模块放到同一个目录下,更复杂的,可以通过插件化来实现功能的分离与加载。

纵向的分层,不同的项目可能就有不同的分法,并且随着项目的复杂度变大,层次可能越来越多。

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

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

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

如果你是个老司机,可以直接参考下面几篇文章(可在 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;@Overrideprotected 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();}@Overridepublic 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 datareturn dataSource.getStringFromRemote() + dataSource.getStringFromCache();}
}

TaskDataSource.java 文件

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

TaskDataSourceImpl.java 文件

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

TaskDataSourceTestImpl.java 文件

public class TaskDataSourceTestImpl implements TaskDataSource {@Overridepublic String getStringFromRemote() {return "Hello ";}@Overridepublic 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(){@Overridepublic void run() {super.run();final String str = taskData.getShowContent();mainHandler.post(new Runnable() {@Overridepublic void run() {mainView.onShowString(str);}});}}.start();
}

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

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

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

三、使用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>() {@Overridepublic String call(String param) {return  taskData.getTaskName();}}    Action1 viewAction = new Action1<String>() {@Overridepublic 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. 复杂的数据变换

四、结语

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

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

如何一步一步实现Android的MVP框架相关推荐

  1. android 最新框架组合,android 官方mvp框架优化:lifecycle-mvp,像前端那样组合式写页面...

    目录 1 前言 虽然在标题上,自己很随意的起了这么一个名字.其实并不是说它起个英文名就牛逼了.说白了,它其实就是mvp的思想加了lifecycle-component,然后加入了分层的思想,最后用Ty ...

  2. 一步一步教你在 Android 里创建自己的账号系统(一)

    大家假设喜欢我的博客,请关注一下我的微博,请点击这里(http://weibo.com/kifile),谢谢 转载请标明出处(http://blog.csdn.net/kifile),再次感谢 大家在 ...

  3. Android华容道之一步一步实现-4-图像块移动算法

    下一个关键点就是图像块的移动,以如图为例. 假设空格处于第二行第三格,那么此时只有触摸第二行以及第三列的图像块的时候才需要移动图像块,因为别的图像块不能移动. 当触摸发生在合法的图像块的时候,即上面图 ...

  4. Android华容道之一步一步实现-3 -手指触摸处理

    华容道关键点之一出现了,就是处理触摸,包括手指按下,抬起,移动等. 自己实现一个处理触摸的类,然后处理 onTouchEvent(MotionEvent event) 事件,在这里处理手指按下,抬起, ...

  5. Android华容道之一步一步实现-2-图片分割

    因为华容道是16个格子,所以要把一张大一点的图片分割成16个相等的小图片. 可以使用Bitmap.createBitmap方法来进行. 直接上代码 ori_bitmap = BitmapFactory ...

  6. Android华容道之一步一步实现-序言

    女儿看了最强大脑的数字华容道节目之后,就缠着要玩数字华容道,买了实物版,玩了几天,感觉好像还没有过瘾,就让我做个手机版的数字华容道游戏. 说明一下,最终要实现的版本并不是最强大脑那种数字版的华容道,而 ...

  7. 一步一步学ROP之Android ARM 32位篇

    蒸米 · 2015/12/17 9:41 0x00 序 ROP的全称为Return-oriented programming(返回导向编程),这是一种高级的内存攻击技术,可以用来绕过现代操作系统的各种 ...

  8. Android一步一步实现一款实用的Android广告栏

    源码:BannerLayoutDemo 有图有真相: bannerLayoutDemo 开源界有一句很有名的话叫"不要重复发明轮子",当然,我今天的观点不是要反驳这句话,轮子理论给 ...

  9. 华南x79主板u盘装系统教程_学不会不收费 几步教你安装Android x86

    1安装Android x86其实并不困难 话说最近操作系统这个话题的确是非常火爆.也许是借助于Windows 8消费者预览版的光芒,凡是与系统搭边的东西大家好像都喜欢与Windows 8进行比较.不管 ...

最新文章

  1. Eclipse 的IOConsole Updater error
  2. android设备报警推送,Firebase推送通知未送达所有android设备
  3. Dojo.Layout下的三个布局组件,浓缩精华
  4. CSS两栏布局之左栏布局
  5. 二叉树中序遍历的下一个节点
  6. 【ArcGIS|空间分析】查找成本最低路径
  7. 第三十一天 MySQL并发控制、存储引擎介绍、用户权限管理、缓存管理和数据类型选择...
  8. bochs运行xp_bochs模拟器xp系统镜像安装教程及注意事项
  9. JavaMail概述
  10. python bt_linux平台使用Python制作BT种子并获取BT种子信息的方法
  11. 天气数据垂手可得-IBM SPSS Modeler 18.0扩展应用实操练习
  12. c语言void delay是什么意思,delay什么意思
  13. wps转换成word如何实现?不妨试试这两个小技巧
  14. Casio DT930扫描软件
  15. 正则表达式去掉回车、换行、空白符号、空格
  16. 【汇编笔记】win10如何搭建汇编环境(dosbox)
  17. 深圳国际会展中心钢结构封顶 总建筑面积相当于6座“鸟巢”
  18. iOS动画——弹窗动画(pop动画)
  19. Ubuntu必备软件
  20. 服务端渲染SSR与客户端渲染

热门文章

  1. 【大学】恭喜发财 利是窦来
  2. 写一个HTML的关于新年倒计时的有烟花绽放,还有名字的,转换为网站
  3. Python爬虫实战Pro | (1) 爬取猫眼电影Top100榜单
  4. win10打开文件安全警告要运行此文件吗怎么关闭-百度经验
  5. 怎样成为顶尖的销售人员
  6. 简单的CSS在页面中点击超链接跳转新的页面
  7. oledbCommand.Parameters 图片varbinary存取
  8. Spring 4.x版本新特性
  9. word文档删除表格后的空白页
  10. javascript 向数组添加元素执行效率