0

新的 Android 程序员在这里。我有一个执行套接字管理和异步 I/O 的服务,我需要在它和我的应用程序中的 Activity 之间建立通信路径。

当前的方法是为服务和活动都配备广播接收器,并使用它们将“命令”意图从活动发送到服务,并将“警报”意图从服务发送到活动。

我的服务有一个可运行的,这是套接字 read() 发生的地方;当接收到数据时,runnable 会向 Service 发送一个“传入数据”意图,然后 Service 会向 Activity 发出警报:

    @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            super.onStartCommand(intent, flags, startId);
            if (m_IsRunning == false) {
                m_IsRunning = true;
                (new Thread(new Runnable() {
                    byte[] inputBuffer = new byte[512];
                    public void run() {
                        while (m_IsRunning) {
                            if (m_IsConnected) {
                                try {
                                    m_Nis = m_Socket.getInputStream();
                                    m_Nis.read(inputBuffer, 0, 512);
                                    Intent broadcast = new Intent();
                                    Bundle bun = new Bundle();
                                    bun.putString("ServiceCmd", "ALERT_INCOMING_DATA");
                                    bun.putByteArray("MsgBuffer", inputBuffer);
                                    broadcast.putExtras(bun);
                                    broadcast.setAction(BROADCAST_TO_SERVICE);
                                    sendBroadcast(broadcast);
                                } catch (IOException e) {
                                    // Send fault to activity
                                }
                            }
                        }
                    }
                })).start();
            }
            return START_STICKY;
        }

我使用 BroadcastReceiver 的方法如下所示:

        private BroadcastReceiver serviceReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                Bundle bun = intent.getExtras();
                String cmdString = bun.getString("ServiceCmd");

                if (cmdString.equals("CMD_SETHOSTINFO")) {
                    // The activity has requested us to set the host info
                    String hostAddr = bun.getString("HostAddressString");
                    int hostPort = bun.getInt("HostPortNumber");
                    processSetHostInfoCommand(hostAddr, hostPort);
                }
                else if (cmdString.equals("CMD_CONNECT")) {
                    // The activity has requested us to connect
                    if ((m_IsRunning) && (m_IsConnected == false)) {
                        // Attempt to connect
                        processConnectCommand();
                    }
                }
                else if (cmdString.equals("CMD_DISCONNECT")) {
                    // The activity has requested us to disconnect
                    if ((m_IsRunning) && (m_IsConnected == true)) {
                        // Attempt to disconnect
                        processDisconnectCommand();
                    }
                }
                else if (cmdString.equals("CMD_SENDDATA")) {
                    // The activity has requested us to send data
                    if ((m_IsRunning) && (m_IsConnected == true)) {
                        // Attempt to send data
                        byte[] msgBuffer = bun.getByteArray("MsgBuffer");
                        processSendDataCommand(msgBuffer);
                    }
                }
                else if (cmdString.equals("ALERT_INCOMING_DATA")) {
                    // Our TCP receiver thread has received data
                    if (m_IsRunning) {
                        byte[] msgBuffer = bun.getByteArray("MsgBuffer");
                        processIncomingDataAlert(msgBuffer);
                    }
                }
            }
        };

(这些processWhatever()方法一般都是做socket管理和数据传输。)

就像我说的,它似乎工作正常,但我想知道这是否不是使用消息和处理程序更合适的情况。

所以,具体的问题是:

  1. 在决定何时使用 BroadcastReceiver/Intents 或 Handler/Messages 时,“Android 之道”是什么?

  2. 在决定使用哪种方法时是否有任何跨线程考虑?

(而且,虽然它是题外话,最后一个问题):

  1. 服务是否适合做我正在尝试做的那种基于套接字的 I/O?
4

2 回答 2

4

广播意图、意图和处理程序之道

广播意图用于一对多的发布/订阅场景,其中一个组件想要让世界知道发生了什么事情,但不关心是否有任何人/有多少听众,或者他们当前是否正在运行。

常规意图用于一对一场景,其中一个组件希望代表它完成特定处理,但不关心/知道是否有特定组件能够执行此操作,或者该组件当前是否正在运行。

另一方面,处理程序用于一对一的同步/异步场景,其中双方都是众所周知的并且当前正在运行。

以上内容与您的情况有何关系

在最简单的实现中,您不需要意图或处理程序,您可以直接从后台 Runnable 调用服务上的方法,如下所示:

MyService.this.processIncomingDataAlert(inputBuffer);

