【起航計劃 037】2015 起航計劃 Android APIDemo的魔鬼步伐 36 App->Service->Remote Service Binding AIDL實現不一樣進程間調用服務接口 k

本例和下個例子Remote Service Controller 涉及到的文件有RemoteService.java ,IRemoteService.aidl, IRemoteServiceCallback.aidl 及ISecondary.aidl。html

Android Interface Definition Language(AIDL)和其它一些支持遠程方法調用RMI的系統的IDL相似,它定義了Service和Client 之間的使用接口約定,這種遠程調用通常須要經過進程間通訊機制(IPC)來實現。在Android系統,一個進程(Process)一般不能直接訪問其它 進程的內存空間,Android系統支持使用AIDL來實現使用不一樣進程間調用服務接口。java

在設計AIDL接口以前,要意識到使用AIDL接口是經過直接函數調用的方法來進行的,但這種遠程調用所發生的線程Thread隨調用者是否和Service提供者屬於同一進程Process的不一樣而不一樣:android

  • 若是調用者與Service屬於同一個進程(能夠稱爲Local Process),那麼AIDL Call將使用與調用者同一線程執行。所以若是你的應用只使用Local Process來訪問AIDL Call,那麼根本就無必要使用AIDL接口,使用Binder便可,參見Android ApiDemo示例解析(39):App->Service->Local Service Binding
  • 如 果使用Remote Process方式來調用AIDL ,Android將會使用由本進程管理的線程池(Thread pool)來分發函數調用。所以你的Service須要可以處理多進程觸發的AIDL Call,換句話來講,AIDL接口的實現必須是Thread-safe的。
  • 關鍵字oneway 能夠修改遠程調用的的行爲,當使用oneway關鍵字時,remote call調用後當即返回,有點相似異步調用。

定義AIDL 接口的步驟以下:api

AIDL接口定義使用Java Interface語法定義在 .aidl文件中,而後必須同時放在Service和Client 的 src目錄下。 當使用Eclipse 編譯時,Android SDK工具會根據 .aidl的接口定義自動生成對應的IBinder接口定義 (定義在gen目錄下) Service必須實現由這個IBinder接口定義。 Client而後能夠經過綁定Service來訪問這些方法。app

1. 建立. aidl 文件異步

AIDL接口定義使用和Java Interface定義一樣的語法,每一個.aidl文件只能定義一個調用接口,並且只能定義接口方法,不能含有靜態變量定義。AIDL缺省支持 int ,long, char, boolean, String, CharSequence, List ,Map 變量類型,也能夠引用其它 .aidl中定義的類型。ide

下面是IRemoteService.aidl 的定義:wordpress

package com.example.android.apis.app;
import com.example.android.apis.app.IRemoteServiceCallback;
/**
 * Example of defining an interface for calling on to a remote service
 * (running in another process).
 */
interface IRemoteService {
 /**
 * Often you want to allow a service to call back to its clients.
 * This shows how to do so, by registering a callback interface with
 * the service.
 */
 void registerCallback(IRemoteServiceCallback cb);
 /**
 * Remove a previously registered callback interface.
 */
 void unregisterCallback(IRemoteServiceCallback cb);<br />
}

 

編譯時,Android SDK 工具自動在gen目錄下生成對應的 IRemoteService.java。函數

2. 實現這個AIDL接口工具

編譯時,Android SDK 工具自動在gen目錄下生成對應的 IRemoteService.java,這個文件中會定義一個子類Stub (IRemoteService.Stub),Stub定義了由.aidl 定義的抽象方法實現。除此以外,Stub還定義了幾個Help 方法,好比asInterface() 參數爲IBinder對象(一般由Client中的onServiceConnected()傳入),返回一個Stub實例供Client使用。

下面是IRemoteService.Stub 在 RemoteService中的實現:

    /**
     * The IRemoteInterface is defined through IDL
     */
    private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
        public void registerCallback(IRemoteServiceCallback cb) {
            if (cb != null) mCallbacks.register(cb);
        }
        public void unregisterCallback(IRemoteServiceCallback cb) {
            if (cb != null) mCallbacks.unregister(cb);
        }
    };

 

3. Expose the interface to Clients

在定義.aidl 和實現AIDL接口定義後,就須要將這個接口提供給Client使用。方法是派生Service並提供onBind方法返回上面實現的Stub的一個實例。

public class RemoteService extends Service {
...
@Override
public IBinder onBind(Intent intent) {
 // Select the interface to return.  If your service only implements
 // a single interface, you can just return it here without checking
 // the Intent.
 if (IRemoteService.class.getName().equals(intent.getAction())) {
 return mBinder;
 }
 if (ISecondary.class.getName().equals(intent.getAction())) {
 return mSecondaryBinder;
 }
 return null;
}

 

RemoteService 中定義了兩個.aidl 接口可供Client使用IRemoteService.aidl和ISecondary.aidl。

有了AIDL定義並在Service中定義了可供Client使用的AIDL實現。下面再來看看Client的實現步驟:

