Bmob算是国内做的比较好的做BaaS或MBaaS服务的公司了,官方写的IM的Demo目前可以实现登录注册、搜索添加好友、与好友进行文字图片音频位置对话功能,但感觉还有很多缺陷,比如说删除表中的数据时只能根据ID查找,没提供数据库事务操作的接口,IM没实现群聊的功能,有些API太少太局限,可能因为公司团队小吧,希望能越做越好。
回到正题,既然Bmob的IM没有实现群聊,那就自己实现,Bmob有一个“数据实时同步”的功能,用来让SDK对某些数据表进行监听,一旦数据表发生改变,就会通知到SDK。群聊时,用户发消息时就发到该群对应的消息表中,群中的所有成员都对这个消息表进行监听,因此一旦监听到表中数据变化就表明某个成员发了消息,就能取到这个消息对象。
数据库设计:
群组分类表TribeTypeBean(分类名、分类描述)
public class TribeTypeBean extends BmobObject {
private String tribeTypeName;
private String tribeTypeDescription;
群组表TribeBean(群组名、群组描述、群组头像、群组创建者、群组成员、群组所属分类),其中,群组创建者和群组分类是一对多关联,在SDK中直接用对象类型,在云端表现为Pointer< T >类型。群组成员是多对多关联,在SDK中要声明为BmobRelation类型,在云端表现为Relation< T >类型。
public class TribeBean extends BmobObject {
private String tribeName;
private String tribeDescription;
private String tribeAvatar;
private BmobUser tribeLeader;
private BmobRelation tribeMember;
private TribeTypeBean tribeTypeBean;
群组消息表GroupChatMsgBean(消息对应群组、消息发送者、消息内容、消息发送时间、是否已读、消息状态),在创建群组时,就要为这个群组创建一个消息表,消息表中要有一个字段表明该消息表对应的群组。这样就建立起了单向的一对一关联关系。但有个问题,我可以通过条件查询群组表知道自己加入了哪些群组,但并不知道这些群组对应的消息表是什么。所以我决定采用一种比较简单的方式来实现,让消息表的表名为“GroupChatMsg”+对应群组的id,这样就在语义上建立起了对应关系,也保证了表名的唯一性,监听表时就监听“GroupChatMsg”+对应群组的id这些表就行了。
public class GroupChatMsgBean extends BmobObject {
private TribeBean tribe;
private UserBean sender;
private String msgContent;
private Integer msgType;
private String msgTime;
private Integer isReaded;
private Integer status;
虽然每个群组对应的表名不一样,但群消息实体还是一样的,因此发消息时不能直接调用BmobObject的save方法,需要用云端代码来完成:
function onRequest(request, response, modules) {
var db = modules.oData;
var tableName = request.body.tableName;
var groupChatMsgObject = JSON.parse(request.body.groupChatMsg);
db.insert({
"table" : tableName,
"data" : groupChatMsgObject
},function(err,data){
response.send(err+","+data);
}
);
}
调用参数是消息表名和消息实体。
因此,发消息时要组装好消息对象并调用云端代码:
UserBean currentUser = BmobUser.getCurrentUser(GroupChatActivity.this, UserBean.class);
try {
AsyncCustomEndpoints ace = new AsyncCustomEndpoints();
JSONObject cloudCodeParams = new JSONObject();
GroupChatMsgBean groupChatMsgBean = new GroupChatMsgBean();
groupChatMsgBean.setTribe(targetTribe);
groupChatMsgBean.setSender(currentUser);
groupChatMsgBean.setMsgContent(et_send.getText().toString());
groupChatMsgBean.setMsgType(BmobConfig.TYPE_TEXT);
groupChatMsgBean.setMsgTime(String.valueOf(BmobUtils.getTimeStamp()));
groupChatMsgBean.setIsReaded(0);
groupChatMsgBean.setStatus(BmobConfig.STATUS_SEND_SUCCESS);
sendMessageToUI(groupChatMsgBean);
Gson gson = new GsonBuilder().setExclusionStrategies(new ExclusionStrategy() {
@Override
public boolean shouldSkipField(FieldAttributes f) {
return f.getName().contains("_c_")|f.getName().contains("increments")|f.getName().contains("tribe")|f.getName().contains("sender");
}
@Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
}).create();
JSONObject jsonObject = new JSONObject(gson.toJson(groupChatMsgBean));
jsonObject.put("tribe",new JSONObject(new Gson().toJson(new BmobPointer(targetTribe))));
jsonObject.put("sender", new JSONObject(new Gson().toJson(new BmobPointer(currentUser))));
cloudCodeParams.put("tableName", "GroupChatMsg" + targetTribe.getObjectId());
cloudCodeParams.put("groupChatMsg", jsonObject);
ace.callEndpoint(GroupChatActivity.this, "sendGroupChatMsg", cloudCodeParams,
new CloudCodeListener() {
@Override
public void onSuccess(Object object) {
// TODO Auto-generated method stub
LogUtils.LOGE("shang", "云端返回:" + object.toString());
}
@Override
public void onFailure(int code, String msg) {
// TODO Auto-generated method stub
LogUtils.LOGE("shang", "访问云端失败:" + msg);
}
});
} catch (Exception e) {
e.printStackTrace();
}
监听消息表的变化时用实时数据处理类BmobRealTimeData:
bmobRealTimeData = new BmobRealTimeData();
bmobRealTimeData.start(getActivity(), new ValueEventListener() {
@Override
public void onDataChange(JSONObject jsonObject) {
// TODO Auto-generated method stub
//{"data":{"content":"我就试试","objectId":"mynjZZZa","createdAt":"2016-01-15 18:02:47"},
// "action":"updateTable","objectId":"","tableName":"99aa3421bf","appKey":"44d4d237ac590c04a4cc0f3e030ab0a9"}
LogUtils.LOGE("shang", "onDataChange "+jsonObject.toString());
if (BmobRealTimeData.ACTION_UPDATETABLE.equals(jsonObject.optString("action"))) {
JSONObject data = jsonObject.optJSONObject("data");
Gson gson = new GsonBuilder().setExclusionStrategies(new ExclusionStrategy() {
@Override
public boolean shouldSkipField(FieldAttributes f) {
return f.getName().contains("sender") | f.getName().contains("tribe");
}
@Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
}).create();
GroupChatMsgBean bean = gson.fromJson(data.toString(), GroupChatMsgBean.class);
//如果收到的是自己的消息更新或者是删除更新,直接return
if (currentUserId.equals(data.optString("sender")) || "".equals(data.optString("sender"))) {
return;
}
getTribeById(bean, data.optString("tribe"));
getUserById(bean, data.optString("sender"));
}
}
@Override
public void onConnectCompleted() {
// TODO Auto-generated method stub
if (bmobRealTimeData.isConnected()) {
LogUtils.LOGE("shang", "连接成功");
for (TribeBean tribeBean :
myTribeBeanList) {
bmobRealTimeData.subTableUpdate("GroupChatMsg" + tribeBean.getObjectId());
}
}
}
});
在onDataChange回调中接收处理新增的消息,如发送广播:
Intent intent = new Intent();
intent.setAction(“cn.minitribe.group_chat_msg”);
intent.putExtra(“GroupChatMsgBean”, bean);
GroupChatFragment.this.getActivity().sendOrderedBroadcast(intent, null);
在onConnectCompleted回调中启动相关表的监听。