请注意,这将在后台线程上执行该方法,并在处理数据时阻塞套接字侦听器。这让我们了解了您询问的线程注意事项。

如果您想解除阻塞套接字侦听器,和/或处理 UI 线程上的数据,您可以在其中创建一个 HandleronStartCommand()并使用它从 runnable 将另一个 runnable 发布回 UI 线程,如下所示:

myServiceHandler.post(new Runnable() {
    public void run() {
        MyService.this.processIncomingDataAlert(inputBuffer);
    }
};

请注意,这将导致 UI 线程处理 Runnable 调用onIncomingData和可能更新的后台线程侦听器之间的竞争条件inputBufffer,因此您可能需要创建一个副本。

当然,现在还存在处理发生在 UI 线程上的问题,该线程在服务和活动之间共享。所以,如果你的数据处理速度很慢,它会影响你的 UI 的响应能力。

如果您想确保后台套接字侦听器和 UI 线程都响应,您应该在(但)另一个线程上处理您的数据。您可以为每个 Runnable 启动一个新线程,但这会导致快速创建一堆线程,并浪费系统资源。更不用说创建单独的线程会导致处理多个数据块之间的竞争条件,这会使您的应用程序逻辑过于复杂。

幸运的是,Android 提供AsyncTask了此类场景。

AsyncTask 有一个后台线程池,它运行通过它调度的 Runnables。如果你在 GingerBread 或更低版本上运行,或者在 ICS 或更高版本上运行,AsyncTask 也会将它们序列化并一次只运行一个任务。

阅读这篇文章以获得关于线程、处理程序和 AsyncTask 的精彩介绍。请注意,文章显示了一个使用子类化 AsyncTask 并实现的示例doOnBackground,但这对您来说有点矫枉过正;静态execute(Runnable)方法对您来说效果很好。

服务是否适合您的场景

这个问题与其他问题有些正交。你需要一个后台线程来监听套接字,这是给定的。但是你需要一个服务不是很清楚。

服务适用于应用程序需要做后台处理,不需要通过 UI 吸引用户,或者用户切换到另一个应用程序后继续处理的场景。

因此,如果您的场景要求您仅在您的活动显示在屏幕上并且用户积极参与其中时才侦听套接字,则您不需要服务。您可以从您的活动中启动后台线程,并使用处理程序将新数据发回给它或 AsyncTask,如上所述。

但是,如果您确实需要在用户关闭您的活动后继续侦听套接字(您是否应该是一个单独的主题:-)),您需要服务。

这里的主要问题是 Android 中的进程生命周期。为了正确管理设备资源,操作系统会杀死它认为空闲的进程。如果进程中没有任何活动或服务正在运行,则该进程被视为空闲。仅仅启动一个后台线程并不足以让操作系统知道该进程仍然很忙。因此,如果您没有服务,一旦您的活动关闭,从 Android 的角度来看,您的进程什么也不做,它可以杀死它。

希望这可以帮助。

于 2012-05-09T04:06:07.027 回答
1

您实际上不必使用服务。如果它们都在同一个进程中运行,只需将您的网络代码设为单例,并让活动直接调用方法。对于通知,您可以简单地让活动实现某个接口(onData(String data)等)并让它们注册到网络类。只需注意在活动消失时取消注册 ( onStop())。另一方面,如果网络代码需要在没有 UI 可见的情况下运行(例如,不同的应用程序在前台),则需要使用服务。

消息和意图实际上是不同的:您使用 handlers.messages 与特定线程进行通信,而意图实际上是 IPC——它们可以传递到不同的应用程序/进程。使用广播接收器的好处是,如果当前没有运行,Android 将创建一个进程来处理传入的意图。

不知道是不是“道”,但一般来说:

  • 如果您需要在没有 UI 的情况下运行某些东西,请使用服务(计划的网络 IO 等)
  • 如果存在 UI,只需让它在不同的主题中运行(AscynTask提供了一种方便的方法)
  • 如果您正在与自己的应用程序通信(在单个进程中),请使用处理程序/消息
  • 如果您需要跨应用(进程)通信,请使用意图
  • 如果您需要处理一些异步事件,并且处理它的应用程序可能无法运行,请使用广播接收器。然后,如果需要一些时间,您可能想要启动一个服务(IntentService在工作线程中进行处理)来完成实际工作。此外,您可能需要获取唤醒锁以防止设备在您执行工作(网络 IO 等)时再次入睡
于 2012-05-09T03:17:25.113 回答