###1. 架构总览

###2. 模块介绍

####2.1 协议封装与任务流程

#####1) 协议与任务的封装

 a. 协议有协议头(协议头因为格式相同,被抽象出来)和协议体组成,协议有两类:请求协议(request)和回复协议(response);
b. 任务(action)由请求协议、回复协议和任务回调(callback)组成;
c. callback是针对客户端主动请求协议的相应处理,分别是成功回调、超时回调和失败回调;

#####2) 消息(任务)流程

a. 由UI或SYSTEM触发一个消息的生成,随之将其投递到发送队列中,等待发送;
b. 消息发送线程会不停的从发送队列中拉取一个消息并发送出去,同时放入超时监测队列;而当网络断开时会等待若干时间并重新循环,若检测该消息在队列等待时间过长,则丢弃该消息并触发相应的失败回调;
d. 对于客户端主动请求,在收到服务端给予回复时,调用该消息的成功回调处理相应回复;
e. 对于超时线程监测超时的消息,移除超时监测队列,并调用超时回调。

####2.2 定时任务

定时任务的实现主要由TimerHelper类与ITimerProcessor两个类,第一个类主要实现了Timer的功能,ITimerProcessor为一个接口,当要建立一个Timer的时候,需要新建立一个类实现ITimerProcessor中得process接口来处理具体的业务。

TimerHelper类的内部封装了系统的Timer及TimerTask来实现,对外提供startTimer以及stopTimer接口。startTimer需要一个布尔类型的参数标志启动定时任务是需要执行一次,还是永久执行。
新生成TimerHelper实例的时候需要制定定时任务的时间以及定时触发处理接口ITimerProcessor的具体实现。

当然在Timer的设计中,我们并没有完全采用系统提供的Timer类实现,有些Timer是我们采用线程模拟实现,这个主要是基于Java 中Timer的一些缺陷来考虑。下面简单介绍下四种Timer

#####2.2.1 心跳Timer

心跳Timer 是维持客户端与服务端长连一个强有力的保证。网络中接收发送都是使用socket的recv与send进行发送与接收,如果此套接字已经断开,则发送与接收数据都会出现问题,创建心跳机制,就是为了及时检测该套接字是否有效。

所谓心跳就是给服务端发送一个自定义的包,来告诉服务端,自己在线,以确保长连的有效性。

#####2.2.2 发送超时Timer

在开发网络应用程序的时候,处理业务和通讯流程之间经常会出现矛盾。这种矛盾主要是由于两者之间的不同步造成的。比如,网络的延迟较大,而实际业务处理的速度则相对比较快,那么如果处理完某一事务然后等待发送成功再处理下一个事务则会大大降低效率。所以我们建立了一个发送消息队列。这是一个典型的“生产者消费者”模型,业务逻辑将需要发送的数据放到消息队列中,SendPacketMonitor从消息队列中取出数据,并发送出去。

由于使用了消息队列的模式,发送就变成了一种异步操作,业务逻辑将消息放入消息队列后,就可以进行其他的操作,而无法知道该消息是否真正发送成功。因此,我们在设计消息队列的时候采用了回调机制,业务方在放入消息队列的时候,必须实现onSuccess接口与onTimeOut接口,分别在发送成功与发送超时调用。

发送超时Timer是自己采用线程来模拟实现的,在SendPacketMonitor类,作为消费者,会不停的从消息队列中取出数据,取出数据后,会判断该消息产生的时刻与当前时间相比较,如果发现时差已经超过系统定义的最大超时时间,则直接调用“生产者”的onTimeOut接口,通知其发送超时。

#####2.2.3 重连“Timer”

重连是另一个保证长连的机制,虽然我们使用了心跳机制来保证长连,但是由于网络环境的复杂性,我们无法保证在一个连接开启后,就永远保持连接,因此,重连就成了另外一个保证。

重连主要是为了当连接断开的时候,客户端能够自动快速的连接到服务端。为了系统的稳定性,及相应快速,重连Timer采用的是线程模拟Timer实现。

重连的逻辑中,会去检测服务端的心跳包,如果发现长时间没有收到服务端的任何数据包,则认为该socket已经失效,并进行重连。

