Service 服务
Service是一个能够在后台运行的服务,能够长时间运行,优先级相对较高;当然系统也有可能在前台程序需要内存的时候会将服务杀掉,但是这种事很极端的情况,一般不会发生;
服务并不需要用户界面,我们可以使用Service来做一些后台的任务,比如监控某些信息,播放音乐等等;
服务的启动和生命周期:
服务可以通过两种方式启动,一种是通过 Content.startService()来启动,另外一种是通过 Context.bindService()来启动;
在一个包含Service的app中,我们需要在AndroidManifest.xml的application节点中增加对应的服务
<service name=".MyFirstService" />
MyFirstService是我们app中的一个类,继承自Service;我们通过重写 onCreate,onStart,onDestroy来处理服务各个时刻要处理的情况;
Service的生命周期:
通过 startService启动时候:
onCreate() (如果为首次启动) ->onStart() -> 运行期 -> stopService() -> onDestroy();
通过 bindService方式的时候:
onCreate()(如果首次启动) -> onBind() -> 运行期 -> 调用者退出或者调用者使用 unbindService时候 -> onUnBind() -> onDestroy();
对于一个只是同一个app中其他的Actvity才会调用的服务来说,一般来说实现onStart和onDestroy的函数,并且在 onBind的时候返回null即可;
注意:
在Android2.0之后,已经不太推荐重写 onStart 了,而是使用重写 onStartCommand这个函数;
针对新的onStartCommand函数,我们重写的时候,在返回值上面,返回的值对于服务有直接影响:
START_STICKY: 服务被kill掉后,保留service为开始状态,不保留启动时候的intent;系统会尝试重启服务,并且传入的intent为null; (需要显式的启动或者停止)
START_NOT_STICKY: 服务被kill掉后,系统不会尝试重启;(只有在收到command的时候,才会处于运行状态)
START_REDELIVER_INTENT: 与START_STICKY类似,系统会重启服务,但是会保留intent重新启动时候传入;(只有在收到command的时候,才会处于运行状态)
START_STICKY_COMPATIBILITY:START_STICKY的兼容版本,但不保证服务被kill后一定能重启。
当然上面几种方式,在用户主动退出服务的情况下,系统不会尝试重启;
服务处理周期:
- onCreate(), onStartCommand(), or onDestroy() 这几个函数中,不能被kill掉,确保这几个函数中的代码都被执行;
- 服务优先级:低于任何可见的app进程,高于任务不可见的app;
- 服务的优先级永远高于连接到服务的进程优先级,即如果有可见的app连接到服务,那么服务也相当于是可见的
- 服务可以使用 startForeground(int, Notification)API来使得服务处于前台状态,使得不会被kill掉(很极端很极端的情况下还是会被kill,但是不需要考虑这种情况)
本地服务:
一般来说服务最常用的方式是作为app的一个次要的组件来运行的,即它是作为一个本地服务运行的,它会在app的同一个包的进程内运行,除非显式的指定其他状态,否则都会这么做;
这个时候,我们需要其实并不需要关系很多的服务交互问题,只需要实现一些基本的服务处理;
下面是本地服务的一个实现,我们要做的,只是在onCreate,onStartCommand,onDestroy里面做一些我们服务启停时候要做的事情即可;
public class LocalService extends Service { private NotificationManager mNM; /** * 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(R.string.local_service_started); // 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. // We use a layout id because it is a unique number. We use it later to cancel. mNM.notify(R.string.local_service_started, notification); } }
客户端调用的代码如下,比如下面的代码放在某个 Activity下面:
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(); }
可以看到的是,我们以上的客户端代码,只是启停服务而已,并不做过多的其他处理;
远程服务:
有时候我们的服务,可以是开放的,其他的app也能够调用到我们的服务,并且发消息给到服务进行处理;可以使用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. */ ArrayListmClients = new ArrayList (); /** 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); } }
完成上面的工作之后,客户端可以绑定到服务,并且发送消息给它;同时也允许用户注册来接收返回的消息;
/** 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."); } }