android简单即时聊天sdk

  • 切换用户登录的实现
  • 联系人列表的实现
  • 聊天页的实现
  • 消息缓存与排序
  • 消息接收和分发——数据库队列和投递队列
  • 有序列表的维护

切换用户登录的实现

  • 由于不同的登录用户需要有不同的联系人以及聊天记录等数据,而切换用户之后再重新登回时也应尽量保持与之前体验一致,所以比较恰当的方式是每一个登录用户建立一个以自己用户id为尾缀的database。每次用户登录成功后即可通过该登录用户的userid去本地读取该用户数据库相关的数据(egg. IMDB_123456)
  • 安卓提供了一个比较方便的数据库抽象类SqliteOpenHelper,自己实现一个IMDBHelper类继承SqliteOpenHelper,并提供一个根据用户id获取实例的方法,我的代码如下
public IMDBHelper getIMDBHelper(long userid) {if (userid > 0 && dbhelpers == null) {dbhelpers = new Hashtable<>();}String dbname = getDBName(userid);if (!dbhelpers.containsKey(dbname)) {synchronized (dbhelpers) {IMDBHelper helper = new IMDBHelper(IMClient.getInstance().getContext(), dbname);dbhelpers.put(dbname, helper);}}return dbhelpers.get(dbname);}

联系人列表的实现

  • 每次进入联系人列表的时候需要先去服务度拉取最新联系人版本号,并与本地对照,如果大于本地版本号,则要去服务端拉取增量数据(这是联系人列表的增量更新逻辑,要求不高也可每次全量更新即可);拉取数据回来后,如果是有新增/删除联系人的操作则需要先新增/删除联系人表的相关记录,然后新增/删除以该联系人id为尾缀的对应的消息表。
  • 联系人列表需要包含的元素有三部分
    • 1、联系人数据(若为单聊联系人即用户头像、id等用户相关数据);
    • 2、未读消息数
    • 3、最后一条消息(消息摘要、消息时间)
  • 联系人表结构比较简单,表名固定t_contacts即可,lastMsg、newMsgTime、newMsgNum、userid四个字段就能完整描述了,userid就是对应的联系人id,真正的用户数据放到另外的user表里面,需要用时通过userid去取出即可。
  • 联系人列表的ui实现也比较简单,用Listview或者RecyclerView都可以,重点是每次新增/删除联系人、有新消息到来时都需要对列表排序,但是大量的排序肯定是很消耗性能的,所以我想到的比较科学的方式是在本地维护一个有序的列表,这样只要在每次插入的时候进行一次o(n)甚至是o(1)复杂度的运算就可以实现了。列表实现代码在文章最后面贴。下面为在新增联系人记录时新建消息表的代码。
 public synchronized long insert(ContactEntity entity,TableOperator<IMSimpleUser> operator) {// 插入用户表if (entity.getContact() != null) {if (operator == null) {operator = new UserTable().attachTable();}operator.insert(entity.getContact());}// 插入或更新contact表List<ContactEntity> users = attachTable().query(USERID,entity.getContact().getUserid() + "");if (users != null && users.size() > 0) {return attachTable().update(USERID, entity.getContact().getUserid() + "", entity);} else {// 如果是插入,需要创建聊天记录表long id = attachTable().insert(entity);new MessageTable(entity.getContact()).attachTable();return id;}}

聊天页的实现

  • 聊天页的底层主要基于一个以会话id为尾缀的消息表,消息表是在联系人表发生变动(新增/删除)时来动态建立/删除的。会在缓存初始化时从表中加载数据,会在新消息产生(发送)或者是新消息到来(接收)时在对应的消息表中做新增、更新等操作。(notes:发送消息的时候聊天界面上可能会有不同的状态展示,例如发送中、发送失败等的状态显示,可以在新增消息后将该消息的自增id返回保存到缓存中,以便下次更新数据状态时精确定位到具体消息)。
  • 聊天界面的ui会相对比较复杂,会有不同的消息类型展示,会有表情、大表情等入口,可能还会有跟自己业务相关的消息类型,具体实现可以参考环信demo的easeui来做。
  • 消息表名类似:t_msg_123445;字段大致包括如下即可大致描述清楚一条消息:uid(消息发送者id)、tuid(消息接收者id)、time、msgid、direct(消息走向:send/receive)、message(消息具体内容)、deliverStatus(消息投递状态:发送消息时用到);
  • 消息表的封装类会提供三个方法以实现初始化数据拉取,加载历史消息,以及消息窗口移动的功能。

    public List<IMSimpleMessage> loadFirst(int size) {return attachTable().query(null, null, size + "", null, TIME + " DESC");
    }public List<IMSimpleMessage> loadFront(long time, int start, int size) {return attachTable().query(new String[] {TIME}, new String[] {time + ""}, start + "," + size, "<", TIME + " DESC");
    }public List<IMSimpleMessage> loadBack(long time, int start, int size) {return attachTable().query(new String[] {TIME}, new String[] {time + ""}, start + "," + size, ">", TIME + " ASC");
    }
  • 聊天页的展示与交互基本是基于对缓存的操作与接收缓存变动通知来实现。

消息缓存与排序

  • 为什么会需要一个常驻内存的消息缓存呢,主要是新消息到来与发送的频率是有可能很高,且实时性要求高,而且在联系人列表以及聊天页面都需要用到消息相关数据。同时,如果没有消息缓存,当较早发送的消息由于网络延时或者其他原因比较晚的消息更晚到达时,可能联系人列表的最后一条消息就会显示的并不是真正的最后一条消息。鉴于此,很有必要维护一个统一的缓存;由于联系人列表需要用到,所以缓存的存在周期应与联系人列表的存在周期相同。
  • 消息缓存的初始化,在联系人列表数据获取成功后完成,初始化时从各个响应的消息表中取出第一页需要取出的数据
  • 加载更多历史消息,聊天页下拉加载更多时会从表中分页加载更多数据进入缓存列表,当缓存窗口未达到限定值时直接将数据加入到列表(每次数据加入缓存时会自动插入恰当的时序位置以实现有序列表)。当缓存窗口达到了限定值的时候,如果是加载较早时间方向的消息,则会先移除较晚时间方向的消息,然后在入列表。若方向相反则移除数据同样相反。
  • 当缓存发生变动时(插入、删除、初始化加载)都会触发缓存变动事件,这个事件会被传递到上层,上层收到事件通知后,会来调用缓存的获取列表接口并刷新显示。
private void notifyReceive() {if (sendCallbacks != null && sendCallbacks.size() > 0) {for (MessageCallback callback : sendCallbacks) {callback.onRecieve();}}}private void notifyLoadEnd(int flag, int size) {if (sendCallbacks != null && sendCallbacks.size() > 0) {for (MessageCallback callback : sendCallbacks) {callback.onLoadEnd(flag, size);}}
}
  • 发送/收到新消息时会先将新消息加入到缓存中,再走不同流程。如果是发送新消息会先加入缓存,然后加入数据库队列,数据库队列将其插入数据库后后再将其加入到投递队列,投递队列完成了,更改缓存状态,更改数据库状态,通知ui缓存变动。
  • 如果是接收到新消息,也会先加入到缓存,然后会通知ui缓存变动,再加入到数据库队列。
public void sendMessage(IMSimpleMessage message) {if (MessageUtils.checkSend(message)) {putIntoList(message);Deliver.getInstance().deliverMessage(message, this);}}public void saveMessage(IMSimpleMessage message) {if (message != null) {putIntoList(message);DBWorker2.getInstance().insertOrUpdate(this, message);}
}public void receiveMessage(IMSimpleMessage message) {if (MessageUtils.checkReceive(message)) {putIntoList(message);DBWorker2.getInstance().insertOrUpdate(this, message);}
}

消息接收和分发——数据库队列和投递队列

  • 由于存数据库和投递消息(我们使用http请求来发送消息)都是阻塞型的事件,所以都会单独建立一个异步消息处理线程来处理消息相关的操作。
  • 每个消息操作(插入、修改、加载)都会被封装成一个数据库操作任务被加入到数据库线程的队列中。
public static class ConversationEntity {public interface Task {int LOAD_FIRST = 0X01;int LOAD_FRONT = 0X02;int LOAD_BACK = 0X03;int INSERT_OR_UPDATE = 0X04;}public int loadStart;public int loadSize;public String conversationid;List<IMSimpleMessage> messages;public DBCallback callback;public int taskid;public long loadTime;public DeliverCallback deliverCallback;public ConversationEntity(String conversationid, long loadTime, int loadStart, int loadSize,int taskid, DBCallback callback) {this.conversationid = conversationid;this.taskid = taskid;this.callback = callback;this.loadStart = loadStart;this.loadSize = loadSize;this.loadTime = loadTime;}public ConversationEntity(String conversationid, int taskid, DBCallback callback,List<IMSimpleMessage> messages) {this.conversationid = conversationid;this.taskid = taskid;this.callback = callback;this.messages = messages;}
}
  • 同理每个需要被投递的消息也会被封装成一个投递任务被发布到投递线程的队列中。
public static class DeliverEntity {public DeliverEntity(IMSimpleMessage message, DeliverCallback callback) {this.message = message;this.callback = callback;}DeliverCallback callback;IMSimpleMessage message;
}

有序列表的维护

  • 消息缓存的方案我网上找了一下,看到了一些通用数据结构的缓存方案,例如FIFO、Lru等等,但貌似与我们所需要的并不相符,我需要的是一个以时间为轴的自动排序列表,所以我自己基于链表实现了一个简单的。
public class TimeOrderCache<T extends ITimeOrder> implements ITimeOrderCache<T> {String key;LinkedList<T> cache;final int MAX_SIZE = 1024;public TimeOrderCache(String key) {this.key = key;cache = new LinkedList<>();}@Overridepublic String getKey() {return key;}@Overridepublic void putIntoList(T entity) {if (entity != null) {synchronized (cache) {if (cache.size() > MAX_SIZE) {cache.removeLast();}Iterator<T> iterator = cache.descendingIterator();int index = cache.size();boolean needInsert = true;while (iterator.hasNext()) {T t = iterator.next();if (entity.equalsMessage(t)) {needInsert = false;break;}if (entity.getTimeOrder() > t.getTimeOrder()) {// 插入该元素的下一个位置break;}index--;}if (needInsert) {if (index < 0)index = 0;cache.add(index, entity);}}}}@Overridepublic T getLast() {if (cache.size() > 0)return cache.getLast();return null;}@Overridepublic T getFirst() {if (cache.size() > 0)return cache.getFirst();return null;}@Overridepublic T removeLast() {synchronized (cache) {if (cache.size() > 0)return cache.removeLast();return null;}}@Overridepublic T removeFirst() {synchronized (cache) {if (cache.size() > 0)return cache.removeFirst();return null;}}@Overridepublic int size() {return cache.size();}public List<T> getCache() {// 使用Collections.unmodifiableList发现ListView数据更换后无法通过notifyDatasetChanged刷新synchronized (cache) {if (cache.size() > 0) {Iterator<T> iterator = cache.iterator();ArrayList<T> caches = new ArrayList<>();while (iterator.hasNext()) {T t = iterator.next();caches.add(t);}return caches;}}return null;}@Overridepublic void clear() {synchronized (cache) {cache.clear();}}
}

ITimeOrder是每一个存入该缓存的数据实体必须实现的简单接口

/*** 用于获取排序的时间值 Created by walljiang on 2017/11/20.*/public interface ITimeOrder {long getTimeOrder();boolean equalsMessage(ITimeOrder entity);
}

android简单即时聊天sdk相关推荐

  1. Android 开发即时聊天工具 YQ :(四) 获取好友列表

    在Android 开发即时聊天工具 YQ :(三) 实现登陆功能中已经实现了登陆功能,离能聊天又近了一步了 :) 在实现聊天之前还有一个重要的东西,?没错,就是好友列表,没的好友你和谁聊呀,是吧, 嘿 ...

  2. Android集成极光聊天SDK

    1.前往官网注册应用 2.添加依赖 //极光统计 implementation 'cn.jiguang.sdk:jmessage:2.9.0' implementation 'cn.jiguang.s ...

  3. Android 开发即时聊天工具 YQ :(六) 最近会话列表

    实现的效果如图: RecentActivity: public class RecentActivity extends Activity{ListView listView;List<Rece ...

  4. android 即时聊天工具 yq,Android 开发即时聊天工具 YQ :(四) 获取好友列表

    实现聊天之前还有一个重要的东西,?没错,就是好友列表,没的好友你和谁聊呀,是吧, 嘿嘿,一切从简,早点实现基本的聊天目标的说,所以代码很懒(or 烂?),为什么呢?看完就知道了, 在服务器端当登陆成功 ...

  5. Android 开发即时聊天工具 YQ :(五) 发送消息

    服务器端转发消息功能上节已经实现,只需将消息转发给消息包中的接收人即可, if(m.getType().equals(YQMessageType.COM_MES)){//如果是普通消息包 //取得接收 ...

  6. Android 开发即时聊天工具 YQ :(三) 实现登陆功能

    前面socket基本通信完了,登陆界面也已经完成,下面就是重点了,实现登陆功能 服务器和客户端的代码当然不肯能用那个控制台的那个了,所以全部得重写,不过原理都一样,代码也差不多,都有注释,一看就明白. ...

  7. android 第三方 im,Android基于环信SDK开发IM即时聊天

    目前市面上我了解的做第三方即时聊天SDK的有两家:环信.融云,这里我使用环信SDK来完成即时聊天的初步开发工作. 下面先奉上1张效果图: 1.开发准备 首先要到环信官网注册开发者账号,目前只有企业账号 ...

  8. Android基于环信SDK开发IM即时聊天(一)

    2016-09-02更新:可以看一下最新的这篇文章和源码,Android基于环信SDK开发IM即时聊天(二) 目前市面上我了解的做第三方即时聊天SDK的有两家:环信.融云,这里我使用环信SDK来完成即 ...

  9. 环信sdk android 聊天,Android基于环信SDK开发IM即时聊天

    根据用户移动地图的位置,显示在视野范围内的建筑物,简单的思路是,添加地图监听,当地图移动结束之后,计算出当前屏幕四个角的GeoPoint,根据这4个点,通过mys 目前市面上我了解的做第三方即时聊天S ...

最新文章

  1. 线程池的介绍及简单实现
  2. gedit emacs
  3. No CurrentSessionContext configured ------Hibernate
  4. 08-Isolation using virtualization in the Secure World_Whitepaper
  5. Python练习题:如何将多个小字符串拼接成一个大的字符串
  6. 函数伪代码_Excel常用函数
  7. bootstrap 导航菜单 折叠位置_python测试开发django44.xadmin自定义菜单项
  8. Wpf中通过绑定来更新textbox的值
  9. tars 部署 oracle,Tars 部署介绍(必看)
  10. 职场上不会“装傻”,才是真的傻!
  11. Python语言解析xml文件
  12. bootstrap-suggest插件处理复杂对象时的解决方案
  13. NPOI导出 The maximum column width for an individual cell is 255 characters
  14. 论文笔记_S2D.19_2018-PR_基于膨胀卷积神经网络与软加权和推理的分层融合单目深度估计
  15. win7安装系统后关闭计算机,完美重装系统win7后电脑为什么总是自动关机?
  16. 代码主题darcula_Intellij idea 中的Darcula主题怎么把颜色改回来?
  17. JSON是什么?对JSON的简单理解
  18. leetcode21 合并两个有序链表
  19. 串口通信时序的位序是先发低位
  20. 配置免密登录报错:ssh: Could not resolve hostname note1: Name or service not known

热门文章

  1. 一部手机全搞定,抖音发工资了,一共2千多,方法人人可以用
  2. 基于核概念的KCCA算法
  3. 工程造价能不能预防超预算
  4. 荣耀magic5pro参数配置
  5. python画椭圆形_Python3 tkinter基础 Canvas create_rectangle 画虚边的矩形 create_oval 画椭圆形 圆形...
  6. Linux中的stdout和stderr
  7. 2020中国大学生程序设计竞赛(CCPC) - 网络选拔赛 1005 Lunch (杭电 6892)
  8. Unsupported major.minor version 52.0那点坑
  9. 难以置信,已经有人用 ChatGPT 做 Excel 报表了?
  10. Hbuilder内更改SVN地址(svn服务器IP变更)