基于Bmob的简单即时通讯

前言:

关于即时通讯,项目中要是需要一个收发消息的功能。从开始到写完即时通讯这块儿,花了大约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>

项目的集成到这儿就结束了,剩下的就是如何使用这些东西了,简单看下我的流程图。

PfTW7D.png

创建会话列表和好友列表(RecycleView)

  • 首先说下个人认为官方文档中最难理解的一个名词:会话。会话分为暂态会话常态会话。我对会话的理解:就好比两个好友打电话时,要有一条连通的电话线一样,要实现IM,也要有连通双方的一根线,而这根线就相当于是会话。应用在本地都会有数据表,用来存储应用中的数据。暂态会话不会保存在本地数据库中;常态会话会被保存到本地数据库中(暂态会话可以用来加好友,常态会话用来聊天)。
  • 怪当时太年轻,以为即时通讯就只是聊天,在使用SDK完成加好友操作之后才看的IM文档,所以暂态会话这里就不说了,但是道理应该是相通的,相信看完常态会话之后,态会话都不是事儿。
  • 即时通讯Demo中有两个RecycleView,一个用来存储好友列表,一个用来存储会话列表。而会话列表在你搞完发消息之前肯定是空的

好友列表:(好友的添加就不说了,自行添加一些好友就行)

PfLMJe.md.jpg

会话列表:(当然你的肯定是空的)

PfLlzd.md.jpg

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即可,之后在发送消息时会用到。

扫描二维码关注公众号,回复: 5729786 查看本文章
  • 我的构造函数:
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:

PfLQRH.md.jpg

  • 我的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就完成了。

猜你喜欢

转载自blog.csdn.net/xieyipeng1998/article/details/81837161