【问题标题】:Communication Approaches Between Activity and Service in AndroidAndroid中Activity和Service之间的通信方式
【发布时间】:2012-05-17 12:40:46
【问题描述】:

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

当前的方法是为 Service 和 Activity 配备 BroadcastReceivers,并使用它们从 Activity 向 Service 发送“命令”Intent,并从 Service 向 Activity 发送“alert”Intent。

我的服务有一个可运行的,这是套接字 read() 发生的地方;当接收到数据时,runnable 会向 Service 发送一个“传入数据”intent,然后 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. Service 是否适合做我正在尝试做的那种基于套接字的 I/O?

【问题讨论】:

    标签: android sockets


    【解决方案1】:

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

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

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

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

    以上内容与您的场景有何关系

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

    MyService.this.processIncomingDataAlert(inputBuffer);
    

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

    如果您想取消阻塞套接字侦听器,和/或处理 UI 线程上的数据,您可以在 onStartCommand() 中创建一个处理程序,并从可运行对象中使用它来将另一个可运行对象发布回 UI 线程,例如这个:

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

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

    当然,现在还存在处理发生在 UI 线程上的问题,该线程在服务和活动之间共享。因此,如果您的数据处理速度较慢,则会影响 UI 的响应速度。

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

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

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

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

    服务是否适合您的场景

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

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

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

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

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

    希望这会有所帮助。

    【讨论】:

    • Franci -- 非常感谢 -- 您的帖子不仅解决了我的问题,还回答了其他几个(未提出的)问题!
    【解决方案2】:

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

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

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

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

    【讨论】:

    • Android will create a process to handle the incoming intent if one is not currently running -- 我有一种预感,你做出了区分,这对我来说很重要;非常感谢! (顺便说一句,我看到了你的博客网站——那里有很多有用的加密信息!)
    猜你喜欢
    • 2013-01-19
    • 1970-01-01
    • 2014-01-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多