Android 開發指導文檔(譯)-- Bound Services

Bound Services

boundservice在客戶端-服務器接口中作爲服務器。一個bound service允許組件(比如activity)綁定到service,發送請求,接收響應,甚至執行進程間通信(IPC)。一個bound service通常在爲其他程序組件服務時才存在並且不會無限期的在後臺運行。         

這個文檔將顯示如何創建一個bound service,包括如何綁定到其他應用程序的service。


基本原理

       一個bound service是一個Servcie類的實現,它允許其他應用程序綁定到它上面並與之交互。爲了爲service提供綁定,你必須實現onBind()回調函數。這個函數返回一個IBinder對象,該對象定義了客戶端與service交互的編程接口。

       一個客戶端可以通過調用bindService()綁定到service。它必須提供一個ServiceConnection的實現,該實現監聽與service的連接。這個bindService()方法沒有返回值立即返回,但是當Android系統創建客戶端與service之間的連接時,它調用ServiceConnection的onServiceConnected()函數來傳遞一個IBinder,客戶端可以用其與service通信。

       多個客戶端可以同時與service連接。然而,在第一個客戶端綁定到service時,系統調用你的service的onBind()方法來獲得IBinder。然後系統將同一個IBinder傳遞給之後添加的所有綁定客戶端,而不會再調用onBinder()。

       當最後一個客戶端從service解除綁定時,系統會destroy這個service(除非service通過調用startService()啓動)。

       當你實現一個bound service,最爲重要的部分是定義你的onBinder()回調函數返回的接口。有多種途徑來定義你的service的IBinder接口。


創建一個Bound Service

       創建IBinder的幾種途徑:

繼承Binder類

 

       如果你的service對你的應用程序是私有的並與客戶端運行在相同的進程中,你應該通過繼承Binder類創建接口並從onBind()返回。客戶端接收到這個Binder並用它直接訪問在Binder實現的甚至是Service的公共可用的方法。

       當你的service僅僅是一個自己應用程序的後臺worker時,這種方法首選。你不會使用這種方法創建一個接口的唯一原因是你的service被其他的應用程序使用或者使用了單獨的進程。

使用Messenger

       如果你需要你的接口在不同的進程使用,可以使用Messenger爲service創建一個接口.使用這種方式,service定義一個Handler來回應不同類型的Message對象。這個Handler作爲可以與客戶端共享IBinder的Messenger的基礎,允許客戶端使用Message對象給service發送命令。另外,這個客戶端可以定義一個自己的Messenger,所以service可以送回消息。

       這是來執行進程間通信最簡單的方式,因爲這個Messenger查詢所有的單獨的線程的請求,所以你不需要將你的service設計成線程安全的。

使用AIDL

       AIDL(Android接口定義語言) 執行所有的工作來將對象分解爲操作系統可以理解的原始狀態並marshall它們來跨線程執行IPC。先前使用Messenger的方式事實上是使用AIDL作爲它的底層結構。如前面提到的,這個Messenger在一個單獨的線程中創建一個所有客戶端請求的隊列,所以service依次接收請求。然而,如果你希望你的service來同時處理多個請求,你可以直接使用AIDL。在這種情況下,你的service必須是多線程的並且可以用線程安全的方式建立。

       爲了直接使用AIDL,你必須創建一個.aidl文件來聲明編程接口。Android SDK工具使用這個文件來生成一個實現了接口並處理IPC的抽象類,該類可以在你的service內部繼承。

       Note:多數應用程序不應該使用AIDL來創建bound service,因爲它或許需要多線程能力並導致更爲複雜的實現。因此,AIDL不適合大多數的應用程序。本文檔不會討論如何爲你的service使用它,如果你確信需要直接使用AIDL,查看AIDL文檔。


下面依次介紹每種途徑:

繼承Binder類

       如果你的service只在本地程序使用並且不需要跨進程工作,這時你可以實現你自己的Binder類來爲你的客戶端提供對service公有函數的直接訪問。

       Note:這種方式只有在客戶端和service在同一個程序中時有效。比如說,一個音樂程序需要綁定一個activity到它自己的service來在後臺播放音樂,採用這種方式將很有效。

      

