您的位置:首页 > 移动开发 > Android开发

Android即时通讯--仿QQ即时聊天:(三)核心代码抽取与登录逻辑

2016-02-27 11:56 627 查看
Android即时通讯的主要功能逻辑有四个:APP1登录-->服务器返回好友列表,APP1发送聊天消息给APP2,服务器转发聊天消息给APP2。其功能逻辑图如下



1、核心代码抽取

在整个项目中都要用到连接服务器、断开连接、发送消息、接收消息这四个功能,所以在项目中为了保证代码的重用性,因此需要封装一个核心代码,用来完成整个项目的连接服务器,断开连接,发送消息以及接收消息的功能。注意:由于服务器与客户端之间要经常进行数据交互,所以这里创建了一个OnMessageListener监听器的接口,服务器与客户端之间进行数据交互的时候产生的监听器全部存放到一个List集合当中,一旦客户端接收到来自服务器的消息,在等待线程中就会调用监听器集合当中的所有监听器的onReceive方法,并将接收到的消息转发给每一个监听器。代码如下

/**
* 对核心代码进行抽取,一共有四个公共的方法,分别是连接,断开连接,发送消息,接收消息
*
* @author ZHY
*
*/
public class QQConnection {

private String host = "";
private int port = 8090;
Socket client;
private DataInputStream reader;
private DataOutputStream writer;
private WaitThread waitThread;
public boolean isWaiting;

/**
* new出QQConnection对象的时候初始化IP地址和端口
*
* @param host
* @param port
*/
public QQConnection(String host, int port) {
super();
this.host = host;
this.port = port;
}

/**
* 创建与服务器之间的连接
*
* @throws IOException
* @throws UnknownHostException
*/
public void connect() throws UnknownHostException, IOException {
// 创建连接
client = new Socket(host, port);
reader = new DataInputStream(client.getInputStream());// 接收消息的时候用
writer = new DataOutputStream(client.getOutputStream());// 发送消息的时候用
// 创建连接的时候开启等待线程,一直等待着接收消息
isWaiting = true;
waitThread = new WaitThread();
waitThread.start();

}

/**
* 断开与服务期间的连接
*
* @throws IOException
*/
public void disConnect() throws IOException {
// 关闭连接就是释放资源
client.close();
reader.close();
writer.close();
isWaiting = false;
}

/**
* 发送xml消息
*
* @param xml
* @throws IOException
*/
public void sendMessage(String xml) throws IOException {
// 发送消息要用到输入输出流,讲流作为类的成员变量,在创建连接的时候初始化,断开连接的时候释放资源
// 发送消息其实就是把消息写出去
writer.writeUTF(xml);

}

/**
* 发送java对象消息
*
* @param xml
* @throws IOException
*/
public void sendMessage(QQMessage msg) throws IOException {
writer.writeUTF(msg.toXML());
}

/**
* 等待线程 接收消息,由于不知道消息什么时候到达,需要一直等待着,等待消息的到达
*
* @author ZHY
*
*/
private class WaitThread extends Thread {

@Override
public void run() {
super.run();
while (isWaiting) {
// 接收消息其实就是将消息读取到
try {
String xml = reader.readUTF();// 获取消息
// 将消息转成Java对象
QQMessage msg = new QQMessage();
msg = (QQMessage) msg.fromXML(xml);
System.out.println(msg.content);
// 这里接收到消息,根据消息中保存的type字段来处理登录,获取联系人列表,登出等操作,将这一部分操作抽取出来一个接口,类似于按钮的点击事件那样,接收到消息就做操作
/*
* 接收到消息之后,依次调用每个监听器的onReceive方法
*/
for (OnMessageListener listener : listeners) {
listener.onReveive(msg);
}

} catch (IOException e) {
e.printStackTrace();
}

}

}
}

// 服务器会经常给客户端发送消息,客户端会有不同的消息到来,所以新建一个监听器的集合,往集合中添加一个监听器就调用一次onReveive方法,
/*
* 集合中有就调用,集合中没有就不调用
*/
private List<OnMessageListener> listeners = new ArrayList<OnMessageListener>();

public void addOnMessageListener(OnMessageListener listener) {
listeners.add(listener);
}

public void removeOnMessageListener(OnMessageListener listener) {
listeners.remove(listener);
}

/**
* 消息的监听器接口,当有消息到来的时候就调用一次onReceive方法
*
* @author ZHY
*
*/
public static interface OnMessageListener {
public void onReveive(QQMessage msg);
}
}


