Android IPC(二)

Android IPC

Android中的IPC方式

使用Bundle

Android的四大組件都支持在Intent中傳遞Bundle數(shù)據(jù)的楣颠,并且由于Bundle實現(xiàn)了Parcelable接口丹拯,所以它可以很方便地在不同進程中進行傳輸。
基于這一點默赂,我們在一個進程中啟動另外一個進程的Activity塞赂、Service、Receiver,我們就可以在Bundle中附加我們需要傳輸給遠程進程的信息并通過Intent發(fā)送出去吕嘀。

除了直接傳輸數(shù)據(jù)這種場景外,還可能出現(xiàn)一種特殊情況贞瞒。比如A進程的組件在進行一個計算偶房,并需要把計算后的結果傳遞給B進程的一個組件,但是計算結果不能直接放入Bundle中军浆,這個時候假如選擇其他IPC方式可能會略顯復雜棕洋。可以考慮使用以下方式乒融,我們先通過Intent在B進程啟動一個組件(比如IntentService)掰盘,讓Service在后臺計算,計算完畢后才啟動真正要啟動的組件赞季,這樣一來因為組件運行在B進程中愧捕,可以讓目標組件通過別的方式直接獲取計算結果,解決了上述跨進程的問題申钩。

使用文件共享

通過兩個進程讀/寫同一個文件來進行數(shù)據(jù)的共享次绘。Android上對并發(fā)讀/寫文件可以沒有限制的進行,甚至兩個線程同時對同一個文件進行寫操作都是允許的。
  而文件共享除了交換信息邮偎,也可以通過序列化的方式進行對象的交換管跺。
  文件共享主要的問題是并發(fā)讀/寫問題,因此我們要盡量避免這種情況的發(fā)生或者考慮使用線程同步來限制多個線程的寫操作禾进。
  通過上述可以得知豁跑,文件共享的方式在對數(shù)據(jù)要求同步要求不高的進程可以進行通信,并且要避免并發(fā)讀寫的問題命迈。
  SharePreferences是一個特例贩绕,底層通過XML文件來實現(xiàn)鍵值對的保存,也屬于文件的一種壶愤,但是Android系統(tǒng)會對它的讀寫有一定的緩存淑倾,即每一個內存中都有一份SharePreserences文件的緩存,因此在多進程模式中征椒,系統(tǒng)對它的讀/寫就變得不可靠娇哆,在高并發(fā)的場景中可能有很大的幾率會丟失數(shù)據(jù)。因為不適宜在多進程中進行使用勃救。

使用Messenger

Messenger底層實現(xiàn)AIDL碍讨,通過它可以在不同進程中傳遞Message對象。在Message中可以放入我們需要傳遞的數(shù)據(jù)蒙秒,就可以輕松地實現(xiàn)數(shù)據(jù)在進程之間的傳遞勃黍。
  Messenger的構造方法如下

public Messenger(Handler target) {
    mTarget = target.getIMessenger();
}

public Messenger(IBinder target) {
    mTarget = IMessenger.Stub.asInterface(target);
}

Messenger的使用方法簡單,它對AIDL進行1了封裝晕讲,使得我們可以更簡便地進行線程間通信覆获。同時又由于它一次處理一個請求,因此在服務端我們可以不同考慮線程同步的問題瓢省,因為服務器不存在并發(fā)執(zhí)行的情形弄息。
  而Messenger的創(chuàng)建分為服務端部分和客戶端部分。

服務端部分

服務端部分通過注冊Service來響應請求勤婚,同時創(chuàng)建一個Handler并通過它來創(chuàng)建一個Messenger對象摹量,然后在OnBind中返回這個Messenger對象底層的Binder,代碼如下

private static class MessengerHandler extends Handler {

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case MyConstants.MSG_FROM_CLIENT:
                // 處理請求
                Messenger client = msg.replyTo;
                Message replyMessage = Message.obtain(null, MyConstants.MSG_FROM_SERVICE);
                Bundle bundle = new Bundle();
                bundle.putString("reply", "message");
                replyMessage.setData(bundle);
                
                try {
                    client.send(replyMessage);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                    
                break;
            default:
                super.handleMessage(msg);
        }
    }
}

private final Messenger mMessenger = new Messenger(new MessengerHandler());

@Override
public IBinder onBind(Intent intent) {
    return mMessenger.getBinder();
}

