共计 9535 个字符,预计需要花费 24 分钟才能阅读完成。
服务器端
我在本机搭建了 Tomcat 服务器作为 Web 应用的容器,使用 Servlet 来实现聊天的业务逻辑。
由于使用到了 JSON 数据,所以需要在相应的 Web 应用项目中的 WEB-INF\lib 文件夹下导入 6 个 jar 包定义了一个 DiffServlet
package com.chatroom;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
@WebServlet(“/DiffServlet”)
public class DiffServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private static JSONArray messageList = new JSONArray();
public DiffServlet() {
super();
// TODO Auto-generated constructor stub
}
public void init()throws ServletException{ // 初始化,创建一个 JSON 对象列表,
if(messageList.isEmpty()) {// 用于存储聊天记录
JSONObject first = new JSONObject();
first.put(“name”, “Server”);
first.put(“message”, “Server Gets Ready”);
messageList.add(first);
}
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 对 http 请求的 Get 方法进行响应,为客户端返回所有聊天记录
request.setCharacterEncoding(“UTF-8”);
response.setContentType(“application/json;charset=UTF-8”);
PrintWriter out = response.getWriter();
out.write(messageList.toString());
out.flush();
out.close();
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 对 http 请求的 Post 方法进行响应,将客户端发来的信息添加到聊天记录列表当中
request.setCharacterEncoding(“UTF-8”);
response.setContentType(“application/json;charset=UTF-8”);
String username = request.getParameter(“name”);
String message = request.getParameter(“message”);
JSONObject obj = new JSONObject();
obj.put(“name”, username);
obj.put(“message”, message);
messageList.add(obj);
}
}
在 web.xml 对该 Servlet 进行注册
DiffServlet
com.ChatRoom.DiffServlet
DiffServlet
/ChatRoom/DiffServlet
客户端
Android 客户端包含两个 Activity,一个是登录界面 MainActivity,一个是聊天界面 ChatRoom。所有的活动要在 AndroidManifest.xml 文件中进行注册,为了使用户在应用中使用输入法使界面的背景图不会被压缩,所以在每个活动标签中加入
android:windowSoftInputMode=”adjustPan”
这样一条代码,就可以解决背景图片被压缩的问题了。
由于项目中要使用网络,所以要在 AndroidManifest.xml 文件中对授权对网络的访问,添加如下代码:
亨达代理申请 http://www.kaifx.cn/broker/ha…
注意
如果我们使用的是 http 协议的域名,使用 Android Studio 开发的应用可能会与主机连接不上,出现这种情况可参考 6 号楼下的大懒喵的博客 OkHttp 请求 http 链接失败的问题
登录界面:MainActivity
布局文件:
android:orientation=”vertical”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:background=”@drawable/picture”>
android:id=”@+id/et_name”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:layout_gravity=”center”
android:gravity=”center”
android:hint=” 用户名 ”/>
android:id=”@+id/btn_cnt”
android:layout_width=”100dp”
android:layout_height=”wrap_content”
android:background=”#07D0F3″
android:gravity=”center”
android:layout_gravity=”center”
android:text=” 连接 ”
android:textColor=”#ffffff” />
源代码:
package com.example.chatroom;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Button btn_cnt;
private EditText et_name;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn_cnt = (Button) findViewById(R.id.btn_cnt);
et_name = findViewById(R.id.et_name);
btn_cnt.setOnClickListener(MainActivity.this);
}
public void onClick(View view) {
String name = et_name.getText().toString();
if (“”.equals(name)) {
Toast.makeText(this, “ 请输入用户名:”, Toast.LENGTH_SHORT).show();
// 如果输入的用户名为空的话,那么下端会出现提示
} else {
Intent intent=new Intent(MainActivity.this,ChatRoom.class);
intent.putExtra(“username”,name);
startActivity(intent);
}
}
}
聊天界面:ChatRoom
布局文件:
xmlns:app=”http://schemas.android.com/apk/res-auto”
android:orientation=”vertical”
android:background=”#d8e0e8″
android:layout_width=”match_parent”
android:layout_height=”match_parent”>
android:id=”@+id/msg_recycler_view”
android:layout_width=”match_parent”
android:layout_height=”0dp”
android:layout_weight=”1″
android:background=”@drawable/background” />
android:orientation=”horizontal”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”>
android:id=”@+id/input_text”
android:layout_width=”0dp”
android:layout_height=”match_parent”
android:layout_weight=”1″
android:background=”#ffffff”
/>
android:id=”@+id/send”
android:layout_width=”wrap_content”
android:layout_height=”50dp”
android:background=”#07D0F3″
android:text=” 发送 ”
android:textColor=”#ffffff” />
源代码:
package com.example.chatroom;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import org.json.JSONArray;
import org.json.JSONObject;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
public class ChatRoom extends AppCompatActivity implements View.OnClickListener{
private List msgList = new ArrayList<>();
private EditText inputText;
private Button send;
private RecyclerView msgRecyclerView;
private MsgAdapter adapter;
boolean isRunning = false;
private boolean isSend=false;
private String myName;
private String responseData;
private int curr; // 当前显示的消息条数
private int jsonLen; // 获取到的 json 列表长度
private Handler handler = new Handler(Looper.myLooper()){
// 获取当前进程的 Looper 对象传给 handler
// 在目前的 Android 开发中,子线程不能改变 UI,
// 所以子线程要对 UI 进行操作需要交给一个 Handler 对象来执行
@Override
public void handleMessage(Message message){
String message_Name = message.getData().getString(“name”);
String message_msgC = message.getData().getString(“msgContent”);
if(!message_msgC.equals(“”)){
if(message_Name.equals(myName))
addNewMessage(message_msgC, Msg.TYPE_SENT);
else
addNewMessage(message_msgC,Msg.TYPE_RECEIVED);
}
}
};
public void addNewMessage(String msg,int type){
Msg message = new Msg(msg,type);
msgList.add(message);
adapter.notifyItemInserted(msgList.size()-1);
msgRecyclerView.scrollToPosition(msgList.size()-1);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_chat_room);
Intent intent =getIntent();
myName=intent.getStringExtra(“username”);
curr=0;
jsonLen=1;
isRunning=true;
inputText = findViewById(R.id.input_text);
send=findViewById(R.id.send);
send.setOnClickListener(this);
runOnUiThread(new Runnable() {
@Override
public void run() {
LinearLayoutManager layoutManager = new
LinearLayoutManager(ChatRoom.this);
msgRecyclerView= findViewById(R.id.msg_recycler_view);
msgRecyclerView.setLayoutManager(layoutManager);
adapter = new MsgAdapter(msgList);
msgRecyclerView.setAdapter(adapter);
}
});
new Thread(new Receive(), “ 接收线程 ”).start();
new Thread(new Send(), “ 发送线程 ”).start();
}
public void parseJSONWithJSONObject(String jsonData) {// 解析 JSON 数据函数
try {
JSONArray jsonArray = new JSONArray(jsonData);
jsonLen = jsonArray.length();
for (; curr < jsonLen; curr++) {
JSONObject jsonObject = jsonArray.getJSONObject(curr);
String name = jsonObject.getString(“name”);
String msgContent = jsonObject.getString(“message”);
Message message = new Message();
Bundle bundle = new Bundle();
bundle.putString(“name”, name);
bundle.putString(“msgContent”, msgContent); // 往 Bundle 中存放数据
message.setData(bundle);//mes 利用 Bundle 传递数据
handler.sendMessage(message);// 用 activity 中的 handler 发送消息
}
} catch (Exception e) {
Looper.prepare();
Toast.makeText(ChatRoom.this, “ 解析 json 错误!”, Toast.LENGTH_SHORT).show();
Looper.loop();
}
}
String msgEntity;
@Override
public void onClick(View view){
String content = inputText.getText().toString();
@SuppressLint(“SimpleDateFormat”)
String date = new SimpleDateFormat(“yyyy-MM-dd hh:mm:ss”).format(new Date());
StringBuilder sb = new StringBuilder();
msgEntity = myName;
sb.append(msgEntity).append(“\n”+date+”\n”+content);
msgEntity = sb.toString();
if(!””.equals(msgEntity)){
Message message = new Message();
Bundle bundle = new Bundle();
bundle.putString(“name”, myName);
bundle.putString(“msgContent”, msgEntity); // 往 Bundle 中存放数据
message.setData(bundle);//mes 利用 Bundle 传递数据
handler.sendMessage(message);// 用 activity 中的 handler 发送消息
inputText.setText(“”);
isSend = true;
curr++;
}
sb.delete(0,sb.length());
}
class Send implements Runnable{
@Override
public void run(){ // 发送线程
while(isRunning){
if(isSend){
RequestBody requestBody = new FormBody.Builder()
.add(“name”,myName)
.add(“message”,msgEntity)
.build();
try {
OkHttpClient client = new OkHttpClient();
Request request2 = new Request.Builder()
// 指定访问的服务器地址
.url(Resource.DiffUrl).post(requestBody)
.build();
Response response = client.newCall(request2).execute();
String responseData = response.body().string();
isSend = false;
} catch (Exception e) {
Looper.prepare();
Toast.makeText(ChatRoom.this, “ 发送失败!”,
Toast.LENGTH_SHORT).show();
Looper.loop();
}
}
}
}
}
class Receive implements Runnable{
public void run(){
while(isRunning){
if(!isSend) {
try {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
// 指定访问的服务器地址
.url(Resource.DiffUrl).get()
.build();
//Resource.DiffUrl 为 DiffSevlet 的 URL 地址
// 其需要根据你的服务端 Servlet 的 URL 地址进行修改
Response response = client.newCall(request).execute();
String responseData = response.body().string();
if (responseData != null && responseData.startsWith(“\ufeff”)){
responseData = responseData.substring(1);
}
parseJSONWithJSONObject(responseData);
} catch (Exception e) {
Looper.prepare();
Toast.makeText(ChatRoom.this, “ 连接服务器失败!!!”,
Toast.LENGTH_SHORT).show();
Looper.loop();
}
}
}
}
}
需要在 build.gradle 中添加库依赖
implementation ‘com.android.support:recyclerview-v7:28.0.0’
implementation ‘com.squareup.okhttp3:okhttp:4.5.0’
implementation ‘com.squareup.okio:okio:2.5.0’
1
2
Msg 实体类以及 MsgAdapter 类的相关代码参考衣侠客的博客 Android 聊天室 (客户端)
————————————————