2、登录逻辑

要实现登录,需要拼接XML格式的字符串

<QQMessage>
<type>login</type>
<sender>0</sender>
<senderNick></senderNick>
<senderAvatar>1</senderAvatar>
<receiver>0</receiver>
<content>账号#密码</content>
<sendTime>05-09 13:01:46</sendTime>
</QQMessage>
根据这个XML协议,构建实体类QQMessage,实现数据交互,使用工具包XStream完成XML与Java对象之间的相互转换。

**
* 根据协议定义即时通讯的实体类,用来进行数据交互
*
* @author ZHY
*
*/
public class QQMessage extends ProtocalObj {
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 = MyTime.geTime(); // 发送时间
}
由于访问网络的操作需要在子线程中完成,而更新UI界面的操作需要在主线程中完成,所以本项目需要时刻切换子线程与主线程,为了方便,构建了一个线程的工具类,用来控制程序在主线程或者子线程中运行。

public class ThreadUtils {

/**
* 运行在子线程(发送数据)
*
* @param r
*/
public static void runInSubThread(Runnable r) {
new Thread(r).start();
}

private static Handler handler = new Handler();

/**
* 运行在主线程(UI 线程 更新界面)
*
* @param r
*/
public static void runInUiThread(Runnable r) {
handler.post(r);// Message-->handler.sendMessage-->handleMessage()
// 主线程-->r.run();
}
}
以上工作准备完成之后,来到登录页面,使用Xutils工具包中的ViewInject注解代替findViewByID,获取布局中的每一个控件;使用onClick注解代替按钮的点击事件。希望没有用过的小伙伴不要惊慌,用用就好了,挺好用的。

程序一进入登录页面就根据服务器的IP地址和端口号与服务器建立连接,然后时刻监听服务器返回回来的数据,此处的监听器用来监听服务器返回回来的好友列表信息,如果返回的数据类型是好友列表,那么在客户端处就保存一个长连接,是的整个项目共享一个连接。这里我使用的是Application,因为在Android应用程序中,application是所有的activity,service,context都能访问到的,所以需要在整个项目级别共享数据的时候可以考虑使用application。当点击登录的时候,将用户输入的账号和密码获取到,根据协议的要求封装成QQMessage对象,发送给服务器。服务器获取客户端发送的消息,进行解析与匹配,匹配成功给客户端返回好友列表,这里将接收到的好友列表信息也保存到了application中,然后跳转到主页完成登录。在登录的activity销毁的时候,解除消息的监听。代码如下

/**
* 登录逻辑
*
* @author ZHY
*
*/
public class LoginActivity extends Activity {

@ViewInject(R.id.account)
private EditText account;
@ViewInject(R.id.password)
private EditText password;
private String accountStr;// 账号
private String passwordStr;// 密码
QQConnection conn;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
ViewUtils.inject(this);// 使注解生效
/*
* 登录的逻辑:首先连接服务器,获取用户输入的账号和密码,讲账户和密码发送给服务器;服务器做一些验证操作,将验证结果返回给客户端,
* 客户端再进行接收消息的操作
*/
// 与网络相关的操作要放在子线程中进行
ThreadUtils.runInSubThread(new Runnable() {

public void run() {
try {
conn = new QQConnection("192.168.0.148", 8090);// Socket
conn.connect();// 建立连接
// 建立连接之后,将监听器添加到连接里面
conn.addOnMessageListener(listener);// 时刻监听服务器返回回来的数据
} catch (Exception e) {
e.printStackTrace();
}

}
});

}

