AIDL的使用筆記

筆墨Android發表於2018-12-01

最近這段時間在專案開發中使用到了AIDL,之前對這塊的內容比較陌生.所以特地分享一下關於AIDL的一些使用知識,如果有什麼寫的不對的地方還請指出.這裡先謝謝了!!!

本文知識點:

    1. AIDL簡介
    1. AIDL的一些簡單說明
    1. AIDL的DEMO演示
    1. AIDL的進階使用

1. AIDL簡介

其實我平時很煩一下概念上的東西,但是有的時候面試會使用到,所以這裡還是簡單提一下吧!你只要記住"AIDL(Android 介面定義語言) 是 Android 提供的一種程式間通訊 (IPC) 機制。其次就是AIDL是實現跨程式通訊的"就可以了,如果想了解更多的概念的話,建議你看看百度百科關於AIDL的說明剩下的就不在這裡多做解釋了!

2. AIDL的一些簡單說明

AIDL說的簡單一些的話,其實就是一個app要訪問另外一個app的服務,訪問者為客戶端,被訪問者是伺服器.就這麼簡單.然後你覺得這一小節就完了?太天真了.這裡還有許多要說的呢?

2.1 AIDL可以傳遞的資料型別

AIDL支援如下型別傳遞

支援的型別 說明 是否需要導包
Java的基本型別 如 int、long、char、boolean 等等 不需要導包
List 只支援ArrayList,裡面的每個元素都必須被AIDL支援 不需要導包
Map 只支援HashMap, 裡面的每個元素都必須被AIDL支援 不需要導包
實現Parcelable的類 必須是實現了序列化的類 需要導包
其他AIDL定義的AIDL介面 符合AIDL定義的介面 需要導包

上面注意一些問題,AIDL定義的介面,其實就是多個AIDL的組合而已,和正常的介面的使用是一樣的!沒什麼高深的...

2.2 AIDL的開發步驟

一般在專案中,我都是先把服務端的程式碼寫好,確定好相應的方法之後,在盡行客戶端的編寫。因為這樣的話,編寫起來比較方便!

    1. (服務端)建立相應的AIDL檔案

AIDL檔案的建立

這裡值得注意的是,建立完AIDL重新定義方法的時候。你最好ReBuild一下!因為這樣可以重新整理一下你的介面檔案!

    1. (服務端)相應介面的編寫

建立一個相應的服務,關聯相應AIDL的一些內容。建立服務就不再這裡說了,這裡只說明一下怎麼關聯相應的AIDL檔案。

public class AidlService extends Service {

    private String TAG = AidlService.class.getSimpleName();

    private IMyAidlInterface.Stub mInterface = new IMyAidlInterface.Stub() {
        @Override
        public int add(int a, int b) throws RemoteException {
            Log.e(TAG, "a的值是:" + a + "b的值是:" + b);
            return a + b;
        }

        @Override
        public int sub(int a, int b) throws RemoteException {
            Log.e(TAG, "a的值是:" + a + "b的值是:" + b);
            return a - b;
        }
    };

    @Override
    public IBinder onBind(Intent intent) {
        return mInterface;
    }
}
複製程式碼

上面是一段Service的程式碼,這個Service主要是提供相應的服務的類。其他APP都是呼叫這個Service中的方法的!

    1. 將客戶端的AIDL檔案複製到客戶端

這一步很重要,否則你根本不知道怎麼你該呼叫那個介面進行操作。具體位置,請看下面這張圖片

複製檔案的路徑

這裡注意一點,注意包名的問題。

    1. (客戶端)繫結相應的服務

客戶端通過相應的bindService繫結這個服務,就能使用裡面的方法了!來段java程式碼。

public class MainActivity extends AppCompatActivity {

    /**
     * AIDL物件,以它來呼叫介面中的方法
     */
    private IMyAidlInterface mIMyAidlInterface;

    /**
     * 是否連線成功
     */
    private boolean mIsConnection;

    private TextView mTvResult;

