这两天自己写了一个多人聊天室,服务器是用Java写的,烂的一批,不想写了,但是安卓客户端上遇到了很多问题还是有必要说一下。
这个APP客户端是基于我自己写的服务器来运行的,不过有需要的伙伴可以根据自己的需要进行一些更改,可以把它做成一个手机套接字工具。
主要功能:通过套接字进行远程通信(异步收发)。
附加功能:每次登陆可以为自己添加个性昵称,通过对服务器发来的消息进行不同的操作。
有了功能,我们就来实现它。
因为是客户端,所以我们这里只需要使用一个套接字连接服务器就可以了,下面是代码
package com.example.a9042.socket;
import android.view.View;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import com.example.a9042.chatclint.MainWindow;
/*这个类用来进行套接字操作,继承父类Thread*/
public class MyScoket extends Thread{
private Socket mySocket;//主要套接字
private String ip="118.24.107.35";//目标服务器IP
private int port=11111;//目标服务器端口
private MainWindow mw;//主窗口实例,用来实现主类的一些方法
private DataOutputStream pw;//输出流,用来发送消息
private Receive rec;//接收类的实例化对象,主要用来启动接收服务器消息线程
/*构造方法,传递主窗口实例*/
public MyScoket(MainWindow w){
this.mw=w;
}
/*子线程run方法*/
public void run(){
if(mySocket==null){
//mw.showToast("正在连接服务器");
while(true){
try {
mySocket=new Socket(ip,port);//通过Socket构造器将服务器IP和端口绑定到套接字
/*如果套接字连接上了*/
if(mySocket.isConnected()){
mw.showToast("登陆成功");//通过弹出消息提示登陆服务器成功
rec=new Receive(this.mySocket,mw);//通过receive类构造器将套接字和主窗口传递给rec对象
rec.start();//启动接收服务器消息线程
pw=new DataOutputStream(mySocket.getOutputStream());//绑定输出流
break;
}
} catch (IOException e) {
//mw.showToast("连接服务器失败");
mw.heart(mySocket);
}
}
}
}
/*向服务器发送消息的方法*/
public void send(final String str){
new Thread(){
@Override
public void run(){
try {
pw.writeUTF(mw.name+":"+str);
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
}
这里,其实第40行的heart()方法也应该放到MySocket类里面的,可是因为这个方法在其他类中也要使用,就把这个方法放在了主类中。
这个heart()方法很重要,因为对于安卓来说,因为智能手机使用的是移动无线网络,所以我们必须使用一种方法每间隔一段时间就要确定一下客户端和服务器是否连接。这个方法就是”心跳机制“。
心跳机制是保证安卓客户端与服务器通过套接字保持长连接的一种机制,大概意思是:客户端每间隔一段时间向服务器发送一条信息,服务器收到后会反馈一个信息给客户端。进行了这么一个操作以后,客户端就可以与服务器保持长链接了。
异步收发消息,其实就是利用一个子线程来读取消息,用另一个线程来发送消息。发送消息在上面的代码已经实现了,下面是读取消息的代码:
package com.example.a9042.socket;
import com.example.a9042.chatclint.MainWindow;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
/*这个类是用来接收服务器消息的*/
public class Receive extends Thread{
private DataInputStream br;//输入流
private MainWindow mw;//获取主窗口实例
private Socket so;//获取套接字实例
/*构造方法,主要是用来传递套接字和主窗口*/
public Receive(Socket s, MainWindow w){
mw=w;
so=s;
}
/*子线程执行接收方法*/
public void run(){
try {
String str;//用来保存接收到的信息
br=new DataInputStream(so.getInputStream());//将输入流绑定为套接字输入流
while (true) {//子线程持续接收服务器发来的消息
str = br.readUTF();//读取服务器发来的消息
mw.getMessage(str);//将服务器发来的消息填充到TextView
}
} catch (IOException e) {//接收消息抛出异常
mw.showToast("接受信息异常");
mw.heart(so);
}
}
}
读取消息的代码比较简单的,除了心跳机制,其他的都是在Java套接字中的常规操作,不多说了。
package com.example.a9042.chatclint;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.method.ScrollingMovementMethod;
import android.view.Gravity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.example.a9042.socket.MyScoket;
import com.example.a9042.socket.Receive;
import java.io.IOException;
import java.net.Socket;
public class MainWindow extends AppCompatActivity implements View.OnClickListener {
private Handler myHnadler; //myHnadler用于在子线程中对UI的修改
public String name; //用户名
private AlertDialog dialog; //显示弹窗
@SuppressLint("HandlerLeak")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_window);
showInputName(); //显示弹窗
myHnadler=new Handler(){
@Override
public void handleMessage(Message mes){
TextView hTest = findViewById(R.id.HistoryTest);
TextView OnLine =findViewById(R.id.online);
if(mes.what==0x0002)
hTest.append(mes.obj.toString());
if(mes.what==0x0001)
OnLine.setText("当前在线人数:"+mes.obj.toString());
}
};
Button Send = findViewById(R.id.Send);//获得发送按钮实例
TextView hText=findViewById(R.id.HistoryTest);//获得显示区域实例
hText.setGravity(Gravity.BOTTOM);//将TextView设置为从底部填写
Send.setOnClickListener(this);//为按钮绑定监听事件
}
MyScoket ms=new MyScoket(this);//实例化MySocket对象
/*按钮绑定监听事件逻辑*/
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.Send:{
EditText sTest = findViewById(R.id.Message);//获取编辑框实例
if(sTest.getText().toString().length()>0) {
ms.send(sTest.getText().toString());//如果编辑框有内容,就将这个内容发送到服务器上
sTest.setText("");//编辑框置空
}
}break;
}
}
/*显示特定内容的弹出消息*/
public void showToast(final String str){
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainWindow.this,str,Toast.LENGTH_SHORT).show();
}
});
}
/*根据参数的标志对UI进行相应的修改*/
public void getMessage(final String str){
switch (str.substring(0, 3)) {
case "mes"://如果参数标志为mes,则在textview追加填写str
myHnadler.sendMessage(Message.obtain(myHnadler,0x0002,str.substring(3)));
break;
case "per"://如果参数标志为pre,则更新在线人数
myHnadler.sendMessage(Message.obtain(myHnadler,0x0001,str.substring(3)));
}
}
/*心跳方法,用来保证套接字长连接,其实就是向服务器发送一个检验位*/
public void heart(Socket s){
if(s!=null){
try {
s.sendUrgentData(0xff);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/*弹窗显示方法*/
public void showInputName(){
AlertDialog.Builder builder = new AlertDialog.Builder(this); //先得到构造器
View view1=View.inflate(this,R.layout.input_name,null);
Button getName=view1.findViewById(R.id.get_name);//获得弹窗按钮实例
final EditText inputName=view1.findViewById(R.id.input_name);//获得弹窗编辑框实例
builder.setTitle("你的名字")//设置弹窗标题
.setView(view1)
.create();
final AlertDialog show=builder.show();//显示弹窗
/*为弹窗按钮添加点击事件*/
getName.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
name=inputName.getText().toString();//将name赋值为编辑框内容
ms.start();//开始尝试连接服务器
show.dismiss();//关闭弹窗
}
});
}
}
看着注释,难度应该不算大,这里有两个点需要注意一下,一个是上面说的心跳机制,就在97行的代码处;另一个是非常重要的一点:在子线程更新UI的问题。
大家知道线程分为主线程(一个)和子线程(多个),假如在子线程里面更新UI会有什么后果呢?是的,这就会发生所谓的线程阻塞。在这里,我们就需要使用Handler 来进行在子线程中更新UI了。
Handler 是什么呢?Handler 其实就是一套消息处理机制,我们可以用它来发消息,也可以用它来处理消息。
这时候可能会问,消息处理机制和更新UI有什么关系呢?其实不然。我们可以在主线程里面实例化这个Handler ,使其只在页面主线程中运行,然后如果有哪个子线程需要去修改UI,那么就向主线程的Handler 发消息,Handler 收到消息后,对消息进行处理,并作出相应的反应,从而达到更新UI的效果。
Handler 的作用大致意思上是这些,如果想要深入的了解Handler 在Android中的应用,敬请自己百度。
https://gitee.com/pangyuworld/simple_chat_small_program.git