1. 服务器端 - ChatServer.java
import java.io.*;
import java.net.*;
import java.util.*;
public class ChatServer {
private static final int PORT = 8080;
private static Set<PrintWriter> clientWriters = new HashSet<>();
public static void main(String[] args) {
System.out.println("聊天服务器启动中...");
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
System.out.println("服务器已启动,监听端口: " + PORT);
while (true) {
new ClientHandler(serverSocket.accept()).start();
}
} catch (IOException e) {
System.out.println("服务器异常: " + e.getMessage());
}
}
private static class ClientHandler extends Thread {
private Socket socket;
private PrintWriter out;
private BufferedReader in;
private String nickname; // 存储实际昵称
public ClientHandler(Socket socket) {
this.socket = socket;
}
public void run() {
try {
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(), true);
// 读取客户端发送的第一条消息作为昵称
nickname = in.readLine();
if (nickname == null || nickname.trim().isEmpty()) {
nickname = "匿名用户";
}
synchronized (clientWriters) {
clientWriters.add(out);
}
// 使用实际昵称广播加入消息
System.out.println(nickname + " 加入了聊天室");
broadcastSystemMessage(nickname + " 加入了聊天室");
String message;
while ((message = in.readLine()) != null) {
System.out.println("收到消息: " + message);
broadcastMessage(message, out);
}
} catch (IOException e) {
System.out.println("客户端断开: " + e.getMessage());
} finally {
try {
socket.close();
} catch (IOException e) {
// 忽略
}
synchronized (clientWriters) {
clientWriters.remove(out);
}
// 使用实际昵称广播退出消息
if (nickname != null) {
System.out.println(nickname + " 离开了聊天室");
broadcastSystemMessage(nickname + " 离开了聊天室");
}
}
}
private void broadcastMessage(String message, PrintWriter senderWriter) {
synchronized (clientWriters) {
for (PrintWriter writer : clientWriters) {
if (writer != senderWriter) {
writer.println(message);
}
}
}
}
private void broadcastSystemMessage(String message) {
long timestamp = System.currentTimeMillis();
String systemMessage = "系统|" + message + "|" + timestamp;
synchronized (clientWriters) {
for (PrintWriter writer : clientWriters) {
writer.println(systemMessage);
}
}
}
}
}
2.Android客户端AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapplication">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="骆树涛群聊"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
<activity android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".ChatActivity" />
<activity android:name=".SettingsActivity" />
</application>
</manifest>
arrays.xml代码
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="color_entries">
<item>黑色</item>
<item>红色</item>
<item>黄色</item>
<item>蓝色</item>
<item>绿色</item>
</string-array>
<string-array name="color_values">
<item>#FF000000</item>
<item>#FFFF0000</item>
<item>#FFFFFF00</item>
<item>#FF0000FF</item>
<item>#FF00FF00</item>
</string-array>
<string-array name="bg_entries">
<item>科技</item>
<item>书籍</item>
<item>简约</item>
</string-array>
<string-array name="bg_values">
<item>bg_books</item>
<item>bg_nature</item>
<item>bg_simple</item>
</string-array>
</resources>
Srimg.xml代码
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<EditTextPreference
android:key="font_size"
android:title="字号设置"
android:summary="输入文字大小(单位:sp)"
android:dialogTitle="设置字号"
android:defaultValue="15"
android:inputType="number"/>
<ListPreference
android:key="text_color"
android:title="文字颜色"
android:summary="选择文字显示颜色"
android:dialogTitle="选择颜色"
android:defaultValue="#FF000000"
android:entries="@array/color_entries"
android:entryValues="@array/color_values"/>
<ListPreference
android:key="bg"
android:title="聊天背景"
android:summary="选择聊天背景图片"
android:dialogTitle="选择背景"
android:entries="@array/bg_entries"
android:entryValues="@array/bg_values"
android:defaultValue="bg_tech"/>
</PreferenceScreen>
ChatMessage.java
package com.example.myapplication;
public class ChatMessage {
private String nickname;
private String message;
private long timestamp;
private boolean isSelf;
public ChatMessage(String nickname, String message, long timestamp, boolean isSelf) {
this.nickname = nickname;
this.message = message;
this.timestamp = timestamp;
this.isSelf = isSelf;
}
public String getNickname() { return nickname; }
public String getMessage() { return message; }
public long getTimestamp() { return timestamp; }
public boolean isSelf() { return isSelf; }
}
ChatAdapter.java
package com.example.myapplication;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.RecyclerView;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
public class ChatAdapter extends RecyclerView.Adapter<ChatAdapter.MessageViewHolder> {
private List<ChatMessage> messages;
private Context context;
public ChatAdapter(List<ChatMessage> messages, Context context) {
this.messages = messages;
this.context = context;
}
@Override
public int getItemViewType(int position) {
return messages.get(position).isSelf() ? 1 : 0;
}
@NonNull
@Override
public MessageViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View view;
if (viewType == 1) {
view = inflater.inflate(R.layout.message_item_self, parent, false);
} else {
view = inflater.inflate(R.layout.message_item, parent, false);
}
return new MessageViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull MessageViewHolder holder, int position) {
ChatMessage message = messages.get(position);
holder.messageText.setText(message.getMessage());
holder.nicknameText.setText(message.getNickname());
// 格式化时间
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm", Locale.getDefault());
holder.timeText.setText(sdf.format(new Date(message.getTimestamp())));
// 应用设置 - 特别处理自己消息的颜色
applySettings(holder, message.isSelf());
}
private void applySettings(MessageViewHolder holder, boolean isSelf) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
// 设置字号
float fontSize = Float.parseFloat(prefs.getString("font_size", "15"));
holder.messageText.setTextSize(TypedValue.COMPLEX_UNIT_SP, fontSize);
holder.nicknameText.setTextSize(TypedValue.COMPLEX_UNIT_SP, fontSize - 2);
holder.timeText.setTextSize(TypedValue.COMPLEX_UNIT_SP, fontSize - 4);
// 设置文字颜色 - 自己消息使用特殊颜色
String colorValue;
if (isSelf) {
// 自己消息使用固定颜色,确保可读性
colorValue = "#FF000000"; // 黑色
} else {
colorValue = prefs.getString("text_color", "#FF000000");
}
holder.messageText.setTextColor(Color.parseColor(colorValue));
holder.nicknameText.setTextColor(Color.parseColor(colorValue));
holder.timeText.setTextColor(Color.parseColor(colorValue));
}
@Override
public int getItemCount() {
return messages.size();
}
static class MessageViewHolder extends RecyclerView.ViewHolder {
TextView nicknameText, messageText, timeText;
public MessageViewHolder(@NonNull View itemView) {
super(itemView);
nicknameText = itemView.findViewById(R.id.nickname);
messageText = itemView.findViewById(R.id.message);
timeText = itemView.findViewById(R.id.time);
}
}
}
MainActivity.java
package com.example.myapplication;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.widget.Button;
import android.widget.EditText;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private static final int SETTINGS_REQUEST = 1;
private EditText nicknameEditText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
nicknameEditText = findViewById(R.id.nickname_edit_text);
Button enterChatButton = findViewById(R.id.enter_chat_button);
Button settingsButton = findViewById(R.id.settings_button);
// 从SharedPreferences加载保存的昵称
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
String savedNickname = prefs.getString("nickname", "");
nicknameEditText.setText(savedNickname);
enterChatButton.setOnClickListener(v -> {
// 保存昵称到SharedPreferences
String nickname = nicknameEditText.getText().toString().trim();
if (nickname.isEmpty()) {
nickname = "匿名用户";
}
SharedPreferences.Editor editor = prefs.edit();
editor.putString("nickname", nickname);
editor.apply();
// 启动聊天室
Intent intent = new Intent(MainActivity.this, ChatActivity.class);
startActivity(intent);
});
settingsButton.setOnClickListener(v ->
startActivityForResult(
new Intent(MainActivity.this, SettingsActivity.class),
SETTINGS_REQUEST));
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == SETTINGS_REQUEST &&
resultCode == SettingsActivity.RESULT_SETTINGS_CHANGED) {
// 设置已更改,更新主界面背景
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
String bgKey = prefs.getString("bg", "bg_tech");
int bgResId = getResources().getIdentifier(bgKey, "drawable", getPackageName());
if (bgResId != 0) {
findViewById(android.R.id.content).setBackgroundResource(bgResId);
}
}
}
}
SettingsActivity.java
package com.example.myapplication;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
public class SettingsActivity extends AppCompatActivity {
public static final int RESULT_SETTINGS_CHANGED = 1001;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.settings_container, new SettingsFragment())
.commit();
}
}
SettingsFragment.java
package com.example.myapplication;
import android.content.SharedPreferences;
import android.os.Bundle;
import androidx.preference.PreferenceFragmentCompat;
public class SettingsFragment extends PreferenceFragmentCompat
implements SharedPreferences.OnSharedPreferenceChangeListener {
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.sring, rootKey);
}
@Override
public void onResume() {
super.onResume();
getPreferenceManager().getSharedPreferences()
.registerOnSharedPreferenceChangeListener(this);
}
@Override
public void onPause() {
super.onPause();
getPreferenceManager().getSharedPreferences()
.unregisterOnSharedPreferenceChangeListener(this);
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
// 设置变更时通知ChatActivity刷新
if (getActivity() != null) {
getActivity().setResult(SettingsActivity.RESULT_SETTINGS_CHANGED);
}
}
}
ChatActivity.java
package com.example.myapplication;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.io.*;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
public class ChatActivity extends AppCompatActivity {
private static final String SERVER_IP = "192.168.199.1"; // 模拟器访问本机
private static final int SERVER_PORT = 8080;
private RecyclerView chatRecyclerView;
private EditText messageEditText;
private Button sendButton;
private ChatAdapter adapter;
private List<ChatMessage> messages = new ArrayList<>();
private PrintWriter out;
private BufferedReader in;
private Socket socket;
private String nickname;
private SharedPreferences prefs;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_chat);
// 从SharedPreferences获取昵称
prefs = PreferenceManager.getDefaultSharedPreferences(this);
nickname = prefs.getString("nickname", "匿名用户");
setupUI();
loadChatHistory();
connectToServer();
}
@Override
protected void onResume() {
super.onResume();
// 重新应用设置(从设置页面返回时)
applyBackgroundSettings();
if (adapter != null) {
adapter.notifyDataSetChanged(); // 刷新消息显示
}
}
private void setupUI() {
chatRecyclerView = findViewById(R.id.chat_recycler_view);
messageEditText = findViewById(R.id.message_edit_text);
sendButton = findViewById(R.id.send_button);
adapter = new ChatAdapter(messages, this);
chatRecyclerView.setAdapter(adapter);
chatRecyclerView.setLayoutManager(new LinearLayoutManager(this));
applyBackgroundSettings(); // 初始应用背景设置
sendButton.setOnClickListener(v -> sendMessage());
}
private void applyBackgroundSettings() {
String bgKey = prefs.getString("bg", "bg_tech");
int bgResId = getResources().getIdentifier(bgKey, "drawable", getPackageName());
if (bgResId != 0) {
chatRecyclerView.setBackgroundResource(bgResId);
}
}
private void loadChatHistory() {
File file = new File(getExternalFilesDir(null), "luoshutao.txt");
if (file.exists()) {
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
String line;
while ((line = reader.readLine()) != null) {
String[] parts = line.split("\\|");
if (parts.length == 4) {
boolean isSelf = Boolean.parseBoolean(parts[3]);
messages.add(new ChatMessage(parts[0], parts[1], Long.parseLong(parts[2]), isSelf));
}
}
adapter.notifyDataSetChanged();
chatRecyclerView.scrollToPosition(messages.size() - 1);
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void saveChatMessage(ChatMessage message) {
File file = new File(getExternalFilesDir(null), "luoshutao.txt");
try (FileWriter writer = new FileWriter(file, true)) {
writer.write(String.format("%s|%s|%d|%b\n",
message.getNickname(),
message.getMessage(),
message.getTimestamp(),
message.isSelf()));
} catch (IOException e) {
e.printStackTrace();
}
}
private void connectToServer() {
new Thread(() -> {
try {
socket = new Socket(SERVER_IP, SERVER_PORT);
out = new PrintWriter(socket.getOutputStream(), true);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out.println(nickname);
// 监听服务器消息
String serverMessage;
while ((serverMessage = in.readLine()) != null) {
if (isSystemJoinLeaveMessage(serverMessage)) {
continue; // 跳过不显示
}
// 解析服务器消息:格式为 "昵称|消息|时间戳"
String[] parts = serverMessage.split("\\|", 3);
if (parts.length == 3) {
final ChatMessage message = new ChatMessage(
parts[0],
parts[1],
Long.parseLong(parts[2]),
false
);
runOnUiThread(() -> {
messages.add(message);
adapter.notifyItemInserted(messages.size() - 1);
chatRecyclerView.scrollToPosition(messages.size() - 1);
saveChatMessage(message);
});
}
}
} catch (IOException e) {
runOnUiThread(() -> Toast.makeText(ChatActivity.this,
"服务器连接失败: " + e.getMessage(), Toast.LENGTH_LONG).show());
}
}).start();
}
private boolean isSystemJoinLeaveMessage(String message) {
return message.contains("加入了聊天室") || message.contains("离开了聊天室");
}
private void sendMessage() {
String messageText = messageEditText.getText().toString().trim();
if (!messageText.isEmpty()) {
long timestamp = System.currentTimeMillis();
final ChatMessage message = new ChatMessage(nickname, messageText, timestamp, true);
// 添加到UI
runOnUiThread(() -> {
messages.add(message);
adapter.notifyItemInserted(messages.size() - 1);
chatRecyclerView.scrollToPosition(messages.size() - 1);
saveChatMessage(message);
});
// 发送到服务器
new Thread(() -> {
if (out != null) {
out.println(nickname + "|" + messageText + "|" + timestamp);
}
}).start();
messageEditText.setText("");
}
}
@Override
protected void onDestroy() {
super.onDestroy();
try {
if (socket != null) socket.close();
if (out != null) out.close();
if (in != null) in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
activity_main.xml
<?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"
android:gravity="center"
android:padding="16dp"
android:background="@drawable/bg_tech">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="骆树涛聊天室"
android:textSize="40sp"
android:layout_gravity="center"
android:layout_marginBottom="16dp"
android:textStyle="bold"
android:textColor="#2196F3"/>
<!-- 昵称设置区域 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="昵称:"
android:textSize="25sp"
android:layout_marginEnd="8dp"/>
<EditText
android:id="@+id/nickname_edit_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="请输入昵称"
android:textSize="25sp"
android:maxLines="1"/>
</LinearLayout>
<Button
android:id="@+id/enter_chat_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="进入聊天室"
android:layout_marginBottom="16dp"/>
<Button
android:id="@+id/settings_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="设置"/>
</LinearLayout>
activity_chat.xml
<?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">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/chat_recycler_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:scrollbars="vertical"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="8dp">
<EditText
android:id="@+id/message_edit_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="输入消息"
android:inputType="textMultiLine"
android:maxLines="3"/>
<Button
android:id="@+id/send_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发送"/>
</LinearLayout>
</LinearLayout>
activity_settings.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/settings_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
message_item.xml
<?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="wrap_content"
android:orientation="vertical"
android:padding="8dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="60dp">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:id="@+id/nickname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"/>
<TextView
android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="10sp"
android:layout_marginStart="8dp"/>
</LinearLayout>
<TextView
android:id="@+id/message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bubble_other"
android:padding="8dp"/>
</LinearLayout>
message_item_self.xml
<?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="wrap_content"
android:orientation="vertical"
android:padding="8dp"
android:layout_marginTop="4dp"
android:layout_marginStart="60dp"
android:gravity="end">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="10sp"
android:layout_marginEnd="8dp"/>
<TextView
android:id="@+id/nickname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"/>
</LinearLayout>
<TextView
android:id="@+id/message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_books"
android:padding="8dp"/>
</LinearLayout>
最新发布