Android -- Service官方文档简译

来源:互联网 发布:苹果8没网络 编辑:程序博客网 时间:2024/06/05 08:48

Android -- Service官方文档简译




Service是一个应用程序组件,它既可以代表应用程序处理当无需和用户交互时的长耗时操作,也能为其他应用程序提供接口支持。每一个Service类都必须在包的Androidmanifest.xml中存在对应的声明。我们可以通过Context.startService()/Context.bindService()启动一个服务。


要注意,Service像其他程序对象一样,运行在它坐在进程的主线程中。这也就意味着,如果你的Service要做任何耗CPU(如播放MP3)或阻塞(如网络请求)操作时,它都应该创建一个新线程来处理。IntentService是可获取的Service的一个标准实现,它拥有自己的线程来处理这些耗时工作。


什么是服务


  • Service并不是一个单独的的进程。一个Service对象不意味着它运行在自己的进程中;除非有额外明确信息,否则它作为应用程序的一部分和它运行在同一个进程中。
  • Service并不是一个线程。它并不意外这自己会在主线程之外处理自己的工作。

因此Service本身是非常简单的,它提供了两个很主要的特征:

  • 它是一个提供给应用程序告知系统它想要在后台(甚至当用户并不直接和应用交互时)做一些处理的工具。这与Context.startService()调用对应,它会告知系统要启动该Service;Service会一直运行到有服务或其他角色显式停止它为止。
  • 他是一个提供给应用对外暴露自己接口给其他应用程序使用的工具。这与Context.bindService()调用对应,为了与该服务交互,该接口允许应用与该服务之间创建一个长保持的连接。

当一个Service组件被创建后,基于以上任何一个原因,系统会做的工作就是初始化组件并在主线程中调用onCreate()和任何其他合适的回调。这取决Service借助合适的操作来实现这些内容,例如,创建一个操作处理的次级线程(对主线程而言)。


要注意,Service是非常简单的,我们能通过我们所想,以尽量简单或复杂的方式去和它交互。通过把它看成本地Java对象,我们可以借助它来调用以AIDL形式提供的所有接口。


服务生命周期


Service的生命周期图示如下:


我们有两种方式来让系统运行一个Service。如果调用了Context.startService(),那么系统就会去启动一个Service(需要的话,创建并调用它的onCreate()方法),并附带Client提供的参数去调用它的onStartCommand(Intent, int, int)方法。此时,除非调用了Context.stopService()或stopSelf()方法,那么服务将一直运行下去。要注意多次调用Context.startService()并不会嵌套生效(尽管这确实会导致onStartCommand()被相应地多次调用),所以无论你多少次启动一个Service,它只会在Context.onStop()/Context.stopSelf()被调用时停止一次;同时,Service可以使用它们的stopSelf(int)方法来确保在启动服务的Intent被处理之前,服务不会被停止。


客户端可以调用Context.bindService()来获取和此Service之间的一个长连接。如果此时服务没有运行的话,它同样会创建服务(调用onCreate()),但是不会调用onStartCommand()。客户端将会在它的onBinder(Intent)方法中接收到一个服务返回的IBinder对象,这允许你通过该对象回调到服务中。只要这个连接一建立,服务就会一直运行(不管客户端是否保持对服务IBinder的引用)。通常情况下,IBinder对象的返回是为了使用在AIDL中定义的那些复杂接口。


一个服务既能被启动,也能同时连接并被绑定。这种场景下,只要服务被启动,或者至少有一个带有Context.BIND_AUTO_CREATE标志的连接被建立,系统就会保持该服务持续运行。一旦这些情况都不成立,服务的onDestroy()方法就会被调用,而且该服务也会被有效地终止。从onDestroy()返回后,Service相关的所有清理工作(停止线程,注销广播接收者)都应该完成。


进程生命周期


