【後臺任務】Android接口定義語言(AIDL)(14)

概要


Android接口定義語言(AIDL)與您可能使用的其他IDL類似。它允許您定義客戶端和服務商通過使用進程間通信(IPC)進行通信的編程接口。在Android上,一個進程無法正常訪問另一個進程的內存。因此要說話,他們需要將他們的對象分解成操作系統可以理解的原語,並且爲你跨越邊界對象。編寫代碼很繁瑣,因此Android會使用AIDL處理它。

注意:只有當您允許來自不同應用程序的客戶端訪問您的IPC服務並希望處理服務中的多線程時,才需要使用AIDL。如果您不需要在不同的應用程序執行併發IPC,你應該創建界面實現活頁夾,或者,如果要執行IPC,但並不需要處理多線程,實現你的界面使用Messenger的。無論如何,確保您在實施AIDL之前瞭解綁定服務。

在開始設計您的AIDL接口之前,請注意對AIDL接口的調用是直接函數調用。您不應該對發生調用的線程做出假設。根據調用是來自本地進程中的線程還是遠程進程,發生的情況會有所不同。特別:

  • 來自本地進程的調用在執行調用的同一線程中執行。如果這是您的主UI線程,則該線程繼續在AIDL接口中執行。如果它是另一個線程,那是在服務中執行你的代碼的那個線程。因此,如果只有本地線程正在訪問該服務,則可以完全控制在其中執行哪些線程(但是,如果是這種情況,則根本不應該使用AIDL,而應該通過實現一個活頁夾來創建接口)。
  • 來自遠程進程的調用將從平臺維護在您自己進程內的線程池中調度。您必須爲來自未知線程的傳入呼叫做好準備,同時發生多個呼叫。換句話說,AIDL接口的實現必須是完全線程安全的。
  • 該oneway關鍵字修改遠程調用的行爲。使用時,遠程呼叫不會阻止; 它只是發送交易數據並立即返回。該接口的實現最終將其作爲來自Binder線程池的常規調用接收爲普通遠程調用。如果oneway用於本地呼叫,則不會造成影響,並且呼叫仍然是同步的。

定義一個AIDL接口


您必須.aidl使用Java編程語言語法在文件中定義您的AIDL接口,然後將其保存在src/承載服務的應用程序的源代碼(在目錄中)以及任何其他綁定到該服務的應用程序的目錄中。

當您構建包含該.aidl文件的每個應用程序時,Android SDK工具會IBinder根據該.aidl文件生成一個界面並將其保存在項目的gen/目錄中。該服務必須IBinder 適當地實現接口。然後,客戶端應用程序可以綁定到服務並從中調用IBinder執行IPC的方法。

要使用AIDL創建有界服務,請按照下列步驟操作:
創建.aidl文件
該文件使用方法簽名來定義編程接口。

實現接口
Android SDK工具根據您的.aidl文件使用Java編程語言生成一個接口 。該接口有一個名爲的內部抽象類Stub,它擴展 Binder並實現了您的AIDL接口中的方法。你必須擴展這個 Stub類並實現這些方法。

將接口公開給客戶端
實現一個Service並重寫onBind()以返回你的Stub 類的實現。

警告:您在第一次發佈後對您的AIDL界面所做的任何更改都必須保持向後兼容,以避免打破使用您的服務的其他應用程序。也就是說,因爲您的.aidl文件必須複製到其他應用程序才能訪問您的服務界面,您必須保持對原始界面的支持。

創建.aidl文件
AIDL使用一種簡單的語法,可以用一個或多個可以接受參數和返回值的方法聲明接口。參數和返回值可以是任何類型,甚至是其他AIDL生成的接口。

您必須.aidl使用Java編程語言構建文件。每個.aidl 文件都必須定義一個接口,並且只需要接口聲明和方法簽名。