在重连Timer中,为了防止雪崩效应的出现,我们在检测到socket失效,并不是立马进行重连,而是让客户端随机Sleep一段时间再去连接服务端,这样就可以使不同的客户端在服务端重启的时候不会同时去连接,从而造成雪崩效应。

#####2.2.4 好友状态Timer

虽然在实现的逻辑中,服务端在好友状态变化的时候,会主动推送消息给客户端,但是我们还是设计了好友状态Timer。因为在网络复杂的环境中,有太多的未知因素。

好友状态Timer的基本实现就是每隔一段时间发送一个数据包请求获得好友的状态信息。当收到响应数据包的时候,就会去更新Cache中的好友状态信息。

####2.3状态管理

#####2.3.1 状态管理StateManager

状态管理就是一个多状态的状态机,其中包括Net状态管理(指硬件网络是否可用),Socket状态管理(指与MsgServer的连接是否可用)。

状态管理主要功能就是采集Net状态与Socket状态,提供两个接口notifyNetState与notifySocketState两个接口供Net状态管理与Socket状态管理调用,当接收到状态变化的时候会调用NetDispach进行网络状态变更分发。

#####2.3.2 Net状态管理NetStateManager

Net状态管理指的是物理网络状态的管理,主要管理当前物理网络是否可用以及进行变化时进行监听,当监听到网络断开或者连上事件的时候,会调用状态管理notifyNetState接口。

在应用启动的时候我们会注册网络变化广播接收

public class ConnectionChangeReceiver extends BroadcastReceiver

Override他的onReceive函数,在onReceive 函数中获取网络连接服务,然后调用NetStateManager的setState接口,通知状态变化。

#####2.3.3 Socket状态管理

Socket状态管理指的是客户端与MsgServer之间的连接状态。当检测到连接不可用时,会调用该接口的setState接口设置状态。当socket的channelConnected、exceptionCaught与channelDisconnected函数被调用的时候以及在重连出现异常或失败的时候会通知进行状态变更。

#####2.3.4 状态变更分发

状态变更分发对外提供三个接口register,unregister与dispachMsg接口。外界如果关心网络状态变化事件,可以注册自己的Handler到该类,当网络状态发生变化的时候,会根据注册的Handler进行事件通知。

register接口为提供注册的接口。

unregister接口为取消注册的忌口。

dispachMsg为事件分发接口,当网络状态发生变化的时候,该接口会被调用,通知各个Handler进行处理。

####2.4 断线重连

断线重连机制是当IM与MsgServer断开后能够自动连接。断线重连为一个单独的线程,进行循环,当检测到NetState为不可用的时候,会随机睡眠1-9秒然后继续检测。同时会检测心跳包,当发现最后一次收到心跳包超过MAX_HEART_BEAT_TIME时间会认为Socket连接不可用而重置SocketState的状态。当检测到网络可用,Socket状态不可用的时候,就会启动断线重连机制,在进行断线重连之前,会进行连接次数判断,如果为第一次重连,则随机睡眠1秒多,这个机制主要是为了防止服务端出现异常而重启的时候大量的客户端同时连接上来而发生雪崩现象。如果不为第一次重连,则睡眠指定时间,该时间的计算公式如下:

nSleep = (long) Math.pow(2, mnReconnectCount);
if(nSleep > 16) {
nSleep = 16;
}
该计算公式主要是为了防止大量的客户端不停的进行重连从而对服务端造成大量的压力,另外从节省客户端的能耗考虑。

每次进行重连都会将重连次数累加,这个主要是为了防止以后需要对重连次数进行限制。
重连过程如下图:

####2.5 登陆

登陆流程主要分为以下几个流程:1、认证;2、获取MsgServer地址;3、登陆MsgServer。下面依次介绍。

#####2.5.1 认证

认证过程主要是对用户合法身份的验证,包括如下两个方面:

从主客获取AppToken。该过程是一个反射调用,IM程序调用主客的获取Token接口获取到主客的AppToken,
Dao等信息。

拿从主客获取到得AppToken及Dao信息到IM的验证服务器去换取一个IMToken,该过程为一个Http调用。服务器会对上传的AppToken及Dao信息进行校验,如果校验成功,则会返回一个IMToken,以及LoginServer地址等信息,后期需要拿该Token到MsgServer进行登陆验证。

认证的过程主要在TokenManager中实现,该类中还对Token时效进行了管理,当获取IMToken的时候会先对判断IMToken是否为空,如果为空则去获取AppToken等信息,再去服务端换取IMToken,否则判断Token的时效是否失效效,如果失效则获取AppToken并换取IMToken信息,如果有效,则直接返回IMToken。

#####2.5.2 获取MsgServer地址

该过程是客户端通过验证时拿到的LoginServer地址建立Socket连接,并发送获取MsgServer请求,LoginServer会返回一个可用的MsgServer的Ip及Port。

#####2.5.3 登陆MsgServer

当经过3.2获取到MsgServer的IP及Port后,会更具给定的IP与Port与MsgServer建立一个Socket连接,当连接建立成功后。会携带获取到得IMToken,用户名等信息发送一个登陆请求包,如果登陆请求验证通过,客户端启动一个Timer与服务端发送心跳包保持长连接。

以下为整个登陆的流程:

####2.6 异步实现

异步的封装有两种实现方式:1、需要更新界面的异步;2、不需要更新界面的异步。两种实现的方式是不同的。同时异步还有一个异步管理类。

#####2.6.1 TaskTrigger类

TaskTrigger类主要用来注册以及管理各个Task,每生成一个异步实例,都需要通过trigger接口如果对于需要更新界面的异步,则直接调用AsyncTask的execute接口执行任务,否则将其放入Task的任务队列中,后台会通过process接口调用dotrigger接口执行具体的任务,执行结束后,调用task的callback接口。

#####2.6.2 需要更新界面的异步

该方式主要集成Android自有的AsyncTask类,对其进行了一个简单的封装,该异步实现方式主要解决一些需要更新UI界面的异步,解决Android中飞UI线程不能更新UI的问题。

#####2.6.3 不需要更新界面的异步

该异步主要提供不需要更新UI的一些异步操作,该实现方式为新开启一个线程执行任务,当任务执行完成之后调用回调函数。

####2.7 本地缓存

存消息时,根据缓存中之前一条消息的ID,获得新消息的唯一自增ID,将该消息存入DB。存消息时依赖三张表,联系人表,主消息表和附加消息表。首先会根据当前收发用户的ID从联系人表中得到两者的唯一联系ID,并设置消息中的联系ID,将该消息存入主消息表和附加消息表。

读消息时,直接从DB中拉取即可,根据参数的不同拉取的消息不同。可以根据消息ID,拉取某一天消息,也可以根据起止消息ID,拉取指定偏移量和指定条数的消息列表。

存用户信息时,判断该用户信息是否为空或合法,若为空或不合法,直接返回;若合法,则更新 缓存中的用户信息,用户信息只保留在缓存中。

取用户信息时,先判断缓存中是否存在该用户信息,若存在,则直接返回该用户信息;否则返回null,由业务端选择是否发起取用户信息的操作。

收发消息时,得到好友ID,判断缓存中得最近联系人ID列表中是否存在,若不存在则添加,否则 忽略;同样地,在获取最近联系人列表时也如此。

⾸首先获得最近联系人ID列表,然后从缓存中读取必要地信息,组合成最近联系人列表。另外,启用一个线程,每500毫秒来检测是否有新消息来通知UI主线程来更新界面

* 作者:蘑菇街IM团队四小虎:纳纳、蓝狐、语鬼、舒沉,和移动团队强力攻城狮支援:海猪  

