service是一个应用程序组件,表示应用程序希望在不与用户交互的情况下执行长时间运行的操作,或者提供供其他应用程序使用的功能。每个service在其包的AndroidManifest.xml使用<service>进行注册. service可以用Context.startService()和Context.bindService()两种方式进行启动。
请注意,与其他应用程序对象一样,servce在其宿主进程的主线程中运行。这意味着,如果您的service要执行任何CPU密集型(如MP3播放)或阻塞(如网络)操作,它应该生成自己的线程来完成这些工作。有关这方面的更多信息可以在进程和线程中找到。JobIntentService类是作为服务的标准实现提供的,它有自己的线程,它在其中调度要完成的工作。
What is a Service?
关于service的大多数困惑实际上围绕着它不是什么:
- service不是一个单独的进程。服务对象本身并不意味着它在自己的进程中运行;除非另有规定,否则它与它所属的应用程序在同一个进程中运行。
- service不是线程。它本身并不是在主线程之外执行工作的方法(以避免应用程序不响应错误)。
因此,service本身实际上非常简单,提供两个主要功能:
- 应用程序告诉系统它想在后台做什么操作(即用户没有直接与应用程序交互)。这对应于调用Context.startService(),它要求系统为服务安排工作,直到service自己或其他人明确停止服务为止。
- 应用程序向其他应用程序公开其某些功能的工具。这对应于调用context.bindService(),它允许与服务建立长期连接,以便与服务交互。
在实际创建service组件时,出于上述任一原因,系统实际所做的只是实例化该组件,并在主线程上调用其onCreate()和任何其他适当的回调。这取决于服务以适当的行为来实现这些功能,例如创建一个辅助线程,它在其中执行其工作。
请注意,由于服service本身非常简单,所以您可以根据需要使与它的交互变得简单或复杂:从将其视为直接调用方法的本地Java对象(就像它一样======>Local Service Sample),到使用AIDL提供完全可远程访问的接口。
Service 生命周期
系统可以运行服务的原因有两个。如果有人调用,Context.startService()则系统将检索服务(创建服务并onCreate()在需要时调用其方法),然后onStartCommand(Intent, int, int)使用客户端提供的参数调用其方法。该服务将在此时继续运行,直到Context.stopService()或被 stopSelf()调用。请注意,对Context.startService()的多次调用不会嵌套(尽管它们确实会导致对onStartCommand()的多次相应调用),因此,无论启动了多少次,一旦Context.stopService()或stopSelf都会停止服务() 叫做; 但是,服务可以使用其stopSelf(int)方法来确保在处理启动的意图之前不停止服务。
对于启动的服务,它们可以决定运行在onStartCommand()上的值而定的另外两种主要操作模式:START_STICKY用于根据需要显式启动和停止的服务,而用于START_NOT_STICKY或START_REDELIVER_INTENT用于仅应在处理发送给他们的任何命令时保持运行状态。有关语义的更多详细信息,请参见链接的文档。
客户端还可以Context.bindService()用于获取与服务的持久连接。如果尚未运行该服务,则同样会创建该服务(onCreate()在运行时进行调用),但不会调用onStartCommand()。客户端IBinder将从其onBind(Intent)方法接收服务返回的 对象 ,从而允许客户端随后调用该服务。只要建立了连接,该服务就会一直运行(无论客户端是否在该服务的IBinder上保留引用)。通常,返回的IBinder是针对已经用aidl编写的复杂接口的。
服务既可以启动,也可以绑定连接。在这种情况下,只要服务启动或带有该Context.BIND_AUTO_CREATE标志的一个或多个连接 ,系统就会保持该服务运行。一旦这两种情况都不成立,onDestroy()便会调用服务的方法,并有效终止该服务。从onDestroy()返回时,所有清理(停止线程,注销接收器)都应完成。
Permissions
在清单的<service> 标记中声明服务时,可以强制对服务进行全局访问。这样,其他应用程序将需要<uses-permission> 在自己的清单中声明一个相应的 元素,以便能够启动,停止或绑定到服务。
从开始Build.VERSION_CODES.GINGERBREAD,使用时 Context#startService(Intent),您还可以设置`Intent#FLAG_GRANT_READ_URI_PERMISSION和/或Intent#FLAG_GRANT_WRITE_URI_PERMISSION使用Intent。这将向服务授予对Intent中特定URI的临时访问权限。访问将一直保留,直到服务调用stopSelf(int)了该启动命令或更高版本为止,或者直到服务完全停止为止。这适用于授予对未请求保护服务权限的其他应用程序的访问权限,甚至在根本不导出服务时也是如此。
此外,服务可以通过ContextWrapper.checkCallingPermission(String) 在执行该调用的实现之前调用该方法来保护具有权限的单个IPC调用 。
有关 一般权限和安全性的更多信息,请参见“ 安全性和权限”文档。
Process LifeCycle
只要服务已启动或绑定了客户端,Android系统就会尝试保持托管该服务的进程。当内存不足并需要终止现有进程时,承载该服务的进程的优先级将是以下可能性中的较高者:
-
如果服务当前正在其onCreate(),onStartCommand()或onDestroy()方法中执行代码 ,则托管进程将是前台进程,以确保该代码可以执行而不会被杀死。
-
如果服务已启动,则其托管过程被认为比当前用户在屏幕上可见的任何过程都没有重要,但比任何不可见的过程都更重要。因为通常只有几个进程对用户可见,所以这意味着除非内存不足,否则不应终止该服务。但是,由于用户并不直接知道后台服务,因此在这种状态下,它被 认为是有效的杀害对象,因此您应该为此做好准备。特别是,长时间运行的服务如果保持足够长的启动时间,将越来越有可能被终止,并保证将其终止(并在适当时重新启动)。
-
如果有绑定到该服务的客户端,那么该服务的托管过程将比最重要的客户端重要。也就是说,如果用户的客户端之一对用户可见,则该服务本身被视为可见。该方法在客户端的重要性,影响到服务的重要性,可以通过调整Context#BIND_ABOVE_CLIENT, Context#BIND_ALLOW_OOM_MANAGEMENT,Context#BIND_WAIVE_PRIORITY, Context#BIND_IMPORTANT,和Context#BIND_ADJUST_WITH_ACTIVITY。
-
启动的服务可以使用startForeground(int, android.app.Notification) API将服务置于前台状态,在该状态下,系统认为该服务是用户主动意识到的东西,因此在内存不足时不是杀死用户的候选者。(在理论上,仍然有可能在当前前台应用程序承受极大内存压力的情况下终止服务,但实际上,这不必担心。)
请注意,这意味着在您的服务运行的大部分时间里,如果它承受沉重的内存压力,则系统可能会将其杀死。如果发生这种情况,系统稍后将尝试重新启动服务。这样做的一个重要后果是,如果您实现onStartCommand() 将工作安排为异步或在另一个线程中进行调度,那么您可能希望使用START_FLAG_REDELIVERY该系统来为您重新提供一个Intent,这样,如果您的服务是在处理过程中被杀死。
Activity当然,与服务在同一进程中运行的其他应用程序组件(例如)可以增加整个流程的重要性,而不仅仅是服务本身的重要性。
Local Service Sample
服务最常见的用途之一是作为次要组件与应用程序的其他部分一起运行,其处理过程与其余组件相同。除非另有明确说明,.apk的所有组件都在同一进程中运行,所以这是一种典型情况。
当以这种方式使用时,通过假定组件处于同一过程中,可以大大简化它们之间的交互:服务的客户端可以简单地将从它们接收到的IBinder转换为服务发布的具体类。
这里显示了使用服务的示例。首先是服务本身,在绑定时发布自定义类:
public class LocalService extends Service {
private NotificationManager mNM;
// Unique Identification Number for the Notification.
// We use it on Notification start, and to cancel it.
private int NOTIFICATION = R.string.local_service_started;
/**
* Class for clients to access. Because we know this service always
* runs in the same process as its clients, we don't need to deal with
* IPC.
*/
public class LocalBinder extends Binder {
LocalService getService() {
return LocalService.this;
}
}
@Override
public void onCreate() {
mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
// Display a notification about us starting. We put an icon in the status bar.
showNotification();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("LocalService", "Received start id " + startId + ": " + intent);
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
// Cancel the persistent notification.
mNM.cancel(NOTIFICATION);
// Tell the user we stopped.
Toast.makeText(this, R.string.local_service_stopped, Toast.LENGTH_SHORT).show();
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
// This is the object that receives interactions from clients. See
// RemoteService for a more complete example.
private final IBinder mBinder = new LocalBinder();
/**
* Show a notification while this service is running.
*/
private void showNotification() {
// In this sample, we'll use the same text for the ticker and the expanded notification
CharSequence text = getText(R.string.local_service_started);
// The PendingIntent to launch our activity if the user selects this notification
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
new Intent(this, LocalServiceActivities.Controller.class), 0);
// Set the info for the views that show in the notification panel.
Notification notification = new Notification.Builder(this)
.setSmallIcon(R.drawable.stat_sample) // the status icon
.setTicker(text) // the status text
.setWhen(System.currentTimeMillis()) // the time stamp
.setContentTitle(getText(R.string.local_service_label)) // the label of the entry
.setContentText(text) // the contents of the entry
.setContentIntent(contentIntent) // The intent to send when the entry is clicked
.build();
// Send the notification.
mNM.notify(NOTIFICATION, notification);
}
}
完成之后,现在可以编写直接访问正在运行的服务的客户端代码,例如:
/**
* Example of binding and unbinding to the local service.
* bind to, receiving an object through which it can communicate with the service.
*
* Note that this is implemented as an inner class only keep the sample
* all together; typically this code would appear in some separate class.
*/
public static class Binding extends Activity {
// Don't attempt to unbind from the service unless the client has received some
// information about the service's state.
private boolean mShouldUnbind;
// To invoke the bound service, first make sure that this value
// is not null.
private LocalService mBoundService;
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// This is called when the connection with the service has been
// established, giving us the service object we can use to
// interact with the service. Because we have bound to a explicit
// service that we know is running in our own process, we can
// cast its IBinder to a concrete class and directly access it.
mBoundService = ((LocalService.LocalBinder)service).getService();
// Tell the user about this for our demo.
Toast.makeText(Binding.this, R.string.local_service_connected,
Toast.LENGTH_SHORT).show();
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
// Because it is running in our same process, we should never
// see this happen.
mBoundService = null;
Toast.makeText(Binding.this, R.string.local_service_disconnected,
Toast.LENGTH_SHORT).show();
}
};
void doBindService() {
// Attempts to establish a connection with the service. We use an
// explicit class name because we want a specific service
// implementation that we know will be running in our own process
// (and thus won't be supporting component replacement by other
// applications).
if (bindService(new Intent(Binding.this, LocalService.class),
mConnection, Context.BIND_AUTO_CREATE)) {
mShouldUnbind = true;
} else {
Log.e("MY_APP_TAG", "Error: The requested service doesn't " +
"exist, or this client isn't allowed access to it.");
}
}
void doUnbindService() {
if (mShouldUnbind) {
// Release information about the service's state.
unbindService(mConnection);
mShouldUnbind = false;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
doUnbindService();
}
Remote Messenger Service Sample
如果您需要能够编写一个服务,该服务可以与远程进程中的客户端执行复杂的通信(不只是使用 Context#startService(Intent)向其发送命令),那么可以使用Messenger该类而不是编写完整的AIDL文件。
此处显示了一个使用Messenger作为其客户端界面的服务示例。首先是服务本身,绑定时将Messenger发布到内部Handler:
public class MessengerService extends Service {
/** For showing and hiding our notification. */
NotificationManager mNM;
/** Keeps track of all current registered clients. */
ArrayList<Messenger> mClients = new ArrayList<Messenger>();
/** Holds last value set by a client. */
int mValue = 0;
/**
* Command to the service to register a client, receiving callbacks
* from the service. The Message's replyTo field must be a Messenger of
* the client where callbacks should be sent.
*/
static final int MSG_REGISTER_CLIENT = 1;
/**
* Command to the service to unregister a client, ot stop receiving callbacks
* from the service. The Message's replyTo field must be a Messenger of
* the client as previously given with MSG_REGISTER_CLIENT.
*/
static final int MSG_UNREGISTER_CLIENT = 2;
/**
* Command to service to set a new value. This can be sent to the
* service to supply a new value, and will be sent by the service to
* any registered clients with the new value.
*/
static final int MSG_SET_VALUE = 3;
/**
* Handler of incoming messages from clients.
*/
class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_REGISTER_CLIENT:
mClients.add(msg.replyTo);
break;
case MSG_UNREGISTER_CLIENT:
mClients.remove(msg.replyTo);
break;
case MSG_SET_VALUE:
mValue = msg.arg1;
for (int i=mClients.size()-1; i>=0; i--) {
try {
mClients.get(i).send(Message.obtain(null,
MSG_SET_VALUE, mValue, 0));
} catch (RemoteException e) {
// The client is dead. Remove it from the list;
// we are going through the list from back to front
// so this is safe to do inside the loop.
mClients.remove(i);
}
}
break;
default:
super.handleMessage(msg);
}
}
}
/**
* Target we publish for clients to send messages to IncomingHandler.
*/
final Messenger mMessenger = new Messenger(new IncomingHandler());
@Override
public void onCreate() {
mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
// Display a notification about us starting.
showNotification();
}
@Override
public void onDestroy() {
// Cancel the persistent notification.
mNM.cancel(R.string.remote_service_started);
// Tell the user we stopped.
Toast.makeText(this, R.string.remote_service_stopped, Toast.LENGTH_SHORT).show();
}
/**
* When binding to the service, we return an interface to our messenger
* for sending messages to the service.
*/
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
/**
* Show a notification while this service is running.
*/
private void showNotification() {
// In this sample, we'll use the same text for the ticker and the expanded notification
CharSequence text = getText(R.string.remote_service_started);
// The PendingIntent to launch our activity if the user selects this notification
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
new Intent(this, Controller.class), 0);
// Set the info for the views that show in the notification panel.
Notification notification = new Notification.Builder(this)
.setSmallIcon(R.drawable.stat_sample) // the status icon
.setTicker(text) // the status text
.setWhen(System.currentTimeMillis()) // the time stamp
.setContentTitle(getText(R.string.local_service_label)) // the label of the entry
.setContentText(text) // the contents of the entry
.setContentIntent(contentIntent) // The intent to send when the entry is clicked
.build();
// Send the notification.
// We use a string id because it is a unique number. We use it later to cancel.
mNM.notify(R.string.remote_service_started, notification);
}
}
如果要使该服务在远程进程中运行(而不是其.apk的标准服务),则可以android:process在其清单标记中使用它来指定一个:
<service android:name=".app.MessengerService"
android:process=":remote" />
请注意,此处选择的名称“ remote”是任意的,如果需要其他处理,则可以使用其他名称。前缀“:”将名称附加到程序包的标准进程名称中。
完成此操作后,客户端现在可以绑定到服务并向其发送消息。请注意,这允许客户端向其注册以接收回邮件:
/**
* Example of binding and unbinding to the remote service.
* This demonstrates the implementation of a service which the client will
* bind to, interacting with it through an aidl interface.
*
* Note that this is implemented as an inner class only keep the sample
* all together; typically this code would appear in some separate class.
*/
public static class Binding extends Activity {
/** Messenger for communicating with service. */
Messenger mService = null;
/** Flag indicating whether we have called bind on the service. */
boolean mIsBound;
/** Some text view we are using to show state information. */
TextView mCallbackText;
/**
* Handler of incoming messages from service.
*/
class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MessengerService.MSG_SET_VALUE:
mCallbackText.setText("Received from service: " + msg.arg1);
break;
default:
super.handleMessage(msg);
}
}
}
/**
* Target we publish for clients to send messages to IncomingHandler.
*/
final Messenger mMessenger = new Messenger(new IncomingHandler());
/**
* Class for interacting with the main interface of the service.
*/
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className,
IBinder service) {
// This is called when the connection with the service has been
// established, giving us the service object we can use to
// interact with the service. We are communicating with our
// service through an IDL interface, so get a client-side
// representation of that from the raw service object.
mService = new Messenger(service);
mCallbackText.setText("Attached.");
// We want to monitor the service for as long as we are
// connected to it.
try {
Message msg = Message.obtain(null,
MessengerService.MSG_REGISTER_CLIENT);
msg.replyTo = mMessenger;
mService.send(msg);
// Give it some value as an example.
msg = Message.obtain(null,
MessengerService.MSG_SET_VALUE, this.hashCode(), 0);
mService.send(msg);
} catch (RemoteException e) {
// In this case the service has crashed before we could even
// do anything with it; we can count on soon being
// disconnected (and then reconnected if it can be restarted)
// so there is no need to do anything here.
}
// As part of the sample, tell the user what happened.
Toast.makeText(Binding.this, R.string.remote_service_connected,
Toast.LENGTH_SHORT).show();
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
mService = null;
mCallbackText.setText("Disconnected.");
// As part of the sample, tell the user what happened.
Toast.makeText(Binding.this, R.string.remote_service_disconnected,
Toast.LENGTH_SHORT).show();
}
};
void doBindService() {
// Establish a connection with the service. We use an explicit
// class name because there is no reason to be able to let other
// applications replace our component.
bindService(new Intent(Binding.this,
MessengerService.class), mConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
mCallbackText.setText("Binding.");
}
void doUnbindService() {
if (mIsBound) {
// If we have received the service, and hence registered with
// it, then now is the time to unregister.
if (mService != null) {
try {
Message msg = Message.obtain(null,
MessengerService.MSG_UNREGISTER_CLIENT);
msg.replyTo = mMessenger;
mService.send(msg);
} catch (RemoteException e) {
// There is nothing special we need to do if the service
// has crashed.
}
}
// Detach our existing connection.
unbindService(mConnection);
mIsBound = false;
mCallbackText.setText("Unbinding.");
}
}
网友评论