Android:基于百度云推送的聊天系统

一、系统运行图

二、原理

下面通过几个问题来说明下实现的原理:

1、如何实现给某个用户发送消息呢?

其实就是利用百度云提供的REST API,直接通过发送Http请求的形式给某个用户推送一条消息;
实例:
push_msg
功能
推送消息,该接口可用于推送单个人、一群人、所有人以及固定设备的使用场景。
HTTP请求方式
POST
URL
http[s]://channel.api.duapp.com/rest/2.0/channel/channel
….

2、的确可以实现给某个用户或者一群用户推送消息,那么用户的暱称神马的咋获取的呢,我印象中百度云中没法存这样的数据?

嗯,其实用了一个比较巧妙的方式;在用户首次安装软件的时候,会要求用户填写注册信息,也就是暱称等;当用户填写完毕时,点击登录的时候:
1、发送一条比较特殊的消息,比如这个消息携带一个hello的字段,广播给所有用户;
2、其他用户在收到消息时,首先判断hello这个字段是否有值,有值则说明新用户加入,把该新用户存入本地数据库,然后更新用户列表;
3、其他用户给这个新用户回一条消息,附带一个特殊字段,比如welcome,当新用户收到携带welcome字段的消息时,表明这是对新用户hello的答复,然后将已存在用户添加到该新用户的用户列表。
基本原理就这样了,大家甚至可以利用上述的原理添加一些删除好友的功能;比如当用户点击删除好友,则发送一条特殊消息给被删除的对象,然后对方收到该消息,也将发送发删除。
相信大家在了解原理之后,这个例子瞬间从高大尚变成矮矬穷了,这尼玛,so easy,谁不会啊~~~

三、核心代码解析

由于代码量还是相当大的,也不想拆成几篇博客,所以准备将核心代码进行讲解下,其他的大家自己看源码~
关于这个例子的主界面,可以参考: 高仿微信5.2.1主界面架构 包含消息通知
关于百度云推送的入门,可以参考:Android推送 百度云推送 入门篇
好了,最主要就是收到百度云推送的Receiver

1、onBind

首先是用户第一次登录时候绑定的回调

[java] view plain copy
@Override  
    public void onBind(final Context context, int errorCode, String appid,  
            String userId, String channelId, String requestId)  
    {  
        String responseString = "onBind errorCode=" + errorCode + " appid="  
                + appid + " userId=" + userId + " channelId=" + channelId  
                + " requestId=" + requestId;  
        Log.e(TAG, responseString);  

        if (errorCode == 0)  
        {  
            SharePreferenceUtil util = PushApplication.getInstance()  
                    .getSpUtil();  
            util.setAppId(appid);  
            util.setChannelId(channelId);  
            util.setUserId(userId);  

        } else  
        // 如果网络正常,则重试  
        {  
            if (NetUtil.isNetConnected(context))  
            {  

                T.showLong(context, "启动失败,正在重试...");  
                new Handler().postDelayed(new Runnable()  
                {  
                    @Override  
                    public void run()  
                    {  
                        PushManager.startWork(context,  
                                PushConstants.LOGIN_TYPE_API_KEY,  
                                PushApplication.API_KEY);  
                    }  
                }, 2000);// 两秒后重新开始验证  
            } else  
            {  
                T.showLong(context, R.string.net_error_tip);  
            }  
        }  
        // 回调函数  
        for (int i = 0; i < bindListeners.size(); i++)  
            bindListeners.get(i).onBind(userId, errorCode);  
    }  

初次绑定的时候,如果绑定成功则使用SharedPreferences存储userId,channelId等数据
然后回调:bindListeners.get(i).onBind(userId, errorCode);给所有注册该事件的发出通知
下面看listener.onBind