    /**
     * Connection物件,用來建立連線
     */
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //初始化AIDL物件
            mIMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            //連線失敗的時候回撥的方法
        }
    };

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

        mTvResult = findViewById(R.id.tv_result);

        //繫結服務的方法
        bindMathService();
    }

    private void bindMathService() {
        Intent intent = new Intent();
        intent.setComponent(new ComponentName("com.along.aidldemo", "com.along.aidldemo.AidlService"));
        bindService(intent, mConnection, BIND_AUTO_CREATE);
    }


    /**
     * 點選事件呼叫AIDL的方法
     */
    public void add(View view) {
        try {
            int add = mIMyAidlInterface.add(3, 4);
            mTvResult.setText(String.valueOf(add));
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //解綁相應的服務
        if (mIsConnection) {
            unbindService(mConnection);
        }
    }
}
複製程式碼

註釋我已經寫的很清楚了,剩下的就沒有什麼好說的了!但是注意一點問題,繫結服務最好在onCreate中進行,這樣能確保你在使用的時候,繫結已經成功了!因為有的時候,你在使用的時候繫結的話,可能會出現空指標的問題。基本上就是上面這些步驟了。

3. AIDL的DEMO演示

關於這個Demo演示,我一直沒有太好的案例,一般網上都是演示加減法。我上面也是演示的加減法,但是我覺得這個有點low。所以我們就來一個新增圖書的案例吧!正好方便下面的延申。

案例演示

案例是這個樣子的,根據你輸入的書名,新增到遠端(AIDL),然後通過服務端(AIDL)把所有的書名顯示回來!但是這裡有一個重要的知識點,就是序列化物件的傳遞。我在用到的時候會著重講解的。其實不是很難了。。。

各位看官請準備好瓜子!!!

1. 建立相應的AIDL檔案

// IBookAidlInterface.aidl
package com.along.aidldemo;

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

import com.along.aidldemo.Book;

interface IBookAidlInterface {

    void addBooks(in Book book);

    List<Book> getBooks();
}
複製程式碼

這個是我建立的AIDL檔案,首先注意這裡的import匯入的檔案,因為這個檔案很重喲,我會在一會建立的時候講解。

public class Book implements Parcelable {

    private String name;
    private String price;

    public Book(String name, String price) {
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPrice() {
        return price;
    }

    public void setPrice(String price) {
        this.price = price;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(this.name);
        dest.writeString(this.price);
    }

    protected Book(Parcel in) {
        this.name = in.readString();
        this.price = in.readString();
    }

    public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() {
        @Override
        public Book createFromParcel(Parcel source) {
            return new Book(source);
        }

        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };
}
複製程式碼

這個是一個序列化的物件,我用的是Android Studio的外掛直接序列化的。這個外掛的名稱叫Android Parcelable code generator至於怎麼安裝自己百度一下就好了!

重頭戲來了。。。

// Book.aidl
package com.along.aidldemo;

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

parcelable Book;
複製程式碼

目錄結構

首先這個檔名稱是Book.aidl 為什麼要有這個檔案呢?其實也不難理解,你在AIDL中使用了Book這個類,如果你沒有這個檔案的話,AIDL不知道怎麼找到這個類啊?但是注意啊?這個AIDL檔名稱和你定義的類是一樣的!

建立了之後,要在build.gradle中增加如下程式碼(這個千萬別忘了):

 sourceSets {
        main {
            manifest.srcFile 'src/main/AndroidManifest.xml'
            java.srcDirs = ['src/main/java', 'src/main/aidl']
            resources.srcDirs = ['src/main/java', 'src/main/aidl']
            aidl.srcDirs = ['src/main/aidl']
            res.srcDirs = ['src/main/res']
            assets.srcDirs = ['src/main/assets']
        }
    }
複製程式碼

PS: 這裡有一個坑! 大家可能注意到了,在 Book.aidl 檔案中,我一直在強調: Book.aidl與Book.java的包名應當是一樣的。 但如果你要是想把他們放在不同的資料夾下也是可以的.如果你對此感興趣的話可以看一下 Android學習AIDL,這一篇文章就夠了這篇文章中講解怎麼去修改相應的位置.其實我覺得只有極度強迫症的人才需要,哈哈!

2.建立相應的Service

這裡其實也不難,就是建立一個Service然後進行相應的繫結,上面已經講解了,所以這裡直接貼程式碼了!

public class BookService extends Service {

