公共抽象类
Service
继承自 ContextWrapper
实现方法ComponentCallbacks
概述
Service是一个应用程序组件,它被用来在应用程序与用户交互时进行耗时操作,或者向其他应用程序提供功能。每个service必须在包的AndroidManifes.xml里用<service>标签声明。有两种方式启动srevice:Context.startService(),Context.bindService。
Service跟其他的应用程序对象一样运行在宿主进程的主线程中。也就是说如果service要进行耗时操作(如播放MP3文件)或者阻塞操作(如连接网络),它必须产生自己的线程来做这些事情。更多信息可以在“应用程序基础:进程与线程”中找到。IntentService类是一个带有自己的工作线程的标准的service。
Service类是应用程序生命周期中重要的一部分。
本文涵盖以下主题:
1,什么是service?
2,service生命周期
3,权限
4,进程生命周期
5,Localservice举例
6,RemoteMessenger Service举例
什么是Service?
围绕service类更多的困扰在于它不是什么:
一个service不是一个单独的进程。service并不一定运行在自己的进程中;除非指定,它运行在它所在的应用程序的进程中。
一个service不是一个线程。它不是脱离主线程工作的一种方式(为了避免应用程序无响应错误)。
所以一个service其实很简单,它提供了两种特性:
提供给应用程序一种途径来告诉系统它要进行的后台操作(即使用户没有跟应用程序直接交互)。这对应着Context.startService()的调用,它告诉系统为service安排工作,并运行它直到service或其他程序停止它。
提供给应用程序一种途径来把自己的部分功能暴露给其他的应用程序。这对应着Context.bindService()的调用,这会允许一个跟service交互的长时间的连接的存在。
当一个service组件创建成功,不管以哪种方式,系统实际做的是初始化一个组件并调用它的onCreate()方法以及主线程中的任何合适的回调。这些回调里的内容则由service实现,比如创建一个另外的线程。
因为service是如此的简单,你可以把同它的交互做的同样简单或者很复杂:可以把它作为一个java对象来直接调用它的方法(就像LocalService举例中的一样),或者用AIDL提供远程的接口。
Service生命周期
有两种情况系统会运行一个service。如果调用了Context.startService()系统会获取这个service(创建并调用它的onCreate()方法,如果需要的话)并调用它的onStartCommand(Intent,int,int)方法。这时service会一直运行直到Context.stopService()或stopSelf()被调用。多次调用Context.startService()不会交织(尽管这样会多次调用onStartCommand方法),所以不管service被启动了多少次只要一次Context.stopService()或stopSelf()调用它就会停止;然而,service可以用它们的stopSelf(init)方法保证service不会停止直到启动命令被处理(?)。
对于已经启动的service,有两种运行模式可以选择,这取决于onStartCommand的返回值:START_STICKY用于显示启动停止服务,START_NOT_STICKY或START_REDELIVERY_INTENT用于在处理命令时才运行的Service。查看文档连接获取更多语法信息。
客户端也可以用Context.bindService()来获取一个service的持续的连接。这也会创建一个service,如果service没有处在运行状态(这时会调用onCreate()方法),但是不会调用onStartCommand方法。客户端会接收到service从onBind(Intent)方法返回的IBinder对象,它允许客户端回调service的方法。service会一直运行只要连接一直建立(不管客户端是否获取了IBinder对象的引用)。通常返回的IBinder对象是针对写在aidl文件中的复杂的接口的。
一个service可以同时被启动和绑定。这样系统会一直运行service一旦它被启动或者存在一个或多个带有Context.BIND_AUTO_CREATE标志的连接。一旦二者都不存在service的onDestroy()方法就会被调用,service被销毁。所有的清理工作(停止线程,反注册接收器)都应该在onDestroy()返回前执行。
权限
service的访问权限可以在manifest的<service>标签里声明。这样其他的应用程序需要声明一个对应的<uses-permission>元素来启动,停止,绑定service。
另外,service可以限定IPC调用,通过在执行这个调用前调用checkCallingPermission(String)方法实现。
查看“Securityand Permissions”获取更多信息。
进程生命周期
Android系统会试图保留拥有处于启动状态或绑定状态的service的进程。当系统运行在需要杀死现有进程的低内存状态时,在以下情况下拥有service的进程优先级会高:
如果service正在执行onCreate(),onStartCommand()或者onDestroy()中的代码,宿主进程会被看作一个前台的进程来确保不被杀死。
如果service已经启动,宿主进程将不会比任何用户在屏幕上可见的进程重要,但会比任何不可见的进程重要。因为只有很少的进程对用户可见,所以service只会在内存极低的情况下被杀死。
如果有客户端绑定到service,宿主进程的重要性将不会低于最重要的客户端。也就是说,如果service的一个客户端对用户可见,service本身会被认为是可见的。
一个启动的service可以用startForeground(int,Notification)方法把它置为前台状态,这样系统会认为它是用户可见的在内存极低的时候也不会被杀死。(在理论上依然存在被杀死的可能性,但是实际上不应该被考虑。)
多数情况下service会一直运行,只有在系统处于严重的内存压力时会被杀死。如果被杀死,系统会在不久后尝试重启service,你可能想用START_FLAG_REDELIVERY标志来让系统重新发送一个Intent这样当你的service被杀死后Intent就不会丢失了。
运行在相同进程里的组件,像service一样,也会增加整个进程的重要性。
Local service举例
Service的一个最常见的用法是像应用程序的其他部分一样作为一个从属部分在相同的进程里运行。除非特别指明,在apk文件里的所有组件运行在相同的进程里。
在这种情况下,通过假定组件在相同的进程空间里,会大大简化组件间的交互:客户端可以简单的将service产生的IBinder对象转换成具体的类对象。
下面是一个例子。首先是service,绑定的时候会产生一个客户端类。
public class LocalService extends Service { private NotificationManager mNM; /** *客户端交互的类。因为service和客户端运行在相同的进程中,我们不需要处理进程 *间通讯。 **/ public class LocalBinder extends Binder { LocalService getService() { return LocalService.this; } } @Override public void onCreate() { mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); // 展示一个启动的提醒。我们在状态栏里放一个图标。 showNotification(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.i("LocalService", "Received start id " + startId + ": " + intent); // 我们要让service一直运行,直到有意停止,所以返回sticky。 return START_STICKY; } @Override public void onDestroy() { // 取消持续的提醒。 mNM.cancel(R.string.local_service_started); //告诉用户服务停止。 Toast.makeText(this, R.string.local_service_stopped, Toast.LENGTH_SHORT).show(); } @Override public IBinder onBind(Intent intent) { return mBinder; } // 这是一个同客户端交互的对象,RemoteService是一个更复杂的例子。 private final IBinder mBinder = new LocalBinder(); /** * 当service运行时显示提醒。 */ private void showNotification() { // 在这个例子中,我们用相同的文本作为ticker和扩展文本 CharSequence text = getText(R.string.local_service_started); // 设置图标,滚动文字,时间戳 Notification notification = new Notification(R.drawable.stat_sample, text, System.currentTimeMillis()); // 当用户选择该提醒时,这个PendingIntent负责跳转到我们的activity。 PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, LocalServiceActivities.Controller.class), 0); // 设置在提醒托盘显示的信息。 notification.setLatestEventInfo(this, getText(R.string.local_service_label), text, contentIntent); // 发送提醒。 // 用布局id,因为它是唯一的。稍候我们会用它来取消提醒。 mNM.notify(R.string.local_service_started, notification); } } 做完了这些,你可以写一段客户端程序直接调用运行的service,比如: private LocalService mBoundService; private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { // 连接建立后会调用该方法,传递一个service对象,用来同service交互。因为我 // 们知道绑定的service在我们自己的进程中,我们可以把IBinder直接转换成具体 // 的类对象。 mBoundService = ((LocalService.LocalBinder)service).getService(); // 告诉用户连接建立 Toast.makeText(Binding.this, R.string.local_service_connected, Toast.LENGTH_SHORT).show(); } public void onServiceDisconnected(ComponentName className) { // 当service连接没有预期的断开了—也就是进程崩溃了,会调用这个方法。因为 //service运行在我们的进程里,这绝不该发生。 mBoundService = null; Toast.makeText(Binding.this, R.string.local_service_disconnected, Toast.LENGTH_SHORT).show(); } }; void doBindService() { // 与service建立一个连接。我们用一个显式的类名因为我们知道将实现的是一个运行在自己进程里的service(因此将不支持同其他应用程序的组件替换)。 bindService(new Intent(Binding.this, LocalService.class), mConnection, Context.BIND_AUTO_CREATE); mIsBound = true; } void doUnbindService() { if (mIsBound) { // 断开现有连接 unbindService(mConnection); mIsBound = false; } } @Override protected void onDestroy() { super.onDestroy(); doUnbindService(); } Remote Messenger Service举例 如果你需要一个运行在远程进程可以与客户端进行复杂通信的service(不仅仅用Context.startService来控制service),你可以用Messenger类来代替写aidl文件。 下面是一个用Messenger来作为客户端接口的一个service的例子。在绑定service时会发送一个Messenger到内部的handler。 public class MessengerService extends Service { /** 用来显示和隐藏 notification. */ NotificationManager mNM; /** 记录所有注册的客户端 */ ArrayList<Messenger> mClients = new ArrayList<Messenger>(); /** 记录客户端上一次的设置值 */ int mValue = 0; /** * *要求service注册一个客户端的命令,客户端会收到service的回调。消息的 *replyTo字段必须是一个接收回调的客户端的Messenger。 */ static final int MSG_REGISTER_CLIENT = 1; /** * *要求service反注册一个客户端的命令,客户端会停止接收service的回调。消息的 *replyo字段必须是之前 MSG_REGISTER_CLIENT 消息得到的客户端Messenger。 */ 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. 要求service设置新值的命令。这个命令可以发送给service来使用一个新值,也可以由service把新值发给任一个注册的客户端。 */ static final int MSG_SET_VALUE = 3; /** * Handler of incoming messages from clients. 处理来自客户端的消息的handler */ 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. 客户端发送消息到IncomingHandler的目标。 */ final Messenger mMessenger = new Messenger(new IncomingHandler()); @Override public void onCreate() { mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); // Display a notification about us starting. // 提示service开始 showNotification(); } @Override public void onDestroy() { // Cancel the persistent notification. // 取消持续的提醒 mNM.cancel(R.string.remote_service_started); // Tell the user we stopped. // 告诉用户service停止了。 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. 绑定服务时,返回一个messenger的接口,通过它可以发消息给service。 */ @Override public IBinder onBind(Intent intent) { return mMessenger.getBinder(); } /** * Show a notification while this service is running. service运行时显示的提醒。 */ private void showNotification() { CharSequence text = getText(R.string.remote_service_started); Notification notification = new Notification(R.drawable.stat_sample, text, System.currentTimeMillis()); PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, Controller.class), 0); notification.setLatestEventInfo(this, getText(R.string.remote_service_label), text, contentIntent); mNM.notify(R.string.remote_service_started, notification); } } 如果你想让service运行在远程进程中(而不是.apk中标准的进程),你可以在manifest标签中用android:process来指定一个: <service android:name=".app.MessengerService" android:process=":remote" /> 注意到这里的”remote”是随意起的,你可以用其他的名字。名字前的”:”前缀会将名字加到标准的包进程名字后面。 这样客户端就能绑定到service并向它发送消息了。注意到客户端也可以注册到service并收到service的消息。 /** 同service通信的Messenger*/ Messenger mService = null; /**标记是否已绑定service*/ boolean mIsBound; /** 显示状态信息的文本控件*/ TextView mCallbackText; /**处理在自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); } } } /** * 发送消息给IncomingHandler的目标。 */ final Messenger mMessenger = new Messenger(new IncomingHandler()); /** * 同service接口交互的类。 */ private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { // 同service的连接建立时调用的方法,传入我们同service交互使用的对象。我们 // 通过IDL接口同service交流,所以将原生的服务对象转换成客户端的表现形式。 mService = new Messenger(service); mCallbackText.setText("Attached."); // 一旦连接到service我们就要监听它。 try { Message msg = Message.obtain(null, MessengerService.MSG_REGISTER_CLIENT); msg.replyTo = mMessenger; mService.send(msg); // 发送一些值。 msg = Message.obtain(null, MessengerService.MSG_SET_VALUE, this.hashCode(), 0); mService.send(msg); } catch (RemoteException e) { // 这种情况下service已经崩溃;我们可以指望断开连接(当服务启动时再次连 // 接),所以这里我们什么也不做。 } // 告诉用户已连接service Toast.makeText(Binding.this, R.string.remote_service_connected, Toast.LENGTH_SHORT).show(); } public void onServiceDisconnected(ComponentName className) { // 当同service的连接无意间断开时会被调用--也就是service进程崩溃了。 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() { // 建立同service的连接。我们使用显示的类名,因为没有理由让其他的应用程序替换 // 我们的组件。 bindService(new Intent(Binding.this, MessengerService.class), mConnection, Context.BIND_AUTO_CREATE); mIsBound = true; mCallbackText.setText("Binding."); } void doUnbindService() { if (mIsBound) { // 如果我们已连接到service,也就会注册到里面,那么我们将取消注册。 if (mService != null) { try { Message msg = Message.obtain(null, MessengerService.MSG_UNREGISTER_CLIENT); msg.replyTo = mMessenger; mService.send(msg); } catch (RemoteException e) { } } // 断开现有连接。 unbindService(mConnection); mIsBound = false; mCallbackText.setText("Unbinding."); } }