凌峰创科服务平台

Android Socket如何稳定连接服务器?

目录

  1. 核心概念
  2. Android 平台的特殊性
  3. 实现步骤
    • 第 1 步:添加网络权限
    • 第 2 步:创建后台线程(避免 ANR)
    • 第 3 步:实现客户端 Socket 连接
    • 第 4 步:实现数据收发(输入/输出流)
    • 第 5 步:处理连接生命周期(连接、断开、异常)
  4. 完整代码示例
  5. 进阶主题
    • 使用 AsyncTask(旧方法,但有助于理解)
    • 使用现代协程
    • 使用第三方库(如 Okio)
  6. 常见问题与最佳实践

核心概念

  • Socket (套接字):网络通信的端点,你可以把它想象成一个电话,客户端(你的 App)通过这个电话号码(IP 地址和端口号)拨打服务器(接听方)的电话。
  • IP 地址:服务器的网络地址,168.1.100(局域网)或 8.8.8(公网)。
  • 端口号:服务器上应用程序的标识,范围是 0-65535,Web 服务器常用 80 端口,HTTPS 用 443 端口,你需要使用服务器应用程序监听的端口号。
  • :数据在 Socket 中是以字节流的形式传输的,你需要将你的数据(如字符串、图片)转换为字节数组,通过输出流发送;然后通过输入流接收字节数组,再转换回原始数据。

Android 平台的特殊性

这是 Android 开发中至关重要的一点,也是新手最容易出错的地方。

Android Socket如何稳定连接服务器?-图1
(图片来源网络,侵删)
  • 网络访问在主线程(UI 线程)中被禁止

    • 原因:网络请求是不可预测的,可能会因为网络延迟、服务器无响应而长时间阻塞,如果在主线程中执行,会导致用户界面卡死,系统会弹出 "Application Not Responding (ANR)" 对话框,甚至可能杀死你的应用。
    • 解决方案:所有的网络操作(Socket 连接、读写数据)都必须在后台线程中执行。
  • 网络权限

    你的应用必须声明它需要访问网络的权限,否则无法进行任何网络通信。


实现步骤

我们将创建一个简单的客户端,它连接到服务器,发送一条消息,并接收服务器的回复。

Android Socket如何稳定连接服务器?-图2
(图片来源网络,侵删)

第 1 步:添加网络权限

AndroidManifest.xml 文件中,在 <application> 标签之前添加以下权限:

<uses-permission android:name="android.permission.INTERNET" />

第 2 步:创建后台线程

我们不能在 onCreate 或按钮点击事件等主线程方法中直接写网络代码,我们将使用一个 Thread 配合 Handler 的方式,这是最经典和易于理解的方式。

  • Thread:负责执行耗时任务(网络连接)。
  • Handler:负责在主线程中更新 UI(显示连接状态、消息内容)。

第 3 步:实现客户端 Socket 连接

在后台线程中,创建 Socket 对象,并提供服务器的 IP 地址和端口号。new Socket() 的过程会阻塞线程,直到连接成功或抛出异常。

Socket socket = new Socket("服务器IP地址", 端口号);

第 4 步:实现数据收发

一旦连接成功,就可以通过 socket.getInputStream()socket.getOutputStream() 获取输入流和输出流。

Android Socket如何稳定连接服务器?-图3
(图片来源网络,侵删)
  • 发送数据:将字符串转换为字节数组,然后用 OutputStream.write() 发送。
    PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
    out.println("你好,服务器!"); // 发送字符串
    out.flush(); // 重要!确保数据被立即发送
  • 接收数据:使用 InputStream.read() 读取服务器返回的字节数组,然后转换成字符串。
    BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
    String response = in.readLine(); // 阻塞,直到读取到一行

第 5 步:处理连接生命周期

  • 连接成功:通过 Handler 发送消息到主线程,更新 UI 显示“连接成功”。
  • 连接失败/异常:使用 try-catch 捕获 IOException,并通过 Handler 通知主线程显示错误信息。
  • 断开连接:在 finally 块中,务必关闭 SocketInputStreamOutputStream,以释放系统资源。

完整代码示例

这是一个完整的 Activity 示例,它使用一个按钮来触发连接,并用 TextView 显示结果。

activity_main.xml (布局文件)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center"
    android:padding="16dp"
    tools:context=".MainActivity">
    <Button
        android:id="@+id/btn_connect"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="连接服务器" />
    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:layout_marginTop="20dp">
        <TextView
            android:id="@+id/tv_log"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:fontFamily="monospace"
            android:text="等待连接..." />
    </ScrollView>
</LinearLayout>

MainActivity.java

import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.widget.Button;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
public class MainActivity extends AppCompatActivity {
    private static final String TAG = "SocketClient";
    private static final String SERVER_IP = "你的服务器IP"; // 替换为你的服务器IP
    private static final int SERVER_PORT = 8888;         // 替换为你的服务器端口
    private TextView tvLog;
    private Button btnConnect;
    // Handler用于在主线程更新UI
    private Handler mainHandler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
            String log = (String) msg.obj;
            tvLog.append(log + "\n");
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tvLog = findViewById(R.id.tv_log);
        btnConnect = findViewById(R.id.btn_connect);
        btnConnect.setOnClickListener(v -> {
            // 禁用按钮,防止重复点击
            btnConnect.setEnabled(false);
            // 启动一个新线程来执行网络任务
            new Thread(new SocketTask()).start();
        });
    }
    // 定义一个HandlerMessage常量,方便识别
    private static final int MSG_UPDATE_LOG = 1;
    private class SocketTask implements Runnable {
        @Override
        public void run() {
            Socket socket = null;
            try {
                // 1. 创建Socket并连接服务器
                sendLogToUI("正在连接服务器 " + SERVER_IP + ":" + SERVER_PORT + "...");
                socket = new Socket(SERVER_IP, SERVER_PORT);
                sendLogToUI("连接成功!");
                // 2. 获取输入输出流
                PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true);
                BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                // 3. 发送数据
                String messageToSend = "Hello, Android Client!";
                sendLogToUI("发送消息: " + messageToSend);
                out.println(messageToSend);
                // 4. 接收数据
                String response = in.readLine();
                if (response != null) {
                    sendLogToUI("服务器回复: " + response);
                }
            } catch (UnknownHostException e) {
                sendLogToUI("错误: 未知的主机 " + SERVER_IP);
                e.printStackTrace();
            } catch (IOException e) {
                sendLogToUI("错误: 连接或读写失败");
                e.printStackTrace();
            } finally {
                // 5. 关闭Socket和流
                if (socket
分享:
扫描分享到社交APP
上一篇
下一篇