Android IPC通信之AIDL

AIDL( Android Interface Definition Language),安卓接口定義語(yǔ)言齐疙,用于定義服務(wù)器和客戶端通信接口的一種描述語(yǔ)言,主要是解決了android進(jìn)程間通信的問(wèn)題艇棕。
在AIDL中咽瓷,不是所有的數(shù)據(jù)類型都是可以使用的∶辰郑可用類型如下:

  • 基本數(shù)據(jù)類型(byte char int long boolean float double)庵寞,short不支持
  • String和CharSequence
  • List:只支持ArrayList,里面的每個(gè)元素都必須被AIDL支持
  • Map:只支持HashMap薛匪,里面的每個(gè)key皇帮,value都必須被AIDL支持
  • Parcelable:所有實(shí)現(xiàn)了Parcelable接口的對(duì)象
  • AIDL 所有的AIDL接口本身也可以在AIDL文件中使用
    需要注意的是:
  • AIDL中自定義的Parcelable對(duì)象和AIDL對(duì)象必須要實(shí)現(xiàn)import進(jìn)來(lái),即使是在同一個(gè)包下面蛋辈。
  • 如果AIDL中用到了Parcelable對(duì)象属拾,那么必須要新建一個(gè)和它同名的AIDL文件,并在其中聲明它為Parcelable對(duì)象冷溶。
  • AIDL中除了基本數(shù)據(jù)渐白,其他類型的參數(shù)必須標(biāo)上方向:in、out逞频、inout纯衍,in表示輸入型參數(shù),out表示輸出型參數(shù)苗胀,inout表示輸入輸出型參數(shù)襟诸。
    接下來(lái)通過(guò)Demo來(lái)講解:
//自定義數(shù)據(jù)類型Book瓦堵,實(shí)現(xiàn)Parcelable接口完成序列化 
public class Book implements Parcelable {
    public int bookId;
    public String bookName;

    public Book(int bookId, String bookName) {
        this.bookId = bookId;
        this.bookName = bookName;
    }

    protected Book(Parcel in) {
        bookId = in.readInt();
        bookName = in.readString();
    }

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

        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(bookId);
        dest.writeString(bookName);
    }
}

接著New->AIDL->AIDL file創(chuàng)建AIDL文件,注意要明確導(dǎo)包

// Book.aidl 歌亲,可能會(huì)出現(xiàn)同名問(wèn)題菇用,可以先建一個(gè)別的名字再改回來(lái)
package com.wtwd.aidl;
parcelable Book;

// IBookManager.aidl
package com.wtwd.aidl;
import com.wtwd.aidl.Book;
import com.wtwd.aidl.IOnNewBookArrivedListener;
interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
    void registerListener(IOnNewBookArrivedListener listener);
    void unregisterListener(IOnNewBookArrivedListener listener);
}

// IOnNewBookArrivedListener.aidl
package com.wtwd.aidl;
import com.wtwd.aidl.Book;
interface IOnNewBookArrivedListener {
    void onNewBookArrived(in Book newbook);
}

接著build一下項(xiàng)目會(huì)在app/build/generated/aidl_source_output_dir/debug/compileDebugAidl/out目錄下找到對(duì)應(yīng)的AIDL生成文件(具體分析見(jiàn)Android中的IPC機(jī)制一文),接下來(lái)創(chuàng)建BookManagerService作為服務(wù)端核心代碼陷揪。

public class BookManagerService extends Service {

    private static final String TAG = "BookManagerService";
    //CopyOnWriteArrayList是為了處理線程同步惋鸥,但是這里就和之前說(shuō)的AIDL只能夠使用ArrayList,為什么這兒可以
    //使用CopyOnWriteArrayList呢悍缠?首先CopyOnWriteArrayList不是繼承自ArrayList的卦绣,且AIDL支持的是抽象的
    //List,而List是一個(gè)接口飞蚓,雖然服務(wù)端返回的是CopyOnWriteArrayList滤港,但是Binder中會(huì)按照List的規(guī)范去訪
    //問(wèn)數(shù)據(jù)并最終形成一個(gè)新的ArrayList給客戶端,同理ConcurrentHashMap也是如此趴拧。
    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();