只要一个服务被启动或者有客户端绑定它后,系统就会尝试去保护该服务所在的进程继续运行。当设备运行在内存极低、系统需要杀死当前存在进程的情况下时,持有服务的进程的优先级在接下来的几种可能性中会是较高的:

  • 如果Service正在执行onCreate()、onStartCommand()或onDestroy()接口中的代码,那么该Service的宿主进程将被认为是前台进程,系统不会杀死它以确保这些代码被执行。
  • 如果一个服务已经被启动,那么该服务的宿主进程会被认为比当前任何在屏幕上对用户可见的那些进程(即直接和用户交互的进程)的重要性低,但比那些不可见的进程的优先级要高。这意味着除了在内存低的条件下,这类服务都不应该被杀死。但是,由于用户并不会直接注意到这些服务,在那种状况下(指内存低),这类服务都是合法的被杀死的候选对象;我们应该对这类情形做一些准备。特别是,那些长期运行的服务被杀死的可能性会越来越高;如果它们运行的时间已经足够长,系统则会保证它们被杀死(或被重启,如果允许的话)。
  • 如果有客户端绑定到一个服务,那么该服务的宿主进程的重要性永远不会比最重要的客户端进程的重要性低多少。也就是说,如果客户端进程对用户可见,那么该服务进程也会被认为是对用户可见。一种由客户端重要性影响服务重要性的方式是通过BIND_ABOVE_CLIENT,BIND_ALLOW_OOM_MANAGEMENT,BIND_WAIVE_PRIORTY,BIND_IMPORTANT和BIND_ADJUST_WITH_ACTIVITY来调节。
  • 一个已经启动的Service可以通过使用startForeground(int, Notifcation)API让它进入前台(foreground)状态,此时系统会认为用户已经主动注意到该服务了,所以当内存很低的时候,它的所属进程将不会成为那个将要被杀死的候选对象。(理论上,在内存极端低的压力下,一个处于前台进程中的Service仍然有可能被杀死,但实际上这不应该看做是个问题。)

需要注意,这意味着绝大多数时候,你的服务都会运行,但当系统的内存压力很大时,它也许会被系统杀死。如果发生了这种情况,系统会在之后尝试重启该服务。这种情形下的一个重要结果就是如果你实现了onStartCommand()方法让所要完成的工作被异步处理或者运行在另外一个线程中,你也许会想使用START_FLAG_REDELIVER标志,让系统重新给你递送一个Intent,这样即使Service在处理它时被杀,也不会丢失这个Intent。


其他作为服务(例如一个Activity)运行在同一进程中的应用程序组件不仅仅可以提升它服务本身的重要性,也可以提升整个进程的重要性。


本地服务示例


服务最常见的一个用途就是作为一个次级组件和应用程序的其他部分一起运行在同一个进程中。除非有明确说明的情况下,否则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);    }}

那部分完成后,我们可以直接写出访问服务端的客户端代码,例如:

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;    }}@Overrideprotected void onDestroy() {    super.onDestroy();    doUnbindService();}


远程Messenger服务示例


如果你需要写一个能和远端进程中的客户端进行复杂沟通的服务(除了简单使用Context.startService()给它发送命令外),这时你就应该使用Messenger类型来代替编写完整的AIDL文件。


下面是展示的一个使用Messenger来做为客户端接口的服务示例。首先是服务自身,当它被绑定时会发布一个指向服务内部Handler的Messenger:

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);    }}

如果想让服务运行在远端进程中,我们可以在它的服务声明标签中使用"android:process"来明确这一点:

<service android:name=".app.MessengerService"        android:process=":remote" />
注意,此处使用的名称"remote"是任意的,如果你需要其他的进程,则使用其他的名字即可。":"后缀会将这个名字附加到包的标准进程名称。


那些完成之后,客户端可以绑定服务并向它发送消息了。注意,这同时也允许客户端注册它的Messenger以用来接收消息:

/** 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.");    }}
(这里需要注意Messenger的用法,它可以指定接收者和发送者)。

PS:会持续润色。


原创粉丝点击