默認情況下,AIDL支持以下數據類型:
所有原始類型的Java編程語言(如int,long, char,boolean,等)
String
CharSequence
List
List該列表中的所有元素都必須是此列表中支持的數據類型之一或者您聲明的其他AIDL生成的接口或其中一個。A List可以可選地用作“通用”類(例如 List<String>)。對方接收的實際具體類始終是一個ArrayList,儘管該方法是使用該List接口生成的。

Map
Map該列表中的所有元素都必須是此列表中支持的數據類型之一或者您聲明的其他AIDL生成的接口或其中一個。通用映射(例如表單的映射 Map<String,Integer>)不受支持。對方接收的實際具體類始終是a HashMap,儘管該方法是爲了使用Map接口而生成的。

您必須import爲上面沒有列出的每種附加類型包含一個聲明,即使它們在與您的界面相同的包中定義。

定義您的服務界面時,請注意:

  • 方法可以採用零個或多個參數,並返回一個值或void。
  • 所有非基本參數都需要一個方向標籤來指示數據傳送的方式。或者in,out或者inout(參見下面的例子)。
    原語是in默認的,不能以其他方式。

小心:您應該將方向限制在真正需要的位置,因爲編組參數很昂貴。

  • 包含在.aidl文件中的所有代碼註釋都包含在生成的IBinder界面中(導入和包裝語句之前的註釋除外)。
  • 只有方法支持; 您不能在AIDL中公開靜態字段。

這是一個示例.aidl文件:

// 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工具會IBinder在項目gen/目錄中生成接口文件。生成的文件名與文件名相匹配.aidl,但帶有.java擴展名(例如IRemoteService.aidl結果IRemoteService.java)。

如果您使用Android Studio,增量構建幾乎立即生成聯編程序類。如果你不使用Android Studio,那麼下次構建應用程序時,Gradle工具會生成binder類 - 應該在完成寫入文件後立即使用gradle assembleDebug (或gradle assembleRelease)構建項目.aidl,以便代碼可以鏈接到生成的類。

2.實現接口

當您構建應用程序時,Android SDK工具會生成一個.java以您的.aidl文件命名的接口文件。生成的接口包含一個名爲的子類Stub ,它是其父接口的抽象實現(例如YourInterface.Stub),並聲明.aidl文件中的所有方法。

注意: Stub還定義了一些輔助方法,最值得注意的是asInterface(),它需要一個IBinder(通常是傳遞給客戶端onServiceConnected()回調方法的方法)並返回存根接口的一個實例。有關如何進行此演員表的更多詳細信息,請參閱調用IPC方法一節。

要實現從此.aidl生成的Binder接口,請擴展生成的接口(例如YourInterface.Stub)並實現從該.aidl文件繼承的方法。

下面是一個使用匿名實例調用的接口的示例實現IRemoteService(由IRemoteService.aidl上面的示例定義 ):

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)的一個實例,它定義了服務的RPC接口。在下一步中,將向客戶展示此實例,以便他們可以與服務進行交互。

在實現您的AIDL接口時,您應該注意一些規則:

  • 傳入的調用不保證在主線程中執行,因此您需要從頭開始考慮多線程,並將服務正確地構建爲線程安全。
  • 默認情況下,RPC調用是同步的。如果您知道該服務需要超過幾毫秒才能完成請求,則不應該從活動的主線程調用該服務,因爲它可能會掛起應用程序(Android可能會顯示“應用程序不響應”對話框) - 您應該通常從客戶端中的單獨線程調用它們。
  • 您拋出的任何異常都會返回給調用者。

3.將接口公開給客戶

一旦你爲你的服務實現了接口,你需要將它暴露給客戶端,以便它們可以綁定到它。要爲您的服務公開接口,請擴展Service並實現onBind()以返回實現已生成的類的實例Stub(如前一節所述)。下面是一個示例服務,將IRemoteService示例界面公開給客戶端。

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

現在,當一個客戶端(如一個活動)調用bindService()連接到這個服務時,客戶端的onServiceConnected()回調會接收mBinder到服務onBind() 方法返回的 實例。

