Android系统回顾(八):网络通信(三)Android与.net服务器通过Socket通信

本文详细介绍了基于Socket的网络通信实现,包括客户端向服务器发送请求和服务器向客户端发送响应数据的过程。客户端使用Android平台进行实现,服务器端使用.NET C#编程语言。客户端与服务器之间的通信通过Socket进行,实现了消息的接收与发送功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文源码参见:https://github.com/YongYuIT/MyQQ


三、基于Socket的网络通信

这个例子实现的是客户端向服务器发送请求,服务器向客户端发送响应数据。

服务端(.net C#)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            //第一个参数指定寻址方案,AddressFamily.InterNetwork代表的是ipv4寻址方案
            //第二个参数指定连接类型,SocketType.Stream代表的是基于字节流的可靠双工连接
            //第三个参数指定运输层协议,这里指定用tcp协议承载
            Socket soc_server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //指定套接字监听的IP及端口。IP是指客户端的IP,Any表示允许任何客户端接入。端口是本地端口。这样的设置意味着允许任意IP的客户端通过本机2222端口接入
            IPEndPoint ip = new IPEndPoint(IPAddress.Any, 2222);
            soc_server.Bind(ip);
            //10表示挂起连接队列的最大长度是10
            soc_server.Listen(10);
            while (true)
            {
                Console.WriteLine(soc_server.LocalEndPoint.ToString() + "等待客户端连接");
                //当有连接接入时,创建新套接字为这个连接服务。我们将使用这个新套接字与客户端通信
                //Accept方法是一个同步方法,会阻塞所在线程。也就是说,此处Accept函数调用时会停下来等待,直到有连接接入才会返回继续执行后面的代码
                Socket soc_new = soc_server.Accept();
                Console.WriteLine(soc_new.RemoteEndPoint.ToString() + "已连接");
                string all = string.Empty;
                while (true)
                {
                    string input = string.Empty;
                    //建立输入缓存
                    byte[] receiveBytes = new byte[1024];
                    //每次读取1024个字节,直到读到指定的字符串,一次读取结束。
                    //如果没有可读取的数据,则 Receive 方法将一直处于阻止状态
                    //如果远程主机使用 Shutdown 方法关闭了 Socket 连接,并且所有可用数据均已收到,则 Receive 方法将立即完成并返回零字节。
                    int byteNums = soc_new.Receive(receiveBytes);
                    if (byteNums == 0)
                    {
                        Console.WriteLine("远程主机主动关闭");
                        break;
                    }
                    //编码数据
                    input = Encoding.ASCII.GetString(receiveBytes, 0, byteNums);
                    all += input;
                    Console.WriteLine("收到:" + input);
                    //当接收到的"[FINAL]"时,会话结束
                    if (input.IndexOf("[final]") > -1)
                    {
                        Console.WriteLine("收到结束字符串");
                        break;
                    }
                }
                byte[] reply = Encoding.ASCII.GetBytes(all + ":receive succeed");
                soc_new.Send(reply);
                //禁止客户端收发数据
                soc_new.Shutdown(SocketShutdown.Both);
                soc_new.Close();
            }
            soc_server.Close();
        }
    }
}

客户端(Android)

/MyQQ/res/drawable-hdpi/sharp_liaotian.xml文件:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
    <!-- 外框 -->
    <stroke 
        android:width="2dp"
        android:color="#DD2ECCFA"
        />
    <!-- 内容 -->
    <solid
        android:color="#ffffffff"
        />
    <!-- 拐角 -->
    <corners
        android:radius="5dp"
        />
</shape>

/MyQQ/res/layout/main.xml文件:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ffffffff"
    android:orientation="vertical" >
    <LinearLayout
        android:id="@+id/linearLayout1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:gravity="center_horizontal"
        android:orientation="horizontal" >
        <ImageView
            android:layout_width="25dp"
            android:layout_height="25dp"
            android:layout_gravity="center_vertical"
            android:background="@drawable/app_logo" >
        </ImageView>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:paddingLeft="20dp"
            android:text="与XXX的聊天"
            android:textSize="20dp" >
        </TextView>
    </LinearLayout>
    <TextView
        android:id="@+id/txt_message"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        android:layout_weight="7"
        android:background="@drawable/sharp_liaotian"
        android:padding="5dp"
        android:text="聊天记录"
        android:textColor="#DD2ECCFA" >
    </TextView>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        android:layout_weight="1"
        android:orientation="horizontal" >
        <EditText
            android:id="@+id/edt_input"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_margin="3dp"
            android:layout_weight="3"
            android:background="@drawable/sharp_liaotian"
            android:gravity="center"
            android:hint="请输入聊天內容"
            android:textColor="#4F2F4F" >
        </EditText>
        <Button
            android:id="@+id/btn_send_message"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_margin="3dp"
            android:layout_weight="1"
            android:gravity="center"
            android:text="发送"
            android:textColor="#DD2ECCFA" />
    </LinearLayout>
