这里有新的Android程序员。我有一个执行套接字管理和异步I / O的服务,我需要在它和我的应用程序中的Activity之间建立一个通信路径。
当前的方法是使用BroadcastReceivers配备Service和Activity,并使用它们将“命令”意图从活动发送到服务,并将“警报”意图从服务发送到活动。
我的服务有一个runnable,这是套接字read()发生的地方;收到数据后,runnable会向服务发送“传入数据”意图,然后服务会向活动发出警告:
@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()
方法通常进行套接字管理和数据传输。)
就像我说的,它似乎工作正常,但我想知道这不是一个使用消息和处理程序不合适的情况。
所以,具体问题是:
在决定何时使用BroadcastReceiver / Intents或Handler / Messages时,“道之道”是什么?
在决定使用哪种方法时是否存在跨线程注意事项?
(并且,虽然这是一个偏离主题,但最后一个问题):
答案 0 :(得分:4)
广播意图,意图和处理程序之道
广播意图用于一对多的发布/订阅方案,其中一个组件想让世界知道发生的事情,但不关心是否有任何人/多少听众,或者他们当前是否正在运行。
常规意图用于一对一场景,其中一个组件需要代表它进行特定处理,但不关心/知道是否有特定组件能够执行此操作,或者此组件当前是否正在运行。
另一方面,处理程序用于一对一同步/异步方案,其中双方都是众所周知的并且当前正在运行。
上述内容与您的方案有何关联
在最简单的实现中,您不需要intent或handler,您可以从后台Runnable直接调用您的服务上的方法,如下所示:
MyService.this.processIncomingDataAlert(inputBuffer);
请注意,这将在后台线程上执行该方法,并在处理数据时阻止套接字侦听器。这让我们了解了您所询问的线程问题。
如果要取消阻塞套接字侦听器和/或处理UI线程上的数据,可以在onStartCommand()
中创建一个Handler,并从runnable中使用它将另一个runnable发布回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与用户互动或在用户切换到其他应用程序后继续处理的情况。
因此,如果您的场景要求您仅在屏幕上显示您的活动并且用户积极参与其中时才听取套接字,则您不需要服务。您可以从您的活动中启动后台线程,并使用handler将新数据发回给它或AsyncTask,如上所述。
但是,如果您确实需要在用户关闭活动后继续收听套接字(是否应该是一个单独的主题:-)),则需要该服务。
这里的主要问题是Android中的流程生命周期。为了正确管理设备资源,操作系统将终止它认为空闲的进程。如果没有活动或服务正在运行,则进程被视为空闲。仅启动后台线程是不足以让操作系统知道进程仍然繁忙。因此,如果您没有服务,一旦您关闭了活动,从Android点视图,您的流程什么都不做,它可以杀死它。
希望这有帮助。
答案 1 :(得分:1)
您实际上不必使用服务。如果它们都在同一个进程中运行,只需将您的网络代码设置为单例,并直接调用方法。对于通知,您可以简单地让活动实现某个接口(onData(String data)
等)并让它们注册到网络类。当活动消失时,请注意取消注册(onStop()
)。另一方面,如果网络代码需要在没有UI可见的情况下运行(例如,不同的应用程序位于前台),则需要使用服务。
消息和意图实际上是不同的:您使用handlers.messages与特定线程进行通信,而意图实际上是IPC - 它们可以被传递到不同的应用程序/进程。使用广播接收器的好处是Android将创建一个进程来处理传入的意图(如果当前没有运行)。
不知道是否是'道',但一般都是:
AscynTask
提供了一种方便的方法来执行此操作)IntentService
在工作线程中进行处理)以执行实际工作(如果需要一段时间)。此外,在执行工作(网络IO等)时,您可能需要获取唤醒锁以防止设备再次入睡。