Concurrency on Android with Service
1. The Service Component
The Service component is a very important part of Android's concurrency framework. It fulfills the need to perform a long-running operation within an application, or it supplies some functionality for other applications. In this tutorial we’ll concentrate exclusively on Service’s long-running task capability, and how to use this power to improve concurrency.
What is a Service?
A Service is a simple component that's instantiated by the system to do some long-running work that doesn't necessarily depend on user interaction. It can be independent from the activity life cycle and can also run on a complete different process.
Before diving into a discussion of what a Service represents, it's important to stress that even though services are commonly used for long-running background operations and to execute tasks on different processes, a Service doesn't represent a Thread or a process. It will only run in a background thread or on a different process if it's explicitly asked to do so.
A Service has two main features:
A facility for the application to tell the system about something it wants to be doing in the background.
A facility for an application to expose some of its functionality to other applications.
Services and Threads
There is a lot of confusion about services and threads. When a Service is declared, it doesn't contain a Thread. As a matter of fact, by default it runs directly on the main thread and any work done on it may potentially freeze an application. (Unless it's a IntentService, a Service subclass that already comes with a worker thread configured.)
So, how do services offer a concurrency solution? Well, a Service doesn't contain a thread by default, but it can be easily configured to work with its own thread or with a pool of threads. We'll see more about that below.
Disregarding the lack of a built-in thread, a Service is an excellent solution for concurrency problems in certain situations. The main reasons to choose a Service over other concurrency solutions like AsyncTask or the HaMeR framework are:
A Service can be independent of activity life cycles.
A Service is appropriate for running long operations.
Services don't depend on user interaction.
When running on different processes, Android can try to keep services alive even when the system is short on resources.
A Service can be restarted to resume its work.
Service Types
There are two types of Service, started and bound.
A started service is launched via Context.startService(). Generally it performs only one operation and it will run indefinitely until the operation ends, then it shuts itself down. Typically, it doesn't return any result to the user interface.
The bound service is launched via Context.bindService(), and it allows a two-way communication between client and Service. It can also connect with multiple clients. It destroys itself when there isn't any client connected to it.
To choose between those two types, the Service must implement some callbacks: onStartCommand() to run as a started service, and onBind() to run as a bound service. A Service may choose to implement only one of those types, but it can also adopt both at the same time without any problems.
1.服务组件
服务组件是Android并发框架的一个非常重要的组成部分。它满足在应用程序中执行长时间运行操作的需要,或者为其他应用程序提供一些功能。在本教程中,我们将专注于服务的长时间运行的任务能力,以及如何使用此能力来提高并发性。
什么是服务?
服务是一个简单的组件,由系统实例化,以执行一些不一定依赖于用户交互的长时间运行的工作。它可以独立于活动生命周期,并且可以在完全不同的过程上运行。
在讨论服务代表什么之前,需要强调的是,即使服务通常用于长期运行的后台操作和在不同进程上执行任务,服务也不代表Thread或进程。它只会在后台线程或不同的进程上运行,如果它明确要求这样做。
服务有两个主要特点:
应用程序的一个设施,告诉系统它想在后台做什么。
应用程序的一些功能向其他应用程序公开的功能。
服务和线程
有很多关于服务和线程的混乱。当声明服务时,它不包含线程。事实上,默认情况下,它直接在主线程上运行,并且对其执行的任何操作都可能冻结应用程序。 (除非它是一个IntentService,一个服务子类,已经配置了一个工作线程。)
那么,服务如何提供并发解决方案?好了,服务默认不包含线程,但它可以很容易地配置为使用自己的线程或线程池。我们将在下面详细了解。
不考虑缺少内置线程,服务是在某些情况下的并发问题的出色解决方案。选择服务而不是其他并发解决方案(例如AsyncTask或HaMeR框架)的主要原因是:
A服务可以独立于活动生命周期。
A服务适合长时间运行。
服务不依赖于用户交互。
当在不同的进程上运行时,Android可以尝试保持服务活动,即使系统资源短缺。
可以重新启动服务以恢复其工作。
服务类型
有两种类型的服务,启动和绑定。
已启动的服务通过Context.startService()启动。通常它只执行一个操作,它将无限期运行,直到操作结束,然后它自己关闭。通常,它不向用户界面返回任何结果。
绑定服务通过Context.bindService()启动,它允许客户端和服务之间的双向通信。它也可以连接多个客户端。当没有任何客户端连接到它时,它会自毁。
要在这两种类型之间进行选择,服务必须实现一些回调:onStartCommand()作为启动服务运行,onBind()作为绑定服务运行。服务可以选择仅实现这些类型中的一种,但是它也可以同时采用两者而没有任何问题。
2. Service Implementation
To use a service, extend the Service class and override its callback methods, according to the type of Service . As mentioned before, for started services the onStartCommand() method must be implemented and for bound services, the onBind() method. Actually, the onBind() method must be declared for either service type, but it can return null for started services.
2.服务实施
要使用服务,请根据服务类型扩展Service类并覆盖其回调方法。如前所述,对于启动的服务,必须实现onStartCommand()方法,对于绑定服务,必须实现onBind()方法。实际上,onBind()方法必须为任一服务类型声明,但它可以为已启动的服务返回null。
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
public
class
CustomService
extends
Service {
@Override
public
int
onStartCommand(Intent intent,
int
flags,
int
startId) {
// Execute your operations
// Service wont be terminated automatically
return
Service.START_NOT_STICKY;
}
@Nullable
@Override
public
IBinder onBind(Intent intent) {
// Creates a connection with a client
// using a interface implemented on IBinder
return
null
;
}
}
|
onStartCommand():由Context.startService()启动。 这通常从活动中调用。 一旦被调用,服务可以无限期地运行,它由你停止它,调用stopSelf()或stopService()。
onBind():当组件想要连接到服务时调用。 通过Context.bindService()在系统上调用。 它返回一个IBinder,它提供了一个与客户端通信的接口。
服务的生命周期也是重要的考虑因素。 应该实现onCreate()和onDestroy()方法来初始化和关闭服务的任何资源或操作。
在清单上声明服务
服务组件必须使用
1
2
3
4
5
6
7
8
9
|
<
manifest
... >
...
<
application
... >
<
service
android:name
=
".ExampleService"
android:process
=
":my_process"
/>
...
application
>
manifest
>
|
2.2。 使用启动服务
要启动已启动的服务,必须调用Context.startService()方法。 Intent必须使用Context和Service类创建。 任何相关信息或数据也应在本意图中传递。
1
2
3
4
5
6
7
8
|
Intent serviceIntent =
new
Intent(
this
, CustomService.
class
);
// Pass data to be processed on the Service
Bundle data =
new
Bundle();
data.putInt(
"OperationType"
,
99
);
data.putString(
"DownloadURL"
,
"http://mydownloadurl.com"
);
serviceIntent.putExtras(data);
// Starting the Service
startService(serviceIntent);
|
在你的Service类中,你应该关心的方法是onStartCommand()。 在这个方法上,你应该调用任何你想在启动的服务上执行的操作。 您将处理Intent以捕获客户端发送的信息。 startId表示为此特定请求自动创建的唯一ID,标记还可以包含有关该标识的额外信息。
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
@Override
public
int
onStartCommand(Intent intent,
int
flags,
int
startId) {
Bundle data = intent.getExtras();
if
(data !=
null
) {
int
operation = data.getInt(KEY_OPERATION);
// Check what operation to perform and send a msg
if
( operation == OP_DOWNLOAD){
// make a download
}
}
return
START_STICKY;
}
|
onStartCommand()返回一个控制行为的常量int:
Service.START_STICKY:如果服务终止,服务将重新启动。
服务.START_NOT_STICKY:服务未重新启动。
Service.START_REDELIVER_INTENT:服务在崩溃后重新启动,并且Intents然后处理将重新提交。
如前所述,启动的服务需要停止,否则将无限期运行。 这可以通过Service本身调用stopSelf()或调用stopService()的客户端来完成。
1
2
3
4
5
|
void
someOperation() {
// do some long-running operation
// and stop the service when it is done
stopSelf();
}
|
绑定到服务
组件可以创建与服务的连接,与它们建立双向通信。 客户端必须调用Context.bindService(),传递Intent,ServiceConnection接口和标志作为参数。 一个服务可以绑定到多个客户端,一旦没有客户端连接到它,它将被销毁。
void
bindWithService() {
Intent intent =
new
Intent(
this
, PlayerService.
class
);
// bind with Service
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
It's possible to send Message
objects to services. To do it you'll need to create a Messenger
on the client side in a ServiceConnection.onServiceConnected
interface implementation and use it to send Message
objects to theService
.
可以向服务发送Message对象。 为此,您需要在客户端在ServiceConnection.onServiceConnected接口实现中创建一个Messenger,并使用它来向服务发送Message对象。
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
private
ServiceConnection mConnection =
new
ServiceConnection() {
@Override
public
void
onServiceConnected(ComponentName className,
IBinder service) {
// use the IBinder received to create a Messenger
mServiceMessenger =
new
Messenger(service);
mBound =
true
;
}
@Override
public
void
onServiceDisconnected(ComponentName arg0) {
mBound =
false
;
mServiceMessenger =
null
;
}
};
|
Messenger
to the
Service
for the client to receive messages. Watch out though, because the client may not no longer be around to receive the service's message. You could also use
BroadcastReceiver
or any other broadcast solution.
还可以将响应Messenger传递给服务以供客户端接收消息。 注意,但是,因为客户端可能不再在周围接收服务的消息。 您还可以使用BroadcastReceiver或任何其他广播解决方案。
01
02
03
04
05
06
07
08
09
10
11
12
13
|
private
Handler mResponseHandler =
new
Handler() {
@Override
public
void
handleMessage(Message msg) {
// handle response from Service
}
};
Message msgReply = Message.obtain();
msgReply.replyTo =
new
Messenger(mResponseHandler);
try
{
mServiceMessenger.send(msgReply);
}
catch
(RemoteException e) {
e.printStackTrace();
}
|
It's important to unbind from the Service when the client is being destroyed.
当客户端被销毁时,解除绑定到服务很重要。
1
2
3
4
5
6
7
8
9
|
@Override
protected
void
onDestroy() {
super
.onDestroy();
// disconnect from service
if
(mBound) {
unbindService(mConnection);
mBound =
false
;
}
}
|
On the Service
side, you must implement the Service.onBind()
method, providing an IBinder
provided from aMessenger
. This will relay a response Handler
to handle the Message
objects received from client.
在服务端,您必须实现Service.onBind()方法,提供从Messenger提供的IBinder。 这将中继响应处理程序以处理从客户端接收的消息对象。
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
IncomingHandler(PlayerService playerService) {
mPlayerService =
new
WeakReference<>(playerService);
}
@Override
public
void
handleMessage(Message msg) {
// handle messages
}
}
public
IBinder onBind(Intent intent) {
// pass a Binder using the Messenger created
return
mMessenger.getBinder();
}
final
Messenger mMessenger =
new
Messenger(
new
IncomingHandler(
this
));
|
3并发使用服务
最后,现在是讨论如何使用服务解决并发问题的时候了。如前所述,标准服务不包含任何额外的线程,它将默认在主线程上运行。要克服这个问题,您必须添加一个工作线程,一个线程池或在不同的进程上执行服务。您还可以使用已经包含Thread的名为IntentService的Service子类。
在服务线程上运行服务
要使服务在后台线程上执行,您可以创建一个额外的线程并在那里运行作业。但Android为我们提供了更好的解决方案。充分利用系统的一种方法是在服务中实现HaMeR框架,例如通过使用可以无限地处理消息的消息队列循环线程。
重要的是要理解这个实现将顺序处理任务。如果您需要同时接收和处理多个任务,则应使用线程池。使用线程池超出了本教程的范围,我们今天不会谈论它。
要使用HaMeR,您必须向服务提供Looper,处理程序和HandlerThread。
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
private
Looper mServiceLooper;
private
ServiceHandler mServiceHandler;
// Handler to receive messages from client
private
final
class
ServiceHandler
extends
Handler {
ServiceHandler(Looper looper) {
super
(looper);
}
@Override
public
void
handleMessage(Message msg) {
super
.handleMessage(msg);
// handle messages
// stopping Service using startId
stopSelf( msg.arg1 );
}
}
@Override
public
void
onCreate() {
HandlerThread thread =
new
HandlerThread(
"ServiceThread"
,
Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler =
new
ServiceHandler(mServiceLooper);
}
|
If there is no need for the Service
to be kept alive for a long time, you could use IntentService
, a Service
subclass that's ready to run tasks on background threads. Internally, IntentService
is a Service
with a very similar implementation to the one proposed above.
To use this class, all you have to do is extend it and implement the onHandleIntent()
, a hook method that will be called every time a client calls startService()
on this Service
. It's important to keep in mind that theIntentService
will stop as soon as its job is completed.
IntentService
如果不需要长时间保持服务,您可以使用IntentService,一个准备在后台线程上运行任务的服务子类。 在内部,IntentService是一个具有非常类似于上面提出的实现的服务。
要使用这个类,所有你需要做的是扩展它并实现onHandleIntent(),一个钩子方法,每当客户端调用此服务上的startService()时将被调用。 重要的是要记住IntentService将在其作业完成后立即停止。
01
02
03
04
05
06
07
08
09
10
11
|
public
class
MyIntentService
extends
IntentService {
public
MyIntentService() {
super
(
"MyIntentService"
);
}
@Override
protected
void
onHandleIntent(Intent intent) {
// handle Intents send by startService
}
}
|
A Service
can run on a completely different Process
, independently from all tasks that are happening on the main process. A process has its own memory allocation, thread group, and processing priorities. This approach can be really useful when you need to work independently from the main process.
Communication between different processes is called IPC (Inter Process Communication). In a Service
there are two main ways to do IPC: using a Messenger
or implementing an AIDL
interface.
We've learned how to send and receive messages between services. All that you have to do is use create aMessenger
using the IBinder
instance received during the connection process and use it to send a reply Messenger
back to the Service
.
IPC(进程间通信)
服务可以在完全不同的进程上运行,独立于主进程上发生的所有任务。进程具有自己的内存分配,线程组和处理优先级。当您需要独立于主流程工作时,此方法可以真正有用。
不同进程之间的通信称为IPC(进程间通信)。在服务中有两种主要方式来做IPC:使用Messenger或实现AIDL接口。
我们学习了如何在服务之间发送和接收消息。所有你需要做的是使用在连接过程中收到的IBinder实例创建一个Messenger,并使用它来发送一个回复Messenger到服务。
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
private
Handler mResponseHandler =
new
Handler() {
@Override
public
void
handleMessage(Message msg) {
// handle response from Service
}
};
private
ServiceConnection mConnection =
new
ServiceConnection() {
@Override
public
void
onServiceConnected(ComponentName className,
IBinder service) {
// use the IBinder received to create a Messenger
mServiceMessenger =
new
Messenger(service);
Message msgReply = Message.obtain();
msgReply.replyTo =
new
Messenger(mResponseHandler);
try
{
mServiceMessenger.send(msgReply);
}
catch
(RemoteException e) {
e.printStackTrace();
}
}
|
The AIDL
interface is a very powerful solution that allows direct calls on Service
methods running on different processes and it's appropriate to use when your Service
is really complex. However, AIDL
is complicated to implement and it's rarely used, so its use won't be discussed in this tutorial.
4. Conclusion
Services can be simple or complex. It depends on the needs of your application. I tried to cover as much ground as possible on this tutorial, however I've focused just on using services for concurrency purposes and there are more possibilities for this component. I you want to study more, take a look at the documentation and Android guides.
4。结论
服务可以简单或复杂。 这取决于您的应用程序的需要。 我试图在本教程覆盖尽可能多的地面,但我只专注于使用服务的并发目的,这个组件有更多的可能性。 我想学习更多,看看文档和Android指南。