</LinearLayout>

/MyQQ/src/com/yongyu/myqq/IConnectSuccess.java文件:

package com.yongyu.myqq;
import java.net.Socket;
public interface IConnectSuccess
{
    public void onGetSocket(Socket s);
}
/MyQQ/src/com/yongyu/myqq/IGetMessage.java文件:

package com.yongyu.myqq;
public interface IGetMessage
{
    public void onGetMessage(String str);
}

/MyQQ/src/com/yongyu/myqq/MainActivity.java文件:

package com.yongyu.myqq;
import java.net.Socket;
import java.util.concurrent.ArrayBlockingQueue;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends Activity implements IConnectSuccess,
        IGetMessage
{
    private Socket             connect;
    private EditText           edt_input;
    private ArrayBlockingQueue queue;
    private TextView           txt_message;
    private Handler            handler = null;
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        Button btn_send_message = (Button) findViewById(R.id.btn_send_message);
        // 每个Handler实例,都会绑定到创建他的线程中。Handler实例可以分发Message对象和Runnable对象其绑定的线程中并伺机执行
        handler = new Handler();
        edt_input = (EditText) findViewById(R.id.edt_input);
        txt_message = (TextView) findViewById(R.id.txt_message);
        btn_send_message.setOnClickListener(new OnClickListener()
        {
            @Override
            public void onClick(View arg0)
            {
                if (connect == null)
                {
                    Toast t = Toast.makeText(MainActivity.this, "网络异常,请检查设置!",
                            2000);
                    t.show();
                    return;
                }
                if (edt_input.getText().toString().equals(""))
                {
                    Toast t = Toast.makeText(MainActivity.this, "请输入消息!", 2000);
                    t.show();
                    return;
                } else
                {
                    String str = edt_input.getText().toString();
                    try
                    {
                        MainActivity.this.queue.put(str);
                    } catch (InterruptedException e)
                    {
                        Log.e("com.yongyu.myqq.MainActivity.onCreate",
                                e.getMessage());
                    }
                    txt_message.setText(txt_message.getText() + "\n发出的信息是:"
                            + str);
                    edt_input.setText("");
                }
            }
        });
        Task_Connect task_conn = new Task_Connect(this);
        task_conn.execute();
    }
    @Override
    public void onGetSocket(Socket s)
    {
        this.connect = s;
        this.queue = new ArrayBlockingQueue<String>(10);
        Thread t_s = new Thread(new MyRunnableSendMessage(connect, queue));
        t_s.start();
        Thread t_r = new Thread(new MyRunnableReceiveMessage(connect, this));
        t_r.start();
    }
    @Override
    public void onGetMessage(String str)
    {
        myUpdateUIRunnable runnable = new myUpdateUIRunnable(str, txt_message);
        handler.post(runnable);
    }
}
class myUpdateUIRunnable implements Runnable
{
    private String   msg;
    private TextView text;
    public myUpdateUIRunnable(String str, TextView t)
    {
        msg = str;
        text = t;
    }
    @Override
    public void run()
    {
        text.setText(text.getText() + "\n收到的信息是:" + msg);
    }
}
/MyQQ/src/com/yongyu/myqq/MyRunnableReceiveMessage.java文件:

