在本教程中,我们将探索Service
组件及其超类IntentService
。 您将学习何时以及如何使用此组件为长时间运行的后台操作创建出色的并发解决方案。 我们还将快速浏览IPC (进程间通信),以学习如何与运行在不同进程上的服务进行通信。
1.服务组件
Service
组件是Android并发框架中非常重要的一部分。 它满足了在应用程序中执行长时间运行的需求,或者为其他应用程序提供了某些功能。 在本教程中,我们将只专注于Service
的长期运行任务功能,以及如何使用此功能来提高并发性。
什么是服务?
Service
是一个简单的组件,由系统实例化以执行一些长期运行的工作,这些工作不一定依赖于用户交互。 它可以独立于活动生命周期,也可以在完全不同的过程中运行。
在深入讨论Service
代表什么之前,必须强调一点,即使服务通常用于长时间运行的后台操作并在不同的进程上执行任务, Service
也不代表Thread
或进程 。 如果明确要求,它将仅在后台线程或其他进程中运行。
Service
具有两个主要功能:
- 应用程序的一种功能,可以在后台将系统要执行的操作告知系统。
- 应用程序将其某些功能公开给其他应用程序的工具。
服务和线程
关于服务和线程有很多困惑。 声明Service
,它不包含Thread
。 实际上,默认情况下,它直接在主线程上运行,并且对它所做的任何工作都可能冻结应用程序。 (除非它是IntentService
,它是已经配置了工作线程的Service
子类。)
那么,服务如何提供并发解决方案? 嗯, Service
默认情况下不包含线程,但是可以轻松地将其配置为与自己的线程或线程池一起使用。 我们将在下面看到更多信息。
无论缺少内置线程,对于某些情况下的并发问题, Service
都是一个很好的解决方案。 选择Service
不是其他并发解决方案(例如AsyncTask
或HaMeR框架)的主要原因是:
-
Service
可以独立于活动生命周期。 -
Service
适合于长时间运行。 - 服务不依赖于用户交互。
- 当在不同的进程上运行时,即使系统资源短缺,Android也可以尝试保持服务活动。
- 可以重新启动
Service
以恢复其工作。
服务类型
有两种Service
类型,即启动和绑定。
已启动的服务通过Context.startService()
启动。 通常,它仅执行一项操作,并且会无限期运行,直到操作结束,然后它会自行关闭。 通常,它不会向用户界面返回任何结果。
绑定服务是通过Context.bindService()
启动的,它允许客户端和Service
之间进行双向通信。 它还可以与多个客户端连接。 当没有任何客户端连接时,它会自我毁灭。
要在这两种类型之间进行选择, Service
必须实现一些回调: onStartCommand()
作为启动服务运行, onBind()
作为绑定服务运行。 Service
可以选择仅实现这些类型中的一种,但也可以同时采用这两种类型,而不会出现任何问题。
2.服务实施
要使用的服务,扩展的Service
类并覆盖其回调方法,根据类型Service
。 如前所述,对于启动的服务,必须实现onStartCommand()
方法,对于绑定的服务,必须实现onBind()
方法。 实际上, onBind()
必须为这两种服务类型声明方法,但是对于已启动的服务,它可以返回null。
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()
方法来初始化和关闭服务的任何资源或操作。
声明舱单服务
必须在清单上使用<service>
元素声明Service
组件。 在此声明中,也可以但非强制性地设置Service
运行的其他过程。
<manifest ... >
...
<application ... >
<service
android:name=".ExampleService"
android:process=":my_process"/>
...
</application>
</manifest>
使用启动服务
要启动已启动的服务,必须调用Context.startService()
方法。 必须使用Context
和Service
类创建Intent
。 任何相关信息或数据也应在此Intent
传递。
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", "https://mydownloadurl.com");
serviceIntent.putExtras(data);
// Starting the Service
startService(serviceIntent);
在Service
类中,您应关注的方法是onStartCommand()
。 在此方法上,您应该调用要在启动的服务上执行的任何操作。 您将处理Intent
以捕获客户端发送的信息。 startId
代表为该特定请求自动创建的唯一ID,并且flags
也可以包含有关此请求的其他信息。
@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
:如果服务终止,则重新启动。
- 服务
Service.START_NOT_STICKY
:服务未重新启动。
-
Service.START_REDELIVER_INTENT
:崩溃后,服务将重新启动,然后将重新传送其意图。
如前所述,已启动的服务需要停止,否则它将无限期地运行。 这可以通过Service
本身调用stopSelf()
或客户端对其调用stopService()
来完成。
void someOperation() {
// do some long-running operation
// and stop the service when it is done
stopSelf();
}
绑定服务
组件可以与服务建立连接,并与它们建立双向通信。 客户端必须调用Context.bindService()
,并传递Intent
, ServiceConnection
接口和flag
作为参数。 一个Service
可以绑定到多个客户端,并且在没有客户端连接的情况下它将被销毁。
void bindWithService() {
Intent intent = new Intent(this, PlayerService.class);
// bind with Service
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
可以将Message
对象发送到服务。 为此,您需要在ServiceConnection.onServiceConnected
接口实现中的客户端上创建Messenger
,并将其用于将Message
对象发送到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);
mBound = true;
}
@Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
mServiceMessenger = null;
}
};
也可以将响应Messenger
传递给Service
以使客户端接收消息。 但是请当心,因为客户端可能不再会收到该服务的消息。 您也可以使用BroadcastReceiver
或任何其他广播解决方案。
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();
}
销毁客户端时,取消与服务的绑定很重要。
@Override
protected void onDestroy() {
super.onDestroy();
// disconnect from service
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
在Service
方面,您必须实现Service.onBind()
方法, IBinder
提供从Messenger
提供的IBinder
。 这将中继一个响应Handler
以处理从客户端收到的Message
对象。
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并发使用服务
最后,是时候讨论如何使用服务解决并发问题了。 如前所述,标准Service
不包含任何额外的线程,默认情况下它将在主Thread
上运行。 为了克服这个问题,您必须添加一个辅助Thread
,线程池或在其他进程上执行Service
。 您还可以使用一个名为IntentService
的Service
子类,该子类已经包含Thread
。
使服务在工作线程上运行
要使Service
在后台Thread
上执行,您可以只创建一个额外的Thread
并在那里运行作业。 但是Android为我们提供了更好的解决方案。 充分利用该系统的一种方法是在Service
内部实现HaMeR框架,例如,通过将Thread
与可以无限期处理消息的消息队列进行循环。
重要的是要了解此实现将按顺序处理任务。 如果需要同时接收和处理多个任务,则应使用线程池。 使用线程池不在本教程的讨论范围内,我们今天不再讨论。
要使用HaMeR,必须为Service
提供一个Looper
,一个Handler
和一个HandlerThread
。
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);
}
IntentService
如果不需要长时间保持Service
处于活动状态,则可以使用IntentService
,它是一个Service
子类,可以在后台线程上运行任务。 在内部, IntentService
是一种Service
,其实现与上述提议的实现非常相似。
要使用此类,您要做的就是扩展它并实现onHandleIntent()
,这是一个挂钩方法,每次客户端在此Service
上调用startService()
时都会调用该方法。 重要的是要记住, IntentService
将在其工作完成后立即停止。
public class MyIntentService extends IntentService {
public MyIntentService() {
super("MyIntentService");
}
@Override
protected void onHandleIntent(Intent intent) {
// handle Intents send by startService
}
}
IPC(进程间通信)
Service
可以在完全不同的Process
上运行,而独立于主流程上发生的所有任务。 进程具有自己的内存分配,线程组和处理优先级。 当您需要独立于主要流程工作时,此方法非常有用。
不同进程之间的通信称为IPC(进程间通信) 。 在Service
,有两种主要的IPC方法:使用Messenger
或实现AIDL
接口。
我们已经学习了如何在服务之间发送和接收消息。 所有你需要做的就是用创建一个Messenger
使用IBinder
在连接过程中接收到的实例,并用它来发送回复 Messenger
回Service
。
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();
}
}
AIDL
接口是一个非常强大的解决方案,它允许直接调用在不同进程上运行的Service
方法,并且当您的Service
非常复杂时,它是合适的。 但是, AIDL
实现复杂且很少使用,因此在本教程中不会讨论它的使用。
4.结论
服务可以是简单的也可以是复杂的。 这取决于您的应用程序的需求。 我在本教程中尝试了尽可能多的内容,但是我只专注于将服务用于并发目的,并且该组件还有更多的可能性。 我想进一步研究,请参阅文档和Android指南 。
再见!
翻译自: https://code.tutsplus.com/tutorials/concurrency-on-android-with-service--cms-27277