android环境下的即时通讯
首先了解一下即时通信的概念。通过消息通道 传输消息对象,一个账号发往另外一账号,只要账号在线,可以即时获取到消息,这就是最简单的即使通讯。消息通道可由TCP/IP UDP实现。通俗讲就是把一个人要发送给另外一个人的消息对象(文字,音视频,文件)通过消息通道(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消息通道的创建:
先配置通道信息进行连接
ConnectionConfiguration configuration = new ConnectionConfiguration(HOST, PORT),
设置Debug信息和安全模式
configuration.setDebuggerEnabled(true);
configuration.setSecurityMode(SecurityMode.disabled),
最后才是建立连接
conn.connect();
在ContentObserver的实现类中观察消息变化。XMPPConnection.getRoster()获取联系人列表对象。用xmpp协议编写通讯协议的大致思路可以如下。进入登陆界面,通过xmppconnection的login方法实现登陆,登陆成功进入主界面。主界面包含两个Fragment,分别用来显示联系人和聊天记录。创建联系人和短信的数据观察者,在联系人、短信服务中分别设定监听RosterListener()、ChatManagerListener(),接受联系人和短信信息,同时将相关信息添加到内容提供者中。在内容提供者中设定一个内容观察者,当数据发生变化时通知界面更新。
本文的重点是利用Socket的接口实现即时通讯,因为绝大多数即时通讯的底层都是通过Socket实现的。其基本的业务逻辑可描述如下。用户进入登陆界面后,提交账号密码 经服务端确定,返回相关参数用于确定连接成功。进入聊天界面或好友界面。点击联系人或聊天记录的条目,进入聊天界面。当移动端再次向服务器发送消息时,由服务器转发消息内容给目标账号。同时更新界面显示。这样就完成即时通讯的基本功能。当然,也可以添加一个后台服务,当用户推出程序时,在后台接受消息。不难看出,对于即时通讯来讲,有三个关注点:消息通道、消息内容、消息对象。因此,主要逻辑也是围绕这三个点展开。消息通道实现传输消息对象的发送和接收。为Socket(String host, int port)传入服务其地址和端口号,即可创建连接。消息内容的格式应该与服务器保持一致。接受数据时,获取输入流并用DataInputStream包装,通过输入流读取server发来的数据。发送数据时,获取输出流并用DataOutputStream包装,通过输出流往server发送数据。消息内容中应该包括发送者、接受者信息、数据类型等。消息对象就是消息的发送者和消息的接受者。接下来在代码中进行详细的讲解。
创建一个消息的基类,实现xml文件和字符串的转换,用到Xsream第三方jar包。这样当创建消息类时,继承该方法,就可以直接在类中实现数据的转换。
/**
* Created by huang on 2016/12/3.
*/
public class ProtacolObjc implements Serializable {
public String toXml() {
XStream stream = new XStream();
//将根节点转换为类名
stream.alias(this.getClass().getSimpleName(), this.getClass());
return stream.toXML(this);
} public Object fromXml(String xml) {
XStream x = new XStream();
x.alias(this.getClass().getSimpleName(), this.getClass());
return x.fromXML(xml);
} //创建Gson数据和字符串之间转换的方法,适应多种数据
public String toGson() {
Gson gson = new Gson();
return toGson();
} public Object fromGson(String result) {
Gson gson = new Gson();
return gson.fromJson(result, this.getClass());
}
}
创建线程工具,指定方法运行在子线程和主线程中。由于网络操作需要在子线程中,界面更新需要在主线程中,创建线程工具可以方便选择线程。
import android.os.Handler;
/**
* Created by huang on 2016/12/5.
*/
public class ThreadUtils {
private static Handler handler = new Handler();
public static void runUIThread(Runnable r){
handler.post(r);
}
public static void runINThread(Runnable r){
new Thread(r).start();
}
}
创建消息的工具类,包括消息内容、消息类型、消息本省等。由于服务器返回的内容中包含消息的包名信息所以消息本身的包名应该于服务其保持一直。
/**
* Created by huang on 2016/12/3.
* 消息内容
*/
public class QQMessage extends ProtacolObjc {
public String type = QQmessageType.MSG_TYPE_CHAT_P2P;// 类型的数据 chat login
public long from = 0;// 发送者 account
public String fromNick = "";// 昵称
public int fromAvatar = 1;// 头像
public long to = 0; // 接收者 account
public String content = ""; // 消息的内容 约不?
public String sendTime = getTime(); // 发送时间 public String getTime() {
Date date = new Date(System.currentTimeMillis());
java.text.SimpleDateFormat format = new java.text.SimpleDateFormat("mm-DD HH:mm:ss");
return format.format(date);
} public String getTime(Long time) {
Date date = new Date(time);
java.text.SimpleDateFormat format = new java.text.SimpleDateFormat("mm-DD HH:mm:ss");
return format.format(date);
}
} /**
* Created by huang on 2016/12/3.
* 消息类型
*/
public class QQmessageType {
public static final String MSG_TYPE_REGISTER = "register";// 注册
public static final String MSG_TYPE_LOGIN = "login";// 登录
public static final String MSG_TYPE_LOGIN_OUT = "loginout";// 登出
public static final String MSG_TYPE_CHAT_P2P = "chatp2p";// 聊天
public static final String MSG_TYPE_CHAT_ROOM = "chatroom";// 群聊
public static final String MSG_TYPE_OFFLINE = "offline";// 下线
public static final String MSG_TYPE_SUCCESS = "success";//成功
public static final String MSG_TYPE_BUDDY_LIST = "buddylist";// 好友
public static final String MSG_TYPE_FAILURE = "failure";// 失败
} import com.example.huang.imsocket.bean.ProtacolObjc;
/*
*消息本身 包括 账号、头像和昵称
*
*/
public class QQBuddy extends ProtacolObjc {
public long account;
public String nick;
public int avatar;
} /**
* Created by huang on 2016/12/3.
*/ public class QQBuddyList extends ProtacolObjc {
public ArrayList<QQBuddy> buddyList = new ArrayList<>();
}
关于socket的创建连接和发送消息、接受消息。
import android.util.Log;
import com.example.huang.imsocket.bean.QQMessage;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List; /**
* Created by huang on 2016/12/3.
* 连接 服务器
*/
public class QQConnection extends Thread {
private static final String TAG = "QQConnection";
private Socket client;
private DataOutputStream write;
private DataInputStream read;
public static final String HOST = "192.168.23.48";
public static final int POST = 5225; private boolean flag = true; private List<OnQQmwssagereceiveLisener> mOnQQmwssagereceiveLisener = new ArrayList<>(); public void addOnQQmwssagereceiveLisener(OnQQmwssagereceiveLisener lisener) {
mOnQQmwssagereceiveLisener.add(lisener);
} public void removeOnQQmwssagereceiveLisener(OnQQmwssagereceiveLisener lisener) {
mOnQQmwssagereceiveLisener.remove(lisener);
} public interface OnQQmwssagereceiveLisener {
public void onReiceive(QQMessage qq);
} @Override
public void run() {
super.run();
while (flag) {
try {
String utf = read.readUTF();
QQMessage message = new QQMessage();
QQMessage msg = (QQMessage) message.fromXml(utf);
if (msg != null) {
for (OnQQmwssagereceiveLisener lisner : mOnQQmwssagereceiveLisener)
lisner.onReiceive(msg);
}
} catch (IOException e) {
e.printStackTrace();
}
}
} public void connect() {
try {
if (client == null) {
client = new Socket(HOST, POST);
write = new DataOutputStream(client.getOutputStream());
read = new DataInputStream(client.getInputStream());
flag = true;
this.start();
Log.e(TAG, "connect: "+(write==null)+"---"+ (read == null));
}
} catch (Exception e) {
e.printStackTrace();
}
} public void disconnect() {
if (client != null) {
flag = false;
this.stop();
try {
read.close();
} catch (IOException e) {
e.printStackTrace();
} try {
write.close();
} catch (IOException e) {
e.printStackTrace();
} try {
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
} public void send(String xml) throws IOException {
write.writeUTF(xml);
write.flush();
} public void send(QQMessage qq) throws IOException {
write.writeUTF(qq.toXml());
write.flush();
}
}
闪屏界面的布局
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns: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秒钟进入登陆界面。一般来见,闪屏界面可以加载数据、获取版本号、更新版本等操作。这里没有做的那么复杂。
import com.example.huang.imsocket.R; public class SplashActivity extends AppCompatActivity { @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getSupportActionBar().hide(); //隐藏标栏
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); //全屏显示
setContentView(R.layout.activity_splash); new Handler().postDelayed(new Runnable() {
@Override
public void run() {
startActivity(new Intent(SplashActivity.this, LoginActivity.class));
finish();
}
}, 4000);
}
}
登陆界面的布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns: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>
登陆界面,创建和服务器的连接,向服务器发送登陆信息,接受服务器返回的信息。
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast; import com.example.huang.imsocket.R;
import com.example.huang.imsocket.bean.Myapp;
import com.example.huang.imsocket.bean.QQBuddyList;
import com.example.huang.imsocket.bean.QQMessage;
import com.example.huang.imsocket.bean.QQmessageType;
import com.example.huang.imsocket.core.QQConnection;
import com.example.huang.imsocket.service.IMService;
import com.example.huang.imsocket.util.ThreadUtils; import java.io.IOException; /**
* Created by huang on 2016/12/3.
*/
public class LoginActivity extends Activity {
private static final String TAG = "LoginActivity"; private EditText et_accoun;
private EditText et_pwd; private String accoun;
private QQConnection conn;
private QQConnection.OnQQmwssagereceiveLisener lisener = new QQConnection.OnQQmwssagereceiveLisener() {
@Override
public void onReiceive(final QQMessage qq) { final QQBuddyList list = new QQBuddyList();
final QQBuddyList list2 = (QQBuddyList) list.fromXml(qq.content);
if (QQmessageType.MSG_TYPE_BUDDY_LIST.equals(qq.type)) {
ThreadUtils.runUIThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getBaseContext(), "成功", Toast.LENGTH_SHORT).show(); Myapp.me = conn;
Myapp.username = accoun;
Myapp.account = accoun + "@qq.com"; Intent intent = new Intent(LoginActivity.this, contactActivity.class);
intent.putExtra("list", list2);
startActivity(intent); Intent data = new Intent(LoginActivity.this, IMService.class);
startService(data);
finish();
}
});
} else {
ThreadUtils.runUIThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getBaseContext(), "登陆失败", Toast.LENGTH_SHORT).show();
}
});
}
}
}; @Override
protected void onCreate(Bundle savedInstanceState) {
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(new Runnable() {
@Override
public void run() {
try {
conn = new QQConnection();
conn.addOnQQmwssagereceiveLisener(lisener);
conn.connect();
} catch (Exception e) {
e.printStackTrace();
}
}
});
} public void sendmessage(View view) {
accoun = et_accoun.getText().toString().trim();
final String password = et_pwd.getText().toString().trim();
Log.i(TAG, "sendmessage: " + accoun + "#" + password);
ThreadUtils.runINThread(new Runnable() {
@Override
public void run() {
QQMessage message = new QQMessage();
message.type = QQmessageType.MSG_TYPE_LOGIN;
message.content = accoun + "#" + password;
String xml = message.toXml();
if (conn != null) {
try {
conn.send(xml);
} catch (IOException e) {
e.printStackTrace();
}
}
}
});
} @Override
protected void onDestroy() {
super.onDestroy();
conn.removeOnQQmwssagereceiveLisener(lisener);
}
}
好友列表界面
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns: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>
好友列表及时收到从哪个服务其发挥的好友更新信息,点击条目跳到聊天界面。
import android.app.Activity;
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast; import com.example.huang.imsocket.R;
import com.example.huang.imsocket.bean.Myapp;
import com.example.huang.imsocket.bean.QQBuddyList;
import com.example.huang.imsocket.bean.QQMessage;
import com.example.huang.imsocket.bean.QQmessageType;
import com.example.huang.imsocket.core.QQConnection;
import com.example.huang.imsocket.util.ThreadUtils; import java.util.ArrayList; import butterknife.Bind;
import butterknife.ButterKnife;
import cn.itcast.server.bean.QQBuddy; /**
* Created by huang on 2016/12/5.
*/ public class contactActivity extends Activity {
private static final String TAG = "contactActivity";
@Bind(R.id.tv_title)
TextView tv_title;
@Bind(R.id.lv_contact)
ListView lv_contact;
private QQBuddyList list;
private ArrayList<QQBuddy> BuddyList = new ArrayList<>();
private ArrayAdapter adapter = null; private QQConnection.OnQQmwssagereceiveLisener listener = new QQConnection.OnQQmwssagereceiveLisener() {
@Override
public void onReiceive(QQMessage qq) {
if (QQmessageType.MSG_TYPE_BUDDY_LIST.equals(qq.type)) {
QQBuddyList qqlist = new QQBuddyList();
QQBuddyList qqm = (QQBuddyList) qqlist.fromXml(qq.content);
BuddyList.clear();
BuddyList.addAll(qqm.buddyList);
ThreadUtils.runUIThread(new Runnable() {
@Override
public void run() {
saveAndNotify();
}
});
}
}
}; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_contact);
ButterKnife.bind(this);
Myapp.me.addOnQQmwssagereceiveLisener(listener);
Intent intent = getIntent();
list = (QQBuddyList) intent.getSerializableExtra("list");
BuddyList.clear();
BuddyList.addAll(list.buddyList);
saveAndNotify();
} @Override
protected void onDestroy() {
super.onDestroy();
Myapp.me.removeOnQQmwssagereceiveLisener(listener);
} private void saveAndNotify() {
if (BuddyList.size() < 1) {
return;
}
if (adapter == null) {
adapter = new ArrayAdapter<QQBuddy>(getBaseContext(), 0, BuddyList) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
viewHolder holder;
if (convertView == null) {
convertView = View.inflate(getContext(), R.layout.item_contacts, null);
holder = new viewHolder(convertView);
convertView.setTag(holder);
} else {
holder = (viewHolder) convertView.getTag();
}
QQBuddy qqBuddy = 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);
}
return convertView;
}
};
lv_contact.setAdapter(adapter); lv_contact.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
QQBuddy qqbuddy = BuddyList.get(position);
if (Myapp.username.equals(qqbuddy.account + "")) {
Toast.makeText(getBaseContext(), "不能和自己聊天", Toast.LENGTH_SHORT).show();
} else {
Intent intent = new Intent(contactActivity.this, ChatActivity.class);
intent.putExtra("account", qqbuddy.account + "");
intent.putExtra("nick", qqbuddy.nick + "");
startActivity(intent);
}
}
});
} else {
adapter.notifyDataSetChanged();
}
} static class viewHolder {
@Bind(R.id.iv_contact)
ImageView iv_contact;
@Bind(R.id.tv_nick)
TextView tv_nick;
@Bind(R.id.tv_account)
TextView tv_account;
public viewHolder(View view) {
ButterKnife.bind(this, view);
}
}
}
聊天界面
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns: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>
聊天界面中消息接收和消息发送都需要及时更新列表。
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast; import com.example.huang.imsocket.R;
import com.example.huang.imsocket.bean.Myapp;
import com.example.huang.imsocket.bean.QQMessage;
import com.example.huang.imsocket.bean.QQmessageType;
import com.example.huang.imsocket.core.QQConnection;
import com.example.huang.imsocket.util.ThreadUtils; import java.io.IOException;
import java.util.ArrayList; import butterknife.Bind;
import butterknife.ButterKnife;
import butterknife.OnClick; /**
* Created by huang on 2016/12/3.
*/
public class ChatActivity extends Activity {
private static final String TAG = "ChatActivity";
@Bind(R.id.tv_name)
TextView tv_name;
@Bind(R.id.lv_chat)
ListView lv_chat;
@Bind(R.id.et_sms)
EditText et_sms; private ArrayAdapter<QQMessage> adapter = null;
private ArrayList<QQMessage> list = new ArrayList<>();
private String account; @OnClick(R.id.btn_send)
public void send(View view) {
String sendsms = et_sms.getText().toString().trim();
if (TextUtils.isEmpty(sendsms)) {
Toast.makeText(this, "消息不能为空", Toast.LENGTH_SHORT).show();
return;
}
et_sms.setText("");
final QQMessage qq = new QQMessage();
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(new Runnable() {
@Override
public void run() {
try {
Myapp.me.send(qq);
} catch (IOException e) {
e.printStackTrace();
}
}
});
} private QQConnection.OnQQmwssagereceiveLisener listener = new QQConnection.OnQQmwssagereceiveLisener() {
@Override
public void onReiceive(final QQMessage qq) {
if (QQmessageType.MSG_TYPE_CHAT_P2P.equals(qq.type)) {
ThreadUtils.runUIThread(new Runnable() {
@Override
public void run() {
list.add(qq);
setAdapteORNotify();
}
});
}
}
}; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_chat);
ButterKnife.bind(this);
Myapp.me.addOnQQmwssagereceiveLisener(listener);
Intent intent = getIntent();
account = intent.getStringExtra("account");
String nick = intent.getStringExtra("nick");
tv_name.setText("和" + nick + "聊天中......");
setAdapteORNotify();
} @Override
protected void onDestroy() {
super.onDestroy();
Myapp.me.removeOnQQmwssagereceiveLisener(listener);
} private void setAdapteORNotify() {
if (list.size() < 1) {
return;
}
if (adapter == null) {
adapter = new ArrayAdapter<QQMessage>(this, 0, list) { @Override
public int getViewTypeCount() {
return 2;
} @Override
public int getItemViewType(int position) {
QQMessage msg = list.get(position);
long fromId = Long.parseLong(Myapp.username);
if (fromId == msg.from) {
return 0;
}
return 1;
} @Override
public View getView(int position, View convertView, ViewGroup parent) {
int type = getItemViewType(position);
if (type == 0) {
viewHolder holder1 = null;
if (convertView == null) {
holder1 = new viewHolder();
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();
}
QQMessage qqMessage = list.get(position);
holder1.tv_send_time.setText(qqMessage.sendTime);
holder1.tv_send.setText(qqMessage.content);
return convertView;
} else if (type == 1) {
viewHolder holder2 = null;
if (convertView == null) {
holder2 = new viewHolder();
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();
}
QQMessage qqMessage = list.get(position);
holder2.tv_receive_time.setText(qqMessage.sendTime);
holder2.tv_receive.setText(qqMessage.content);
return convertView;
}
return convertView;
}
};
lv_chat.setAdapter(adapter);
} else {
adapter.notifyDataSetChanged();
} if (lv_chat.getCount() > 0) {
lv_chat.setSelection(lv_chat.getCount() - 1);
}
} class viewHolder {
TextView tv_send_time;
TextView tv_send;
TextView tv_receive_time;
TextView tv_receive;
}
}
最后可以添加一个服务当程序退出时,接受消息。
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.widget.Toast; import com.example.huang.imsocket.bean.Myapp;
import com.example.huang.imsocket.bean.QQMessage;
import com.example.huang.imsocket.core.QQConnection;
import com.example.huang.imsocket.util.ThreadUtils; /**
* Created by huang on 2016/12/7.
*/ public class IMService extends Service {
private QQConnection.OnQQmwssagereceiveLisener lisener = new QQConnection.OnQQmwssagereceiveLisener() {
@Override
public void onReiceive(final QQMessage qq) {
ThreadUtils.runUIThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getBaseContext(), "收到好友消息: " + qq.content, Toast.LENGTH_SHORT).show();
}
});
}
}; @Override
public IBinder onBind(Intent intent) {
return null;
} @Override
public void onCreate() {
super.onCreate();
Toast.makeText(getBaseContext(), "服务开启", Toast.LENGTH_SHORT).show();
Myapp.me.addOnQQmwssagereceiveLisener(lisener);
} @Override
public void onDestroy() {
Myapp.me.removeOnQQmwssagereceiveLisener(lisener);
super.onDestroy();
}
}
Activity和Service节点配置,以及相应的权限。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.huang.imsocket"> <uses-permission android: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"> <activity android:name="com.example.huang.imsocket.activity.SplashActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" /> <category android: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> <service android:name=".service.IMService" />
</application> </manifest>
android环境下的即时通讯的更多相关文章
- Android基于XMPP的即时通讯3-表情发送
这篇博文主要讲表情发送的一些东西. 参考:Android基于XMPP的即时通讯1-基本对话 1.准备好资源文件 采用的是emoji的表情,我打包好了,下载地址:http://files.cnblogs ...
- Android基于XMPP的即时通讯2-文件传输
本文是在上一篇博文Android基于XMPP的即时通讯1-基本对话的基础上,添加新的功能,文件传输 1.初始化文件传输管理类 public static FileTransferManager get ...
- cocos2d-x 在android环境下开发遇到的一些bug
今天在弄一个关于android环境下解析xml的东东,遇到了2个比较麻烦问题 1.android的apk下文件是压缩文件,io.open模式无法读取到数据的, 解决思路就是: CCFileUtils: ...
- Android 环境下编译FFmpeg
Android 环境下编译FFmpeg 开发环境:Ubuntu 12.04.2 LTS , android-sdk-linux, android-ndk-r8e 一 .X264 编译 1. X2 ...
- Android基于XMPP的即时通讯1-基本对话
闲暇之余,自己写了个简单的即时通讯,基于OpenFire服务器平台. 整个项目包括两个部分,一个是服务器端,一个是android手机端: 一.关于服务器端没什么好说的,下载安装配置即可 推荐下载带ja ...
- android环境下两种md5加密方式
在平时开发过程中,MD5加密是一个比较常用的算法,最常见的使用场景就是在帐号注册时,用户输入的密码经md5加密后,传输至服务器保存起来.虽然md5加密经常用,但是md5的加密原理我还真说不上来,对md ...
- 在高通平台Android环境下编译内核模块【转】
本文转载自:http://blog.xeonxu.info/blog/2012/12/04/zai-gao-tong-ping-tai-androidhuan-jing-xia-bian-yi-nei ...
- Android基于xmpp的即时通讯应用
xmpp是一个通信协议.因为这是个开放的协议,为了节俭开发成本,很多即时应用都采用了这个协议.Android上最常用的组合asmack +openfire.Asmack是smack的android版, ...
- cocos2d-x 3.x丨搭建Android环境下的开发环境
所需要的一些工具软件: 1.JDK 官网下载地址:http://www.oracle.com/ttechnetwork/java/javase/downloads/index.html 2.Andr ...
随机推荐
- [转]Java常用工具类集合
转自:http://blog.csdn.net/justdb/article/details/8653166 数据库连接工具类——仅仅获得连接对象 ConnDB.java package com.ut ...
- useful Ansible commands
This article includes some useful Ansible commands. I will try to write blogs by English. You may wa ...
- No result defined for action com.lk.IndexAction and result success
意图访问一个 /es/index.action 竟然出现: [SAE ] ERROR [05-11 13:54:32] [http-80-5] com.opensymphony.xwork2.util ...
- Android开发之Android Material Design Toolbar自定义随笔
一.自定义Toolbar的menu: 在menu下新建menu.xml文件,自定义menu的样式: <menu xmlns:android="http://schemas.androi ...
- 浅谈JavaScript中forEach与each
forEach是ES5中操作数组的一种方法,主要功能是遍历数组,例如: var arr = [1,2,3,4]; arr.forEach(alert); 等价于: var arr = [1, 2, 3 ...
- .Net中的RealProxy实现AOP
序言 这个AOP要从我们公司的一个事故说起,前段时间公司的系统突然在乌云中出现,数据被泄露的一览无余,乌云上显示是SQL注入攻击.呵,多么贴近生活的一个露洞,可谓是人尽皆知啊.然而却华丽丽的给拉我们一 ...
- 【Win10 应用开发】集成文件打开选择器
有朋友给老周提出建议:老周,能不能在写博客时讲一下有深度的小故事?技术文章谁不会写.讲一下对人生有启发性的故事会更好. 哎呀,这要求真是越来越高了.好吧,尽量吧,如果有小故事的话,老周在就每次写博客时 ...
- ASP.NET MVC5+EF6+EasyUI 后台管理系统--系统模块部分图
系统日志,系统异常,组织架构等
- 1. 使用Filter 作为控制器
最近整理一下学习笔记,并且准备放到自己的博客上.也顺便把Struts2 复习一遍 1. MVC 设计模式概览 实现 MVC(Model.View.Controller) 模式的应用程序由 3 大部分构 ...
- YYModel 源码解读(二)之YYClassInfo.h (1)
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END 为了兼容Swift 中的 ? 和 ! oc 在6.3引入了两个新的类型注释:__nullable和__non ...