    //private CopyOnWriteArrayList<IOnNewBookArrivedListener> mListenerList = new CopyOnWriteArrayList<>();
    //Binder從客戶端傳過(guò)來(lái)的對(duì)象經(jīng)過(guò)反序列化后和原先是不同的溅漾,所以需要采用RemoteCallbackList來(lái)實(shí)現(xiàn)
    //RemoteCallbackList是系統(tǒng)專門用來(lái)刪除跨進(jìn)程的listener接口,它是一個(gè)泛型八堡,支持管理任意的AIDL接口樟凄,這是因?yàn)樗訟IDL接口繼承自IInterface
    //public class RemoteCallbackList<E extends IInterface> {}
    //內(nèi)部采用Map結(jié)構(gòu)保存回調(diào),key是IBinder類型兄渺,value是CallBack類型
    //ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback>();
    //在CallBack中封裝了真正的遠(yuǎn)程listener缝龄,客戶端注冊(cè)listener的時(shí)候會(huì)把這個(gè)listener信息存到mCallbacks,key和value通過(guò)如下獲取
    //IBinder binder = callback.asBinder();
    //Callback cb = new Callback(callback, cookie);
    //RemoteCallbackList內(nèi)部已實(shí)現(xiàn)線程同步
    private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<>();
    //用來(lái)記錄Service是否銷毀的標(biāo)志位挂谍,默認(rèn)false
    private AtomicBoolean mIsServiceDestroyed = new AtomicBoolean();
    //服務(wù)端的遠(yuǎn)程方法
    private Binder mBinder = new IBookManager.Stub() {
        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBookList;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            mBookList.add(book);
        }

        @Override
        public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
//            if (!mListenerList.contains(listener)) {
//                mListenerList.add(listener);
//            } else {
//                Log.e(TAG,"already exists.");
//            }
//            Log.e(TAG,"registerListener,size:"+mListenerList.size());
            mListenerList.register(listener);
        }
        @Override
        public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