客戶端還必須能夠訪問接口類,因此如果客戶端和服務位於不同的應用程序中,則客戶端的應用程序必須.aidl在其src/目錄中擁有該文件的副本(這會生成android.os.Binder 接口 - 爲客戶端提供對AIDL方法的訪問)。

當客戶端收到IBinder的onServiceConnected()回調,它必須調用 YourServiceInterface.Stub.asInterface(service)投返回的參數YourServiceInterface類型。例如:

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

有關更多示例代碼,請參閱ApiDemos中的RemoteService.java類。

通過IPC傳遞對象


如果你有一個你想通過IPC接口從一個進程發送到另一個進程的類,你可以這樣做。但是,您必須確保您的類的代碼可用於IPC通道的另一端,並且您的類必須支持該Parcelable接口。支持該Parcelable接口非常重要,因爲它允許Android系統將對象分解爲可跨進程進行編組的基元。

要創建支持Parcelable協議的類,您必須執行以下操作:

  1. 讓你的課程實現Parcelable界面。
  2. 實現writeToParcel,它採用對象的當前狀態並將其寫入到Parcel。
  3. CREATOR爲您的類添加一個名爲實現該Parcelable.Creator接口的對象的靜態字段。
  4. 最後,創建一個.aidl聲明你的parcelable類的Rect.aidl文件(如下面的 文件所示)。
  5. 如果您正在使用自定義構建過程,請不要將該.aidl文件添加到構建中。與C語言中的頭文件類似,該.aidl文件未被編譯。

AIDL在它生成的代碼中使用這些方法和字段來編組和解組對象。

例如,以下是Rect.aidl創建可Rect分段類的文件:

package android.graphics;

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

這裏是這個Rect類如何實現 Parcelable協議的例子

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, int flags) {
        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();
    }

    public int describeContents() {
        return 0;
    }
}

Rect班上的編組非常簡單。查看其他方法Parcel以查看可以寫入Parcel的其他類型的值。
警告:不要忘記從其他進程接收數據的安全隱患。在這種情況下,可以Rect從中讀取四個數字Parcel,但是要確保這些值位於值的可接受範圍內,無論呼叫者正在嘗試執行什麼操作。有關如何保護您的應用程序免受惡意軟件×××的更多信息,請參閱安全和權限。

調用IPC方法


以下是調用類必須用來調用使用AIDL定義的遠程接口的步驟:

  1. 將該.aidl文件包含在項目src/目錄中。
  2. 聲明IBinder接口的實例(基於AIDL生成)。
  3. 執行ServiceConnection。
  4. 打電話Context.bindService(),傳遞你的ServiceConnection實現。
  5. 在您的實施中onServiceConnected(),您將收到一個IBinder實例(稱爲service)。調用 將返回的參數轉換爲YourInterface類型。YourInterfaceName.Stub.asInterface((IBinder)service)
  6. 調用你在界面上定義的方法。您應該始終捕獲 DeadObjectException連接斷開時引發的異常。您還應該捕獲SecurityException在IPC方法調用中涉及的兩個進程具有衝突的AIDL定義時引發的異常。
  7. 要斷開連接,請Context.unbindService()與您的界面實例通話。

關於調用IPC服務的幾點意見:

  • 對象是跨進程的引用計數。
  • 您可以發送匿名對象作爲方法參數。

有關綁定到服務的更多信息,請閱讀綁定服務 文檔。

下面是一些演示調用AIDL創建服務的示例代碼,取自ApiDemos項目中的Remote Service示例。

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

        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.
            Intent intent = new Intent(Binding.this, RemoteService.class);
            intent.setAction(IRemoteService.class.getName());
            bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
            intent.setAction(ISecondary.class.getName());
            bindService(intent, 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);
            }
        }

    };
}
Lastest Update:2018.04.24

聯繫我

QQ:94297366
微信打賞:https://pan.baidu.com/s/1dSBXk3eFZu3mAMkw3xu9KQ

公衆號推薦:

【後臺任務】Android接口定義語言(AIDL)(14)

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