  1. 將. aidl定義包含着 src目錄下,因爲本例Service ,Client 都包含在ApiDemos中,.aidl已在src中定義了。
  2. 根據.aidl接口生成IBinder接口定義(編譯時由Android SDK工具自動生成)。
  3. 實現ServiceConnection接口
  4. 調用Context.bindService 來綁定需調用的Service。
  5. 在ServiceConnection 的onServiceConnected方法中,根據傳入的IBinder對象(被調用的Service),使用 YourInterfaceName.Stub.asInterface((IBinder)service)) 將 service轉換爲YourInterfaceName類型。
  6. 調用YourInterfaceName定義的方法,這裏須要捕獲DeadObjectException 異常,DeadObjectException會在連接斷裂時拋出。
  7. 使用完Service,使用Context.unbindService斷開與Service之間的綁定。

Remote Service Binding 例子中 Service 端實現了兩個Service:

  • IRemoteService :提供registerCallback,unregisterCallback用來在RemoteCallbackList 中註冊或註銷一個Client的Callback。
  • ISecondary: 實際Client會調用的服務,getPid返回當前進程Process ID。basicTypes 介紹了通常參數類型用法,本例中Client爲使用。
        /**
         * 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 = IRemoteService.Stub.asInterface(service);
                mKillButton.setEnabled(true);
                mCallbackText.setText("Attached.");

                // We want to monitor the service for as long as we are
                // connected to it.
                try {
                    mService.registerCallback(mCallback);
                } 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;
                mKillButton.setEnabled(false);
                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();
            }
        };

 

在RemoteService 的主線程中定義了一個mHandler,它每隔1秒鐘將value值加1,並經過IRemoteServiceCallback 將這個新值發給註冊過的Client。

                    // Up it goes.
                    int value = ++mValue;
                    
                    // Broadcast to all clients the new value.
                    final int N = mCallbacks.beginBroadcast();
                    for (int i=0; i<N; i++) {
                        try {
                            mCallbacks.getBroadcastItem(i).valueChanged(value);
                        } catch (RemoteException e) {
                            // The RemoteCallbackList will take care of removing
                            // the dead object for us.
                        }
                    }
                    mCallbacks.finishBroadcast();
                    
                    // Repeat every 1 second.
                    sendMessageDelayed(obtainMessage(REPORT_MSG), 1*1000);

 

當運行這個示例時,若是沒有事先運行Remote Service Controller 的start Service ,這個值會從1開始,每隔1秒鐘加1.

再來看看Client如何綁定Service的。

bindService(new Intent(IRemoteService.class.getName()),
 mConnection, Context.BIND_AUTO_CREATE);
bindService(new Intent(ISecondary.class.getName()),
 mSecondaryConnection, Context.BIND_AUTO_CREATE);

 

注意這裏的Context.BIND_AUTO_CREATE,這意味這若是在綁定的過程當中,若是Service因爲某種緣由被Destroy 了,Android還會自動從新啓動被綁定的Service。你能夠點擊Kill Process 殺死Service看看結果 :-)。

        private OnClickListener mKillListener = new OnClickListener() {
            public void onClick(View v) {
                // To kill the process hosting our service, we need to know its
                // PID.  Conveniently our service has a call that will return
                // to us that information.
                if (mSecondaryService != null) {
                    try {
                        int pid = mSecondaryService.getPid(); // Note that, though this API allows us to request to
                        // kill any process based on its PID, the kernel will
                        // still impose standard restrictions on which PIDs you
                        // are actually able to kill.  Typically this means only
                        // the process running your application and any additional
                        // processes created by that app as shown here; packages
                        // sharing a common UID will also be able to kill each
                        // other's processes.
 Process.killProcess(pid);
                        mCallbackText.setText("Killed service process.");
                    } catch (RemoteException ex) {
                        // Recover gracefully from the process hosting the
                        // server dying.
                        // Just for purposes of the sample, put up a notification.
                        Toast.makeText(Binding.this,
                                R.string.remote_call_failed,
                                Toast.LENGTH_SHORT).show();
                    }
                }
            }
        };

在RemoteService.Binding 中也定義了一個mHandler,是用來顯示從Service發回的當前的Value值,Servcie發回Client的接口 IRemoteServiceCallback 一樣也須要對應的AIDL定義。使用mHandler是由於valueChanged 須要更新UI。Android 系統Handler用法簡介

本例按unBind Service 會解除與Service之間的綁定,一樣若是沒有事先運行Remote Service Controller 的start Service,這個操做會致使Destory Service,由於」Bound」 Service 在沒有與之綁定的Service是自動退出,下次綁定時,Value又會從1開始計數。

此外本例中用到了Android.os.RemoteCallbackList 這個類是爲了方便管理Remote調用時向Client發送Callback而提供的,具體不詳述了,能夠參見http://developer.android.com/reference/android/os/RemoteCallbackList.html

相關文章
相關標籤/搜索