[java] view plain copy
/** 
     * 收到通知 
     */  
    @Override  
    public void onBind(String userId, int errorCode)  
    {  
        Log.e("TAG", "Login Activity onBind ");  
        if (errorCode == 0)  
        {  
            Log.e("TAG", "Login Activity onBind success ");  
            // 如果绑定账号成功,由于第一次运行,给同一tag的人推送一条新人消息  
            User u = new User(mSpUtil.getUserId(), mSpUtil.getChannelId(),  
                    mSpUtil.getNick(), mSpUtil.getHeadIcon(), 0);  
            mUserDB.addUser(u);// 把自己添加到数据库  
            Message firstSendMsg = new Message(System.currentTimeMillis(), "");  
            firstSendMsg.setHello("hello");  
            task = new SendMsgAsyncTask(mGson.toJson(firstSendMsg), "");  
            task.setOnSendScuessListener(new OnSendScuessListener()  
            {  
                @Override  
                public void sendScuess()  
                {  
                    if (mLoadingDialog != null && mLoadingDialog.isVisible())  
                        mLoadingDialog.dismiss();  
                    mHandler.removeCallbacks(mConnTimeoutCallback);  
                    finish();  
                    Intent intent = new Intent(LoginActivity.this,  
                            MainActivity.class);  
                    startActivity(intent);  
                }  
            });  
            task.send();  
        }  
    }  

首先将自己保存到本地数据库,然后发送一个Message给所有的用户,设置一个特殊字段hello;也就是上述的原理分析中的部分~

2、onMessage

收到百度云推送的Receiver中的onMessage

[java] view plain copy
@Override  
    public void onMessage(Context context, String message,  
            String customContentString)  
    {  
        String messageString = "收到消息 message=\"" + message  
                + "\" customContentString=" + customContentString;  
        Log.e(TAG, messageString);  
        Message receivedMsg = PushApplication.getInstance().getGson()  
                .fromJson(message, Message.class);  
        // 对接收到的消息进行处理  
        parseMessage(receivedMsg);  

    }  

    /** 
     * 消息:1、携带hello,表示新人加入,应该自动回复一个world 消息; 2、普通消息; 
     *  
     * @param msg 
     */  
    private void parseMessage(Message msg)  
    {  
        String userId = msg.getUserId();  
        // 自己的消息  
        if (userId  
                .equals(PushApplication.getInstance().getSpUtil().getUserId()))  
            return;  
        if (checkHasNewFriend(msg) || checkAutoResponse(msg))  
            return;  
        // 普通消息  
        UserDB userDB = PushApplication.getInstance().getUserDB();  
        User user = userDB.selectInfo(userId);  
        // 漏网之鱼  
        if (user == null)  
        {  
            user = new User(userId, msg.getChannelId(), msg.getNickname(), 0, 0);  
            userDB.addUser(user);  
            // 通知监听的面板  
            for (onNewFriendListener listener : friendListeners)  
                listener.onNewFriend(user);  
        }  
        if (msgListeners.size() > 0)  
        {// 有监听的时候,传递下去  
            for (int i = 0; i < msgListeners.size(); i++)  
                msgListeners.get(i).onNewMessage(msg);  
        } else  
        // 当前没有任何监听,即处理后台状态  
        {  
            // 将新来的消息进行存储  
            ChatMessage chatMessage = new ChatMessage(msg.getMessage(), true,  
                    userId, msg.getHeadIcon(), msg.getNickname(), false,  
                    TimeUtil.getTime(msg.getTimeSamp()));  
            PushApplication.getInstance().getMessageDB()  
                    .add(userId, chatMessage);  
            showNotify(msg);  
        }  
    }  

    /** 
     * 检测是否是自动回复 
     *  
     * @param msg 
     */  
    private boolean checkAutoResponse(Message msg)  
    {  
        String world = msg.getWorld();  
        String userId = msg.getUserId();  
        if (!TextUtils.isEmpty(world))  
        {  
            User u = new User(userId, msg.getChannelId(), msg.getNickname(),  
                    msg.getHeadIcon(), 0);  
            PushApplication.getInstance().getUserDB().addUser(u);// 存入或更新好友  
            // 通知监听的面板  
            for (onNewFriendListener listener : friendListeners)  
                listener.onNewFriend(u);  

            return true;  
        }  
        return false;  
    }  

    /** 
     * 检测是否是新人加入 
     *  
     * @param msg 
     */  
    private boolean checkHasNewFriend(Message msg)  
    {  
        String userId = msg.getUserId();  
        String hello = msg.getHello();  
        // 新人发送的消息  
        if (!TextUtils.isEmpty(hello))  
        {  
            Log.e("checkHasNewFriend", msg.getUserId());  
            // 新人  
            User u = new User(userId, msg.getChannelId(), msg.getNickname(),  
                    msg.getHeadIcon(), 0);  
            PushApplication.getInstance().getUserDB().addUser(u);// 存入或更新好友  
            T.showShort(PushApplication.getInstance(), u.getNick() + "加入");  

            // 给新人回复一个应答  
            Message message = new Message(System.currentTimeMillis(), "");  
            message.setWorld("world");  
            new SendMsgAsyncTask(PushApplication.getInstance().getGson()  
                    .toJson(message), userId);  
            // 通知监听的面板  
            for (onNewFriendListener listener : friendListeners)  
                listener.onNewFriend(u);  

            return true;  
        }  

        return false;  
    }  

