标签:hybrid 独立性 调用接口 过程 roi 删除本地文件 ram 实践 运行
转载请注明出处:http://blog.csdn.net/ahence/article/details/56678126
技术发展日新月异,业界各种Android客户端架构设计,五花八门,但我们不能简单地说哪种架构更好,因为脱离业务谈架构是没有任何意义的,适合业务的才是好架构。而架构也不是一成不变的,随着业务的发展,也许当初设计的架构已不足以支撑目前的业务,那么就需要改变之前的架构。接下来将分享下我们Android客户端的架构设计,在App的某个业务发展阶段或许有一些参考意义。
分层化与模块化
分层化与模块化应该是任何软件开发的共识。
分层化
在Android应用开发中通常可以分为如下几层:
这里需要注意的是SDK层与基础框架层并不是一成不变的,但它们的变化周期往往是比较长的,一般来说当基础功能不能满足最上层的业务逻辑时,就需要对其做扩展。由于基础框架层的功能模块已经是功能级别的粒度划分,因此扩展往往是模块级别的扩展,通常是新增基础功能框架而不是修改原有基础功能框架,这也符合“开放-闭合”原则。
模块化
至于模块化,对于分层化来说则是更细粒度的划分,即将每一层细分为不同的模块,各功能模块尽可能遵循“高内聚、低耦合”的原则,功能模块之间仅提供必要的交互接口。
对于基础框架层,由上图可见,往往是根据功能来划分。这里的基础框架层细分为网络支持功能、图片库、日志系统、数据库支持等模块,如果不足以支撑业务发展,可能会新增其他基础功能模块。
而业务逻辑层则主要由业务需求来决定,如分为扫描功能、电商、快递查询等模块。业务逻辑层的模块化还有一种驱动因素,那就是通用功能的封装,这一点大家应该都有体会,随着App业务逻辑的增加,不同业务功能之间可能会用到相同的功能,如用户登录、分享功能等,我们不希望在每个需要的地方都复写一遍相关代码,于是就需要把通用功能抽取成独立于具体业务需求的模块,如登录模块、分享模块,在模块内部实现通用的业务逻辑,同时对外暴露调用接口,不同的业务只需调用通用模块即可。
业务数据流程设计
由于业务逻辑、数据处理逻辑或网络框架的不同,相信各家应用都有自己的一套数据请求流程。最直接的就是从Activity或Fragment中调用网络请求的方法,然后通过回调将结果返回到Activity或Fragment中,虽然流程最清晰,但这种方式存在几个严重的问题:
上述设计思路是需要摒弃的,结合自身业务及架构演化,我们没有跟风MVP、MVVM,而是设计了下面一套业务数据请求流程:
首先,视图层通常表现为Activity或Fragment,并由视图层发起数据请求,与上述不同,视图层并不直接跟网络框架打交道,而是先将数据请求发送到数据代理层DataAgent。需要注意到是,视图层与数据代理层之间没有采用直接通信的方式,而是插入了一个消息调度器MessageScheduler中转。这样做的好处是将视图层与数据代理层解耦,视图层无需关注数据代理层的具体实现,有了MessageScheduler,视图层所要做的就是发出一个数据请求的消息而已,然后就可以静静等待一个回复消息,该回复消息会附带最终需要的数据对象,这样在视图层就免除了数据处理的逻辑,拿到结果直接展示到UI上即可。使用这种方式,一般来讲Activity或Fragment三五百行代码即可搞定,UI逻辑或接口逻辑(如一个页面有多个接口)比较复杂的代码量基本也能控制在1000行左右,逻辑非常清爽。
消息调度器将视图层的请求消息转发到数据代理层后,DataAgent解析出数据请求类型DataType(该类型对应着具体数据对象模型)、必要参数(接口参数、是否需要缓存结果、分页页码等),然后再执行具体的操作:
当然,由于数据请求流程是耗时的,因此上述步骤都是走的线程池,这点上图中并未注明。
数据代理层
DataAgent在上文中已简单提及,它的主要作用是对数据的一系列操作,包括实际的数据请求、数据解析处理、数据缓存等逻辑。下图为从服务端接口获取JSON数据并处理的流程:
从上图可知,DataAgent的大致工作流程为:
业务视图逻辑
虽然不同的业务页面有不同的视图逻辑,这里以一个应用中最常见的页面为例来说明,假设该页面有一个列表。大家都知道ListView(此处为泛指,可能大家都在用RecyclerView了)的工作方式,它需要ViewHolder来填充视图,需要Adapter来填充数据,如果每个需要ListView的界面都维护各自的一套ViewHolder及Adapter,那么页面逻辑又将变得臃肿。
我们在实践中是这样做的:
经过上述封装之后,视图层只需要向Adapter公共处理类传入一个type参数即可得到对应的Adapter;等数据返回到视图层后,再将数据传给Adapter公共处理类,其他什么都不用管,就可以展示列表数据了。原本需要很多代码实现的逻辑从视图层抽离之后,视图层只需要几行代码就能够完成一个列表展示了。
Hybrid框架
自Android诞生以来,就有Native App与Web App之争,这两种开发方式虽然各有优缺点,但Native App一直占据上风。近一两年来,移动应用中的Web页面越来越多,而纯Native的应用则相对越来越少。但是纯Web App由于其渲染效率、性能问题、对硬件的调用限制导致其也并未广泛地应用。于是一种折中的方案成为主流,即Hybrid App。
所谓Hybrid App,即混合开发方式,部分功能使用Native开发,部分功能使用H5开发。为了充分利用Web开发的优点并避开其缺点,并非所有业务功能都适合使用Web方式来开发。在我们的应用中,主要将H5用于以下方面:
截止到目前,我们App中的Web页所占比重是上升的,大概占到所有功能的25%左右。使用Web开发的优势非常明显,可以支持多变的UI视图效果、节省开发人力(Android、iOS共用)、Bug的在线修复而不用App发版等。
为了满足App的Web页面需求,于是我们在基础框架层扩展了一个Hybrid功能模块。该框架主要是自行封装了Android原生的WebView控件,且分为不同层级的封装,可根据需要灵活使用,核心功能及特性如下:
虽然后来出现了React Native,但由于学习成本及其Android版本的局限性,结合我们自己团队的人力资源原因,我们尚未在应用中正式使用。目前仍然以Hybrid开发为主,且其在整个应用中的比重越来越大,因此Hybrid框架是我们架构中重要的一个组成部分。
消息调度中心
前面业务数据流程的设计中,在视图层与数据代理层之间插入了一个消息调度器——MessageScheduler,MessageScheduler主要功能就是管理消息及消息调度。
MessageScheduler核心原理是维护了一个哈希表,当收到视图层的数据请求时就使用唯一的key将发起者保存到哈希表中,以便稍后收到DataAgent的返回数据后,能够找到发起者。存储好消息发起者的信息后,即向DataAgent发送数据请求,多个数据请求是可以并行的,主要在于线程池的线程数控制机制。DataAgent返回数据之后,MessageScheduler根据唯一key找到初始的请求者,同样利用消息机制将请求结果返回给视图层,同时在哈希表中清除该元素。其示意图如下:
消息分发器
既然有了消息调度机制,就需要消息分发器MessageDispatcher,来负责发送消息。
MessageDispatcher本质上是利用了Android的消息机制来对业务需求进行封装和扩展。看过Android Framework层源码就会发现其实Android框架本身就有很多地方使用了消息机制来进行通信,Android消息机制可以在模块页面间、线程间通信,甚至可以在进程间使用Messenger通信(Messenger方式是利用了消息机制,当然还有其他进程间通信方式)。
MessageDispatcher功能比较简单,支持两种方式:
其示意图如下:
模块路由中心
一个完整的应用中,免不了模块之间、功能页面之间的跳转。当然在需要的地方通过Intent可以实现跳转,但这不是一个好的方案,很明显不同模块或页面之间的耦合度增加了。而我们的原则是模块和页面之间尽可能解耦,于是设计了一个模块路由(Module Routing)中心,App中所有的页面跳转均由其控制。
模块路由的核心原理是给功能页面进行唯一编码,编码的逻辑可以跟随产品版本定义到应用中,并保证兼容之前版本。这样就可以在应用的任何地方只需要向模块路由中心发送对应模块页面的编码即可,由模块路由负责打开目标页面。
以下几点需要注意:
使用模块路由的好处有:
其他
日志系统
在开发过程中,甚至运行过程中,日志都是很重要的一部分。当然Android提供了Log相关的API,但不建议这一行那一行地零星使用,否则如果想统一控制Tag或关闭Log时非常麻烦。建议对Log API进行简单封装或者使用现有第三方Log库,将Log功能独立出来,提供统一的调用接口、级别控制、开关控制,这样既方便调试也方便管理,同时也能为整个应用代码的清晰做出一点贡献。
线上崩溃监控
对线上应用的Crash监控是提高应用稳定性、优化应用性能的一个重要方法。我们构建了一个小型的全局监控系统,主要由以下功能特性:
服务器收到上传的线上崩溃信息后,也按一定策略通过邮件方式通知到开发者,以便开发者及时修复异常。线上崩溃监测系统虽然小而简单,但作用非常重要,利用线上崩溃反馈可以有效地提高应用的稳定性,建议在应用设计中务必给它留出一个位置。
统计系统
相信大部分应用都有统计分析后台,可以统计应用的日活、PV、UV或其他用户行为,也可能有一部分应用是使用的第三方统计功能,如友盟等。结合公司BI部门的统计需求,我们客户端自行设计了一套统计方案,用于Android与iOS两个客户端。之所以不用第三方统计,主要是因为我们无法根据需求自由定制且数据不在自家服务器,另一方面也有些许数据泄露的风险。
基于客户端的统计系统主要包括三个方面的功能:
对于数据采集,主要针对统计部门的需求,如采集设备信息、定位信息、App启动时间次数、PV、UV、甚至用户行为,如点击、切换Tab、页面流向跟踪等。
为了避免每次采集完数据后就即时上传,因此需要数据存储,将采集的统计数据暂存到本地,一般使用SQLite。然后采用一定策略进行上传,如数据累积到50条或者应用切换到后台时进行上传。
对于数据上传,除了上传时机的选择策略外,还要遵循一定的结构字段,该结构可以根据数据统计部门的需求来定义。数据上传的流程同样可以使用之前的数据请求框架,只不过返回值可能为一个成功提示而已。
基于上述功能,我们自定义的统计功能模块提供了方便的调用接口,并支持灵活扩展,目前可以完美支持日常的统计需求,调用也非常简单,只需要在需要统计的地方插入一行代码即可。
域名劫持应对策略
最近遇到域名劫持的问题,真是头疼,另一方面也说明我们的流量引起运营商注意了。目前主流的有几下几种方案:
理论上讲第二种是最佳方案,但由于httpDNS为第三方服务,也无法保证效果,外加上付费及接入成本等因素,我们暂时采用了第三种容灾方案,主要实施逻辑如下:
上述步骤其实是有漏洞的,比如启动时获取最新IP的接口如果被劫持了,那么就无法获取最新IP,假如刚好同时服务器IP也改变了,因此预先内置的IP已经失效,此时就彻底没办法了。不过上述两个条件同时满足的概率比较小,因此可以使用该方案解决很大一部分域名劫持问题。另外从服务端获取的IP,如果有多个的话,还需要增加一些策略,即考虑到负载均衡、访问速度、稳定性、网络运营商等因素,如何确定客户端拿到的哪一个是最优IP,当然这点可以优化,但首先能保证用户看到页面数据或许更加重要。
上述应对域名劫持的策略本身并不能独立成一个模块,我们把它集成为网络框架的扩展。
总结
上文提到的是我们Android应用架构中的核心部分,可能你发现并没有什么花哨的、潮流的玩意儿,没有MVP,没有RxAndroid,没有插件化,也没有热修复……但就是这样它仍然支撑起了上亿的用户量。世上没有完美的架构,只有符合自身业务的架构,上述架构还有很多缺点,我们也在有选择、有步骤地重构,而随着业务需求的扩展,架构也会不断演化,最后希望本文能给大家带来一点参考意义。
标签:hybrid 独立性 调用接口 过程 roi 删除本地文件 ram 实践 运行
原文地址:http://blog.csdn.net/ahence/article/details/56678126