You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

Android中如何从Activity停止触发SocketTimeoutException的线程?

解决Android跨Activity停止Socket接收线程的问题

嘿,我来帮你搞定这个跨Activity停止线程的问题!核心思路是安全控制线程状态+让两个Activity能共享线程实例,具体步骤如下:

1. 改造接收线程,添加安全停止逻辑

首先,你需要把控制线程终止的end变量改成volatile(保证多线程下的变量可见性,不然另一个Activity修改了end,接收线程可能看不到),然后给线程加一个对外的停止方法,同时关闭ServerSocket来中断阻塞的accept()调用——这样线程能更快终止,不用等超时:

public class ReceiveThread extends Thread {
    private int puerto;
    // volatile保证多线程下变量修改能立刻被其他线程看到
    private volatile boolean end = false;
    private ServerSocket ss;

    public ReceiveThread(int puerto) {
        this.puerto = puerto;
    }

    @Override
    public void run() {
        try {
            ss = new ServerSocket(puerto);
            int contador = 0;
            while (!end) {
                try {
                    ss.setSoTimeout(6000);
                    Socket s = ss.accept();
                    BufferedReader input = new BufferedReader(new InputStreamReader(s.getInputStream()));
                    PrintWriter output = new PrintWriter(s.getOutputStream());
                    String stringData = input.readLine();
                    output.flush();
                    contador++;
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    output.close();
                    s.close();
                } catch (SocketTimeoutException e) {
                    e.printStackTrace();
                    // 超时触发时自动停止线程
                    stopThread();
                }
            }
            // 最后关闭ServerSocket释放资源
            if (ss != null) {
                ss.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 对外暴露的停止方法,供其他Activity调用
    public void stopThread() {
        end = true;
        // 关闭ServerSocket,中断accept()的阻塞状态
        if (ss != null) {
            try {
                ss.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

2. 用ViewModel共享线程实例(避免内存泄漏)

直接在两个Activity之间传线程引用很容易导致内存泄漏,推荐用Android的ViewModel来管理线程——它能在Activity重建时保留数据,生命周期更安全:

import androidx.lifecycle.ViewModel;

public class SocketThreadsViewModel extends ViewModel {
    private ReceiveThread hebraRecibir;
    // 如果需要管理发送线程,也可以在这里添加
    // private SendThread hebraEnviar;

    // 初始化接收线程(只创建一次,避免重复创建)
    public void initReceiveThread(int puerto) {
        if (hebraRecibir == null) {
            hebraRecibir = new ReceiveThread(puerto);
        }
    }

    // 启动线程
    public void startThreads() {
        if (hebraRecibir != null && !hebraRecibir.isAlive()) {
            hebraRecibir.start();
        }
        // 发送线程的启动逻辑同理
        // if (hebraEnviar != null && !hebraEnviar.isAlive()) {
        //     hebraEnviar.start();
        // }
    }

    // 停止接收线程
    public void stopReceiveThread() {
        if (hebraRecibir != null) {
            hebraRecibir.stopThread();
            // 停止后可以把引用置空,下次启动重新创建(因为Thread只能start一次)
            hebraRecibir = null;
        }
    }
}

3. 在启动线程的Activity中初始化ViewModel

// 这是你原来启动线程的Activity
public class StartSocketActivity extends AppCompatActivity {
    private SocketThreadsViewModel viewModel;
    private int puerto = 8888; // 替换成你的端口号

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_start_socket);

        // 获取ViewModel实例
        viewModel = new ViewModelProvider(this).get(SocketThreadsViewModel.class);
        // 初始化接收线程
        viewModel.initReceiveThread(puerto);

        Button buttonSend = findViewById(R.id.buttonSend);
        buttonSend.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String dirIP = editTextDirIP.getText().toString();
                // 通过ViewModel启动线程
                viewModel.startThreads();
                // 这里可以处理发送线程的初始化和启动逻辑
            }
        });
    }
}

4. 在另一个Activity中调用停止方法

// 这是你要停止线程的另一个Activity
public class StopSocketActivity extends AppCompatActivity {
    private SocketThreadsViewModel viewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_stop_socket);

        // 获取同一个ViewModel实例(确保两个Activity属于同一个ViewModel作用域,比如同一个任务栈)
        viewModel = new ViewModelProvider(this).get(SocketThreadsViewModel.class);

        Button buttonStop = findViewById(R.id.buttonStop);
        buttonStop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // 调用ViewModel的停止方法,终止接收线程
                viewModel.stopReceiveThread();
            }
        });
    }
}

几个关键注意点

  • volatile的作用:如果不用volatile,接收线程可能会缓存end变量的旧值,导致即使另一个Activity把end改成true,接收线程还是会继续循环。
  • 关闭ServerSocket:调用ss.close()会让阻塞在accept()的线程立刻抛出IOException,直接跳出循环,比等6秒超时快得多。
  • Thread只能启动一次:线程调用start()后就不能再调用了,所以停止后把hebraRecibir置空,下次启动要重新创建实例。

内容的提问来源于stack exchange,提问作者Francisco Manuel Pozo Del Mora

火山引擎 最新活动