当收到一个新的消息:
1、首先判断是不是自己发的,是,忽略;
2、判断是否携带hello字段,代表新人加入,携带,则保存到好友列表,且回复一个携带welcome字段的消息;
3、判断是否包含welcome字段,包含,存入好友列表
4、不是1、2、3则肯定是普通消息,将消息保存到本地数据库,然后为所有设置消息监听的发送回调即可~

MainTabFriends.java用户列表中收到新消息的回调:

[java] view plain copy
/** 
     * 收到新消息时 
     */  
    @Override  
    public void onNewMessage(Message message)  
    {  
        // 如果是自己发送的,则直接返回  
        if (message.getUserId() == mSpUtils.getUserId())  
            return;  
        // 如果该用户已经有未读消息,更新未读消息的个数,并通知更新未读消息接口,最后notifyDataSetChanged  
        String userId = message.getUserId();  
        if (mUserMessages.containsKey(userId))  
        {  
            mUserMessages.put(userId, mUserMessages.get(userId) + 1);  
        } else  
        {  
            mUserMessages.put(userId, 1);  
        }  
        mUnReadedMsgs++;  
        notifyUnReadedMsg();  
        // 将新来的消息进行存储  
        ChatMessage chatMessage = new ChatMessage(message.getMessage(), true,  
                userId, message.getHeadIcon(), message.getNickname(), false,  
                TimeUtil.getTime(message.getTimeSamp()));  
        mApplication.getMessageDB().add(userId, chatMessage);  
        // 通知listview数据改变  
        mAdapter.notifyDataSetChanged();  
    }  

1、如果是自己发的不做任何处理
2、如果当前消息发送用户已有未读消息,则更新该用户未读消息个数(用户Item上显示未读消息通知数),更新所有未读消息总和(朋友Tab上显示未读消息总和),然后存储该消息
3、刷新界面

ChattingActivity.java聊天界面的新消息回调

[java] view plain copy
@Override  
    public void onNewMessage(Message message)  
    {  

        // 获得回复的消息  
        if (mFromUser.getUserId().equals(message.getUserId()))  
        {  
            ChatMessage chatMessage = new ChatMessage();  
            chatMessage.setComing(true);  
            chatMessage.setDate(new Date(message.getTimeSamp()));  
            chatMessage.setMessage(message.getMessage());  
            chatMessage.setUserId(message.getUserId());  
            chatMessage.setNickname(message.getNickname());  
            chatMessage.setReaded(true);  
            mDatas.add(chatMessage);  
            mAdapter.notifyDataSetChanged();  
            mChatMessagesListView.setSelection(mDatas.size() - 1);  
            // 存入数据库,当前聊天记录  
            mApplication.getMessageDB().add(mFromUser.getUserId(), chatMessage);  
        }  
    }  

首先判断是否是正在聊天的用户发的消息,如果是,直接存入数据库,刷新聊天界面;

具体的细节大家可以去看源码,下面是源码的下载地址:

源码点击下载

如果下载错误,可能是我资源空间不足,会被我清除,需要源码的可以留言

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章