Service 官方解释。
Class Overview
service 是实现一个需要长时间运行的操作,并提供结果给应用,而且不去影响用户其他操作的应用组件。每一个Service都必须在AndroidMainifest.xml中进行声明。Services可以使用Context.startservice()和Context.bindservice()进行启动。
注意Service,和其他应用对象一样。运行在所在进程的主线程中。这样就意味着如果你的Service需要执行一些耗时的操作如:音乐播放、网络连接,就需要开辟一个新的线程去完成这些操作。IntentService(一个子类)就是一个标准的由独立的工作线程去完成工作的Service
What is a Service?
要了解Service,首先要确定Service不是什么:
1.Service不是一个从应用中分离出来的进程,一个Service不是在暗示它是工作在一个自己的新线程中。除非特殊情况下,Service是工作在应用的同一线程内作为应用的一部分。
2.Service不是一个线程,所以它不是脱离主线程在独立工作(所以耗时操作需要开辟独立线程)
Service其实非常简单,适用于两种主要的场景:
1.应用中的一个设备去告诉操作系统有些东西希望在后台运行(当用户不直接与应用进行交互的时候),这时候去调用Context.startservice(),让系统把工作安排给Service,直到Service或其他线程等去明确的停止它(Service的优先级比较高,一般情况下不会被系统自动停止)
2.应用中的一个设备去把本应用的功能暴露给其他应用去使用,这时候去调用Context.bindService(),允许其他应用与Service进行一个长连接Servic进行交互
当一个Service由于上面的原因被当做应用组件创建的时候,操作系统实际上做得就是实例化Service并调用他的onCreate(),并且在主线程中添加上一些适当的回调操作,这些操作取决于Service本身,例如创建另一个工作线程。
注意虽然Service本身非常简单,但是你可以更改和Service之间的交互操作使其变得复杂或者简单,就像把Service当做一个local java object 去修改它里面的方法,然后使用AIDL提供一个完整的接口。
Service Lifecycle
一个Service运行在系统中有两个原因,一个是调用了Context.startService(),然后系统就会新建Service(创建Service并且调用onCreate() ),之后调用onStartConmmand(Intent, int, int)方法其中参数由client提供,这时Service会一直运行直到调用Context.stopService() 或者stopSelf()。注意多次调用Context.Service()不会多次创建(即使多次调用了onStartConmmend()),所以无论调用了几次Service,一旦调用Context.stopService(),或者stopSelf(),Service就会被停止。可是,Service本身可以通过调用stopSelf(int)来确定在启动的Intent被处理前,Service没有停止。
启动一个Service,你有两种运行模式可以进行选择,取决于onStartCommand()的返回值,START_STICKY表示一个Service需要明确的Started和Stoped,而START_NOT_STICKY或者START_REDELIVER_INTENT表示Service需要保持一直运行的状态等待进程发来请求。
Clients可以通过Context.bindService()获取一个Service的持续的连接,如果Service还没有启动通过调用它也可以创建该Service(同时调用onCreate()),但是还没有调用onStartConmmand()。Clients会接收到一个从Service 的onBinde(Intent)方法返回的IBinder对象,允许Clients在之后对Service的回调。Service会保持运行只要连接被建立(无论Clients是否持有Service 的 IBinder的引用)。通常返回的IBinder复杂接口已经写在了AIDL里面。
一个Service可以同时被启动和绑定。在这种情况下,操作系统会保持Service运行只要它被启动,或者有一个或多个连接(通过Context.BIND_AUTO_CREATE查看连接数)。一旦两种情况都不存在,Service会调用onDestroy()方法,终止Service。清理所有(停止线程,注销接收器),在得到onDestory()返回值之前完成。
Permissions
在manifest中<service>标签中可以把Service声明为全局变量。这样做之后,其他应用需要声明一个相应的<users-permission>在他们自己的manifest中,用来启动、停止、绑定这个Service。
Android 2.3 之后,当你使用Context.startService(Intent),你可以在Intent中设置Intent.FLAG_GRANDT_READ_URI_PREMISSION和/或Intent.FLAG_GRANT_WRITE_URI_PREMISSION 这样会获得临时访问特殊URIs的权限,权限一直保持到Service调用stopSelf(int),或者知道Service完全停止,这样可以去访问那些没有权限访问限制的Apps,甚至当Service没有暴露给他们时。
另外,一个Service可以通过权限来保护自己,当IPC中被调用的时候,通过调用checkCallingPermission(String)方法查看是否允许被调用。
Process Lifecycle
操作系统会尽力去保持一个含有Service的进程,只要一个Service已经开启或者绑定。如果系统运行到内存不够的时候,含有Service的进程在可以选的Kill掉的进程中有更高的优先级,也就是会更晚被Kill掉(虽然很少会被Kill掉)。
如果Service正在执行onCreate(),onStartCommand(),或者onDestory() 方法。宿主进程会变成一个前台进程用来保证执行而不会被Kill。
如果Service已经被开启,那么他的宿主进程被认为比用户可见进程的优先级要低,但是比一些其他的不可见进程要高。因为只有很少进程是被用户可见的,这样意味着Service不会被Kill,除非是在内存十分紧张的时候。
如果有Clients绑定到Service,那么Service的宿主进程一定不会比Client的优先级低。如果Client被认为是用户可见的,那么Service本身也被认为是用户可见的。
一个开启的Service可以通过使用startForeground(int, Notification)接口设置为前台状态,那样操作系统就认为他是用户关注的,内存低时就不会去Kill这个Service了(在理论上依然有可能在内存极低的情况下去Kil掉,但是在实际上不用去担心这个问题)。
根据这些描述说明,大多数是时间上你的Service是在运行的,在内存极低的情况下可能被操作系统回收。如果发生这样的情况,操作系统之后会重启Service。一个重要的结论是如果你使用onStartConmmand()去安排一些异步或者在其他线程中的工作,你需要使用START_FLAG_REDELIVERY去让操作系统重新发送一个Intent给你(当Service被Kill后启动的时候)。这样就会防止在Service处理它时被Kil掉造成的数据丢失了。
其他应用组件也能和Service运行在同一进程(例如Activity),当然,包含其他组件的进程会比只有Service本身重要性更高。
Local Service Sample
一个Service最普通的用法,就是把它作为应用中运行在“一边”的部件(注意:不是两个进程),与应用的其他部件运行在同一个进程当中。除非特殊说明所有APK的其他部件运行在同一个进程中,这是一个定性的情况。
在这种情况下,通过确认部件在同一进程,你可以十分简单地让他们进行交互:Service的Clients可以通过IBinder获取到Service的类。
一个这种使用方法下的例子,首先是Service会在绑定的时候返回Service本身的类:
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); // We want this service to continue running until it is explicitly // stopped, so return sticky. return START_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); // Set the icon, scrolling text and timestamp Notification notification = new Notification(R.drawable.stat_sample, text, System.currentTimeMillis()); // 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.setLatestEventInfo(this, getText(R.string.local_service_label), text, contentIntent); // Send the notification. mNM.notify(NOTIFICATION, notification); } }Service完成以后,一个可以直接访问Service的Client的代码,例如:
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() { // 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). bindService(new Intent(Binding.this, LocalService.class), mConnection, Context.BIND_AUTO_CREATE); mIsBound = true; } void doUnbindService() { if (mIsBound) { // Detach our existing connection. unbindService(mConnection); mIsBound = false; } } @Override protected void onDestroy() { super.onDestroy(); doUnbindService(); }
Remote Messenger Service Sample(通过Message远程管理Service的例子)
如果你需要一个Service可以和Client进行跨进程的复杂的交互(除了简单地使用Context.startService去发送commands),然后你可以通过一个Messenges类,而不是去写一个AIDL文件。
一个使用Messenges作为与Client交互接口的Service。首先是Service,当绑定的时候返回一个核心的Handler所需要的Messenges类:
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); // Set the icon, scrolling text and timestamp Notification notification = new Notification(R.drawable.stat_sample, text, System.currentTimeMillis()); // 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.setLatestEventInfo(this, getText(R.string.remote_service_label), text, contentIntent); // 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); } }如果我们想让这个Service运行在远程的进程里(在本身的apk 中),我们可以在manifest中进行标识:
<service android:name=".app.MessengerService" android:process=":remote" />注意 ":remote"在这只是一个随意起的名字,你可以为你的另一个进程起其他的名字,“:”表示一个包中的标准进程。
完成那些以后,Clients现在可以绑定Service并且向他发送Messenges。注意这里也允许Clients接收到Messenges后再进行注册:
/** 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."); } }