Android聊天软件的开发(七)--聊天通信
聊天通信通过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>
聊天服务线程
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)
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);
}
关于使用Socket实现聊天通信,我之前遇到一个奇怪的问题。当手机连接电脑发出来的共享Wifi,进行聊天通信,服务器可以接收到客户端的消息。但是当服务器将消息发送给目标客户端时,出现了Software caused connection abort: recv failed异常。
如果手机实用移动网络或路由器的Wifi,就不会出现这个问题。
现在还不知道为什么会这样。
每天进步一点点!