上述代碼中,MessengerHandler用來處理客戶端發(fā)送的消息馒胆,可以從消息中獲取客戶端發(fā)送的數(shù)據(jù)缨称。而mMessenger是一個Messenger對象,它和Messenger相關聯(lián)祝迂,并在OnBinder里面返回它里面的Binder對象睦尽。
  而這里Messenger的作用是將客戶端發(fā)送的消息傳遞給MessengerHandler處理。
  在Hanler中液兽,可以通過Message的reply參數(shù)獲取客戶端發(fā)送的Messenger對象骂删,并對它調用send方法發(fā)送數(shù)據(jù)到客戶端處理。

客戶端部分

客戶端進程中四啰,首先要綁定服務端的Service宁玫,綁定成功后用服務端返回的IBinder對象創(chuàng)建一個Messenger,通過這個Messenger就可以向服務器發(fā)送Message類型的消息柑晒。
  如果需要服務端也能回應客戶端欧瘪,那么則需要在客戶端上創(chuàng)建一個Handler并創(chuàng)建一個新的Messenger,同時將這個Messenger對象通過Message的replyTo參數(shù)傳遞給服務端匙赞,代碼如下佛掖。

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    private Messenger mService;
    
    private Messenger mMessenger = new Messenger(new MessengerHandler());

    private static class MessengerHandler extends Handler {

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MyConstants.MSG_FROM_SERVICE:
                    Log.i(TAG, "handleMessage: " + msg.getData().get("reply"));
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mService = new Messenger(service);
            Message msg = Message.obtain(null, MyConstants.MSG_FROM_CLIENT);
            Bundle data = new Bundle();
            data.putString("msg", "hello, this is client");
            msg.setData(data);
            msg.replyTo = mMessenger;
            try {
                mService.send(msg);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };


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

        Intent intent = new Intent(this, MessengerService.class);
        bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(mServiceConnection);
    }

上述代碼中,客戶端首先綁定了遠程服務進程的MessengerService涌庭,綁定成功后芥被,根據(jù)服務端返回的binder對象創(chuàng)建Messenger對象,構造Message對象并并通過Messenger對象向服務端發(fā)送消息坐榆。
  而為了接受服務端的回復信息拴魄,客戶端也需要準備一個接受消息的Handler和Messenge對象,并在發(fā)送消息的時候席镀,通過reply參數(shù)將接受服務端回復的Messenger傳遞給服務端匹中。

總結

Binder工作原理

  上圖是Messenger的工作原理,可以通過Messenger實現(xiàn)更復雜的功能。
  需要注意的是豪诲,Messenger是使用串行的方式來處理客戶端發(fā)來的消息顶捷,而如果大量的消息同時發(fā)送到服務端,服務端仍只能一個個地處理屎篱,而可以看出Messenger不適合用于處理大量的并發(fā)請求這種場景服赎。同時Messenger的作用主要是為了傳遞信息,而我們很多時候需要跨進程調用服務端的方芳室,這種情形Messenger就無法做到了专肪,需要使用AIDL。

AIDL

Messenger是通過AIDL實現(xiàn)的堪侯,而使用AIDL同樣也可以進行進程間通信嚎尤。這里先介紹使用AIDL進行進程間通信的流程,分為客戶端和服務器部分伍宦。

服務端

服務端首先要創(chuàng)建一個Service用來監(jiān)聽客戶端的連接請求芽死,然后創(chuàng)建一個AIDL文件,將暴露給客戶端的接口在這個AIDL文件中聲明次洼,最后在Service中實現(xiàn)這個AIDL接口关贵。

客戶端

客戶端首先需要綁定服務端的Service,綁定成功后卖毁,將服務端返回的Binder對象轉成AIDL接口所屬的類型揖曾,接著就可以調用AIDL中定義的方法了落萎。
  而AIDL實現(xiàn)的過程還要更復雜一些,包含著許多難點和細節(jié)炭剪,以下將詳細介紹练链。

AIDL接口的創(chuàng)建

IBookManager.aidl
package com.daijie.aldlapp;

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

import com.daijie.aldlapp.Book;

interface IBookManager {

    List<Book> getBookList();

    void addBook(in Book book);
}