下面是如何建立它:

1.        在你的service中創建一個Binder的實例,該實例:

  •  或者包含客戶端調用的公共函數
  • 或者返回當前Service的實例,該實例包含客戶端可以調用的公共函數
  • 或者返回另一個類的實例,這個類包含客戶端可以調用的公共函數且其宿主爲service

2.        從onBind()回調函數返回這個Binder實例

3.        在客戶端從onServiceConnected()回調函數接收這個Binder並使用其提供的方法訪問bound service。

Note:service和客戶端必須在同一個程序的原因是客戶端可以對返回的對象轉換並正確的調用其API。service和客戶端同樣必須在同一個進程中的原因是因爲這項技術不執行任何跨進程編組(marshalling)。

比如,下面的例子顯示service通過實現Binder使客戶端能訪問其內部方法:

    public class LocalService extends Service {  
        // Binder given to clients  
        private final IBinder mBinder = new LocalBinder();  
        // Random number generator  
        private final Random mGenerator = new Random();  
        /** 
         * Class used for the client Binder.  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 this instance of LocalService so clients can call public methods  
                return LocalService.this;  
            }  
        }  
        @Override  
        public IBinder onBind(Intent intent) {  
            return mBinder;  
        }  
        /** method for clients */  
        public int getRandomNumber() {  
          return mGenerator.nextInt(100);  
        }  
    }  

LocalBinder爲客戶端提供getService()方法來檢索當前LocalService實例。這種方式使得客戶端可以使用service中的公共的方法。比如說,客戶端可以從service調用getRandomNumber()。

       下面是一個activity綁定到LocalService並在按鈕按下時調用getRandomNumber()方法。

    public class BindingActivity extends Activity {  
        LocalService mService;//要綁定的目標service  
        boolean mBound = false;//是否綁定了service  
        @Override  
        protected void onCreate(Bundle savedInstanceState) {  
            super.onCreate(savedInstanceState);  
            setContentView(R.layout.main);  
        }  
        @Override  
        protected void onStart() {  
            super.onStart();  
            //綁定到LocalService  
            Intent intent = new Intent(this, LocalService.class);  
            bindService(intent, mConnection, Context.BIND_AUTO_CREATE);   
        }  
        @Override  
        protected void onStop() {  
            super.onStop();  
            //從service解除綁定  
            if (mBound) {  
                unbindService(mConnection);   
                mBound = false;  
            }  
        }  
        /** Called when a button is clicked (the button in the layout file attaches to 
          * this method with the android:onClick attribute) */  
        public void onButtonClick(View v) {  
            if (mBound) {  
                // Call a method from the LocalService.  
                // However, if this call were something that might hang, then this request should  
                // occur in a separate thread to avoid slowing down the activity performance.  
                int num = mService.getRandomNumber();  
                Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();  
            }  
        }  
        /** Defines callbacks for service binding, passed to bindService() */  
        private ServiceConnection mConnection = new ServiceConnection() {  
            @Override  
            public void onServiceConnected(ComponentName className,  
                                            IBinder service) {  
                // We've bound to LocalService, cast the IBinder and get LocalService instance  
                LocalBinder binder = (LocalBinder) service;  
                mService = binder.getService();  
                mBound = true;  
            }  
            @Override  
            public void onServiceDisconnected(ComponentName arg0) {  
                mBound = false;  
            }  
        };  
    }  

上面的例子簡單的演示瞭如何使用ServiceConnection和onServiceConnection()回調函數將客戶端綁定到service。下一節將提供更多關於處理綁定的service的信息。

          Note:上面的例子沒有明確的從service解綁定,但是所有客戶端應該在合適的時機解除綁定(如當activitypause 時)。


使用Messenger

   如果你需要service與遠程進程通信,你可以使用Messenger來爲你的service提供接口。這項技術允許你執行進程間通信(IPC)而不需要使用AIDL。

   下面是如何使用Messenger:

  •  Service實現一個Handler爲來自客戶端的訪問接收回調
  • Handler是用來創建Messenger對象(它是一個對Handler的引用)
  •  Messenger創建一個IBinder並由service通過onBind()返回給客戶端。
  • 客戶端使用IBinder來實例化Messenger,客戶端使用它來給service發送Message對象
  • Service在其Handler接收每個Message---尤其是在handleMessage()方法中。通過這種方式客戶端沒有”方法”來訪問service,取而代之的是客戶端傳遞”消息”(Message對象),消息在service的Handler中接收。
下面是一個使用Messenger接口的簡單例子:

    public class MessengerService extends Service {  
        /** Command to the service to display a message */  
        static final int MSG_SAY_HELLO = 1;  
        /** 
         * Handler of incoming messages from clients. 
         */  
        class IncomingHandler extends Handler {  
            @Override  
            public void handleMessage(Message msg) {  
                switch (msg.what) {  
                    case MSG_SAY_HELLO:  
                        Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show();  
                        break;  
                    default:  
                        super.handleMessage(msg);  
                }  
            }  
        }  
        /** 
         * Target we publish for clients to send messages to IncomingHandler. 
         */  
        final Messenger mMessenger = new Messenger(new IncomingHandler());  
        /** 
         * When binding to the service, we return an interface to our messenger 
         * for sending messages to the service. 
         */  
        @Override  
        public IBinder onBind(Intent intent) {  
            Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();  
            return mMessenger.getBinder();  
        }  
    }  

 

注意Handler中的handleMessage()方法是service接收傳遞過來的Message和基於what參數決定做什麼的地方。

   客戶端要做的是基於service返回的IBinder創建一個Messenger並使用send()發送消息。比如說,下面是一個簡單的綁定到service的activity並傳遞了一個MSG_SAY_HELLO消息給service。

    public class ActivityMessenger extends Activity {  
        /** Messenger for communicating with the service. */  
        Messenger mService = null;  
        /** Flag indicating whether we have called bind on the service. */  
        boolean mBound;  
        /** 
         * 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 object we can use to  
                // interact with the service.  We are communicating with the  
                // service using a Messenger, so here we get a client-side  
                // representation of that from the raw IBinder object.  
                mService = new Messenger(service);  
                mBound = true;  
            }  
            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;  
                mBound = false;  
            }  
        };  
        public void sayHello(View v) {  
            if (!mBound) return;  
            // Create and send a message to the service, using a supported 'what' value  
            Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);  
            try {  
                mService.send(msg);  
            } catch (RemoteException e) {  
                e.printStackTrace();  
            }  
        }  
        @Override  
        protected void onCreate(Bundle savedInstanceState) {  
            super.onCreate(savedInstanceState);  
            setContentView(R.layout.main);  
        }  
        @Override  
        protected void onStart() {  
            super.onStart();  
            // Bind to the service  
            bindService(new Intent(this, MessengerService.class), mConnection,  
                Context.BIND_AUTO_CREATE);  
        }  
        @Override  
        protected void onStop() {  
            super.onStop();  
            // Unbind from the service  
            if (mBound) {  
                unbindService(mConnection);  
                mBound = false;  
            }  
        }  
    }  
注意到這個例子沒有顯示service如何能響應客戶端,如果你想service作出迴應,你需要在客戶端創建一個Messenger。然後在客戶端接收到onServiceConnected()回調時,它發送一個Message給服務器,該消息在send()方法的replyTo參數包含了客戶端的Messenger。


綁定到Service

應用程序組件(客戶端)可以通過調用bindService()來綁定到service。然後Android系統調用service的onBind()方法,該方法返回一個IBinder來與service交互。

       這個綁定是異步的。bindService()立即返回並且不將IBinder返回給客戶端,爲了接收這個IBinder,客戶端必須創建一個ServiceConnection的實例然後將其傳遞給bindService()。這個ServiceConnection包含供系統傳遞IBinder的回調函數。

       Note:只有activity、service和content provider可以綁定到service---你不能把一個broadcast receiver綁定到service。

所以,爲了將客戶端綁定到一個service,你必須:

1、  實現ServiceConnection。

你的實現必須重寫兩個回調函數:

onServiceConnected():系統調用該函數來傳遞由service的onBind()方法返回的IBinder。

onServiceDisconnection():對service的連接意外丟失,比如當service崩潰或被kill時,Android系統會調用該函數。當客戶端解除綁定時不會調用。

2、  調用bindService(),傳遞ServiceConnection實現。

3、  當系統調用你的onServiceConnected()回調函數是,你就可以使用接口中定義的方法開始訪問service

4、  爲了從service解除連接,可以調用unbindService()。

當你的客戶端被銷燬是,它將從service解除綁定,但是你因該在與service完成交互或者你的activity pause時解除綁定,這樣service就可以在其不用時關閉。

比如說,下面一個片段連接一個客戶端到service,該service是之前用繼承Binder類方式創建的。所有要做的是將返回的IBinder換作LocalService類並請求LocalService實例。

    LocalService mService;  
    private ServiceConnection mConnection = new ServiceConnection() {  
        // Called when the connection with the service is established  
        public void onServiceConnected(ComponentName className, IBinder service) {  
            // Because we have bound to an explicit  
            // service that is running in our own process, we can  
            // cast its IBinder to a concrete class and directly access it.  
            LocalBinder binder = (LocalBinder) service;  
            mService = binder.getService();  
            mBound = true;  
        }  
        // Called when the connection with the service disconnects unexpectedly  
        public void onServiceDisconnected(ComponentName className) {  
            Log.e(TAG, "onServiceDisconnected");  
            mBound = false;  
        }  
    };  

客戶端可以綁定到service,並將ServiceConnection傳遞給bindService()。比如:
Intent intent = new Intent(this, LocalService.class);  
bindService(intent, mConnection, Context.BIND_AUTO_CREATE); 

  • bindService()的第一個參數是一個Intent指明要綁定的service的名字(雖然這個Intent可以是implicit的)
  • 第二個參數是ServiceConnection對象
  • 第三個參數是一個表示(flag),來指明綁定的選項,它常常是BIND_AUTO_CREATE來在service不存在時創建。其它的值是BIND_DEBUG_UNBIND和BIND_NOT_FOREGROUND或0。

附加註釋:

下面是關於綁定到service的重要註釋:

  • 你必須總是捕獲DeadObjectException,該異常在連接損壞是拋出。這是遠程方法拋出的唯一異常
  •  對象是跨進程計算的引用(Objects are reference counted across processes).
  •  在客戶端聲生命週期的匹配的建立(bring-up)和卸載(tear-down)時刻,你應該總是匹配綁定和解綁定,比如說:

         1、  如果你只需要在你的activity可見時與service交互,應該在onStart()中綁定,並在onStop()中解除綁定。

         2、  如果你需要你的activity即使在後臺停止的時候也可以接收響應,你可以在onCreate()時綁定service,然後在onDestroy()中解除綁定。當心,這意味着你的activity在整個生命週期都需要用到service(即使是在後臺),所以如果service在其他的進程中,你增加了這個進程的負擔,它變得更容易被系統kill。

Note:你不因該在onResume()和onPause()綁定以及解綁定,因爲這些回調函數發生在每次生命週期變換,你應該確保這些處理髮生在轉換的最低限度。同樣,如果在你的應用程序中多個activity綁定到同一個service並且兩個activity之間有一個轉換(transition),在當前activity解除綁定(正在pause),而在下一個activity綁定之前,service或許會被銷燬和重新創建。

 

管理Bound Service的生命週期

當一個service從所有的客戶端解除綁定時,Android系統會銷燬它(c除非它是通過onStartCommand()啓動的)。因此,

如果service只是個bound service,你不需要管理它的生命週期。---Android系統會根據其是否綁定到客戶端來管理它。

       然而,如果你選擇實現onStartCommand()回調函數,此時你必須明確的stop service,因爲這個service此時被認定是started。在這種情況下,這個service一直運行,直到service通過調用stopSelf()停止自己或者其他的組件調用stopService()來停止它,此時不用考慮是否有組件綁定到它上面。

       另外,如果你的service是started並接受綁定,在系統調用你的onUnbind()方法時,如果你想在下一次一個客戶綁定到service時接收onRebind()的調用,你可以選擇返回true。onRebind()返回void,但是客戶端仍在其onServiceConnected()回調中接收到IBinder,下圖闡明瞭這種生命週期的邏輯:

 

 


 
發佈了5 篇原創文章 · 獲贊 1 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章