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

 


 

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


android 写一个聊天软件 安卓聊天软件开发步骤_android 写一个聊天软件

   通信流程:

   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>

聊天服务线程

 

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;

	@Override
	public 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());
				// 获得客户端上传的用户ID
				Object 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)

聊天通信连接,并且需要通过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();
		}
	}
	
	@Override
	public 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();
			} 
		}
		
		//线程结束,关闭Socket
		closeSocket();
		
		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不存在则返回null
			conn.closeConn();// 终止线程
		
		connManager.put(id, connThread);
	}

	/** 移除客户端通信线程 */
	public static void removeConnThread(String id) {
		ChatConnThread connThread = connManager.remove(id);
		if (connThread != null)// 如果id不存在则返回null
			connThread.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)

 

聊天消息(ChatData.MSG)发送到服务器

 

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

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

服务器维护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);
	}

	@Override
	public 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
{
	@Override
	public 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
{
	@Override
	public void onReceive(Context ctx, Intent intent) {
		updateChatList();
	}
}
public void updateChatList()
{
	mSourceDateList = DataUtils.GetChatList();
	mAdapter.updateListView(mSourceDateList);
}

android 写一个聊天软件 安卓聊天软件开发步骤_php_02






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

 

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

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

 

每天进步一点点!