星期一, 9月 19, 2011

Service(七)

Android應用程式學習筆記

Service(七)

Bound Services
The Basics
Bound services實現了Service類別,並允許其他應用程式綁定它與它互動。service要提供binding,必須實現onBind()回呼方法,此方法會回傳IBinder物件,IBinder定義了客戶端與service之間互動的接口。

客戶端通過呼叫bindService()方法綁定service,當客戶端執行綁定後,客戶端必須提供ServiceConnection,監視與service的連結。bindService()方法會立即返回,但是當android系統產生客戶端與service之間的連結,系統會呼叫ServiceConnection上的onServiceConnected()方法來傳遞IBinder物件,使客戶端可以與service通訊。

許多客戶端可以同時與service連結,然而,系統只會在第一個客戶端綁定時呼叫你的service的onBind()方法來取得IBinder物件,系統接著直接傳送相同的IBinder物件給另外的客戶端,不會再次呼叫onBind()方法來取得IBinder物件。

當最後一個客戶端不再綁定service時,系統就會銷毀service。

當你實現bound service時,最重要的部分就是定義onBind()方法,有幾種方式能定義onBind()方法。Handler是Messenger的基礎,與客戶端分享IBninder(),允許客戶端用Messenge物件寄送指令給service,除此之外,客戶端也可以擁有自己的messenger,所以service能寄送messenge回去。

這是一個進程間互相溝通的簡單方式,因為messenger排所有的請求入單一的執行續中,所以你不需設計你的service為thread-safe。

Creating a Bound Service
當我們要實現一個service並且提供綁定的功能,你必須提供IBinder物件給客戶端作為與service互動的接口,有三種方式可以定義接口。

Extending the Binder class
如果你的service是私有的,只有自己的應用程式且在同一個進程作為客戶端,你應該藉著Binder類別來產生接口,且是從onBind()方法回傳接口,客戶端取得接口(Binder)後就可以直接使用不管是在Binder或者是service的public方法。

這樣的方式比較適合使用在當你的service作為你的應用程式的後台工作者時。如果你想要你的service被其他應用程式使用或者被不同進程存取,你就不應該使用這種方式來實現bound service。

Using a Messenger
如果你需要接口能存取其他進程,你能用Messenger來產生service的接口。在此方式,service必須定義Handler來回應不同型態的Messenger,

Using AIDL
(看不太懂android官網上的解釋,先跳過,之後再加上來)

Extending the Binder class
繼承binder類別建立bound service有三個步驟:

  1. 在service中,定義Binder:
    • 包含客戶端可以呼叫的public方法。
    • 或回傳當前的service,它有客戶端可以呼叫的方法。
  2. 從onBind()回乎方法回傳Binder物件。
  3. 在客戶端,從onServiceConnected()中取得Binder物件。


以下為一個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中的public方法。例如,客戶端可以呼叫service的getRandomNumber()方法。

以下是一個activity要綁定LocalService並當按鈕被點擊後呼叫getRandomNumber()方法。

public class BindingActivity extends Activity {
    LocalService mService;
    boolean mBound = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }

    @Override
    protected void onStart() {
        super.onStart();
        // Bind to LocalService
        Intent intent = new Intent(this, LocalService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        // Unbind from the 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;
        }
    };
}

Using a Messenger
如果你需要你的service與遠端的進程通訊,你可以使用Messenger物件為你的service提供接口。此技術允許你執行進程間通訊卻無需AIDL。

以下總結如何使用Messenger:
  • Service實作Handler物件來接收每一個從客戶端的呼叫。
  • Handler用來產生Messenger物件(必須引用Handler類別)。
  • Messenger產生新的IBinder物件,service從onBind()方法中回傳給客戶端。
  • 客戶端使用IBinder傳送Messenge給service來實現Messenger。
  • Service在它的Handler接收每個Messenge,特別是在handleMessenge()方法中。
在此方式中,客戶端沒有方法去呼叫service,取代的是客戶端傳送Messenge(Messenge物件),service的handler接收Messenge來啟動service。

以下有一個使用Messenge為接口的例子

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中的handleMessenege()方法是service是接收傳入messenege及決定要執行甚麼的動作地方。

所有客戶端需要做的是基於回傳自service的IBinder以及用send()方法寄送messenge創建Messenger。以下有一個簡單的activity去綁定service,並且傳送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()的回呼時,它寄送messenge給service。

Binding to a Service
應用程式組件(客戶端)能通過呼叫bindService(0方法來綁定service,android系統接著呼叫service的onBind()方法,onBind()方法會回傳IBinder為了與service互動。

綁定是異步的,bindService(0立即回傳而沒有回傳IBinder給客戶端,為了接收IBinder,客戶端必須創建ServiceConnection並傳送它給bindService()。ServiceConnection包括系統呼叫來傳遞IBinder的回呼方法。

所以,從你的客戶端綁定service,你必須:
  1. 實現ServiceConnection。必須複寫兩個回呼方法:
    • onServiceConnected():
      系統呼叫此方法來傳遞回傳自service的onBind()方法的IBinder。
    • onServiceDisconnected():
      當與service連結在非預期情況下消失時,系統呼叫此方法。並不是在客戶端不再綁定service時呼叫。
  2. 呼叫bindService(),傳遞ServiceConnection。
  3. 當系統呼叫onSeviceConnected()回呼方法,你能開始呼叫service的方法。
  4. 從service解除連結,呼叫unbindService()。
例子:

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;
    }
};
用此ServiceConnection,客戶端將ServiceConnection通過bindService()方法綁定service。

Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
第一個參數是一個Intent物件,裡面明確地定義了service的名稱。
第二個參數是ServiceConnection物件。
第三個參數是綁定的選擇的旗標,

沒有留言:

張貼留言