@Override
protected void onDestroy() {
super.onDestroy();
// activity销毁的时候取消监听
conn.removeOnMessageListener(listener);
}

/**
* 按钮的点击事件
*
* @param view
*/
@OnClick(R.id.login)
public void login(View view) {
// Toast.makeText(getBaseContext(), "haha", 0).show();
accountStr = account.getText().toString().trim();
passwordStr = password.getText().toString().trim();
// 获得输入的账号和密码,发送
/*
* 登录的协议 <QQMessage> <type>login</type> <sender>0</sender>
* <senderNick></senderNick> <senderAvatar>1</senderAvatar>
* <receiver>0</receiver> <content>1001#123</content> <sendTime>05-09
* 13:01:46</sendTime> </QQMessage>
*/
/*
* 用户登录的时候获取账号和密码,封装成Java对象,字段包括类型和内容,内容是用户名和密码之间的拼接
*/

// 封装好Java对象,讲数据写到服务器,在子线程中运行
ThreadUtils.runInSubThread(new Runnable() {

public void run() {
try {
QQMessage msg = new QQMessage();
msg.type = QQMessageType.MSG_TYPE_LOGIN;
msg.content = accountStr + "#" + passwordStr;
conn.sendMessage(msg);
} catch (IOException e) {
e.printStackTrace();
}
}
});

}

/**
* 登录成功之后返回好友列表 <QQMessage> <type>buddylist</type> <from>0</from>
* <fromNick></fromNick> <fromAvatar>1</fromAvatar> <to>0</to>
* <content>{"
* ;buddyList":[{"account":101,"nick":"QQ
* 1","avatar":0}]}</content> <sendTime>05-21
* 21:51:39</sendTime> </QQMessage>
*/
// 客户端这里要完成接收的逻辑
// 使用接收消息的监听器
private OnMessageListener listener = new OnMessageListener() {

public void onReveive(final QQMessage msg) {
System.out.println(msg.toXML());
// 接收服务器返回的结果.处理数据的显示,运行在主线程中
ThreadUtils.runInUiThread(new Runnable() {

public void run() {
if (QQMessageType.MSG_TYPE_BUDDY_LIST.equals(msg.type)) {
// 登录成功,返回的数据是好友列表
/**
* <QQMessage>
* <content>{"buddyList":[{"account
* ":101,"nick":"金莉
* 1","avatar":0}]}</content>
* <type>buddylist</type> <sendTime>02-26
* 15:18:19</sendTime> <fromNick></fromNick>
* <from>0</from> <to>0</to> <fromAvatar>1</fromAvatar>
* </QQMessage>
*/
// 有用的信息是content的json串
Toast.makeText(getBaseContext(), "登录成功 !", 0).show();
// 将连接conn保存到application中,作为一个长连接存在,这样在其他的activity,server中都能拿到这个连接,保证了项目连接的唯一性
// 新建一个application类,给出get,set方法,使用application可以在整个项目中共享数据
ImApp app = (ImApp) getApplication();
// 保存一个长连接
app.setMyConn(conn);
// 保存好友列表的json串
app.setBuddyListJson(msg.content);
// 保存当前登录用户的账号
app.setMyAccount(Long.parseLong(accountStr));
// 打开主页
Intent intent = new Intent();
intent.setClass(getBaseContext(), MainActivity.class);
intent.putExtra("account", accountStr);
startActivity(intent);

finish();

} else {
Toast.makeText(getBaseContext(), "登录失败", 0).show();
}
}
});

}
};

}

OK,完成了登录的逻辑,接下来的好友列表的处理以及聊天模块的处理就大同小异了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: