前言:
关于即时通讯,项目中要是需要一个收发消息的功能。从开始到写完即时通讯这块儿,花了大约3天时间。但真的想吐槽下bmobIM的服务器,有短板时间都在等待连接:disconnect
或者java.util.concurrent.TimeoutException
。问他们客服,他们说是IM的带宽满了。原话是这样:IM带宽满了。心里也是一万个无奈呀,但是也没办法,项目负责人点名了bmob,那就来吧:
集成:
bmob的文档这块儿写得算是不错了,我来整理一下:
- IM SDK使用Data SDK的BmobFile用于图片、语音等文件消息的发送,即特定的IM SDK只能和特定版本的Data SDK匹配。
IM SDK 版本 | Data SDK 版本 |
---|---|
bmob-im:2.0.1 至 2.0.2 | bmob-sdk:3.4.6-0304 |
bmob-im:2.0.3 至 2.0.4 | bmob-sdk:3.4.6 |
bmob-im:2.0.5 | bmob-sdk:3.4.7-aar |
bmob-im:2.0.6 至 2.0.8 | bmob-sdk:3.5.5 |
bmob-im:2.0.9 | bmob-sdk:3.5.6 |
bmob-im:2.1.1 | bmob-sdk:3.6.3 |
- 在project下面的build.gradle文件中添加maven仓库地址:
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.3.0'
}
}
allprojects {
repositories {
jcenter()
//TODO 集成:1.1、配置Bmob的maven仓库地址
maven { url "https://raw.github.com/bmob/bmob-android-sdk/master" }
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
- 在app下的build.gradle文件中添加dependencies外部依赖:(此处注意Data和IM的SDK版本的对应关系)
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
//TODO 集成:1.2、配置IM SDK(bmob-im)版本和Data SDK(bmob-sdk)版本:特定版本的bmob-im依赖特定版本的bmob-sdk
compile 'cn.bmob.android:bmob-im:2.1.1@aar'
compile 'cn.bmob.android:bmob-sdk:3.6.3'
}
- 在AndroidManifest.xml下添加你的APP KEY:
<?xml version="1.0" encoding="utf-8"?>
<manifest
...//权限
<application
...
<meta-data
android:name="Bmob_APP_KEY"
android:value="fef642bee9678388a478d8b5b25bafa0" />
...
</application>
</manifest>
- 权限:
<!--TODO 1.4、配置IM SDK需要的权限-->
<!--网络权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 监听网络的变化 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<!-- 设备休眠 -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<!-- sd卡存储-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!--摄像头-->
<uses-permission android:name="android.permission.CAMERA" />
<!--录音-->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!--通知震动-->
<uses-permission android:name="android.permission.VIBRATE" />
- 在AndroidManifest.xml下添加service、receiver标签:
<?xml version="1.0" encoding="utf-8"?>
<manifest
...//权限
<application
...
<!--TODO 集成:1.5、配置IM SDK需要的广播和服务-->
<receiver android:name="cn.bmob.newim.core.ConnectChangeReceiver" >
<intent-filter>
<action android:name="cn.bmob.action.RECONNECT" />
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.USER_PRESENT" />
</intent-filter>
</receiver>
<service
android:name="cn.bmob.newim.core.service.BmobIMService"
android:process=":bmobcore" />
<service
android:name="cn.bmob.newim.core.service.NotifyService"
android:process=":bmobcore" />
<service android:name="cn.bmob.newim.core.service.ReConnectService" />
<service android:name="cn.bmob.newim.core.service.HeartBeatService" />
...
</application>
</manifest>
项目的集成到这儿就结束了,剩下的就是如何使用这些东西了,简单看下我的流程图。
创建会话列表和好友列表(RecycleView)
- 首先说下个人认为官方文档中最难理解的一个名词:
会话
。会话分为暂态会话
和常态会话
。我对会话
的理解:就好比两个好友打电话时,要有一条连通的电话线一样,要实现IM,也要有连通双方的一根线,而这根线就相当于是会话
。应用在本地都会有数据表,用来存储应用中的数据。暂态会话不会保存在本地数据库中;常态会话会被保存到本地数据库中(暂态会话可以用来加好友,常态会话用来聊天)。 - 怪当时太年轻,以为即时通讯就只是聊天,在使用SDK完成加好友操作之后才看的IM文档,所以暂态会话这里就不说了,但是道理应该是相通的,相信看完常态会话之后,态会话都不是事儿。
- 即时通讯Demo中有两个RecycleView,一个用来存储好友列表,一个用来存储会话列表。而会话列表在你搞完发消息之前肯定是空的。
好友列表:(好友的添加就不说了,自行添加一些好友就行)
会话列表:(当然你的肯定是空的)
RecycleView点击事件-创建会话入口
首先看下官方的Demo:
adapter.setOnRecyclerViewListener(new OnRecyclerViewListener() {
@Override
public void onItemClick(int position) {
if (position == 0) {//跳转到新朋友页面
startActivity(NewFriendActivity.class, null);
} else {
Friend friend = adapter.getItem(position);
User user = friend.getFriendUser();
BmobIMUserInfo info = new BmobIMUserInfo(user.getObjectId(), user.getUsername(), user.getAvatar());
//TODO 会话:4.1、创建一个常态会话入口,好友聊天
BmobIMConversation conversationEntrance = BmobIM.getInstance().startPrivateConversation(info, null);
Bundle bundle = new Bundle();
bundle.putSerializable("c", conversationEntrance);
Intent intent = new Intent();
intent.setClass(getActivity(), ChatActivity.class);
if (bundle != null) {
intent.putExtra(getActivity().getPackageName(), bundle);
}
getActivity().startActivity(intent);
}
}
由Demo可知,用BmobIMConversation来创建常态会话,传入参数(info,null)
- info:会话肯定要知道会话双方的信息,方便服务器转发消息。一方是自己,由
BmobUser.getCurrentUser()
即可得到自己的信息,自己的信息应该默认已经传进去了,不需要我们管;而另一方,也就是通信对方的信息,就需要info传入会话人口了,info为BmobIMUserInfo
类型,需传入(用户id,用户name,用户头像(Url)),其构造和函数:
public BmobIMUserInfo(String var1, String var2, String var3) {
this.userId = var1;
this.name = var2;
this.avatar = var3;
}
startPrivateConversation(info, null)
默认为创建常态会话,若要创建暂态会话,可BmobIMConversation conversationEntrance = BmobIM.getInstance().startPrivateConversation(info, true, null);
- Demo中后面代码意思就是把创建好的
conversationEntrance
传给会话界面,我的方法是:
BmobIMUserInfo info = new BmobIMUserInfo(list.get(i).getObjectId(), list.get(i).getNickName(), list.get(i).getPicture_head().getFileUrl());
BmobIMConversation conversation = BmobIM.getInstance().startPrivateConversation(info, null);
fragmentManager.beginTransaction()
.hide(fragmentManager.findFragmentByTag("text_button_wodeqiuyou"))
.remove(fragmentManager.findFragmentByTag("text_button_wodeqiuyou"))
.add(R.id.fragment_container, FragmentIM.newInstance(list.get(i).getNickName(), list.get(i).getObjectId(), conversation), "im_Layout")
.commit();
意思就是在构造方法中传个参数conversationEntrance
即可,之后在发送消息时会用到。
- 我的构造函数:
public static FragmentIM newInstance(String name, String id, BmobIMConversation conversation) {
FragmentIM fragmentIM = new FragmentIM();
fragmentIM.name = name;
fragmentIM.id = id;
fragmentIM.conversation = conversation;
return fragmentIM;
}
发送消息
- 在会话界面首先初始化会话管理员:
private BmobIMConversation mConversationManager;
...
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_im, container, false);
mConversationManager = BmobIMConversation.obtain(BmobIMClient.getInstance(), conversation);
...
return view;
}
- 在这之后,就可以设置button的点击发送消息:
btn_chat_send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String text = editText.getText().toString();//text即发送的消息
if (BmobIM.getInstance().getCurrentStatus().getCode() != ConnectionStatus.CONNECTED.getCode()) {
Toast.makeText(getContext(), "尚未连接IM服务器", Toast.LENGTH_SHORT).show();
} else if (text.trim().equals("")) {
Toast.makeText(getContext(), "请输入内容", Toast.LENGTH_SHORT).show();
} else {
final BmobIMTextMessage message = new BmobIMTextMessage();
message.setContent(text);
//可随意设置额外信息
Map<String, Object> map = new HashMap<>();
map.put("level", "1");
message.setExtraMap(map);
message.setExtra("OK");
mConversationManager.sendMessage(message, listener);
}
}
});
- 我的消息发送器:(如果打出Toast,那么说明发送成功)
/**
* 消息发送监听器
*/
public MessageSendListener listener = new MessageSendListener() {
@Override
public void onProgress(int value) {
super.onProgress(value);
//文件类型的消息才有进度值
Log.e(TAG, "onProgress: " + value);
}
@Override
public void onStart(BmobIMMessage msg) {
super.onStart(msg);
chatAdapter.addMessage(msg);
editText.setText("");
scrollToBottom();
}
@Override
public void done(BmobIMMessage msg, BmobException e) {
Toast.makeText(getContext(), "发送成功", Toast.LENGTH_SHORT).show();
chatAdapter.notifyDataSetChanged();
editText.setText("");
scrollToBottom();
if (e != null) {
Toast.makeText(getContext(), e.getMessage(), Toast.LENGTH_SHORT).show();
Log.e(TAG, "done: " + e.getMessage());
}
}
};
接收消息
- 按照官方文档所说,创建全局消息接收器(个人建议创建全局消息接收器)
//TODO 集成:1.6、自定义消息接收器处理在线消息和离线消息
public class DemoMessageHandler extends BmobIMMessageHandler {
@SuppressLint("SimpleDateFormat")
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private static final String TAG = "DemoMessageHandler";
@Override
public void onMessageReceive(final MessageEvent event) {
//在线消息
Log.e(TAG, "bindView: getFromId "+event.getMessage().getFromId() );
Log.e(TAG, "bindView: getContent "+event.getMessage().getContent() );
Log.e(TAG, "bindView: getExtra "+event.getMessage().getExtra() );
Log.e(TAG, "bindView: getToId "+event.getMessage().getToId() );
Log.e(TAG, "bindView: getCreateTime "+df.format(event.getMessage().getCreateTime()));
Log.e(TAG, "bindView: getReceiveStatus "+event.getMessage().getReceiveStatus() );
FragmentIM.chatAdapter.addMessage(event.getMessage());
}
@Override
public void onOfflineReceive(final OfflineMessageEvent event) {
//离线消息,每次connect的时候会查询离线消息,如果有,此方法会被调用
Map<String, List<MessageEvent>> map = event.getEventMap();
//挨个检测下离线消息所属的用户的信息是否需要更新
Toast.makeText(MainActivity.context, "有" + map.size() + "个用户发来离线消息", Toast.LENGTH_SHORT).show();
for (Map.Entry<String, List<MessageEvent>> entry : map.entrySet()) {
List<MessageEvent> list = entry.getValue();
int size = list.size();
Log.e(TAG, "onOfflineReceive: "+"用户" + entry.getKey() + "发来" + size + "条消息" );
for (int i = 0; i < size; i++) {
Log.e(TAG, "bindView: 离线消息: "+i+ " getFromId "+list.get(i).getMessage().getFromId() );
Log.e(TAG, "bindView: 离线消息: "+i+ " getContent "+list.get(i).getMessage().getContent() );
Log.e(TAG, "bindView: 离线消息: "+i+ " getExtra "+list.get(i).getMessage().getExtra() );
Log.e(TAG, "bindView: 离线消息: "+i+ " getToId "+list.get(i).getMessage().getToId() );
Log.e(TAG, "bindView: 离线消息: "+i+ " getCreateTime "+df.format(list.get(i).getMessage().getCreateTime()));
Log.e(TAG, "bindView: 离线消息: "+i+ " getReceiveStatus "+list.get(i).getMessage().getReceiveStatus() );
}
}
}
}
- 在消息界面也有个RecycleView:
- 我的Adapter的addMessage和addMessages方法:
public void addMessage(BmobIMMessage message) {
messageList.addAll(Arrays.asList(message));
notifyDataSetChanged();
FragmentIM.scrollToBottom();//将RecycleView滑动到最低端
}
public void addMessages(List<BmobIMMessage> messages) {
messageList.addAll(0, messages);
notifyDataSetChanged();
FragmentIM.scrollToBottom();//将RecycleView滑动到最低端
}
- FragmentIM.scrollToBottom()方法:
public static void scrollToBottom() {
layoutManager.scrollToPositionWithOffset(chatAdapter.getItemCount() - 1, 0);
}
如此,一个简单的即时通讯Demo就完成了。