详解android环境下的即时通讯
首先了解一下即时通信的概念。通过消息通道传输消息对象,一个账号发往另外一账号,只要账号在线,可以即时获取到消息,这就是最简单的即使通讯。消息通道可由TCP/IPUDP实现。通俗讲就是把一个人要发送给另外一个人的消息对象(文字,音视频,文件)通过消息通道(C/S实时通信)进行传输的服务。即时通讯应该包括四种形式,在线直传、在线代理、离线代理、离线扩展。在线直传指不经过服务器,直接实现点对点传输。在线代理指消息经过服务器,在服务器实现中转,最后到达目标账号。离线代理指消息经过服务器中转到达目标账号,对方不在线时消息暂存服务器的数据库,在其上线再传发。离线扩展指将暂存消息以其它形式,例如邮件、短信等转发给目标账号。
此外,我们还需要认识一下计算机网络相关的概念。经典的计算机网络四层模型中,TCP和UDP是传输层协议,包含着消息通信内容。ip为网络层协议,是一种网络地址。TCP/IP,即传输控制协议/网间协议,定义了主机如何连入因特网及数据如何在它们之间传输的标准。Socket,又称“套接字”,在应用层和传输层之间的一个抽象层,用于描述IP地址和端口,是一个通信连的句柄,应用程序通常通过“套接字”向网络发送请求或者应答网络请求,它就是网络通信过程中端点的抽象表示。它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。XMPP(可扩展消息处理现场协议)是基于可扩展标记语言(XML)的协议,应用于即时通讯场景的应用层协议,底层通过Socket实现。它用于即时消息(IM)以及在线现场探测。它在促进服务器之间的准即时操作。这个协议可能最终允许因特网用户向因特网上的其他任何人发送即时消息,即使其操作系统和浏览器不同。这样实现即时通讯就有两种方案,一是从套接字入手,直接利用socket提供的接口进行数据的传送。二是借助开源工具(服务器openfire),用XMPPConnection创建连接。
XMPP是实现即时通讯使用较为普遍的做法。XMPP中,各项工作都是通过在一个XMPP流上发送和接收XMPP节来完成的。核心XMPP工具集由三种基本节组成,这三种节分别为<presence>、出席<message>、<iq>。XMPP流由两份XML文档组成,通信的每个方向均有一份文档。这份文档有一个根元素<stream:stream>,这个根元素的子元素由可路由的节以及与流相关的顶级子元素构成。xmpp协议同样包括客户端和服务器。客户端基于Android平台进行开发。负责初始化通信过程,进行即时通信时,由客户端负责向服务器发起创建连接请求。系统通过GPRS无线网络与Internet网络建立连接,通过服务器实现与Android客户端的即时通信脚。服务器端则采用Openfire作为服务器。允许多个客户端同时登录并且并发的连接到一个服务器上。服务器对每个客户端的连接进行认证,对认证通过的客户端创建会话,客户端与服务器端之间的通信就在该会话的上下文中进行。使用了asmark开源框架实现的即时通讯功能.该框架基于开源的XMPP即时通信协议,采用C/S体系结构,通过GPRS无线网络用TCP协议连接到服务器,以架设开源的Openfn'e服务器作为即时通讯平台。xmpp消息通道的创建:
先配置通道信息进行连接
ConnectionConfigurationconfiguration=newConnectionConfiguration(HOST,PORT)
设置Debug信息和安全模式
configuration.setDebuggerEnabled(true); configuration.setSecurityMode(SecurityMode.disabled)
最后才是建立连接
conn.connect();
在ContentObserver的实现类中观察消息变化。XMPPConnection.getRoster()获取联系人列表对象。用xmpp协议编写通讯协议的大致思路可以如下。进入登陆界面,通过xmppconnection的login方法实现登陆,登陆成功进入主界面。主界面包含两个Fragment,分别用来显示联系人和聊天记录。创建联系人和短信的数据观察者,在联系人、短信服务中分别设定监听RosterListener()、ChatManagerListener(),接受联系人和短信信息,同时将相关信息添加到内容提供者中。在内容提供者中设定一个内容观察者,当数据发生变化时通知界面更新。
本文的重点是利用Socket的接口实现即时通讯,因为绝大多数即时通讯的底层都是通过Socket实现的。其基本的业务逻辑可描述如下。用户进入登陆界面后,提交账号密码经服务端确定,返回相关参数用于确定连接成功。进入聊天界面或好友界面。点击联系人或聊天记录的条目,进入聊天界面。当移动端再次向服务器发送消息时,由服务器转发消息内容给目标账号。同时更新界面显示。这样就完成即时通讯的基本功能。当然,也可以添加一个后台服务,当用户推出程序时,在后台接受消息。不难看出,对于即时通讯来讲,有三个关注点:消息通道、消息内容、消息对象。因此,主要逻辑也是围绕这三个点展开。消息通道实现传输消息对象的发送和接收。为Socket(Stringhost,intport)传入服务其地址和端口号,即可创建连接。消息内容的格式应该与服务器保持一致。接受数据时,获取输入流并用DataInputStream包装,通过输入流读取server发来的数据。发送数据时,获取输出流并用DataOutputStream包装,通过输出流往server发送数据。消息内容中应该包括发送者、接受者信息、数据类型等。消息对象就是消息的发送者和消息的接受者。接下来在代码中进行详细的讲解。
创建一个消息的基类,实现xml文件和字符串的转换,用到Xsream第三方jar包。这样当创建消息类时,继承该方法,就可以直接在类中实现数据的转换。
/** *Createdbyhuangon2016/12/3. */ publicclassProtacolObjcimplementsSerializable{ publicStringtoXml(){ XStreamstream=newXStream(); //将根节点转换为类名 stream.alias(this.getClass().getSimpleName(),this.getClass()); returnstream.toXML(this); } publicObjectfromXml(Stringxml){ XStreamx=newXStream(); x.alias(this.getClass().getSimpleName(),this.getClass()); returnx.fromXML(xml); } //创建Gson数据和字符串之间转换的方法,适应多种数据 publicStringtoGson(){ Gsongson=newGson(); returntoGson(); } publicObjectfromGson(Stringresult){ Gsongson=newGson(); returngson.fromJson(result,this.getClass()); } }
创建线程工具,指定方法运行在子线程和主线程中。由于网络操作需要在子线程中,界面更新需要在主线程中,创建线程工具可以方便选择线程。
importandroid.os.Handler; /** *Createdbyhuangon2016/12/5. */ publicclassThreadUtils{ privatestaticHandlerhandler=newHandler(); publicstaticvoidrunUIThread(Runnabler){ handler.post(r); } publicstaticvoidrunINThread(Runnabler){ newThread(r).start(); } }
创建消息的工具类,包括消息内容、消息类型、消息本省等。由于服务器返回的内容中包含消息的包名信息所以消息本身的包名应该于服务其保持一直。
/** *Createdbyhuangon2016/12/3. *消息内容 */ publicclassQQMessageextendsProtacolObjc{ publicStringtype=QQmessageType.MSG_TYPE_CHAT_P2P;//类型的数据chatlogin publiclongfrom=0;//发送者account publicStringfromNick="";//昵称 publicintfromAvatar=1;//头像 publiclongto=0;//接收者account publicStringcontent="";//消息的内容约不? publicStringsendTime=getTime();//发送时间 publicStringgetTime(){ Datedate=newDate(System.currentTimeMillis()); java.text.SimpleDateFormatformat=newjava.text.SimpleDateFormat("mm-DDHH:mm:ss"); returnformat.format(date); } publicStringgetTime(Longtime){ Datedate=newDate(time); java.text.SimpleDateFormatformat=newjava.text.SimpleDateFormat("mm-DDHH:mm:ss"); returnformat.format(date); } } /** *Createdbyhuangon2016/12/3. *消息类型 */ publicclassQQmessageType{ publicstaticfinalStringMSG_TYPE_REGISTER="register";//注册 publicstaticfinalStringMSG_TYPE_LOGIN="login";//登录 publicstaticfinalStringMSG_TYPE_LOGIN_OUT="loginout";//登出 publicstaticfinalStringMSG_TYPE_CHAT_P2P="chatp2p";//聊天 publicstaticfinalStringMSG_TYPE_CHAT_ROOM="chatroom";//群聊 publicstaticfinalStringMSG_TYPE_OFFLINE="offline";//下线 publicstaticfinalStringMSG_TYPE_SUCCESS="success";//成功 publicstaticfinalStringMSG_TYPE_BUDDY_LIST="buddylist";//好友 publicstaticfinalStringMSG_TYPE_FAILURE="failure";//失败 } importcom.example.huang.imsocket.bean.ProtacolObjc; /* *消息本身包括账号、头像和昵称 * */ publicclassQQBuddyextendsProtacolObjc{ publiclongaccount; publicStringnick; publicintavatar; } /** *Createdbyhuangon2016/12/3. */ publicclassQQBuddyListextendsProtacolObjc{ publicArrayList<QQBuddy>buddyList=newArrayList<>(); }
关于socket的创建连接和发送消息、接受消息。
importandroid.util.Log; importcom.example.huang.imsocket.bean.QQMessage; importjava.io.DataInputStream; importjava.io.DataOutputStream; importjava.io.IOException; importjava.net.Socket; importjava.util.ArrayList; importjava.util.List; /** *Createdbyhuangon2016/12/3. *连接服务器 */ publicclassQQConnectionextendsThread{ privatestaticfinalStringTAG="QQConnection"; privateSocketclient; privateDataOutputStreamwrite; privateDataInputStreamread; publicstaticfinalStringHOST="192.168.23.48"; publicstaticfinalintPOST=5225; privatebooleanflag=true; privateList<OnQQmwssagereceiveLisener>mOnQQmwssagereceiveLisener=newArrayList<>(); publicvoidaddOnQQmwssagereceiveLisener(OnQQmwssagereceiveLisenerlisener){ mOnQQmwssagereceiveLisener.add(lisener); } publicvoidremoveOnQQmwssagereceiveLisener(OnQQmwssagereceiveLisenerlisener){ mOnQQmwssagereceiveLisener.remove(lisener); } publicinterfaceOnQQmwssagereceiveLisener{ publicvoidonReiceive(QQMessageqq); } @Override publicvoidrun(){ super.run(); while(flag){ try{ Stringutf=read.readUTF(); QQMessagemessage=newQQMessage(); QQMessagemsg=(QQMessage)message.fromXml(utf); if(msg!=null){ for(OnQQmwssagereceiveLisenerlisner:mOnQQmwssagereceiveLisener) lisner.onReiceive(msg); } }catch(IOExceptione){ e.printStackTrace(); } } } publicvoidconnect(){ try{ if(client==null){ client=newSocket(HOST,POST); write=newDataOutputStream(client.getOutputStream()); read=newDataInputStream(client.getInputStream()); flag=true; this.start(); Log.e(TAG,"connect:"+(write==null)+"---"+(read==null)); } }catch(Exceptione){ e.printStackTrace(); } } publicvoiddisconnect(){ if(client!=null){ flag=false; this.stop(); try{ read.close(); }catch(IOExceptione){ e.printStackTrace(); } try{ write.close(); }catch(IOExceptione){ e.printStackTrace(); } try{ client.close(); }catch(IOExceptione){ e.printStackTrace(); } } } publicvoidsend(Stringxml)throwsIOException{ write.writeUTF(xml); write.flush(); } publicvoidsend(QQMessageqq)throwsIOException{ write.writeUTF(qq.toXml()); write.flush(); } }
闪屏界面的布局
<?xmlversion="1.0"encoding="utf-8"?> <RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_splash" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@mipmap/splash_bg"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:src="@mipmap/conversation_bg_logo"/> </RelativeLayout>
闪屏界面,保持4秒钟进入登陆界面。一般来见,闪屏界面可以加载数据、获取版本号、更新版本等操作。这里没有做的那么复杂。
importcom.example.huang.imsocket.R; publicclassSplashActivityextendsAppCompatActivity{ @Override protectedvoidonCreate(BundlesavedInstanceState){ super.onCreate(savedInstanceState); getSupportActionBar().hide();//隐藏标栏 getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);//全屏显示 setContentView(R.layout.activity_splash); newHandler().postDelayed(newRunnable(){ @Override publicvoidrun(){ startActivity(newIntent(SplashActivity.this,LoginActivity.class)); finish(); } },4000); } }
登陆界面的布局
<?xmlversion="1.0"encoding="utf-8"?> <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#aabbdd" android:gravity="center" android:orientation="vertical"> <TableLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@mipmap/conversation_bg_logo"/> <TableRow android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="20dp" android:layout_marginRight="20dp" android:layout_marginTop="8dp" android:gravity="center_horizontal"> <TextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:gravity="center" android:text="账号:" android:textColor="#000"/> <EditText android:id="@+id/et_accoun" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="3" android:gravity="center" android:hint="输入账号"/> </TableRow> <TableRow android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="20dp" android:layout_marginRight="20dp" android:layout_marginTop="4dp" android:gravity="center_horizontal"> <TextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:gravity="center" android:text="密码:" android:textColor="#000"/> <EditText android:id="@+id/et_pwd" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="3" android:gravity="center" android:hint="输入密码"/> </TableRow> <Button android:id="@+id/btn_login" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="80dp" android:layout_marginRight="80dp" android:layout_marginTop="8dp" android:onClick="sendmessage" android:text="登录"/> </TableLayout> </LinearLayout>
登陆界面,创建和服务器的连接,向服务器发送登陆信息,接受服务器返回的信息。
importandroid.app.Activity; importandroid.content.Intent; importandroid.os.Bundle; importandroid.util.Log; importandroid.view.View; importandroid.widget.EditText; importandroid.widget.Toast; importcom.example.huang.imsocket.R; importcom.example.huang.imsocket.bean.Myapp; importcom.example.huang.imsocket.bean.QQBuddyList; importcom.example.huang.imsocket.bean.QQMessage; importcom.example.huang.imsocket.bean.QQmessageType; importcom.example.huang.imsocket.core.QQConnection; importcom.example.huang.imsocket.service.IMService; importcom.example.huang.imsocket.util.ThreadUtils; importjava.io.IOException; /** *Createdbyhuangon2016/12/3. */ publicclassLoginActivityextendsActivity{ privatestaticfinalStringTAG="LoginActivity"; privateEditTextet_accoun; privateEditTextet_pwd; privateStringaccoun; privateQQConnectionconn; privateQQConnection.OnQQmwssagereceiveLisenerlisener=newQQConnection.OnQQmwssagereceiveLisener(){ @Override publicvoidonReiceive(finalQQMessageqq){ finalQQBuddyListlist=newQQBuddyList(); finalQQBuddyListlist2=(QQBuddyList)list.fromXml(qq.content); if(QQmessageType.MSG_TYPE_BUDDY_LIST.equals(qq.type)){ ThreadUtils.runUIThread(newRunnable(){ @Override publicvoidrun(){ Toast.makeText(getBaseContext(),"成功",Toast.LENGTH_SHORT).show(); Myapp.me=conn; Myapp.username=accoun; Myapp.account=accoun+"@qq.com"; Intentintent=newIntent(LoginActivity.this,contactActivity.class); intent.putExtra("list",list2); startActivity(intent); Intentdata=newIntent(LoginActivity.this,IMService.class); startService(data); finish(); } }); }else{ ThreadUtils.runUIThread(newRunnable(){ @Override publicvoidrun(){ Toast.makeText(getBaseContext(),"登陆失败",Toast.LENGTH_SHORT).show(); } }); } } }; @Override protectedvoidonCreate(BundlesavedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); et_accoun=(EditText)findViewById(R.id.et_accoun); et_pwd=(EditText)findViewById(R.id.et_pwd); ThreadUtils.runINThread(newRunnable(){ @Override publicvoidrun(){ try{ conn=newQQConnection(); conn.addOnQQmwssagereceiveLisener(lisener); conn.connect(); }catch(Exceptione){ e.printStackTrace(); } } }); } publicvoidsendmessage(Viewview){ accoun=et_accoun.getText().toString().trim(); finalStringpassword=et_pwd.getText().toString().trim(); Log.i(TAG,"sendmessage:"+accoun+"#"+password); ThreadUtils.runINThread(newRunnable(){ @Override publicvoidrun(){ QQMessagemessage=newQQMessage(); message.type=QQmessageType.MSG_TYPE_LOGIN; message.content=accoun+"#"+password; Stringxml=message.toXml(); if(conn!=null){ try{ conn.send(xml); }catch(IOExceptione){ e.printStackTrace(); } } } }); } @Override protectedvoidonDestroy(){ super.onDestroy(); conn.removeOnQQmwssagereceiveLisener(lisener); } }
好友列表界面
<?xmlversion="1.0"encoding="utf-8"?> <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#aabbcc" android:orientation="vertical"> <TextView android:id="@+id/tv_title" android:layout_width="match_parent" android:layout_height="50dp" android:gravity="center" android:text="联系人列表" android:textColor="#6d00" android:textSize="23dp"/> <ListView android:id="@+id/lv_contact" android:layout_width="match_parent" android:layout_height="match_parent"></ListView> </LinearLayout>
好友列表及时收到从哪个服务其发挥的好友更新信息,点击条目跳到聊天界面。
importandroid.app.Activity; importandroid.content.Intent; importandroid.graphics.Color; importandroid.os.Bundle; importandroid.view.View; importandroid.view.ViewGroup; importandroid.widget.AdapterView; importandroid.widget.ArrayAdapter; importandroid.widget.ImageView; importandroid.widget.ListView; importandroid.widget.TextView; importandroid.widget.Toast; importcom.example.huang.imsocket.R; importcom.example.huang.imsocket.bean.Myapp; importcom.example.huang.imsocket.bean.QQBuddyList; importcom.example.huang.imsocket.bean.QQMessage; importcom.example.huang.imsocket.bean.QQmessageType; importcom.example.huang.imsocket.core.QQConnection; importcom.example.huang.imsocket.util.ThreadUtils; importjava.util.ArrayList; importbutterknife.Bind; importbutterknife.ButterKnife; importcn.itcast.server.bean.QQBuddy; /** *Createdbyhuangon2016/12/5. */ publicclasscontactActivityextendsActivity{ privatestaticfinalStringTAG="contactActivity"; @Bind(R.id.tv_title) TextViewtv_title; @Bind(R.id.lv_contact) ListViewlv_contact; privateQQBuddyListlist; privateArrayList<QQBuddy>BuddyList=newArrayList<>(); privateArrayAdapteradapter=null; privateQQConnection.OnQQmwssagereceiveLisenerlistener=newQQConnection.OnQQmwssagereceiveLisener(){ @Override publicvoidonReiceive(QQMessageqq){ if(QQmessageType.MSG_TYPE_BUDDY_LIST.equals(qq.type)){ QQBuddyListqqlist=newQQBuddyList(); QQBuddyListqqm=(QQBuddyList)qqlist.fromXml(qq.content); BuddyList.clear(); BuddyList.addAll(qqm.buddyList); ThreadUtils.runUIThread(newRunnable(){ @Override publicvoidrun(){ saveAndNotify(); } }); } } }; @Override protectedvoidonCreate(BundlesavedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.activity_contact); ButterKnife.bind(this); Myapp.me.addOnQQmwssagereceiveLisener(listener); Intentintent=getIntent(); list=(QQBuddyList)intent.getSerializableExtra("list"); BuddyList.clear(); BuddyList.addAll(list.buddyList); saveAndNotify(); } @Override protectedvoidonDestroy(){ super.onDestroy(); Myapp.me.removeOnQQmwssagereceiveLisener(listener); } privatevoidsaveAndNotify(){ if(BuddyList.size()<1){ return; } if(adapter==null){ adapter=newArrayAdapter<QQBuddy>(getBaseContext(),0,BuddyList){ @Override publicViewgetView(intposition,ViewconvertView,ViewGroupparent){ viewHolderholder; if(convertView==null){ convertView=View.inflate(getContext(),R.layout.item_contacts,null); holder=newviewHolder(convertView); convertView.setTag(holder); }else{ holder=(viewHolder)convertView.getTag(); } QQBuddyqqBuddy=BuddyList.get(position); holder.tv_nick.setText(qqBuddy.nick); holder.tv_account.setText(qqBuddy.account+"@qq.com"); if(Myapp.username.equals(qqBuddy.account+"")){ holder.tv_nick.setText("[自己]"); holder.tv_nick.setTextColor(Color.GRAY); }else{ holder.tv_nick.setTextColor(Color.RED); } returnconvertView; } }; lv_contact.setAdapter(adapter); lv_contact.setOnItemClickListener(newAdapterView.OnItemClickListener(){ @Override publicvoidonItemClick(AdapterView<?>parent,Viewview,intposition,longid){ QQBuddyqqbuddy=BuddyList.get(position); if(Myapp.username.equals(qqbuddy.account+"")){ Toast.makeText(getBaseContext(),"不能和自己聊天",Toast.LENGTH_SHORT).show(); }else{ Intentintent=newIntent(contactActivity.this,ChatActivity.class); intent.putExtra("account",qqbuddy.account+""); intent.putExtra("nick",qqbuddy.nick+""); startActivity(intent); } } }); }else{ adapter.notifyDataSetChanged(); } } staticclassviewHolder{ @Bind(R.id.iv_contact) ImageViewiv_contact; @Bind(R.id.tv_nick) TextViewtv_nick; @Bind(R.id.tv_account) TextViewtv_account; publicviewHolder(Viewview){ ButterKnife.bind(this,view); } } }
聊天界面
<?xmlversion="1.0"encoding="utf-8"?> <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/tv_name" android:layout_width="match_parent" android:layout_height="40dp" android:background="#aa119988" android:gravity="center" android:text="和谁谁聊天中........." android:textSize="19dp"/> <ListView android:id="@+id/lv_chat" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"/> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <EditText android:id="@+id/et_sms" android:layout_width="0dp" android:layout_height="40dp" android:layout_weight="1" android:hint="输入聊天"/> <Button android:id="@+id/btn_send" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="发送"/> </LinearLayout> </LinearLayout>
聊天界面中消息接收和消息发送都需要及时更新列表。
importandroid.app.Activity; importandroid.content.Intent; importandroid.os.Bundle; importandroid.text.TextUtils; importandroid.view.View; importandroid.view.ViewGroup; importandroid.widget.ArrayAdapter; importandroid.widget.EditText; importandroid.widget.ListView; importandroid.widget.TextView; importandroid.widget.Toast; importcom.example.huang.imsocket.R; importcom.example.huang.imsocket.bean.Myapp; importcom.example.huang.imsocket.bean.QQMessage; importcom.example.huang.imsocket.bean.QQmessageType; importcom.example.huang.imsocket.core.QQConnection; importcom.example.huang.imsocket.util.ThreadUtils; importjava.io.IOException; importjava.util.ArrayList; importbutterknife.Bind; importbutterknife.ButterKnife; importbutterknife.OnClick; /** *Createdbyhuangon2016/12/3. */ publicclassChatActivityextendsActivity{ privatestaticfinalStringTAG="ChatActivity"; @Bind(R.id.tv_name) TextViewtv_name; @Bind(R.id.lv_chat) ListViewlv_chat; @Bind(R.id.et_sms) EditTextet_sms; privateArrayAdapter<QQMessage>adapter=null; privateArrayList<QQMessage>list=newArrayList<>(); privateStringaccount; @OnClick(R.id.btn_send) publicvoidsend(Viewview){ Stringsendsms=et_sms.getText().toString().trim(); if(TextUtils.isEmpty(sendsms)){ Toast.makeText(this,"消息不能为空",Toast.LENGTH_SHORT).show(); return; } et_sms.setText(""); finalQQMessageqq=newQQMessage(); qq.type=QQmessageType.MSG_TYPE_CHAT_P2P; qq.content=sendsms; qq.from=Long.parseLong(Myapp.username); qq.to=Long.parseLong(account); list.add(qq); setAdapteORNotify(); ThreadUtils.runINThread(newRunnable(){ @Override publicvoidrun(){ try{ Myapp.me.send(qq); }catch(IOExceptione){ e.printStackTrace(); } } }); } privateQQConnection.OnQQmwssagereceiveLisenerlistener=newQQConnection.OnQQmwssagereceiveLisener(){ @Override publicvoidonReiceive(finalQQMessageqq){ if(QQmessageType.MSG_TYPE_CHAT_P2P.equals(qq.type)){ ThreadUtils.runUIThread(newRunnable(){ @Override publicvoidrun(){ list.add(qq); setAdapteORNotify(); } }); } } }; @Override protectedvoidonCreate(BundlesavedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.activity_chat); ButterKnife.bind(this); Myapp.me.addOnQQmwssagereceiveLisener(listener); Intentintent=getIntent(); account=intent.getStringExtra("account"); Stringnick=intent.getStringExtra("nick"); tv_name.setText("和"+nick+"聊天中......"); setAdapteORNotify(); } @Override protectedvoidonDestroy(){ super.onDestroy(); Myapp.me.removeOnQQmwssagereceiveLisener(listener); } privatevoidsetAdapteORNotify(){ if(list.size()<1){ return; } if(adapter==null){ adapter=newArrayAdapter<QQMessage>(this,0,list){ @Override publicintgetViewTypeCount(){ return2; } @Override publicintgetItemViewType(intposition){ QQMessagemsg=list.get(position); longfromId=Long.parseLong(Myapp.username); if(fromId==msg.from){ return0; } return1; } @Override publicViewgetView(intposition,ViewconvertView,ViewGroupparent){ inttype=getItemViewType(position); if(type==0){ viewHolderholder1=null; if(convertView==null){ holder1=newviewHolder(); convertView=View.inflate(getBaseContext(),R.layout.item_sms_send,null); holder1.tv_send_time=(TextView)convertView.findViewById(R.id.tv_send_time); holder1.tv_send=(TextView)convertView.findViewById(R.id.tv_send); convertView.setTag(holder1); }else{ holder1=(viewHolder)convertView.getTag(); } QQMessageqqMessage=list.get(position); holder1.tv_send_time.setText(qqMessage.sendTime); holder1.tv_send.setText(qqMessage.content); returnconvertView; }elseif(type==1){ viewHolderholder2=null; if(convertView==null){ holder2=newviewHolder(); convertView=View.inflate(getBaseContext(),R.layout.item_sms_receive,null); holder2.tv_receive_time=(TextView)convertView.findViewById(R.id.tv_receive_time); holder2.tv_receive=(TextView)convertView.findViewById(R.id.tv_receive); convertView.setTag(holder2); }else{ holder2=(viewHolder)convertView.getTag(); } QQMessageqqMessage=list.get(position); holder2.tv_receive_time.setText(qqMessage.sendTime); holder2.tv_receive.setText(qqMessage.content); returnconvertView; } returnconvertView; } }; lv_chat.setAdapter(adapter); }else{ adapter.notifyDataSetChanged(); } if(lv_chat.getCount()>0){ lv_chat.setSelection(lv_chat.getCount()-1); } } classviewHolder{ TextViewtv_send_time; TextViewtv_send; TextViewtv_receive_time; TextViewtv_receive; } }
最后可以添加一个服务当程序退出时,接受消息。
importandroid.app.Service; importandroid.content.Intent; importandroid.os.IBinder; importandroid.widget.Toast; importcom.example.huang.imsocket.bean.Myapp; importcom.example.huang.imsocket.bean.QQMessage; importcom.example.huang.imsocket.core.QQConnection; importcom.example.huang.imsocket.util.ThreadUtils; /** *Createdbyhuangon2016/12/7. */ publicclassIMServiceextendsService{ privateQQConnection.OnQQmwssagereceiveLisenerlisener=newQQConnection.OnQQmwssagereceiveLisener(){ @Override publicvoidonReiceive(finalQQMessageqq){ ThreadUtils.runUIThread(newRunnable(){ @Override publicvoidrun(){ Toast.makeText(getBaseContext(),"收到好友消息:"+qq.content,Toast.LENGTH_SHORT).show(); } }); } }; @Override publicIBinderonBind(Intentintent){ returnnull; } @Override publicvoidonCreate(){ super.onCreate(); Toast.makeText(getBaseContext(),"服务开启",Toast.LENGTH_SHORT).show(); Myapp.me.addOnQQmwssagereceiveLisener(lisener); } @Override publicvoidonDestroy(){ Myapp.me.removeOnQQmwssagereceiveLisener(lisener); super.onDestroy(); } }
Activity和Service节点配置,以及相应的权限。
<?xmlversion="1.0"encoding="utf-8"?> <manifestxmlns:android="http://schemas.android.com/apk/res/android" package="com.example.huang.imsocket"> <uses-permissionandroid:name="android.permission.INTERNET"/> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activityandroid:name="com.example.huang.imsocket.activity.SplashActivity"> <intent-filter> <actionandroid:name="android.intent.action.MAIN"/> <categoryandroid:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> <activity android:name="com.example.huang.imsocket.activity.LoginActivity" android:theme="@android:style/Theme.NoTitleBar"></activity> <activity android:name="com.example.huang.imsocket.activity.ChatActivity" android:theme="@android:style/Theme.NoTitleBar"></activity> <activity android:name="com.example.huang.imsocket.activity.contactActivity" android:theme="@android:style/Theme.NoTitleBar"></activity> <serviceandroid:name=".service.IMService"/> </application> </manifest>
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。