    private final List<Book> mBooks;

    private IBookAidlInterface.Stub mIBookAidlInterface = new IBookAidlInterface.Stub() {
        @Override
        public void addBooks(Book book) throws RemoteException {
            mBooks.add(book);
        }

        @Override
        public List<Book> getBooks() throws RemoteException {
            return mBooks;
        }
    };


    public BookService() {
        //注意這裡只能用Arraylist
        mBooks = new ArrayList<>();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mIBookAidlInterface;
    }
}
複製程式碼

3. 複製相應的檔案到客戶端

這個比較簡單沒有什麼可以說的了!直接ctrl+c然後ctrl+v就可以了!

4. 在客戶端繫結相應的服務

繫結服務的程式碼,基本上就大同小異了.這裡就直接貼程式碼了!

public class MainActivity extends AppCompatActivity {

    /**
     * 是否連線成功
     */
    private boolean mIsConnection;

    private TextView mTvResult;

    /**
     * Connection物件,用來建立連線
     */
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //初始化AIDL物件
            mIBookAidlInterface = IBookAidlInterface.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            //連線失敗的時候回撥的方法
        }
    };
    private IBookAidlInterface mIBookAidlInterface;

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

        mTvResult = findViewById(R.id.tv_result);

        //繫結服務的方法
        bindMathService();
    }

    private void bindMathService() {
        Intent intent = new Intent();
        intent.setComponent(new ComponentName("com.along.aidldemo", "com.along.aidldemo.BookService"));
        bindService(intent, mConnection, BIND_AUTO_CREATE);
    }


    /**
     * 點選事件呼叫AIDL的方法
     */
    public void add(View view) {
        Book book = new Book("java基礎", "$1.00");
        try {
            mIBookAidlInterface.addBooks(book);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    public void getBooks(View view) {
        try {
            List<Book> books = mIBookAidlInterface.getBooks();
            StringBuilder sb = new StringBuilder();
            for (Book book : books) {
                sb.append(book.toString());
            }
            mTvResult.setText(sb.toString());
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //解綁相應的服務
        if (mIsConnection) {
            unbindService(mConnection);
        }
    }
}
複製程式碼

沒有什麼注意的點!

定向tag

不知道大家有沒有注意到一個問題,在入參的地方多了一個in這個標識?這個是什麼呢?簡單的說就是資料的流向.那麼都有哪些資料的流向呢?

  • in 表示資料只能由客戶端流向服務端
  • out 表示資料只能由服務端流向客戶端
  • inout 表示資料可在服務端與客戶端之間雙向流通

其中,資料流向是針對在客戶端中的那個傳入方法的物件而言的。in 為定向 tag 的話表現為服務端將會接收到一個那個物件的完整資料,但是客戶端的那個物件不會因為服務端對傳參的修改而發生變動;out 的話表現為服務端將會接收到那個物件的引數為空的物件,但是在服務端對接收到的空物件有任何修改之後客戶端將會同步變動;inout 為定向 tag 的情況下,服務端將會接收到客戶端傳來物件的完整資訊,並且客戶端將會同步服務端對該物件的任何變動。

關於這個如果要是有不懂的,可以看看 你真的理解AIDL中的in,out,inout麼?分析的還是比較透徹的!


關於AIDL基本上我在專案中只做三方登入,還沒有用它做過和音樂播放器相關的內容,有時間的話可以嘗試一下,關於以上內容,如果有童鞋不是很瞭解的話,可以私聊我哦!!!

相關文章