天天看點

Android -- Service官方文檔簡譯Android -- Service官方文檔簡譯

Android -- Service官方文檔簡譯

Android -- Service官方文檔簡譯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的生命周期圖示如下:

Android -- Service官方文檔簡譯Android -- 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;
    }
}

@Override
protected 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:會持續潤色。

繼續閱讀