【Android官方文檔】翻譯Android官方文檔-AIDL(三)

AIDL(Android Interface Definition Language 接口描述語言),它與其他IDL類似,你可以使用進程間通信的方法,以達到客戶端到服務端交互的目的。在Android中,不同進程不能直接訪問對方內存,因此,它們需要分解成操作系統能夠理解的單位,使用AIDL可以處理這個問題。

注:只有在你允許來自不同應用的客戶端跨進程通信訪問你的service時,才能使用AIDL。如果你不使用IPC,那麼你就需要使用Binder機制,如果你在不需要使用多線程的情況下使用IPC,那麼你需要利用Message。(If you do not need to perform concurrent IPC across different applications, you should create your interface by implementing a Binder or, if you want to perform IPC, but do not need to handle multithreading, implement your interface using a Messenger. Regardless, be sure that you understand Bound Services before implementing an AIDL.)

在你設計AIDL接口前,要注意,調用AIDL接口要直接調用的函數。(You should not make assumptions about the thread in which the call occurs. What happens is different depending on whether the call is from a thread in the local process or a remote process. Specifically:)這取決是調用的是本地進程還是遠程進程,具體如下:

  • 本地進程的調用是在同一線程中執行的. 如果是在主線程中, 該線程會在AIDL中繼續執行,如果是在主線程之外, (that is the one that executes your code in the service). 因此, 如果只有本地線程訪問服務,你完全可以控制線程執行它(如果是這樣的話,那麼你不應該如此,而是應該通過使用AIDL實現Binder創建接口)。
  • (Calls from a remote process are dispatched from a thread pool the platform maintains inside of your own process.) 你必須要爲未知線程的調用做好準備,尤其是在同一時間內的多次調用. 總之,一句話,AIDL的實現必須要是線程安全的。
  • (The oneway keyword modifies the behavior of remote calls.)單向關鍵詞修改遠程調用的行爲。 使用時,遠程調用不阻塞; 發送一個簡單的交互數據並且立即返回. (The implementation of the interface eventually receives this as a regular call from the Binder thread pool as a normal remote call. )如果oneway 使用的本地call, 那麼沒有任何影響,呼叫仍然是同步的。

定義AIDL接口

我們必須要在 .aidl文件中定義AIDL接口, 然後保存這些代碼(在 src/ 路徑下) (of both the application hosting the service and any other application that binds to the service.)

當你創建的每個應用程序包含aidl文件,那麼Android SDK工具將生成一個基於IBinder接口。在項目的根目錄中保存它。服務必須實現IBinder接口。客戶端應用程序可以綁定到服務和從IBinder調用執行IPC方法。

通過AIDL來實現創建綁定服務,要按照以下步驟:

  • 創建 .aidl文件:該文件定義了方法簽名的編程接口。
  • 繼承接口:Android SDK 會基於你的 .aidl文件生成 以java 程序語言的 接口。This interface has an inner abstract class named Stub that extends Binder and implements methods from your AIDL interface. 你必須繼承 Stub class 並且實現它的方法 。
  • expose the interface to client:實現Service 並且重寫onBind() to return your implementation of the Stub class.

注意: (Any changes that you make to your AIDL interface after your first release must remain backward compatible in order to avoid breaking other applications that use your service. ),因爲爲了提供服務接口,你的 .aidl 文件 必須複製到 other applications, (you must maintain support for the original interface.)


創建 .aidl 文件
AIDL 使用簡單的語法規則,讓你定義一個接口,其中可以包含一個或多個方法。這裏的參數及方法的返回值可以定義爲任何的類型 , 甚至可以是其他AIDL生成的接口.

你必須要用Java 來構建你的 .Aidl文件。 每一個aidl文件必須定義一個接口,僅僅只需要做的是:接口聲明和方法簽名。