在AIDL中,并不是所有的數(shù)據(jù)類型都可以使用的奴拦,AIDL支持的數(shù)據(jù)類型如下媒鼓。

  • 基本數(shù)據(jù)類型(int、long错妖、char绿鸣、boolean、double等)
  • String和CharSequence;
  • List:只支持ArrayList暂氯,并且里面的每個元素都必須能夠被AIDL支持
  • Map:只支持HashMap潮模,并且里面的每個元素都能夠被AIDL支持,包括key和value
  • Parcelable:所有實現(xiàn)了Parcel接口的對象
  • AIDL:所有的AIDL接口本身也可以在AIDL文件中使用

在以上的6種類型中痴施,自定義的Parcel對象和AIDL對象不管是否與當前的AIDL文件位于同一個包內再登,都必須顯示的import進來。
  在AIDL文件中如果用到了Parcel對象晾剖,則必須新建一個與它同名edAIDL文件锉矢,并在其中聲明它為Parcel類型。在上面IBookManager.aidl中用到了Book類齿尽,所以必須創(chuàng)建Book.aidl沽损,并添加以下的內容。

Book.aidl
package com.daijie.aldlapp;

parcelable Book;

AIDL中每個實現(xiàn)了Parcelable的接口的類都需要按照上面的那種方式去創(chuàng)建響應的AIDL文件并聲明那個類為parcelable循头。除此之外绵估,AIDL除了基本數(shù)據(jù)類型,其他類型的參數(shù)必須標上方面:in卡骂、out或者inout国裳,in表示輸入型參數(shù),out表示輸出型參數(shù)全跨,inout表示輸入輸出型參數(shù)缝左。這三個參數(shù)需要根據(jù)實際情況去指定。不能一概使用out或者inout浓若,因為底層是存在開銷的渺杉。
  最后,區(qū)別于傳統(tǒng)的java接口挪钓,AIDL只支持方法是越,不支持聲明靜態(tài)常量。

為了方便AIDL的開發(fā)碌上,建議把所有和AIDL相關的類和文件全部放入同一個包中倚评,這樣子做的原因是浦徊,當客戶端是另外一個應用的時候,可以直接將整個包復制到那個客戶端工程中天梧,而避免麻煩和出錯辑畦。
  需要注意的是,AIDL的包結構在服務端和客戶端要保持一致腿倚,否則會運行出錯。因為在客戶端需要反序列化服務端中和AIDL相關的所有類蚯妇,如果類的路徑不一樣敷燎,就會造成反序列化失敗。

遠程服務端Service的實現(xiàn)

public class BookManagerService extends Service {

    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();

    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 onCreate() {
        super.onCreate();
        mBookList.add(new Book(1, "Android"));
        mBookList.add(new Book(2, "iOS"));
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
}

以上是一個遠程Service的實現(xiàn)箩言,在onCreate中初始化添加了兩本書硬贯,然后創(chuàng)建了一個Binder對象并在onBind中返回,而這個Binder對象繼承了IBookManager.Stub并實現(xiàn)了它內部的AIDL方法陨收。
  而這里管理書籍內容的List是通過CopyOnWriteArrayList進行管理的饭豹,它支持并發(fā)的讀/寫,因為AIDL方法是在服務端的Binder線程池中執(zhí)行的务漩,因此當多個客戶端同時連接的時候拄衰,會存在多個線程同時訪問的情形,所以需要在AIDL方法中處理線程同步饵骨。

雖然AIDL中能使用的List只有ArrayList翘悉,但是這里卻使用了CopyOnWriteArrayList(CopyOnWriteArrayList并非是ArrayList的子類)。這里的原因是因為AIDL中支持的是一個抽象的List居触,而List只是一個接口妖混,因此雖然服務端返回的是CopyOnWriteArrayList,但是在Binder中會按照List的規(guī)范去訪問數(shù)據(jù)并最終形成一個ArrayList給客戶端轮洋。
  與此同時制市,ConcurrentMap也是可以被支持的。

客戶端的實現(xiàn)

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IBookManager bookManager = IBookManager.Stub.asInterface(service);
            mRemoteBookManager = bookManager;
            try {
                List<Book> list = bookManager.getBookList();
                Log.i(TAG, "query book list: " + list.toString());
                Book newBook = new Book(3, "Android開發(fā)藝術探索");
                bookManager.addBook(newBook);
                List<Book> newList = bookManager.getBookList();
                Log.i(TAG, "query book list: " + newList.toString());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent(MainActivity.this, BookManagerService.class);
        bindService(intent, mConnection, BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        unbindService(mConnection);
        super.onDestroy();
    }
}

客戶端的代碼比較簡單弊予,首先要綁定遠程服務祥楣,綁定成功后將服務端返回的Binder對象轉化為AIDL接口,然后就可以通過這個接口去調用服務端的遠程方法汉柒。
  需要注意的是荣堰,服務端的方法可能會比較耗時,而在UI線程中直接運行可能會導致ANR竭翠。因為客戶端線程會在調用遠程方法后掛起直至方法返回振坚。

觀察者模式

我們可以設計出一個需求,每個感興趣的用戶都在觀察著新書斋扰,而圖書館可以在新書到來的時候通知這些用戶渡八,這就可以利用觀察者模式來實現(xiàn)啃洋。
  而要實現(xiàn)這個功能,需要定義一個AIDL接口屎鳍,然后讓客戶端需要實現(xiàn)這個接口并且向圖書館申請新書的提醒功能宏娄,當然也可以隨時取消訂閱。這里使用AIDL接口而并非普通接口的原因是逮壁,在AIDL文件中無法使用普通接口孵坚。
  這里創(chuàng)建一個IOnNewBookArrivedListener文件,當有新書來的時候去通知每一個訂閱的用戶窥淆,從程序上來說就是調用一個方法卖宠,并把新書的參數(shù)傳遞進去,代碼如下

IOnNewBookArrivedListener
package com.daijie.aldlapp;

import com.daijie.aldlapp.Book;

interface IOnNewBookArrivedListener {
    void OnNewBookArrived(in Book newBook);
}
IBookManager.aidl
package com.daijie.aldlapp;

import com.daijie.aldlapp.Book;
import com.daijie.aldlapp.IOnNewBookArrivedListener;

interface IBookManager {
    List<Book> getBookList();

