Android聊天软件的开发(七)--聊天通信

2014-06-20 23:17:49CSDN-vaintwyt-点击数:338

 聊天通信通过Socket实现,大概的框架如下图:

通信流程:

1.服务器在启动时开启聊天服务线程

可以通过ServletContextListener监听Servlet的初始化和销毁,来开启和关闭聊天服务。

ServiceListener实现ServletContextListener接口

public class ServiceListener implements ServletContextListener {ChatServerThread <span id="46_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="46_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=21a41a7e92302f2b&k=server&k0=server&kdi0=0&luki=4&n=10&p=baidu&q=smileking_cpr&rb=0&rs=1&seller_id=1&sid=2b2f30927e1aa421&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1682280&u=http%3A%2F%2Fwww%2Eth7%2Ecn%2FProgram%2FAndroid%2F201406%2F219517%2Eshtml&urlid=0" target="_blank" mpid="46" style="text-decoration: none; color: rgb(90, 128, 238);"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">server</span></a></span>Thread = null;public void contextDestroyed(ServletContextEvent arg0) {// 关闭<span id="47_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="47_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=21a41a7e92302f2b&k=%C1%C4%CC%EC&k0=%C1%C4%CC%EC&kdi0=0&luki=7&n=10&p=baidu&q=smileking_cpr&rb=0&rs=1&seller_id=1&sid=2b2f30927e1aa421&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1682280&u=http%3A%2F%2Fwww%2Eth7%2Ecn%2FProgram%2FAndroid%2F201406%2F219517%2Eshtml&urlid=0" target="_blank" mpid="47" style="text-decoration: none; color: rgb(90, 128, 238);"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">聊天</span></a></span>通信线程if (serverThread != null && !serverThread.isInterrupted())serverThread.closeServer();}public void contextInitialized(ServletContextEvent event) {// 开启聊天通信线程if (serverThread == null) {serverThread = new ChatServerThread();serverThread.start();}}
}

在web.xml中配置监听器

<listener><listener-class>vaint.wyt.chat.ServiceListener</listener-class>
</listener>

ChatServerThread:聊天服务线程

public class ChatServerThread extends Thread {static Logger logger = Logger.getLogger(ChatServerThread.class);private ServerSocket ss = null;/** 线程运行终止标识 */private volatile boolean <span id="38_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="38_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=21a41a7e92302f2b&k=flag&k0=flag&kdi0=0&luki=8&n=10&p=baidu&q=smileking_cpr&rb=0&rs=1&seller_id=1&sid=2b2f30927e1aa421&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1682280&u=http%3A%2F%2Fwww%2Eth7%2Ecn%2FProgram%2FAndroid%2F201406%2F219517%2Eshtml&urlid=0" target="_blank" mpid="38" style="text-decoration: none; color: rgb(90, 128, 238);"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">flag</span></a></span> = true;@Overridepublic void run() {logger.info("开启<span id="39_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="39_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=21a41a7e92302f2b&k=%C1%C4%CC%EC&k0=%C1%C4%CC%EC&kdi0=0&luki=7&n=10&p=baidu&q=smileking_cpr&rb=0&rs=1&seller_id=1&sid=2b2f30927e1aa421&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1682280&u=http%3A%2F%2Fwww%2Eth7%2Ecn%2FProgram%2FAndroid%2F201406%2F219517%2Eshtml&urlid=0" target="_blank" mpid="39" style="text-decoration: none; color: rgb(90, 128, 238);"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">聊天</span></a></span>服务");Socket <span id="40_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="40_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=21a41a7e92302f2b&k=socket&k0=socket&kdi0=0&luki=9&n=10&p=baidu&q=smileking_cpr&rb=0&rs=1&seller_id=1&sid=2b2f30927e1aa421&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1682280&u=http%3A%2F%2Fwww%2Eth7%2Ecn%2FProgram%2FAndroid%2F201406%2F219517%2Eshtml&urlid=0" target="_blank" mpid="40" style="text-decoration: none; color: rgb(90, 128, 238);"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">socket</span></a></span> = null;ObjectInputStream in = null;try {ss = new ServerSocket(Constants.SERVER_PORT);} catch (IOException e1) {e1.printStackTrace();}while (flag) {try {socket = ss.accept();logger.debug("有一个新的连接");in = new ObjectInputStream(socket.getInputStream());// 获得客户端上传的用户IDObject obj = in.readObject();logger.debug("获取到数据");if (obj instanceof ChatData.ID) {ChatData.ID id = (ID) obj;String userId = id.getUserId();logger.debug(userId + "连接到<span id="41_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="41_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=21a41a7e92302f2b&k=%C1%C4%CC%EC&k0=%C1%C4%CC%EC&kdi0=0&luki=7&n=10&p=baidu&q=smileking_cpr&rb=0&rs=1&seller_id=1&sid=2b2f30927e1aa421&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1682280&u=http%3A%2F%2Fwww%2Eth7%2Ecn%2FProgram%2FAndroid%2F201406%2F219517%2Eshtml&urlid=0" target="_blank" mpid="41" style="text-decoration: none; color: rgb(90, 128, 238);"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">聊天</span></a></span>服务器");// 开启新的线程管理连接ChatConnThread connThread = new ChatConnThread(userId, <span id="42_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="42_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=21a41a7e92302f2b&k=socket&k0=socket&kdi0=0&luki=9&n=10&p=baidu&q=smileking_cpr&rb=0&rs=1&seller_id=1&sid=2b2f30927e1aa421&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1682280&u=http%3A%2F%2Fwww%2Eth7%2Ecn%2FProgram%2FAndroid%2F201406%2F219517%2Eshtml&urlid=0" target="_blank" mpid="42" style="text-decoration: none; color: rgb(90, 128, 238);"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">socket</span></a></span>, in);ChatConnManager.addConnThread(userId, connThread);connThread.start();}} catch (Exception e) {e.printStackTrace();// 关闭与客户端的连接。<span id="43_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="43_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=21a41a7e92302f2b&k=%B7%FE%CE%F1%C6%F7&k0=%B7%FE%CE%F1%C6%F7&kdi0=0&luki=6&n=10&p=baidu&q=smileking_cpr&rb=0&rs=1&seller_id=1&sid=2b2f30927e1aa421&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1682280&u=http%3A%2F%2Fwww%2Eth7%2Ecn%2FProgram%2FAndroid%2F201406%2F219517%2Eshtml&urlid=0" target="_blank" mpid="43" style="text-decoration: none; color: rgb(90, 128, 238);"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">服务器</span></a></span>的ServerSocket不要轻易关闭try {if(in != null)in.close();if (socket != null) socket.close();} catch (IOException e1) {e1.printStackTrace();}}}logger.info("<span id="44_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="44_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=21a41a7e92302f2b&k=%C1%C4%CC%EC&k0=%C1%C4%CC%EC&kdi0=0&luki=7&n=10&p=baidu&q=smileking_cpr&rb=0&rs=1&seller_id=1&sid=2b2f30927e1aa421&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1682280&u=http%3A%2F%2Fwww%2Eth7%2Ecn%2FProgram%2FAndroid%2F201406%2F219517%2Eshtml&urlid=0" target="_blank" mpid="44" style="text-decoration: none; color: rgb(90, 128, 238);"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">聊天</span></a></span>服务结束");closeSocket();}/**关闭聊天服务*/public void closeServer() {<span id="45_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="45_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=21a41a7e92302f2b&k=flag&k0=flag&kdi0=0&luki=8&n=10&p=baidu&q=smileking_cpr&rb=0&rs=1&seller_id=1&sid=2b2f30927e1aa421&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1682280&u=http%3A%2F%2Fwww%2Eth7%2Ecn%2FProgram%2FAndroid%2F201406%2F219517%2Eshtml&urlid=0" target="_blank" mpid="45" style="text-decoration: none; color: rgb(90, 128, 238);"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">flag</span></a></span> = false;}private void closeSocket(){if (ss != null) {try {ss.close();} catch (IOException e) {e.printStackTrace();}}}
}

2.客户端发起聊天连接请求(上图步骤1)

客户端通过创建Socket,实现与服务器的聊天通信连接,并且需要通过ChatData.ID类上传用户ID到服务器,用于维护聊天线程。

由于网络不稳定,通信连接容易断开,所以需要在创建连接和发送消息前,判断连接是否断开,如果断开,需要重连。但是Socket的isConnected和isClosed并不能正真判断连接是否断开,因此可通过sendUrgentData发送测试数据,若没有出现异常,则说明连接正常。

ChatConnThread:客户端聊天通信线程

public class ChatConnThread extends Thread{private static final String TAG = ChatConnThread.class.getSimpleName();private Context mContext;private Socket <span id="26_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="26_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=21a41a7e92302f2b&k=socket&k0=socket&kdi0=0&luki=9&n=10&p=baidu&q=smileking_cpr&rb=0&rs=1&seller_id=1&sid=2b2f30927e1aa421&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1682280&u=http%3A%2F%2Fwww%2Eth7%2Ecn%2FProgram%2FAndroid%2F201406%2F219517%2Eshtml&urlid=0" target="_blank" mpid="26" style="text-decoration: none; color: rgb(90, 128, 238);"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">socket</span></a></span>;//输入输出流保持唯一,不要多次创建,避免错误private ObjectOutputStream out;private ObjectInputStream in;/**线程运行终止标识*/private volatile boolean <span id="27_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="27_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=21a41a7e92302f2b&k=flag&k0=flag&kdi0=0&luki=8&n=10&p=baidu&q=smileking_cpr&rb=0&rs=1&seller_id=1&sid=2b2f30927e1aa421&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1682280&u=http%3A%2F%2Fwww%2Eth7%2Ecn%2FProgram%2FAndroid%2F201406%2F219517%2Eshtml&urlid=0" target="_blank" mpid="27" style="text-decoration: none; color: rgb(90, 128, 238);"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">flag</span></a></span> = true;public ChatConnThread(Context ctx){mContext = ctx;}/**发送消息*/public void sendMsg(ChatData.MSG msg) {try {//检查是否断开连接checkConnSocket();out.writeObject(msg);// 发送消息out.flush();} catch (IOException e) {e.printStackTrace();}}@Overridepublic void run() {//接收消息while(flag){try {//如果<span id="28_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="28_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=21a41a7e92302f2b&k=socket&k0=socket&kdi0=0&luki=9&n=10&p=baidu&q=smileking_cpr&rb=0&rs=1&seller_id=1&sid=2b2f30927e1aa421&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1682280&u=http%3A%2F%2Fwww%2Eth7%2Ecn%2FProgram%2FAndroid%2F201406%2F219517%2Eshtml&urlid=0" target="_blank" mpid="28" style="text-decoration: none; color: rgb(90, 128, 238);"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">socket</span></a></span>已经关闭,则需要重新连接checkConnSocket();Object obj =  in.readObject();if(obj instanceof ChatData.MSG)//<span id="29_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="29_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=21a41a7e92302f2b&k=%C1%C4%CC%EC&k0=%C1%C4%CC%EC&kdi0=0&luki=7&n=10&p=baidu&q=smileking_cpr&rb=0&rs=1&seller_id=1&sid=2b2f30927e1aa421&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1682280&u=http%3A%2F%2Fwww%2Eth7%2Ecn%2FProgram%2FAndroid%2F201406%2F219517%2Eshtml&urlid=0" target="_blank" mpid="29" style="text-decoration: none; color: rgb(90, 128, 238);"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">聊天</span></a></span>消息或添加好友请求{//通知栏提示TipsUtils.MsgNotificaction();ChatData.MSG msg = (ChatData.MSG)obj;<span id="30_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="30_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=21a41a7e92302f2b&k=switch&k0=switch&kdi0=0&luki=5&n=10&p=baidu&q=smileking_cpr&rb=0&rs=1&seller_id=1&sid=2b2f30927e1aa421&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1682280&u=http%3A%2F%2Fwww%2Eth7%2Ecn%2FProgram%2FAndroid%2F201406%2F219517%2Eshtml&urlid=0" target="_blank" mpid="30" style="text-decoration: none; color: rgb(90, 128, 238);"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">switch</span></a></span> (msg.getType()) {case CHATTING://显示消息MsgUtils.ShowChattingMsg(mContext, msg);break;case ADD_FRIEND://显示好友请求消息MsgUtils.ShowAddFriendMsg(mContext, msg);break;case ADD_AGREE://更新好友列表MsgUtils.AddNewFriend(mContext, msg);break;    }}Thread.sleep(250);} catch (Exception e) {e.printStackTrace();//关闭Socket,让其重新连接closeSocket();} }//线程结束,关闭SocketcloseSocket();Log.d(TAG,  " 连接线程已经关闭");}/**关闭Socket连接*/private void closeSocket(){try {if(<span id="31_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="31_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=21a41a7e92302f2b&k=socket&k0=socket&kdi0=0&luki=9&n=10&p=baidu&q=smileking_cpr&rb=0&rs=1&seller_id=1&sid=2b2f30927e1aa421&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1682280&u=http%3A%2F%2Fwww%2Eth7%2Ecn%2FProgram%2FAndroid%2F201406%2F219517%2Eshtml&urlid=0" target="_blank" mpid="31" style="text-decoration: none; color: rgb(90, 128, 238);"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">socket</span></a></span> != null){out.close();in.close();socket.close();socket = null;Log.d(TAG,  "关闭Socket");}} catch (IOException e) {e.printStackTrace();}}/**检查连接,如果断开需要重新连接*/public synchronized void checkConnSocket(){while( <span id="32_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="32_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=21a41a7e92302f2b&k=flag&k0=flag&kdi0=0&luki=8&n=10&p=baidu&q=smileking_cpr&rb=0&rs=1&seller_id=1&sid=2b2f30927e1aa421&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1682280&u=http%3A%2F%2Fwww%2Eth7%2Ecn%2FProgram%2FAndroid%2F201406%2F219517%2Eshtml&urlid=0" target="_blank" mpid="32" style="text-decoration: none; color: rgb(90, 128, 238);"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">flag</span></a></span> && !isOnLine()){try {Log.d(TAG, CacheUtils.GetUserId() + " 创建连接");socket = new Socket(Constants.SERVER_IP, Constants.SERVER_PORT);<span id="33_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="33_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=21a41a7e92302f2b&k=socket&k0=socket&kdi0=0&luki=9&n=10&p=baidu&q=smileking_cpr&rb=0&rs=1&seller_id=1&sid=2b2f30927e1aa421&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1682280&u=http%3A%2F%2Fwww%2Eth7%2Ecn%2FProgram%2FAndroid%2F201406%2F219517%2Eshtml&urlid=0" target="_blank" mpid="33" style="text-decoration: none; color: rgb(90, 128, 238);"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">socket</span></a></span>.setKeepAlive(true);out = new ObjectOutputStream(socket.getOutputStream());//通过ID号建立与<span id="34_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="34_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=21a41a7e92302f2b&k=%B7%FE%CE%F1%C6%F7&k0=%B7%FE%CE%F1%C6%F7&kdi0=0&luki=6&n=10&p=baidu&q=smileking_cpr&rb=0&rs=1&seller_id=1&sid=2b2f30927e1aa421&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1682280&u=http%3A%2F%2Fwww%2Eth7%2Ecn%2FProgram%2FAndroid%2F201406%2F219517%2Eshtml&urlid=0" target="_blank" mpid="34" style="text-decoration: none; color: rgb(90, 128, 238);"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">服务器</span></a></span>的连接ChatData.ID id = new ChatData.ID();id.setUserId(CacheUtils.GetUserId());Log.d(TAG, "发送连接<span id="35_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="35_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=21a41a7e92302f2b&k=%C1%C4%CC%EC&k0=%C1%C4%CC%EC&kdi0=0&luki=7&n=10&p=baidu&q=smileking_cpr&rb=0&rs=1&seller_id=1&sid=2b2f30927e1aa421&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1682280&u=http%3A%2F%2Fwww%2Eth7%2Ecn%2FProgram%2FAndroid%2F201406%2F219517%2Eshtml&urlid=0" target="_blank" mpid="35" style="text-decoration: none; color: rgb(90, 128, 238);"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">聊天</span></a></span>服务的请求");out.writeObject(id);out.flush();//输入流的创建要在输出流写过数据之后。不然服务器和客户端都会阻塞in = new ObjectInputStream(socket.getInputStream());Thread.sleep(250);}catch (Exception e) {e.printStackTrace();}}}/**关闭连接线程*/public void closeConn(){<span id="36_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="36_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=21a41a7e92302f2b&k=flag&k0=flag&kdi0=0&luki=8&n=10&p=baidu&q=smileking_cpr&rb=0&rs=1&seller_id=1&sid=2b2f30927e1aa421&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1682280&u=http%3A%2F%2Fwww%2Eth7%2Ecn%2FProgram%2FAndroid%2F201406%2F219517%2Eshtml&urlid=0" target="_blank" mpid="36" style="text-decoration: none; color: rgb(90, 128, 238);"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">flag</span></a></span> = false;}/**判断客户端连接*/public boolean isOnLine(){if(<span id="37_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="37_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=21a41a7e92302f2b&k=socket&k0=socket&kdi0=0&luki=9&n=10&p=baidu&q=smileking_cpr&rb=0&rs=1&seller_id=1&sid=2b2f30927e1aa421&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1682280&u=http%3A%2F%2Fwww%2Eth7%2Ecn%2FProgram%2FAndroid%2F201406%2F219517%2Eshtml&urlid=0" target="_blank" mpid="37" style="text-decoration: none; color: rgb(90, 128, 238);"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">socket</span></a></span>==null)return false;boolean ret = true;try{/** 发送测试数据* 往输出流发送一个字节的数据,只要对方Socket的SO_OOBINLINE属性没有打开,* 就会自动舍弃这个字节,而SO_OOBINLINE属性默认情况下就是关闭的*///心跳测试socket.sendUrgentData(0xFF);}catch(Exception e){Log.d(TAG, CacheUtils.GetUserId()+"连接已经断开了");ret = false;closeSocket();}return ret;}
}

这里有两点需要注意:

a.ObjectOutputStream和ObjectInputStream的创建顺序要与服务器的创建顺序一致。即服务器先创建ObjectInputStream,那么客户端就应该先创建ObjectOutputStream。否则会一直处于阻塞状态。

b.最好将ObjectOutputStream和ObjectInputStream定义成全局,不要多次创建,否则会出现java.io.StreamCorruptedException: invalid type code: AC异常。

3.服务器接收到连接请求(上图步骤2)
   服务器的ServerSocket在监听到客户端的连接请求时,会得到一个Socket对象。服务器首先开启一个聊天线程,来维护该Socket,然后,将来聊天线程以用户ID为标识,进行管理。

ChatConnManager:线程管理器

public class ChatConnManager {static Logger logger = Logger.getLogger(ChatConnManager.class);// <用户ID,连接线程>private static Map<String, ChatConnThread> connManager = new HashMap<String, ChatConnThread>();/** 添加客户端通信线程 */public static void addConnThread(String id, ChatConnThread connThread) {logger.info(id+"创建通信连接");//如果连接已经存在,则需要断开之前的连接ChatConnThread conn = connManager.remove(id);if (conn != null)// 如果id不存在则返回nullconn.closeConn();// 终止线程connManager.put(id, connThread);}/** 移除客户端通信线程 */public static void removeConnThread(String id) {ChatConnThread connThread = connManager.remove(id);if (connThread != null)// 如果id不存在则返回nullconnThread.closeConn();// 终止线程}/** 给指定客户端发送消息 */public static void sendMsg(ChatData.MSG msg) {String toId = msg.getToId();ChatConnThread connThread = connManager.get(toId);// 判断接收方是否在线if (connThread != null && connThread.isOnLine())// 接收方在线{// 发送消息connThread.sendMsg(msg);} else if (connThread != null && !connThread.isOnLine())// 接收方断开连接{removeConnThread(toId);// 缓存消息MsgManager.addMsgCache(toId, msg);}else// 接收方不在线{// 缓存消息MsgManager.addMsgCache(toId, msg);}}
}

4.客户端发送消息(上图步骤3,4)

a.客户端通过ChatConnThread的sendMsg方法,将聊天消息(ChatData.MSG)发送到服务器。

public void sendMsg(ChatData.MSG msg) {try {//检查是否断开连接checkConnSocket();out.writeObject(msg);// 发送消息out.flush();} catch (IOException e) {e.printStackTrace();}
}

b.服务器对应Socket接收到数据后,先判断消息类型,然后再处理。

ChatConnThread为服务器维护Socket的通信线程。通过in.readObject()获取消息对象后,要判断消息类型。如果是聊天消息,或者添加好友相关的消息,则需要通过ChatConnManager的sendMsg方法将消息发送到目标客户端(上图步骤4);如果是获取离线消息,则使用sendOfflineMsg将离线消息发送给当前Socket对应的客户端。

public class ChatConnThread extends Thread {static Logger logger = Logger.getLogger(ChatConnThread.class);private Socket <span id="20_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="20_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=21a41a7e92302f2b&k=socket&k0=socket&kdi0=0&luki=9&n=10&p=baidu&q=smileking_cpr&rb=0&rs=1&seller_id=1&sid=2b2f30927e1aa421&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1682280&u=http%3A%2F%2Fwww%2Eth7%2Ecn%2FProgram%2FAndroid%2F201406%2F219517%2Eshtml&urlid=0" target="_blank" mpid="20" style="text-decoration: none; color: rgb(90, 128, 238);"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">socket</span></a></span>;private ObjectOutputStream out;private ObjectInputStream in;private String userId;/**线程运行终止标识*/private volatile boolean <span id="21_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="21_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=21a41a7e92302f2b&k=flag&k0=flag&kdi0=0&luki=8&n=10&p=baidu&q=smileking_cpr&rb=0&rs=1&seller_id=1&sid=2b2f30927e1aa421&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1682280&u=http%3A%2F%2Fwww%2Eth7%2Ecn%2FProgram%2FAndroid%2F201406%2F219517%2Eshtml&urlid=0" target="_blank" mpid="21" style="text-decoration: none; color: rgb(90, 128, 238);"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">flag</span></a></span> = true;public ChatConnThread(String id, Socket s, ObjectInputStream ois) throws IOException {userId = id;socket = s;in = ois;out = new ObjectOutputStream(socket.getOutputStream());}/**发送消息给本客户端*/public void sendMsg(ChatData.MSG msg) {try {out.writeObject(msg);// 发送消息out.flush();} catch (IOException e) {e.printStackTrace();}}/**发送离线消息给本客户端*/public void sendOfflineMsg(String userId) {// 发送离线消息给客户端List<MSG> list = MsgManager.getOfflineMsg(userId);if (list == null)return;for (int i = 0; i < list.size(); i++) {sendMsg(list.get(i));}// 清除对应离线消息MsgManager.removeMsgCache(userId);}@Overridepublic void run() {while (<span id="22_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="22_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=21a41a7e92302f2b&k=flag&k0=flag&kdi0=0&luki=8&n=10&p=baidu&q=smileking_cpr&rb=0&rs=1&seller_id=1&sid=2b2f30927e1aa421&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1682280&u=http%3A%2F%2Fwww%2Eth7%2Ecn%2FProgram%2FAndroid%2F201406%2F219517%2Eshtml&urlid=0" target="_blank" mpid="22" style="text-decoration: none; color: rgb(90, 128, 238);"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">flag</span></a></span>) {try {Object obj = in.readObject();if (obj instanceof ChatData.MSG) {ChatData.MSG msg = (MSG) obj;// 判断消息类型<span id="23_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="23_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=21a41a7e92302f2b&k=switch&k0=switch&kdi0=0&luki=5&n=10&p=baidu&q=smileking_cpr&rb=0&rs=1&seller_id=1&sid=2b2f30927e1aa421&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1682280&u=http%3A%2F%2Fwww%2Eth7%2Ecn%2FProgram%2FAndroid%2F201406%2F219517%2Eshtml&urlid=0" target="_blank" mpid="23" style="text-decoration: none; color: rgb(90, 128, 238);"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">switch</span></a></span> (msg.getType()) {case CHATTING:// 普通消息logger.debug(msg.getFromId() + " 发送消息给 "+ msg.getToId());// 发送消息给对应客户端ChatConnManager.sendMsg(msg);break;case OFFLINE_MSG:// 获取离线消息logger.debug(msg.getFromId() + "获取离线消息");sendOfflineMsg(msg.getFromId());break;case ADD_FRIEND:// 添加好友请求logger.debug(msg.getFromId() + " 发起添加 " + msg.getToId()+ " 的好友请求");// 发送消息给对应客户端ChatConnManager.sendMsg(msg);break;case ADD_AGREE:// 同意添加好友logger.debug(msg.getFromId() + " 同意添加 " + msg.getToId()+ " 为好友");FriendsUtils.AddFriend(msg.getFromId(), msg.getToId());ChatConnManager.sendMsg(msg);break;case LOGOUT:// 注销登录logger.debug(msg.getFromId() + " 退出登录");ChatConnManager.removeConnThread(msg.getFromId());break;}}} catch (Exception e) {e.printStackTrace();logger.debug(userId+" 通信线程出现异常");//出现异常,退出循环。即断开连接线程<span id="24_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="24_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=21a41a7e92302f2b&k=flag&k0=flag&kdi0=0&luki=8&n=10&p=baidu&q=smileking_cpr&rb=0&rs=1&seller_id=1&sid=2b2f30927e1aa421&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1682280&u=http%3A%2F%2Fwww%2Eth7%2Ecn%2FProgram%2FAndroid%2F201406%2F219517%2Eshtml&urlid=0" target="_blank" mpid="24" style="text-decoration: none; color: rgb(90, 128, 238);"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">flag</span></a></span> = false;break;} }closeSocket();}/**关闭客户端连接线程*/public void closeConn(){flag = false;}/**关闭Socket连接*/private void closeSocket(){try {if(<span id="25_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="25_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=21a41a7e92302f2b&k=socket&k0=socket&kdi0=0&luki=9&n=10&p=baidu&q=smileking_cpr&rb=0&rs=1&seller_id=1&sid=2b2f30927e1aa421&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1682280&u=http%3A%2F%2Fwww%2Eth7%2Ecn%2FProgram%2FAndroid%2F201406%2F219517%2Eshtml&urlid=0" target="_blank" mpid="25" style="text-decoration: none; color: rgb(90, 128, 238);"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">socket</span></a></span> != null){out.close();in.close();socket.close();socket = null;}} catch (IOException e) {e.printStackTrace();}}/**判断客户端连接*/public boolean isOnLine(){boolean ret = true;try{/** 发送测试数据* 往输出流发送一个字节的数据,只要对方Socket的SO_OOBINLINE属性没有打开,* 就会自动舍弃这个字节,而SO_OOBINLINE属性默认情况下就是关闭的*/socket.sendUrgentData(0xFF);}catch(Exception e){logger.debug(userId + " 掉线了");ret = false;}return ret;}
}

ChatConnManager的sendMsg方法:发送消息前,需要判断该客户端的连接是否断开,如果断开,则将消息作为离线消息进行缓存。

public static void sendMsg(ChatData.MSG msg) {String toId = msg.getToId();ChatConnThread connThread = connManager.get(toId);// 判断接收方是否在线if (connThread != null && connThread.isOnLine())// 接收方在线{// 发送消息connThread.sendMsg(msg);} else if (connThread != null && !connThread.isOnLine())// 接收方断开连接{removeConnThread(toId);// 缓存消息MsgManager.addMsgCache(toId, msg);}else// 接收方不在线{// 缓存消息MsgManager.addMsgCache(toId, msg);}
}

MsgManager:管理离线消息

public class MsgManager {static Logger logger = Logger.getLogger(MsgManager.class);// <用户ID,消息序列(旧-新)>private static Map<String, List<ChatData.MSG>> msgManager = new HashMap<String, List<ChatData.MSG>>();/** 添加离线消息缓存 */public static void addMsgCache(String id, ChatData.MSG msg) {List<ChatData.MSG> list;if (msgManager.containsKey(id))// 原先存在离线消息{list = msgManager.get(id);list.add(msg);} else {list = new ArrayList<ChatData.MSG>();list.add(msg);}logger.debug("ID:"+id+"有一条离线消息:"+msg.getMsg());msgManager.put(id, list);}/** 获得离线消息序列 */public static List<ChatData.MSG> getOfflineMsg(String id) {return msgManager.get(id);}/** 移除消息序列 */public static void removeMsgCache(String id) {msgManager.remove(id);}
}

5.客户端接收消息(上图步骤5)
   客户端ChatConnThread接收到消息后,通过广播进行消息显示或存储。

while(<span id="16_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="16_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=21a41a7e92302f2b&k=flag&k0=flag&kdi0=0&luki=8&n=10&p=baidu&q=smileking_cpr&rb=0&rs=1&seller_id=1&sid=2b2f30927e1aa421&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1682280&u=http%3A%2F%2Fwww%2Eth7%2Ecn%2FProgram%2FAndroid%2F201406%2F219517%2Eshtml&urlid=0" target="_blank" mpid="16" style="text-decoration: none; color: rgb(90, 128, 238);"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">flag</span></a></span>)
{try {//如果<span id="17_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="17_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=21a41a7e92302f2b&k=socket&k0=socket&kdi0=0&luki=9&n=10&p=baidu&q=smileking_cpr&rb=0&rs=1&seller_id=1&sid=2b2f30927e1aa421&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1682280&u=http%3A%2F%2Fwww%2Eth7%2Ecn%2FProgram%2FAndroid%2F201406%2F219517%2Eshtml&urlid=0" target="_blank" mpid="17" style="text-decoration: none; color: rgb(90, 128, 238);"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">socket</span></a></span>已经关闭,则需要重新连接checkConnSocket();Object obj =  in.readObject();if(obj instanceof ChatData.MSG)//<span id="18_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="18_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=21a41a7e92302f2b&k=%C1%C4%CC%EC&k0=%C1%C4%CC%EC&kdi0=0&luki=7&n=10&p=baidu&q=smileking_cpr&rb=0&rs=1&seller_id=1&sid=2b2f30927e1aa421&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1682280&u=http%3A%2F%2Fwww%2Eth7%2Ecn%2FProgram%2FAndroid%2F201406%2F219517%2Eshtml&urlid=0" target="_blank" mpid="18" style="text-decoration: none; color: rgb(90, 128, 238);"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">聊天</span></a></span>消息或添加好友请求{//通知栏提示TipsUtils.MsgNotificaction();ChatData.MSG msg = (ChatData.MSG)obj;<span id="19_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="19_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=21a41a7e92302f2b&k=switch&k0=switch&kdi0=0&luki=5&n=10&p=baidu&q=smileking_cpr&rb=0&rs=1&seller_id=1&sid=2b2f30927e1aa421&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1682280&u=http%3A%2F%2Fwww%2Eth7%2Ecn%2FProgram%2FAndroid%2F201406%2F219517%2Eshtml&urlid=0" target="_blank" mpid="19" style="text-decoration: none; color: rgb(90, 128, 238);"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">switch</span></a></span> (msg.getType()) {case CHATTING://显示消息MsgUtils.ShowChattingMsg(mContext, msg);break;case ADD_FRIEND://显示好友请求消息MsgUtils.ShowAddFriendMsg(mContext, msg);break;case ADD_AGREE://更新好友列表MsgUtils.AddNewFriend(mContext, msg);break;}}Thread.sleep(250);} catch (Exception e) {e.printStackTrace();//关闭Socket,让其重新连接closeSocket();}
}

MsgUtils:发送消息的工具类,也用于广播消息。广播消息时,需要判断当前的状态。比如广播聊天消息有三种状态:1.处于该好友的会话界面:直接显示消息。2.处于聊天列表界面:更新聊天列表和存储消息。3.其他界面:存储消息,回来聊天列表界面进行显示。

public class MsgUtils {private static ChatConnThread connThread;private static String currBroadcast;/**设置连接线程*/public static void SetConnThread(ChatConnThread thread){connThread = thread;}/**获得离线消息*/public static void GetOfflineMsg(){ChatData.MSG msg = new ChatData.MSG();msg.setType(ChatData.Type.OFFLINE_MSG);msg.setFromId(CacheUtils.GetUserId());connThread.sendMsg(msg);}/**发送消息给好友,或服务端*/public static void SendMsg(ChatData.MSG msg){connThread.sendMsg(msg);}/**关闭连接线程*/public static void CloseConn(){//只有当连接没有断开的情况下,才通知服务端if(connThread.isOnLine()){Log.d("vaint", "通知<span id="9_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="9_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=21a41a7e92302f2b&k=%B7%FE%CE%F1%C6%F7&k0=%B7%FE%CE%F1%C6%F7&kdi0=0&luki=6&n=10&p=baidu&q=smileking_cpr&rb=0&rs=1&seller_id=1&sid=2b2f30927e1aa421&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1682280&u=http%3A%2F%2Fwww%2Eth7%2Ecn%2FProgram%2FAndroid%2F201406%2F219517%2Eshtml&urlid=0" target="_blank" mpid="9" style="text-decoration: none; color: rgb(90, 128, 238);"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">服务器</span></a></span>,关闭连接");MSG msg = new MSG();msg.setFromId(CacheUtils.GetUserId());msg.setType(Type.LOGOUT);SendMsg(msg);}connThread.closeConn();}/*****************************消息显示********************************//** 显示接收的消息* 有三种情况* 1.处于该好友的会话界面:直接显示消息* 2.处于<span id="10_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="10_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=21a41a7e92302f2b&k=%C1%C4%CC%EC&k0=%C1%C4%CC%EC&kdi0=0&luki=7&n=10&p=baidu&q=smileking_cpr&rb=0&rs=1&seller_id=1&sid=2b2f30927e1aa421&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1682280&u=http%3A%2F%2Fwww%2Eth7%2Ecn%2FProgram%2FAndroid%2F201406%2F219517%2Eshtml&urlid=0" target="_blank" mpid="10" style="text-decoration: none; color: rgb(90, 128, 238);"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">聊天</span></a></span>列表界面:更新聊天列表* 3.其他界面:存储消息,回来聊天列表界面进行显示* *//**显示聊天消息*/public static void ShowChattingMsg(Context ctx, MSG msg){//判断当前处于那种情况if(msg.getFromId().equals(currBroadcast))//情况1,该好友的会话界面{//发送广播Intent intent = new Intent(Constants.Actions.CHATTING_PREFIX + msg.getFromId());intent.putExtra(Constants.Flags.MSG, msg);ctx.sendBroadcast(intent);return;}String friendId = msg.getFromId();User friend = CacheUtils.GetFriend(friendId);{//<span id="11_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="11_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=21a41a7e92302f2b&k=%C1%C4%CC%EC&k0=%C1%C4%CC%EC&kdi0=0&luki=7&n=10&p=baidu&q=smileking_cpr&rb=0&rs=1&seller_id=1&sid=2b2f30927e1aa421&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1682280&u=http%3A%2F%2Fwww%2Eth7%2Ecn%2FProgram%2FAndroid%2F201406%2F219517%2Eshtml&urlid=0" target="_blank" mpid="11" style="text-decoration: none; color: rgb(90, 128, 238);"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">聊天</span></a></span>记录ChattingModel model = new ChattingModel();model.setPhoto(friend.getPhoto());model.setSend(false);model.setMsg(msg.getMsg());model.setTime(msg.getTime());model.setShowTime(true);//设置显示时间//添加一条聊天记录DataUtils.AddChattingItem(friendId, model, true);}{//聊天列表ChatListModel model = new ChatListModel();model.setPhoto(friend.getPhoto());model.setTitle(friend.getName());model.setUnread(1);model.setFriendId(friendId);model.setContent(msg.getMsg());model.setTime(msg.getTime());//更新<span id="12_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="12_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=21a41a7e92302f2b&k=%C1%C4%CC%EC&k0=%C1%C4%CC%EC&kdi0=0&luki=7&n=10&p=baidu&q=smileking_cpr&rb=0&rs=1&seller_id=1&sid=2b2f30927e1aa421&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1682280&u=http%3A%2F%2Fwww%2Eth7%2Ecn%2FProgram%2FAndroid%2F201406%2F219517%2Eshtml&urlid=0" target="_blank" mpid="12" style="text-decoration: none; color: rgb(90, 128, 238);"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">聊天</span></a></span>列表DataUtils.UpdateChatList(friendId, model);}if(Constants.Actions.CHAT_LIST.equals(currBroadcast))//情况2,处于聊天列表界面{//发送广播Intent intent = new Intent(Constants.Actions.CHAT_LIST);ctx.sendBroadcast(intent);}//情况3,其他界面}/** 显示添加好友的消息* 有三种情况* 1.处于新的好友的界面:直接显示消息* 2.处于好友列表界面:显示提示* 3.其他界面:存储消息* *//**显示好友请求消息*/public static void ShowAddFriendMsg(Context ctx, MSG msg){NewFriendsModel model = new NewFriendsModel();model.setPhoto(msg.getPhoto());model.setName(msg.getName());model.setUserId(msg.getFromId());model.setVerifyMsg(msg.getMsg());model.setTime(msg.getTime());model.setAgreed(false);//好友请求,设置为未同意添加List<NewFriendsModel> list = CacheUtils.GetNewFriendsList();//判断原来是否有该好友的请求for(int i=0;i<list.size();i++){NewFriendsModel m = list.get(i);if(m.getUserId().equals(msg.getFromId()))//如果存在{//判断是否已经同意其请求if(!m.isAgreed())//如果之前没有同意,才再次覆盖显示,否则不再更新显示{list.remove(i);break;}}}list.add(0, model);// 添加到首位//更新好友请求列表DataUtils.UpdateNewFriendsList(list);if(Constants.Actions.NEW_FRIEND_LIST.equals(currBroadcast))//情况1.处于新的好友的界面{//发送广播Intent intent = new Intent(Constants.Actions.NEW_FRIEND_LIST);intent.putExtra(Constants.Flags.NEW_FRIEND_LIST, model);ctx.sendBroadcast(intent);}else if(Constants.Actions.CONTACTS_LIST.equals(currBroadcast))//情况2.处于好友列表界面{// 发送广播Intent intent = new Intent(Constants.Actions.NEW_FRIEND_TIPS);ctx.sendBroadcast(intent);}//情况3.其他界面  }/**添加一个新的好友<BR/>并且产生一个空白的<span id="13_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="13_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=21a41a7e92302f2b&k=%C1%C4%CC%EC&k0=%C1%C4%CC%EC&kdi0=0&luki=7&n=10&p=baidu&q=smileking_cpr&rb=0&rs=1&seller_id=1&sid=2b2f30927e1aa421&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1682280&u=http%3A%2F%2Fwww%2Eth7%2Ecn%2FProgram%2FAndroid%2F201406%2F219517%2Eshtml&urlid=0" target="_blank" mpid="13" style="text-decoration: none; color: rgb(90, 128, 238);"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">聊天</span></a></span>会话*/public static void AddNewFriend(Context ctx, MSG msg){//更新好友列表FriendList friendList = CacheUtils.GetFriendList();List<User> friends = friendList.getFriends();//判断该好友是否已经在通讯录中for(int i=0;i<friends.size();i++){User user = friends.get(i);if(user.getUserId().equals(msg.getFromId()))//已经存在{return;}}User user = new User();user.setPhoto(msg.getPhoto());user.setName(msg.getName());user.setUserId(msg.getFromId());friends.add(user);friendList.setFriends(friends);//更新好友列表DataUtils.UpdateFriendList(friendList);//<span id="14_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="14_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=21a41a7e92302f2b&k=%C1%C4%CC%EC&k0=%C1%C4%CC%EC&kdi0=0&luki=7&n=10&p=baidu&q=smileking_cpr&rb=0&rs=1&seller_id=1&sid=2b2f30927e1aa421&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1682280&u=http%3A%2F%2Fwww%2Eth7%2Ecn%2FProgram%2FAndroid%2F201406%2F219517%2Eshtml&urlid=0" target="_blank" mpid="14" style="text-decoration: none; color: rgb(90, 128, 238);"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">聊天</span></a></span>列表ChatListModel model = new ChatListModel();model.setUnread(1);model.setFriendId(msg.getFromId());model.setPhoto(msg.getPhoto());model.setTitle(msg.getName());model.setTime(MsgUtils.GetFormatTime());model.setContent(ctx.getString(R.string.txt_say_hi));//更新聊天列表DataUtils.UpdateChatList(msg.getFromId(), model);if(Constants.Actions.CONTACTS_LIST.equals(currBroadcast))//处于好友列表界面{// 发送广播Intent intent = new Intent(Constants.Actions.CONTACTS_LIST);ctx.sendBroadcast(intent);}else if(Constants.Actions.CHAT_LIST.equals(currBroadcast))//处于<span id="15_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="15_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=21a41a7e92302f2b&k=%C1%C4%CC%EC&k0=%C1%C4%CC%EC&kdi0=0&luki=7&n=10&p=baidu&q=smileking_cpr&rb=0&rs=1&seller_id=1&sid=2b2f30927e1aa421&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1682280&u=http%3A%2F%2Fwww%2Eth7%2Ecn%2FProgram%2FAndroid%2F201406%2F219517%2Eshtml&urlid=0" target="_blank" mpid="15" style="text-decoration: none; color: rgb(90, 128, 238);"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">聊天</span></a></span>列表界面{// 发送广播Intent intent = new Intent(Constants.Actions.CHAT_LIST);ctx.sendBroadcast(intent);}}/**存储当前的Broadcast*/public static void SetCurrBroadCast(String broadcastName){currBroadcast = broadcastName;}/**清除当前的Broadcast*/public static void ClearCurrBroadCast(){currBroadcast = null;}/**获得MM-dd HH:mm格式的当前时间*/public static String GetFormatTime(){long currTime = System.currentTimeMillis();SimpleDateFormat formatter = new SimpleDateFormat("MM-dd HH:mm");Date curDate = new Date(currTime);// 获取当前时间String time = formatter.format(curDate);return time;}
}

6.注册广播(以聊天消息为例)

接收聊天消息需要分别在聊天界面和聊天列表界面注册广播。

聊天界面的广播,收到广播后,直接将消息显示在ListView中,属于上述说的情况1.

class MsgBroadcastReceiver extends BroadcastReceiver
{@Overridepublic void onReceive(Context ctx, Intent intent) {MSG msg = (MSG) intent.getSerializableExtra(Constants.Flags.MSG);ChattingModel model = new ChattingModel();User friend = CacheUtils.GetFriend(msg.getFromId());model.setPhoto(friend.getPhoto());model.setMsg(msg.getMsg());model.setSend(false);model.setTime(msg.getTime());long currTime = System.currentTimeMillis();model.setShowTime((currTime - lastTime) > 60000);//间隔超过60秒,则显示消息的时间lastTime = currTime;//记录<span id="8_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="8_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=21a41a7e92302f2b&k=%C1%C4%CC%EC&k0=%C1%C4%CC%EC&kdi0=0&luki=7&n=10&p=baidu&q=smileking_cpr&rb=0&rs=1&seller_id=1&sid=2b2f30927e1aa421&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1682280&u=http%3A%2F%2Fwww%2Eth7%2Ecn%2FProgram%2FAndroid%2F201406%2F219517%2Eshtml&urlid=0" target="_blank" mpid="8" style="text-decoration: none; color: rgb(90, 128, 238);"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">聊天</span></a></span>记录mNewChattingList.add(model);mChattingList.add(model);//显示消息mAdapter = new ChattingAdapter(mContext, mChattingList);mListView.setAdapter(mAdapter);}
}

聊天列表的广播,接收到广播后,以未读消息的形式提示用户,属于上述说的情况2。由于情况2在发送广播前,先存储了聊天记录,以及更新的聊天列表数据,因此这里只需要更新聊天列表的显示即可。

class MsgBroadcastReceiver extends BroadcastReceiver
{@Overridepublic void onReceive(Context ctx, Intent intent) {updateChatList();}
}
public void updateChatList()
{mSourceDateList = DataUtils.GetChatList();mAdapter.updateListView(mSourceDateList);
}

关于使用Socket实现聊天通信,我之前遇到一个奇怪的问题。当手机连接电脑发出来的共享Wifi,进行聊天通信,服务器可以接收到客户端的消息。但是当服务器将消息发送给目标客户端时,出现了Software caused connection abort: recv failed异常。

如果手机实用移动网络或路由器的Wifi,就不会出现这个问题。

现在还不知道为什么会这样。

转载于:https://www.cnblogs.com/berylqliu/p/6261504.html

Android聊天软件的开发--聊天通信相关推荐

  1. Android聊天软件的开发(七)--聊天通信

    聊天通信通过Socket实现,大概的框架如下图: 通信流程: 1.服务器在启动时开启聊天服务线程 可以通过ServletContextListener监听Servlet的初始化和销毁,来开启和关闭聊天 ...

  2. Android聊天软件的开发(三)--网络连接

    一,服务器网络接口    服务器网络接口通过Servlet实现,可以获得客户端提交的数据,对数据进行查询存储操作,以及返回结果数据给客户端.客户端可以通过HTTP协议直接访问网络接口.    HTTP ...

  3. 基于Android的聊天软件,Socket即时通信,实现用户在线聊天

    基于Android的聊天软件,Socket即时通信,单聊,聊天室,可自行扩展功能,完善细节. [实例功能] 1.运行程序,登录界面, 注册账号功能 2.进入主界面,有通讯录, 个人信息. 3.点击好友 ...

  4. Android聊天软件界面开发

    聊天软件界面开发 前言:           这是开始学习Android的开发的第5天,一直是跟着郭霖大师的第一行代码学习,              这里边发篇博文记录,边帮自己整理下思路,毕竟思路 ...

  5. Android聊天软件的开发(二)--数据库

    一,服务器数据库    服务器端的数据库是MySQL,使用Hibernate实现数据的增删改查.主要存储的数据有:用户信息,好友列表.             其中,好友列表中的friend_list ...

  6. Android聊天软件的开发(四)--通讯录

    一,好友排序    好友排序是按照昵称拼音进行A-Z排序.效果如下图:      对好友昵称进行排序,需要先将首字转换为ASCII码,然后根据ASCII码得到对应的拼音,最后根据拼音进行A-Z排序.点 ...

  7. Android聊天软件的开发(六)--表情

    表情用于聊天对话的输入,实现的原理主要是:在EditText或TextView中,使用SpannableString,将特定字符串替换为图片. 首先,我们可以规定,表情的字符串为[**],图片名称为s ...

  8. android社交软件开题报告,开题报告-基于android社交软件的开发.doc

    盐城师范学院 毕业设计开题报告 题 目: 基于android社交软件的开发 姓 名: 学 院: 信息科学与技术学院 专 业: 数字媒体技术 班 级: 12(2) 学 号: 指导教师: 职称: 讲 师 ...

  9. THINKPHP聊天软件H5实时聊天室自动分配账户全开源商业源码

    介绍: THINKPHP聊天软件H5实时聊天室,自动分配账户,全开源商业源码 都是去年买的,很多买的源码基本都下架了, 源码的优点: 运营版本的聊天室,可以添加好友,建立群组,私聊,禁言功能 H5+T ...

最新文章

  1. [WPF]学习笔记二---主窗体
  2. 解决BitLocker反复提示恢复密钥正确而无法进入系统的问题
  3. WCF服务的REST / SOAP端点
  4. 分享一下cookies操作(增、删、改、查)小经验
  5. Zookeeper和分布式环境中的假死脑裂问题(转)
  6. java和C++有什么异同
  7. AMD5470显卡Ubuntu下的U盘的使用
  8. 《从Paxos到Zookeeper:分布式一致性原理与实践》第一章读书笔记
  9. cuda linux 算力_华为AI再进化,CANN 3.0释放算力狂魔
  10. jQuery验证码发送时间秒递减(刷新存储cookie)
  11. python打印四种三角形
  12. SqlParameter类中的两对好基友:SqlDbType与DbType、SqlValue与Value
  13. 主板找不到SSD解决一例
  14. Python:内置类型
  15. 2021-08-01 Python-爬虫练手:爬取上千张“萌妹子“网美图
  16. mysql如何防止sql注入
  17. 家庭监控方案设计及施工-无线监控
  18. 松翰单片机--SN8F5702学习笔记(二)HelloWorld
  19. LiveCharts
  20. 专访Barefoot:被Intel收购后的五倍爆发力

热门文章

  1. python中xlrd模块的使用详解
  2. 视频编码(3):H.266 编码性能比 H.265 再提升 49% 的关键丨音视频基础
  3. 无线传感网学习笔记(3)—— MAC协议 和 CSMA协议
  4. OBS 插件推流以及日志模块
  5. 车牌识别(一)BMP文件读写
  6. write.table函数语法:
  7. php30m限制,住建部:严格控制高度超过 30 米或宽度超过 45 米的大型雕塑
  8. RC振荡器工作原理分析,案例+公式,几分钟,带你搞定RC振荡器
  9. 《图形图像处理》课程项目设计任务书
  10. excel函数提取计算机登录名,EXCEL常用函数应用实例:如何提取姓名中的姓