//            if (!mListenerList.contains(listener)) {
//                mListenerList.remove(listener);
//                Log.e(TAG,"unregister listener success");
//            } else {
//                Log.e(TAG,"not found,can not unregister.");
//            }
//            Log.e(TAG,"unregisterListener,size:"+mListenerList.size());
            mListenerList.unregister(listener);
        }
    };
    
    //服務(wù)端返回的IBinder對(duì)象
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
    @Override
    public void onCreate() {
        super.onCreate();
        mBookList.add(new Book(1,"Android"));
        mBookList.add(new Book(2,"Ios"));
        //開(kāi)啟子線程叔壤,執(zhí)行任務(wù)
        new Thread(new ServiceWorker()).start();
    }

    @Override
    public void onDestroy() {
        mIsServiceDestroyed.set(true);
        super.onDestroy();
    }

    private void onNewBookArrived(Book book) throws RemoteException {
        mBookList.add(book);
        final int N = mListenerList.beginBroadcast();
//        Log.e(TAG,"onNewBookArrived,notify listener:"+mListenerList.size());
        for (int i=0;i<N;i++) {
            IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i);
            Log.e(TAG,"onNewBookArrived,notify listener:"+listener);
            if (listener != null) {
                listener.onNewBookArrived(book);
            }
        }
        mListenerList.finishBroadcast();
    }

    private class ServiceWorker implements Runnable {
        @Override
        public void run() {
            while (!mIsServiceDestroyed.get()) {
                try {
                    //每隔5s執(zhí)行任務(wù),回調(diào)給客戶端
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                int bookId = mBookList.size()+1;
                Book newBook = new Book(bookId,"new book#"+bookId);
                try {
                    onNewBookArrived(newBook);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
AndroidManafist.png

以上就是AIDL服務(wù)端的代碼了口叙,雖然看起來(lái)比較復(fù)雜炼绘,但是細(xì)細(xì)評(píng)味下來(lái)還是獲益匪淺的。接下來(lái)就開(kāi)始客戶端的代碼編寫(xiě)了妄田,通常我們接觸最多的也是這方面俺亮,服務(wù)端會(huì)把相關(guān)的AIDL代碼提供過(guò)來(lái),我們直接把它復(fù)制粘貼到項(xiàng)目中就可以了疟呐,需要注意一點(diǎn)的是,如果存在自定義數(shù)據(jù)類型脚曾,需要將JavaBean放到j(luò)ava目錄和AIDL相同的包下面,如下所示:


AIDL客戶端復(fù)制.png

接下來(lái)Build一下Project就可以生成對(duì)應(yīng)的AIDL接口文件了

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "kris_ni";
    private static final int MESSAGE_NEW_BOOK_ARRIVED = 0x1;

    private IBookManager iBookManager;

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case MESSAGE_NEW_BOOK_ARRIVED:
                    Log.e(TAG,"receive new book :" + msg.obj);
                    break;
                default:
                    break;
            }
        }
    };

    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            iBookManager = IBookManager.Stub.asInterface(iBinder);
            try {
                List<Book> bookList = iBookManager.getBookList();
                Log.e(TAG,"query book list,list type: "+ bookList.getClass().getCanonicalName());
                Log.e(TAG,"query book list: "+bookList.toString());
                Book newBook = new Book(3,"Kotlin");
                iBookManager.addBook(newBook);
                Log.e(TAG,"add new book: "+newBook);
                List<Book> newList = iBookManager.getBookList();
                Log.e(TAG,"query book list: "+newList.toString());
                iBookManager.registerListener(listener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            iBookManager = null;
            Log.e(TAG,"binder died");
        }
    };

    private IOnNewBookArrivedListener listener = new IOnNewBookArrivedListener.Stub() {
        @Override
        public void onNewBookArrived(Book book) throws RemoteException {
            mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED,book).sendToTarget();
        }
    };

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

        Intent intent = new Intent("com.example.aidldemo.BookManagerService");
        intent.setPackage("com.example.aidldemo");
        bindService(intent,serviceConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        if (iBookManager != null && iBookManager.asBinder().isBinderAlive()) {
            try {
                iBookManager.unregisterListener(listener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        unbindService(serviceConnection);
        super.onDestroy();
    }
}

總體看下來(lái)AIDL也就這樣了么启具,當(dāng)然不止這些本讥。有沒(méi)有考慮過(guò)AIDL被濫用的情況呢?可以通過(guò)權(quán)限來(lái)限制客戶端,簡(jiǎn)單的寫(xiě)法就是在服務(wù)端聲明權(quán)限

    <permission android:name="com.example.aidldemo.permission.ACCESS_BOOK_SERVICE"
        android:protectionLevel="normal"/>
    <uses-permission android:name="com.example.aidldemo.permission.ACCESS_BOOK_SERVICE"/>

接著就可以在Service里面判斷是否將IBinder返回了

    @Override
    public IBinder onBind(Intent intent) {
        int check = checkCallingOrSelfPermission("com.example.aidldemo.permission.ACCESS_BOOK_SERVICE");
        if (check == PackageManager.PERMISSION_DENIED) {
            Log.e(TAG,"has no permission");
            return null;
        }
        return mBinder;
    }

客戶端所做的是需要申明權(quán)限才可以進(jìn)行ServiceConnection

    <uses-permission android:name="com.example.aidldemo.permission.ACCESS_BOOK_SERVICE"/>

另外一種寫(xiě)法如下:

     public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
                throws RemoteException {
            int check = checkCallingOrSelfPermission("com.example.aidldemo.permission.ACCESS_BOOK_SERVICE");
            Log.d(TAG, "check=" + check);
            if (check == PackageManager.PERMISSION_DENIED) {
                return false;
            }

            String packageName = null;
            String[] packages = getPackageManager().getPackagesForUid(
                    getCallingUid());
            if (packages != null && packages.length > 0) {
                packageName = packages[0];
            }
            Log.d(TAG, "onTransact: " + packageName);
            if (!packageName.startsWith("com.example")) {
                return false;
            }

            return super.onTransact(code, data, reply, flags);
        }

AIDL的介紹到此就差不多結(jié)束了拷沸,有沒(méi)有思考過(guò)Binder可能會(huì)意外被銷毀呢色查?在接下來(lái)的Binder連接池里面會(huì)重點(diǎn)為大家介紹,感謝您的閱讀撞芍,記得隨手點(diǎn)個(gè)贊喔秧了!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市勤庐,隨后出現(xiàn)的幾起案子示惊,更是在濱河造成了極大的恐慌好港,老刑警劉巖愉镰,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異钧汹,居然都是意外死亡丈探,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門拔莱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)碗降,“玉大人,你說(shuō)我怎么就攤上這事塘秦∷显ǎ” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵尊剔,是天一觀的道長(zhǎng)爪幻。 經(jīng)常有香客問(wèn)我,道長(zhǎng)须误,這世上最難降的妖魔是什么挨稿? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮京痢,結(jié)果婚禮上奶甘,老公的妹妹穿的比我還像新娘。我一直安慰自己祭椰,他們只是感情好臭家,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著方淤,像睡著了一般钉赁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上臣淤,一...
    開(kāi)封第一講書(shū)人閱讀 51,301評(píng)論 1 301
  • 那天橄霉,我揣著相機(jī)與錄音,去河邊找鬼。 笑死姓蜂,一個(gè)胖子當(dāng)著我的面吹牛按厘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播钱慢,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼逮京,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了束莫?” 一聲冷哼從身側(cè)響起懒棉,我...
    開(kāi)封第一講書(shū)人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎览绿,沒(méi)想到半個(gè)月后策严,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡饿敲,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年妻导,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片怀各。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡倔韭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出瓢对,到底是詐尸還是另有隱情寿酌,我是刑警寧澤鬼譬,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布嫉到,位于F島的核電站兽赁,受9級(jí)特大地震影響脉让,放射性物質(zhì)發(fā)生泄漏跨跨。R本人自食惡果不足惜柿冲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一衡查、第九天 我趴在偏房一處隱蔽的房頂上張望用僧。 院中可真熱鬧壶栋,春花似錦辰如、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至毙玻,卻和暖如春豌蟋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背桑滩。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工梧疲, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓幌氮,卻偏偏與公主長(zhǎng)得像缭受,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子该互,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容