在默認情況下, AIDL 以下數據類型:

  • 所有Java語言下的原始類型 (例如 int, long, char, boolean, 等等)
  • String
  • CharSequence
  • List
    任何元素在 List 中都必須有數據類型 ,甚至可以是AIDL生成的接口或者是聲明的parcelables。List 中填充的數據是任選的(例如可以是, List). (The actual concrete class that the other side receives is always an ArrayList, although the method is generated to use the List interface.)
  • Map
    任何元素在Map中都必須有數據類型,甚至可以是AIDL生成的接口或者是聲明的parcelables,Generic maps, (such as those of the form Map
// IRemoteService.aidl
package com.example.android;

// Declare any non-default types here with import statements

/** Example service interface */
interface IRemoteService {
    /** Request the process ID of this service, to do evil things with it. */
    int getPid();

    /** Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}

當你在你的應用中創建 aidl文件時,會保存在你的項目的 src/ 路徑下, 並且 SDK tools 會生成 IBinder 接口文件在項目的 gen/ 路徑下. 這個生成的文件名稱是和 aidl 文件名稱一樣的, 但是卻是 .java 文件(例如, IRemoteService.aidl 生成IRemoteService.java).

如果你使用的是Ecilipse, 幾乎立即生成綁定類。如果你使用的不是Ecilipse ,Ant tool會在你編寫項目的時候生成binder 類, (then the Ant tool generates the binder class next time you build your application)—在你完成對.aidl文件的編寫後,就可以開始編譯你的項目了,你的代碼會再次與生成的類關聯。(you should build your project with ant debug (or ant release) as soon as you’re finished writing the .aidl file, so that your code can link against the generated class.)


實現接口
在構建你的項目時, Android SDK tools 會在你創建了 .aidl文件後創建一個 .java的接口文件。在生成的接口中會有個名爲 Stub的 stuclass類(The generated interface includes a subclass named Stub that is an abstract implementation of its parent interface (例如, YourInterface.Stub)在.aidl 中聲明瞭所有的方法.)

注意: Stub also defines a few helper methods, most notably asInterface(), which takes an IBinder (usually the one passed to a client’s onServiceConnected() callback method) and returns an instance of the stub interface. See the section Calling an IPC Method for more details on how to make this cast.

需要實現的接口是來自 aidl文件的,實現生成的接口 (例如 YourInterface.Stub) .

這裏是一個例子,實現了一個接口。 (defined by the IRemoteService.aidl example, above) using an anonymous instance:

private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
    public int getPid(){
        return Process.myPid();
    }
    public void basicTypes(int anInt, long aLong, boolean aBoolean,
        float aFloat, double aDouble, String aString) {
        // Does nothing
    }
};

現在 mBinder 是 Stub 類的實例 (a Binder), (which defines the RPC interface for the service.)( In the next step, this instance is exposed to clients so they can interact with the service.)

以下是一些規則,你需要在實現你的AIDL文件時遵守。(There are a few rules you should be aware of when implementing your AIDL interface:)

  • (Incoming calls are not guaranteed to be executed on the main thread), 因此你需要考慮多線程,並且是線程安全的。(so you need to think about multithreading from the start and properly build your service to be thread-safe.)
  • 默認的情況下, RPC 調用是同步的, 如果你瞭解到Service完成需求應當就幾毫秒,所以不應該將耗時的放在主線程中, 因爲它可能會掛起應用程序 (Android 可能會出現”應用程序無響應” 對話框)—你應該做一個分離的線程在客戶端中。
    -沒有異常拋出給回調者。 No exceptions that you throw are sent back to the caller.

Expose the interface to clients
一旦你已經實現了你的服務的接口,你需要把它Expose給客戶,讓他們可以綁定到它。將接口Expose給你的服務,需要繼承服務 ,並且實現 onBind() 方法 然後返回你的類的實例,實現的是 generated Stub .以下是個例子展示:.

public class RemoteService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public IBinder onBind(Intent intent) {
        // Return the interface
        return mBinder;
    }

    private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
        public int getPid(){
            return Process.myPid();
        }
        public void basicTypes(int anInt, long aLong, boolean aBoolean,
            float aFloat, double aDouble, String aString) {
            // Does nothing
        }
    };
}

現在,當一個客戶端 (例如 一個 Activity) 調用 bindService() 方法去連接 service,客戶端的 onServiceConnected() 方法回調接收了mBinder 的實例,通過 service的onBind() 方法返回。

客戶端必須連接接口類, 如果在程序中服務端和客戶端是分離的, 那麼 client 需要在 src/ 路徑下拷貝 .aidl文件 (which generates the android.os.Binder interface—提供客戶端連接AIDL的方法).

當客戶端在onServiceConnected()方法回調中接收IBinder, 必須要調用 YourServiceInterface.Stub.asInterface(service) to cast the returned parameter to YourServiceInterface type. 例如:

IRemoteService mIRemoteService;
private ServiceConnection mConnection = new ServiceConnection() {
    // Called when the connection with the service is established
    public void onServiceConnected(ComponentName className, IBinder service) {
        // Following the example above for an AIDL interface,
        // this gets an instance of the IRemoteInterface, which we can use to call on the service
        mIRemoteService = IRemoteService.Stub.asInterface(service);
    }

    // Called when the connection with the service disconnects unexpectedly
    public void onServiceDisconnected(ComponentName className) {
        Log.e(TAG, "Service has unexpectedly disconnected");
        mIRemoteService = null;
    }
};

想看更多例子, 請看 RemoteService.java class


Passing Objects over IPC
如果你想從一個進程中發送數據到另一個進程中,那麼就需要通過 IPC接口. 然而,你必須確保你的類的代碼是可用的IPC通道和類必須支持Parcelable接口。 (Supporting the Parcelable interface is important because it allows the Android system to decompose objects into primitives that can be marshalled across processes.)

創建的類要用上 Parcelable 協議, 你必須遵守以下規則:

  • 創建的類需要實現Parcelable接口。
  • 實現 writeToParcel, (which takes the current state of the object and writes it to a Parcel.)
  • Add a static field called CREATOR to your class which is an object implementing the Parcelable.Creator interface.
  • 最後,創建一個 .aidl 文件並且聲明你的parcelable 類(就像如下的Rect.aidl 文件所示).
    如果您使用的是自定義生成進程,那麼添加AIDL文件不會加入到你的項目構建。類似於C語言的頭文件,AIDL文件不會被編譯。

AIDL使用這些方法在代碼中生成 marshall 和 unmarshall 對象。(AIDL uses these methods and fields in the code it generates to marshall and unmarshall your objects.)

如下例子:

package android.graphics;

// Declare Rect so AIDL can find it and knows that it implements
// the parcelable protocol.
parcelable Rect;

And here is an example of how the Rect class implements the Parcelable protocol.

import android.os.Parcel;
import android.os.Parcelable;

public final class Rect implements Parcelable {
    public int left;
    public int top;
    public int right;
    public int bottom;

    public static final Parcelable.Creator<Rect> CREATOR = new
Parcelable.Creator<Rect>() {
        public Rect createFromParcel(Parcel in) {
            return new Rect(in);
        }

        public Rect[] newArray(int size) {
            return new Rect[size];
        }
    };

    public Rect() {
    }

    private Rect(Parcel in) {
        readFromParcel(in);
    }

    public void writeToParcel(Parcel out) {
        out.writeInt(left);
        out.writeInt(top);
        out.writeInt(right);
        out.writeInt(bottom);
    }

    public void readFromParcel(Parcel in) {
        left = in.readInt();
        top = in.readInt();
        right = in.readInt();
        bottom = in.readInt();
    }
}

(The marshalling in the Rect class is pretty simple. Take a look at the other methods on Parcel to see the other kinds of values you can write to a Parcel.)


Calling an IPC Method

這裏是實現步驟:

  • .aidl 文件要在項目的 src/ 路徑下。
  • 聲明 IBinder接口的實例 。 (generated based on the AIDL).
  • 實現 ServiceConnection.
  • 調用 Context.bindService(),( passing in your ServiceConnection implementation.)
    -在你的 onServiceConnected() 實習方法中, 你將會得到 IBinder實例 (called service). 調用YourInterfaceName.Stub.asInterface((IBinder)service) to cast the returned parameter to YourInterface type.
  • 調用你接口中的方法.( You should always trap DeadObjectException exceptions, which are thrown when the connection has broken; this will be the only exception thrown by remote methods.)
  • 斷開連接, 調用Context.unbindService()斷開連接

調用IPC服務的幾點意見:

  • 對象是進程間的引用。(Objects are reference counted across processes.)
  • 可以發送匿名對象作爲方法參數。

    想了解更多信息, 請閱讀 Bound Services 文檔.

這裏是一些示例代碼演示調用AIDL創建服務,從項目的apidemos遠程服務的例子。

public static class Binding extends Activity {
    /** The primary interface we will be calling on the service. */
    IRemoteService mService = null;
    /** Another interface we use on the service. */
    ISecondary mSecondaryService = null;

    Button mKillButton;
    TextView mCallbackText;

    private boolean mIsBound;

    /**
     * Standard initialization of this activity.  Set up the UI, then wait
     * for the user to poke it before doing anything.
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.remote_service_binding);

        // Watch for button clicks.
        Button button = (Button)findViewById(R.id.bind);
        button.setOnClickListener(mBindListener);
        button = (Button)findViewById(R.id.unbind);
        button.setOnClickListener(mUnbindListener);
        mKillButton = (Button)findViewById(R.id.kill);
        mKillButton.setOnClickListener(mKillListener);
        mKillButton.setEnabled(false);

        mCallbackText = (TextView)findViewById(R.id.callback);
        mCallbackText.setText("Not attached.");
    }

    /**
     * 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();
        }
    };

    /**
     * Class for interacting with the secondary interface of the service.
     */
    private ServiceConnection mSecondaryConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className,
                IBinder service) {
            // Connecting to a secondary interface is the same as any
            // other interface.
            mSecondaryService = ISecondary.Stub.asInterface(service);
            mKillButton.setEnabled(true);
        }


1857
public void onServiceDisconnected(ComponentName className) {
            mSecondaryService = null;
            mKillButton.setEnabled(false);
        }
    };

    private OnClickListener mBindListener = new OnClickListener() {
        public void onClick(View v) {
            // Establish a couple connections with the service, binding
            // by interface names.  This allows other applications to be
            // installed that replace the remote service by implementing
            // the same interface.
            bindService(new Intent(IRemoteService.class.getName()),
                    mConnection, Context.BIND_AUTO_CREATE);
            bindService(new Intent(ISecondary.class.getName()),
                    mSecondaryConnection, Context.BIND_AUTO_CREATE);
            mIsBound = true;
            mCallbackText.setText("Binding.");
        }
    };

    private OnClickListener mUnbindListener = new OnClickListener() {
        public void onClick(View v) {
            if (mIsBound) {
                // If we have received the service, and hence registered with
                // it, then now is the time to unregister.
                if (mService != null) {
                    try {
                        mService.unregisterCallback(mCallback);
                    } catch (RemoteException e) {
                        // There is nothing special we need to do if the service
                        // has crashed.
                    }
                }

                // Detach our existing connection.
                unbindService(mConnection);
                unbindService(mSecondaryConnection);
                mKillButton.setEnabled(false);
                mIsBound = false;
                mCallbackText.setText("Unbinding.");
            }
        }
    };

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

    // ----------------------------------------------------------------------
    // Code showing how to deal with callbacks.
    // ----------------------------------------------------------------------

    /**
     * This implementation is used to receive callbacks from the remote
     * service.
     */
    private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() {
        /**
         * This is called by the remote service regularly to tell us about
         * new values.  Note that IPC calls are dispatched through a thread
         * pool running in each process, so the code executing here will
         * NOT be running in our main thread like most other things -- so,
         * to update the UI, we need to use a Handler to hop over there.
         */
        public void valueChanged(int value) {
            mHandler.sendMessage(mHandler.obtainMessage(BUMP_MSG, value, 0));
        }
    };

    private static final int BUMP_MSG = 1;

    private Handler mHandler = new Handler() {
        @Override public void handleMessage(Message msg) {
            switch (msg.what) {
                case BUMP_MSG:
                    mCallbackText.setText("Received from service: " + msg.arg1);
                    break;
                default:
                    super.handleMessage(msg);
            }
        }

    };
}

有的地方解讀的不準確,會繼續改進,勿噴~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章