Android-IM架构设计的更多相关文章

  1. Android APP架构设计——MVP的使用示例

    0. 前言 为了更好地进行移动端架构设计,我们最常用的就是MVC.MVP和MVVM,作为三个最耳熟能详的三大架构,应用可谓非常广泛.对于这三种架构设计以及优缺点已经在Android APP架构设计-- ...

  2. Android APP架构设计——MVC、MVP和MVVM介绍

    )对于过大的项目,数据绑定需要花费更多的内存. 关于APP的架构设计就介绍到这吧,转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/529 ...

  3. android app 架构设计02

    二:在开放的过程中,尽量把工具类,BaseActivity 放在指定的位置. DateFormat Bitmap Notification Shared Preference Environment ...

  4. android app 架构设计01

    1:本文有摘抄, 1 2 3 4 5 - 开发过程中.需求.设计.编码的一致性 - 整个程序具有统一的风格,比方对话框样式,button风格,色调等UI元素 - 整个程序详细统一的结构,比方不同模块訪 ...

  5. Android App的设计架构:MVC,MVP,MVVM与架构经验谈

    相关:http://www.cnblogs.com/wytiger/p/5996876.html 和MVC框架模式一样,Model模型处理数据代码不变在Android的App开发中,很多人经常会头疼于 ...

  6. [转]Android App整体架构设计的思考

    1. 架构设计的目的 对程序进行架构设计的原因,归根到底是为了提高生产力.通过设计使程序模块化,做到模块内部的高聚合和模块之间的低耦合.这样做的好处是使得程序在开发的过程中,开发人员只需要专注于一点, ...

  7. Android App的架构设计:从VM、MVC、MVP到MVVM

    随着Android应用开发规模的扩大,客户端业务逻辑也越来越复杂,已然不是简单的数据展示了.如同后端开发遇到瓶颈时采用的组件拆分思想,客户端也需要进行架构设计,拆分视图和数据,解除模块之间的耦合,提高 ...

  8. 王家林的81门一站式云计算分布式大数据&移动互联网解决方案课程第14门课程:Android软硬整合设计与框架揭秘: HAL&Framework &Native Service &App&HTML5架构设计与实战开发

    掌握Android从底层开发到框架整合技术到上层App开发及HTML5的全部技术: 一次彻底的Android架构.思想和实战技术的洗礼: 彻底掌握Andorid HAL.Android Runtime ...

  9. 高焕堂《android从程序员到架构师之路》 YY讲坛直面大师学习架构设计

    <android从程序员到架构师之路>YY讲坛活动:  sundy携手高焕堂老师全程YY答疑 与大师一起,分享android技术 时间:7月21日下午2:00   报名联系QQ:22243 ...

  10. Android架构设计和软硬整合完整训练

    Android架构设计和软硬整合完整训练 Android架构设计和软硬整合完整训练:HAL&Framework&Native Service&Android Service&a ...

随机推荐

  1. 封装curl类,post get方法实现网站请求

    <?phpclass RamDemo{    //get方法    function RamGet($url,$arr)    {        if($arr!=''){           ...

  2. nginx反向代理nginx,RealServer日志打印真实ip

    title: nginx反向代理nginx,RealServer日志打印真实ip date: 2016-05-11 19:15:37 tags: --- nginx反向代理nginx,RealServ ...

  3. 使用vs中的工具进行架构比较

    使用vs自带的架构比较工具可以对不同库中的结构进行比较,也可以将源中的架构更新到目标架构中.当然这种更新只是架构的更新,数据并不会同步.

  4. Web ADF 编程步骤.

    从Web Controls 开始(工具来中的 ArcGIS Web Controls). 访问Resource Manager. 找到待访问的 Resource. 决定 Resource支持哪个 Fu ...

  5. 截取字符串 substring substr slice

    截取字符串   substring 方法用于提取字符串中介于两个指定下标之间的字符  substring(start,end) 开始和结束的位置,从零开始的索引 参数     描述 start     ...

  6. window下Slik SVN的安装配置

    我相信各位都应该对SVN不会陌生吧,我相信绝大多数人都使用过,但是并不是人人都自己配置过SVN服务器.下面就是我配置SVN服务器的步骤,以及在配置过程中碰见的一些问题,在此记录,希望对你有所帮助. 安 ...

  7. DataTable 对象 转换为Json 字符串

    /// <summary> /// DataTable 对象 转换为Json 字符串 /// </summary> /// <param name="dt&qu ...

  8. keypress和keydown事件及keyCode和keyChar

    二:keypress和keydown事件: 现只在IE10.chrome(版本 32.0.1700.107 m).firefox(25.0.1)中测试了.   IE8 chrome firefox $ ...

  9. js和jquery中的触发事件

    改别人的坑,遇到jquery选择器和fireEvent混用,不认识fireEvent方法报错. js的方法不能使用jquery的选择器去调用. 1.fireEvent (IE上的js方法 ) 我们来看 ...

  10. cf C. Levko and Array Recovery

    http://codeforces.com/contest/361/problem/C 这道题倒着一次,然后正着一次,在正着的一次的时候判断合不合法就可以. #include <cstdio&g ...