package com.yongyu.myqq;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.SocketException;
public class MyRunnableReceiveMessage implements Runnable
{
    private Socket      socket;
    private IGetMessage iget;
    private String      message = "";
    public MyRunnableReceiveMessage(Socket _socket, IGetMessage _get)
    {
        this.socket = _socket;
        this.iget = _get;
    }
    @Override
    public void run()
    {
        InputStream in = null;
        if (!isConnected())
            return;
        try
        {
            // 尝试打开输入流
            in = this.socket.getInputStream();
        } catch (IOException e1)
        {
            return;
        }
        while (true)
        {
            try
            {
                if (!isConnected())
                {
                    break;
                }
                // 读取消息-------------------------
                InputStreamReader inReader = new InputStreamReader(
                        socket.getInputStream());
                BufferedReader reader = new BufferedReader(inReader);
                message = reader.readLine();
                // --------------------------------
                // 直接在异步线程里更新UI会导致:Only the original thread that created a view
                // hierarchy can touch its views.
                if (message == null)
                    continue;
                this.iget.onGetMessage(message);
                message = "";
            } catch (SocketException e)
            {
                // 如果本地套接字关闭,退出输入尝试
                if (e.getMessage().toLowerCase().trim()
                        .equals(("Socket is closed").toLowerCase().trim()))
                    break;
            } catch (Exception e0)
            {
                // 继续输入尝试
                continue;
            }
        }
        try
        {
            socket.shutdownOutput();
        } catch (IOException e)
        {
        }
    }
    private boolean isConnected()
    {
        // 注意,在套接字编程中,我们要时刻注意对网络连接状态进行判断,java中的Socket提供的isClosed(),isConnected(),isInputShutdown(),sOutputShutdown()都是用于检查本地Socket状态的,不是用于检查远程Socket状态的,所以是无效的的。
        // 要检查远程Socket的状态可以用:
        try
        {
            socket.sendUrgentData(0xFF);
            return true;
        } catch (Exception e)
        {
            return false;
        }
    }
}

/MyQQ/src/com/yongyu/myqq/MyRunnableSendMessage.java文件:

package com.yongyu.myqq;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;
import java.util.concurrent.ArrayBlockingQueue;
public class MyRunnableSendMessage implements Runnable
{
    private Socket             socket;
    private ArrayBlockingQueue queue;


    public MyRunnableSendMessage(Socket _socket, ArrayBlockingQueue _que)
    {
        this.socket = _socket;
        this.queue = _que;
    }
    @Override
    public void run()
    {
        OutputStream out;
        if (!isConnected())
            return;
        try
        {
            // 尝试打开输出流
            out = this.socket.getOutputStream();
        } catch (IOException e1)
        {
            return;
        }
        while (true)
        {
            try
            {
                String message = (String) queue.take();
                if (!isConnected())
                {
                    break;
                }
                out.write((message).getBytes());
            } catch (SocketException e)
            {


                // 如果本地套接字关闭,退出输出尝试
                if (e.getMessage().toLowerCase().trim()
                        .equals(("Socket is closed").toLowerCase().trim()))
                    break;
            } catch (Exception e0)
            {


                // 继续输出尝试
                continue;
            }
        }
        try
        {
            // 对于在socket上建立的输入(输出)流一旦调用close函数关闭,会造成socket关闭。这样基于socket的输出(输入)流也将不可用
            // 调用socket.shutdownOutputStream()只会单方面关闭输出流,不会导致socket关闭,此时基于socket的输入流还是可用的
            socket.shutdownOutput();
        } catch (IOException e)
        {
        }
    }
    private boolean isConnected()
    {
        // 注意,在套接字编程中,我们要时刻注意对网络连接状态进行判断,java中的Socket提供的isClosed(),isConnected(),isInputShutdown(),sOutputShutdown()都是用于检查本地Socket状态的,不是用于检查远程Socket状态的,所以是无效的的。
        // 要检查远程Socket的状态可以用:
        try
        {
            socket.sendUrgentData(0xFF);
            return true;
        } catch (Exception e)
        {
            return false;
        }
    }
}
/MyQQ/src/com/yongyu/myqq/Task_Connect.java文件:

package com.yongyu.myqq;
import java.net.Socket;
import android.os.AsyncTask;
import android.util.Log;
public class Task_Connect extends AsyncTask<Void, Void, Socket>
{
    private IConnectSuccess iget;
    public Task_Connect(IConnectSuccess _iget)
    {
        this.iget = _iget;
    }
    @Override
    protected Socket doInBackground(Void... arg0)
    {
        Socket connect = null;
        try
        {
            connect = new Socket("192.168.10.111", 2222);
        } catch (Exception e)
        {
            connect = null;
            Log.e(" com.yongyu.myqq.Task_Connect.doInBackground",
                    e.getMessage());
        }
        return connect;
    }
    @Override
    protected void onPostExecute(Socket result)
    {
        this.iget.onGetSocket(result);
    }
}
效果:






















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>
最新发布
06-30
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值