    void addBook(in Book book);

    void registerListener(IOnNewBookArrivedListener listener);

    void unregisterListener(IOnNewBookArrivedListener listener);
}

這里除了要新加一個AIDL接口,還需要在原有的接口上添加兩個新的方法忧饭。
  而服務端的Service的Binder也要去實現(xiàn)這兩個新加的接口扛伍,同時在服務端開啟一個新的線程,每隔5s就添加一本書并通知訂閱更新的客戶端词裤,代碼如下刺洒。

服務端代碼
public class BookManagerService extends Service {

    private static final String TAG = "BookManagerService";

    private AtomicBoolean mIsServiceDestroy = new AtomicBoolean(false);

    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();

    private RemoteCallbackList<IOnNewBookArrivedListener> mListeners =
            new RemoteCallbackList<>();

    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 {
            mListeners.register(listener);
            Log.i(TAG, "registerListener: " + mListeners.beginBroadcast());
            mListeners.finishBroadcast();
        }

        @Override
        public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
            mListeners.unregister(listener);
            Log.i(TAG, "unregisterListener: " + mListeners.beginBroadcast());
            mListeners.finishBroadcast();
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        mBookList.add(new Book(1, "Android"));
        mBookList.add(new Book(2, "iOS"));
        new Thread(new ServiceWorker()).start();

    }


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

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    private void onNewBookArrived(Book book) throws RemoteException {
        mBookList.add(book);
        final int N = mListeners.beginBroadcast();
        for (int i = 0; i < N; i++) {
            IOnNewBookArrivedListener l = mListeners.getBroadcastItem(i);
            if (l != null) {
                try {
                    l.OnNewBookArrived(book);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
        mListeners.finishBroadcast();
    }

    private class ServiceWorker implements Runnable {

        @Override
        public void run() {
            while (!mIsServiceDestroy.get()) {
                try {
                    Thread.sleep(5 * 1000);
                } 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();
                }
            }
        }
    }
}

而修改完服務端代碼后,客戶端代碼也要進行修改吼砂,主要在兩方面:客戶端需要注冊OnNewBookArrivedListener到遠程服務端逆航,這樣當有新書的時,服務端才能通知當前客戶端渔肩,同時我們需要在Activity銷毀纸泡;另一方面,當有新書的時候赖瞒,服務端會回調客戶單的IOnNewBookArrivedListener對象的onNewBookArrived方法女揭,但是這個方法是在客戶端的Binder線程池中執(zhí)行的,因此為了方便UI操作栏饮,需要有一個Handler可以將其切換到客戶端的主線程中去執(zhí)行吧兔。

客戶端代碼
public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    public static final int MESSAGE_NEW_BOOK_ARRIVED = 1;

    private IBookManager mRemoteBookManager;

    private Handler mHandler = new Handler() {

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

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IBookManager bookManager = IBookManager.Stub.asInterface(service);
            mRemoteBookManager = bookManager;
            try {
                List<Book> list = bookManager.getBookList();
                Log.i(TAG, "query book list: " + list.toString());
                Book newBook = new Book(3, "Android開發(fā)藝術探索");
                bookManager.addBook(newBook);
                List<Book> newList = bookManager.getBookList();
                Log.i(TAG, "query book list: " + newList.toString());
                bookManager.registerListener(mIOnNewBookArrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }

        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mRemoteBookManager = null;
            Log.e(TAG, "onServiceDisconnected: ");
        }
    };

    private IOnNewBookArrivedListener mIOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() {

        @Override
        public void OnNewBookArrived(Book newBook) throws RemoteException {
            mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, newBook).sendToTarget();
        }
    };


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent(MainActivity.this, BookManagerService.class);
        bindService(intent, mConnection, BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        if (mRemoteBookManager != null && mRemoteBookManager.asBinder().isBinderAlive()) {
            try {
                Log.i(TAG, "unregister listener: " + mIOnNewBookArrivedListener);
                mRemoteBookManager.unregisterListener(mIOnNewBookArrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        unbindService(mConnection);
        super.onDestroy();
    }
}

以上代碼可以正確運行,并且每隔5s袍嬉,客戶端就收到了來自服務端的新書推送境蔼。
但在Service的代碼中使用的是RemoteCallbackList而并非CopyOnWriteArrayList,原因如下伺通。
  假如這里使用到了CopyOnWriteArrayList箍土,我們則需要通過客戶端傳入的listener來判斷是否在CopyOnWriteArrayList中是否存在該對象后進行移除,盡管在訂閱和取消訂閱的時候使用的都是同一個客戶端對象罐监,但是由于Binder的機制吴藻,在客戶端傳輸進來的對象會重新轉化并生成一個新的對象,而無法進行匹配弓柱。對象的跨進程傳輸本就是一個序列化和反序列化的團沟堡,所以自定義對象才需要實現(xiàn)Parcelable接口侧但。
  RemoteCallbackList是系統(tǒng)專門提供的用于刪除跨進程listener的接口。RemoteCallbackList是一個泛型類航罗,支持管理任意的AIDL接口禀横,這個從它的聲明可以看出,因為所有的AIDL接口都繼承于IInterface粥血,以下是它的聲明柏锄。

public class RemoteCallbackList<E extends IInterface> 

它的工作原理很簡單,在它內部有一個Map結構專門用來保存所有的AIDL回調复亏,這個Map的key是IBinder類型趾娃,value是Callback類型,如下所示

ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback>();

其中Callback屬性封裝了真正的遠程listener蜓耻。當客戶端注冊listener的時候,它就將這個listener的信息存入mCallbacks中械巡,其中key和value即分別通過以下方式獲取刹淌。

IBinder key = listener.asBinder();
Callback value = new Callback(listener,cookie);

雖然多次跨進程傳輸客戶端的同一個對象在服務端會生成不同的對象,但是這些對象都有一個共同點讥耗,那么就是它們的底層Binder對象都是同一個有勾,而利用這個特性,客戶端取消訂閱的時候古程,只要遍歷服務端所有的listener蔼卡,找出那個和取消訂閱的listener具有相同對象的服務端listener并把它刪掉即可。
  以上就是RemoteCallbackList為我們做的事情挣磨,同時它可以在客戶端進程終止的時候雇逞,它能夠自動移除客戶單進程所注冊的listener。另外茁裙,它內部還自動實現(xiàn)了線程同步的功能塘砸,所以不用做額外的線程同步工作,
  使用RemoteCallbackList要注意的是晤锥,它雖然名字里面有List掉蔬,但是我們不能像操作一個List那樣操作它,遍歷RemoteCallbackList矾瘾,必須按照下面方式進行女轿,而其中必須beginBroadcast和finishBroadcast配對使用,哪怕只是獲取RemoteCallbackList的元素個數(shù)壕翩。

final int N = mListeners.beginBroadcast();
for (int i = 0; i < N; i++) {
    IOnNewBookArrivedListener l = mListeners.getBroadcastItem(i);
    if (l != null) {
        //TODO hander l
    }
}
mListeners.finishBroadcast();

AIDL基本使用方法已經(jīng)介紹完了蛉迹,但是還是有幾點需要注意。
  客戶端調用遠程服務的方法放妈,被調用的方法運行在服務端的Binder線程池中婿禽,同時客戶端線程會被掛起赏僧,這個時候如果服務端方法比較耗時,就會導致客戶端長時間的阻塞在這里扭倾,而如果這個客戶端線程是UI線程的話淀零,就會導致UI線程ANR。因此如果這個遠程方法是耗時的話膛壹,就要避免在客戶端的UI線程去訪問遠程方法驾中。由于客戶端的onServiceConnected和onServiceDisconnected都運行在UI線程中,所以也不可以在它們里面直接調用服務端的耗時方法模聋。
  另外肩民,由于服務端的方法本身就是運行在服務端的Binder線程池中,所以服務端方法本身就可以執(zhí)行大量耗時工作链方,這個時候就切記不要在服務端中開線程去執(zhí)行異步任務持痰,除非明確是要干什么,否則不建議祟蚀。

Binder重連

為了程序的健壯性考慮工窍,Binder是可能會出現(xiàn)意外死亡的,這往往是由于服務端進程意外終止了前酿,這個時候則需要重新連接服務患雏,有兩種辦法。
  第一種方法是給Binder設置DeathRecipient監(jiān)聽罢维,當Binder死亡時淹仑,我們會收到binderDied方法的回調,在binderDied中重連遠程服務肺孵。
  另一種方法是在onServiceDisconneced中重連遠程服務匀借。
  它們的區(qū)別在于,onServiceDisconneced是在客戶端的UI線程被回調平窘,而bindDied在客戶端的Binder線程池被回調怀吻。

權限驗證

Binder中驗證

第一種辦法,我們可以在onBind中驗證初婆,驗證不通過就返回null蓬坡,這樣驗證失敗的客戶端就無法綁定服務,而驗證方式可以有多種磅叛,比如使用permission進行驗證屑咳。

在onTransact方法中做權限驗證

第二種方法,在onTransact方法中做權限驗證弊琴,如果驗證失敗就直接返回false兆龙,這樣服務端就不會終止執(zhí)行AIDL的方法達到保護服務端的掉過,驗證的方式也很多,比如permission驗證紫皇。慰安,還可以通過getCallingUid和getCallingPid獲取客戶端所屬的Uid和Pid進行驗證。

其他方法

除了以上兩種比較常用的方法外聪铺,還有其他方法化焕,比如在Service中指定android:permission屬性等。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末铃剔,一起剝皮案震驚了整個濱河市撒桨,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌键兜,老刑警劉巖凤类,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異普气,居然都是意外死亡谜疤,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門现诀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來夷磕,“玉大人,你說我怎么就攤上這事赶盔∑笮浚” “怎么了榆浓?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵于未,是天一觀的道長。 經(jīng)常有香客問我陡鹃,道長烘浦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任萍鲸,我火速辦了婚禮闷叉,結果婚禮上,老公的妹妹穿的比我還像新娘脊阴。我一直安慰自己握侧,他們只是感情好,可當我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布嘿期。 她就那樣靜靜地躺著品擎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪备徐。 梳的紋絲不亂的頭發(fā)上萄传,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天,我揣著相機與錄音蜜猾,去河邊找鬼秀菱。 笑死振诬,一個胖子當著我的面吹牛,可吹牛的內容都是我干的衍菱。 我是一名探鬼主播赶么,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼梦碗!你這毒婦竟也來了禽绪?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤洪规,失蹤者是張志新(化名)和其女友劉穎印屁,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體斩例,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡雄人,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了念赶。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片础钠。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖叉谜,靈堂內的尸體忽然破棺而出旗吁,到底是詐尸還是另有隱情,我是刑警寧澤停局,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布很钓,位于F島的核電站,受9級特大地震影響董栽,放射性物質發(fā)生泄漏码倦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一锭碳、第九天 我趴在偏房一處隱蔽的房頂上張望袁稽。 院中可真熱鬧,春花似錦擒抛、人聲如沸推汽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至寂恬,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間栈妆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留鳞尔,地道東北人嬉橙。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像寥假,于是被迫代替她去往敵國和親市框。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內容