Android IPC(二)

Android IPC

Android中的IPC方式

使用Bundle

Android的四大組件都支持在Intent中傳遞Bundle數(shù)據(jù)的,并且由于Bundle實(shí)現(xiàn)了Parcelable接口,所以它可以很方便地在不同進(jìn)程中進(jìn)行傳輸。
基于這一點(diǎn),我們?cè)谝粋€(gè)進(jìn)程中啟動(dòng)另外一個(gè)進(jìn)程的Activity、Service、Receiver,我們就可以在Bundle中附加我們需要傳輸給遠(yuǎn)程進(jìn)程的信息并通過(guò)Intent發(fā)送出去。

除了直接傳輸數(shù)據(jù)這種場(chǎng)景外,還可能出現(xiàn)一種特殊情況。比如A進(jìn)程的組件在進(jìn)行一個(gè)計(jì)算,并需要把計(jì)算后的結(jié)果傳遞給B進(jìn)程的一個(gè)組件,但是計(jì)算結(jié)果不能直接放入Bundle中,這個(gè)時(shí)候假如選擇其他IPC方式可能會(huì)略顯復(fù)雜。可以考慮使用以下方式,我們先通過(guò)Intent在B進(jìn)程啟動(dòng)一個(gè)組件(比如IntentService),讓Service在后臺(tái)計(jì)算,計(jì)算完畢后才啟動(dòng)真正要啟動(dòng)的組件,這樣一來(lái)因?yàn)榻M件運(yùn)行在B進(jìn)程中,可以讓目標(biāo)組件通過(guò)別的方式直接獲取計(jì)算結(jié)果,解決了上述跨進(jìn)程的問(wèn)題。

使用文件共享

通過(guò)兩個(gè)進(jìn)程讀/寫(xiě)同一個(gè)文件來(lái)進(jìn)行數(shù)據(jù)的共享。Android上對(duì)并發(fā)讀/寫(xiě)文件可以沒(méi)有限制的進(jìn)行,甚至兩個(gè)線程同時(shí)對(duì)同一個(gè)文件進(jìn)行寫(xiě)操作都是允許的。
  而文件共享除了交換信息,也可以通過(guò)序列化的方式進(jìn)行對(duì)象的交換。
  文件共享主要的問(wèn)題是并發(fā)讀/寫(xiě)問(wèn)題,因此我們要盡量避免這種情況的發(fā)生或者考慮使用線程同步來(lái)限制多個(gè)線程的寫(xiě)操作。
  通過(guò)上述可以得知,文件共享的方式在對(duì)數(shù)據(jù)要求同步要求不高的進(jìn)程可以進(jìn)行通信,并且要避免并發(fā)讀寫(xiě)的問(wèn)題。
  SharePreferences是一個(gè)特例,底層通過(guò)XML文件來(lái)實(shí)現(xiàn)鍵值對(duì)的保存,也屬于文件的一種,但是Android系統(tǒng)會(huì)對(duì)它的讀寫(xiě)有一定的緩存,即每一個(gè)內(nèi)存中都有一份SharePreserences文件的緩存,因此在多進(jìn)程模式中,系統(tǒng)對(duì)它的讀/寫(xiě)就變得不可靠,在高并發(fā)的場(chǎng)景中可能有很大的幾率會(huì)丟失數(shù)據(jù)。因?yàn)椴贿m宜在多進(jìn)程中進(jìn)行使用。

使用Messenger

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

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

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

Messenger的使用方法簡(jiǎn)單,它對(duì)AIDL進(jìn)行1了封裝,使得我們可以更簡(jiǎn)便地進(jìn)行線程間通信。同時(shí)又由于它一次處理一個(gè)請(qǐng)求,因此在服務(wù)端我們可以不同考慮線程同步的問(wèn)題,因?yàn)榉?wù)器不存在并發(fā)執(zhí)行的情形。
  而Messenger的創(chuàng)建分為服務(wù)端部分和客戶端部分。

服務(wù)端部分

服務(wù)端部分通過(guò)注冊(cè)Service來(lái)響應(yīng)請(qǐng)求,同時(shí)創(chuàng)建一個(gè)Handler并通過(guò)它來(lái)創(chuàng)建一個(gè)Messenger對(duì)象,然后在OnBind中返回這個(gè)Messenger對(duì)象底層的Binder,代碼如下

private static class MessengerHandler extends Handler {

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case MyConstants.MSG_FROM_CLIENT:
                // 處理請(qǐng)求
                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用來(lái)處理客戶端發(fā)送的消息,可以從消息中獲取客戶端發(fā)送的數(shù)據(jù)。而mMessenger是一個(gè)Messenger對(duì)象,它和Messenger相關(guān)聯(lián),并在OnBinder里面返回它里面的Binder對(duì)象。
  而這里Messenger的作用是將客戶端發(fā)送的消息傳遞給MessengerHandler處理。
  在Hanler中,可以通過(guò)Message的reply參數(shù)獲取客戶端發(fā)送的Messenger對(duì)象,并對(duì)它調(diào)用send方法發(fā)送數(shù)據(jù)到客戶端處理。

客戶端部分

客戶端進(jìn)程中,首先要綁定服務(wù)端的Service,綁定成功后用服務(wù)端返回的IBinder對(duì)象創(chuàng)建一個(gè)Messenger,通過(guò)這個(gè)Messenger就可以向服務(wù)器發(fā)送Message類型的消息。
  如果需要服務(wù)端也能回應(yīng)客戶端,那么則需要在客戶端上創(chuàng)建一個(gè)Handler并創(chuàng)建一個(gè)新的Messenger,同時(shí)將這個(gè)Messenger對(duì)象通過(guò)Message的replyTo參數(shù)傳遞給服務(wù)端,代碼如下。

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

上述代碼中,客戶端首先綁定了遠(yuǎn)程服務(wù)進(jìn)程的MessengerService,綁定成功后,根據(jù)服務(wù)端返回的binder對(duì)象創(chuàng)建Messenger對(duì)象,構(gòu)造Message對(duì)象并并通過(guò)Messenger對(duì)象向服務(wù)端發(fā)送消息。
  而為了接受服務(wù)端的回復(fù)信息,客戶端也需要準(zhǔn)備一個(gè)接受消息的Handler和Messenge對(duì)象,并在發(fā)送消息的時(shí)候,通過(guò)reply參數(shù)將接受服務(wù)端回復(fù)的Messenger傳遞給服務(wù)端。

總結(jié)

Binder工作原理

  上圖是Messenger的工作原理,可以通過(guò)Messenger實(shí)現(xiàn)更復(fù)雜的功能。
  需要注意的是,Messenger是使用串行的方式來(lái)處理客戶端發(fā)來(lái)的消息,而如果大量的消息同時(shí)發(fā)送到服務(wù)端,服務(wù)端仍只能一個(gè)個(gè)地處理,而可以看出Messenger不適合用于處理大量的并發(fā)請(qǐng)求這種場(chǎng)景。同時(shí)Messenger的作用主要是為了傳遞信息,而我們很多時(shí)候需要跨進(jìn)程調(diào)用服務(wù)端的方,這種情形Messenger就無(wú)法做到了,需要使用AIDL。

AIDL

Messenger是通過(guò)AIDL實(shí)現(xiàn)的,而使用AIDL同樣也可以進(jìn)行進(jìn)程間通信。這里先介紹使用AIDL進(jìn)行進(jìn)程間通信的流程,分為客戶端和服務(wù)器部分。

服務(wù)端

服務(wù)端首先要?jiǎng)?chuàng)建一個(gè)Service用來(lái)監(jiān)聽(tīng)客戶端的連接請(qǐng)求,然后創(chuàng)建一個(gè)AIDL文件,將暴露給客戶端的接口在這個(gè)AIDL文件中聲明,最后在Service中實(shí)現(xiàn)這個(gè)AIDL接口。

客戶端

客戶端首先需要綁定服務(wù)端的Service,綁定成功后,將服務(wù)端返回的Binder對(duì)象轉(zhuǎn)成AIDL接口所屬的類型,接著就可以調(diào)用AIDL中定義的方法了。
  而AIDL實(shí)現(xiàn)的過(guò)程還要更復(fù)雜一些,包含著許多難點(diǎn)和細(xì)節(jié),以下將詳細(xì)介紹。

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,并且里面的每個(gè)元素都必須能夠被AIDL支持
  • Map:只支持HashMap,并且里面的每個(gè)元素都能夠被AIDL支持,包括key和value
  • Parcelable:所有實(shí)現(xiàn)了Parcel接口的對(duì)象
  • AIDL:所有的AIDL接口本身也可以在AIDL文件中使用

在以上的6種類型中,自定義的Parcel對(duì)象和AIDL對(duì)象不管是否與當(dāng)前的AIDL文件位于同一個(gè)包內(nèi),都必須顯示的import進(jìn)來(lái)。
  在AIDL文件中如果用到了Parcel對(duì)象,則必須新建一個(gè)與它同名edAIDL文件,并在其中聲明它為Parcel類型。在上面IBookManager.aidl中用到了Book類,所以必須創(chuàng)建Book.aidl,并添加以下的內(nèi)容。

Book.aidl
package com.daijie.aldlapp;

parcelable Book;

AIDL中每個(gè)實(shí)現(xiàn)了Parcelable的接口的類都需要按照上面的那種方式去創(chuàng)建響應(yīng)的AIDL文件并聲明那個(gè)類為parcelable。除此之外,AIDL除了基本數(shù)據(jù)類型,其他類型的參數(shù)必須標(biāo)上方面:in、out或者inout,in表示輸入型參數(shù),out表示輸出型參數(shù),inout表示輸入輸出型參數(shù)。這三個(gè)參數(shù)需要根據(jù)實(shí)際情況去指定。不能一概使用out或者inout,因?yàn)榈讓邮谴嬖陂_(kāi)銷的。
  最后,區(qū)別于傳統(tǒng)的java接口,AIDL只支持方法,不支持聲明靜態(tài)常量。

為了方便AIDL的開(kāi)發(fā),建議把所有和AIDL相關(guān)的類和文件全部放入同一個(gè)包中,這樣子做的原因是,當(dāng)客戶端是另外一個(gè)應(yīng)用的時(shí)候,可以直接將整個(gè)包復(fù)制到那個(gè)客戶端工程中,而避免麻煩和出錯(cuò)。
  需要注意的是,AIDL的包結(jié)構(gòu)在服務(wù)端和客戶端要保持一致,否則會(huì)運(yùn)行出錯(cuò)。因?yàn)樵诳蛻舳诵枰葱蛄谢?wù)端中和AIDL相關(guān)的所有類,如果類的路徑不一樣,就會(huì)造成反序列化失敗。

遠(yuǎn)程服務(wù)端Service的實(shí)現(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;
    }
}

以上是一個(gè)遠(yuǎn)程Service的實(shí)現(xiàn),在onCreate中初始化添加了兩本書(shū),然后創(chuàng)建了一個(gè)Binder對(duì)象并在onBind中返回,而這個(gè)Binder對(duì)象繼承了IBookManager.Stub并實(shí)現(xiàn)了它內(nèi)部的AIDL方法。
  而這里管理書(shū)籍內(nèi)容的List是通過(guò)CopyOnWriteArrayList進(jìn)行管理的,它支持并發(fā)的讀/寫(xiě),因?yàn)锳IDL方法是在服務(wù)端的Binder線程池中執(zhí)行的,因此當(dāng)多個(gè)客戶端同時(shí)連接的時(shí)候,會(huì)存在多個(gè)線程同時(shí)訪問(wèn)的情形,所以需要在AIDL方法中處理線程同步。

雖然AIDL中能使用的List只有ArrayList,但是這里卻使用了CopyOnWriteArrayList(CopyOnWriteArrayList并非是ArrayList的子類)。這里的原因是因?yàn)锳IDL中支持的是一個(gè)抽象的List,而List只是一個(gè)接口,因此雖然服務(wù)端返回的是CopyOnWriteArrayList,但是在Binder中會(huì)按照List的規(guī)范去訪問(wèn)數(shù)據(jù)并最終形成一個(gè)ArrayList給客戶端。
  與此同時(shí),ConcurrentMap也是可以被支持的。

客戶端的實(shí)現(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開(kāi)發(fā)藝術(shù)探索");
                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();
    }
}

客戶端的代碼比較簡(jiǎn)單,首先要綁定遠(yuǎn)程服務(wù),綁定成功后將服務(wù)端返回的Binder對(duì)象轉(zhuǎn)化為AIDL接口,然后就可以通過(guò)這個(gè)接口去調(diào)用服務(wù)端的遠(yuǎn)程方法。
  需要注意的是,服務(wù)端的方法可能會(huì)比較耗時(shí),而在UI線程中直接運(yùn)行可能會(huì)導(dǎo)致ANR。因?yàn)榭蛻舳司€程會(huì)在調(diào)用遠(yuǎn)程方法后掛起直至方法返回。

觀察者模式

我們可以設(shè)計(jì)出一個(gè)需求,每個(gè)感興趣的用戶都在觀察著新書(shū),而圖書(shū)館可以在新書(shū)到來(lái)的時(shí)候通知這些用戶,這就可以利用觀察者模式來(lái)實(shí)現(xiàn)。
  而要實(shí)現(xiàn)這個(gè)功能,需要定義一個(gè)AIDL接口,然后讓客戶端需要實(shí)現(xiàn)這個(gè)接口并且向圖書(shū)館申請(qǐng)新書(shū)的提醒功能,當(dāng)然也可以隨時(shí)取消訂閱。這里使用AIDL接口而并非普通接口的原因是,在AIDL文件中無(wú)法使用普通接口。
  這里創(chuàng)建一個(gè)IOnNewBookArrivedListener文件,當(dāng)有新書(shū)來(lái)的時(shí)候去通知每一個(gè)訂閱的用戶,從程序上來(lái)說(shuō)就是調(diào)用一個(gè)方法,并把新書(shū)的參數(shù)傳遞進(jìn)去,代碼如下

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

這里除了要新加一個(gè)AIDL接口,還需要在原有的接口上添加兩個(gè)新的方法。
  而服務(wù)端的Service的Binder也要去實(shí)現(xiàn)這兩個(gè)新加的接口,同時(shí)在服務(wù)端開(kāi)啟一個(gè)新的線程,每隔5s就添加一本書(shū)并通知訂閱更新的客戶端,代碼如下。

服務(wù)端代碼
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();
                }
            }
        }
    }
}

而修改完服務(wù)端代碼后,客戶端代碼也要進(jìn)行修改,主要在兩方面:客戶端需要注冊(cè)O(shè)nNewBookArrivedListener到遠(yuǎn)程服務(wù)端,這樣當(dāng)有新書(shū)的時(shí),服務(wù)端才能通知當(dāng)前客戶端,同時(shí)我們需要在Activity銷毀;另一方面,當(dāng)有新書(shū)的時(shí)候,服務(wù)端會(huì)回調(diào)客戶單的IOnNewBookArrivedListener對(duì)象的onNewBookArrived方法,但是這個(gè)方法是在客戶端的Binder線程池中執(zhí)行的,因此為了方便UI操作,需要有一個(gè)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開(kāi)發(fā)藝術(shù)探索");
                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();
    }
}

以上代碼可以正確運(yùn)行,并且每隔5s,客戶端就收到了來(lái)自服務(wù)端的新書(shū)推送。
但在Service的代碼中使用的是RemoteCallbackList而并非CopyOnWriteArrayList,原因如下。
  假如這里使用到了CopyOnWriteArrayList,我們則需要通過(guò)客戶端傳入的listener來(lái)判斷是否在CopyOnWriteArrayList中是否存在該對(duì)象后進(jìn)行移除,盡管在訂閱和取消訂閱的時(shí)候使用的都是同一個(gè)客戶端對(duì)象,但是由于Binder的機(jī)制,在客戶端傳輸進(jìn)來(lái)的對(duì)象會(huì)重新轉(zhuǎn)化并生成一個(gè)新的對(duì)象,而無(wú)法進(jìn)行匹配。對(duì)象的跨進(jìn)程傳輸本就是一個(gè)序列化和反序列化的團(tuán),所以自定義對(duì)象才需要實(shí)現(xiàn)Parcelable接口。
  RemoteCallbackList是系統(tǒng)專門(mén)提供的用于刪除跨進(jìn)程listener的接口。RemoteCallbackList是一個(gè)泛型類,支持管理任意的AIDL接口,這個(gè)從它的聲明可以看出,因?yàn)樗械腁IDL接口都繼承于IInterface,以下是它的聲明。

public class RemoteCallbackList<E extends IInterface> 

它的工作原理很簡(jiǎn)單,在它內(nèi)部有一個(gè)Map結(jié)構(gòu)專門(mén)用來(lái)保存所有的AIDL回調(diào),這個(gè)Map的key是IBinder類型,value是Callback類型,如下所示

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

其中Callback屬性封裝了真正的遠(yuǎn)程listener。當(dāng)客戶端注冊(cè)listener的時(shí)候,它就將這個(gè)listener的信息存入mCallbacks中,其中key和value即分別通過(guò)以下方式獲取。

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

雖然多次跨進(jìn)程傳輸客戶端的同一個(gè)對(duì)象在服務(wù)端會(huì)生成不同的對(duì)象,但是這些對(duì)象都有一個(gè)共同點(diǎn),那么就是它們的底層Binder對(duì)象都是同一個(gè),而利用這個(gè)特性,客戶端取消訂閱的時(shí)候,只要遍歷服務(wù)端所有的listener,找出那個(gè)和取消訂閱的listener具有相同對(duì)象的服務(wù)端listener并把它刪掉即可。
  以上就是RemoteCallbackList為我們做的事情,同時(shí)它可以在客戶端進(jìn)程終止的時(shí)候,它能夠自動(dòng)移除客戶單進(jìn)程所注冊(cè)的listener。另外,它內(nèi)部還自動(dòng)實(shí)現(xiàn)了線程同步的功能,所以不用做額外的線程同步工作,
  使用RemoteCallbackList要注意的是,它雖然名字里面有List,但是我們不能像操作一個(gè)List那樣操作它,遍歷RemoteCallbackList,必須按照下面方式進(jìn)行,而其中必須beginBroadcast和finishBroadcast配對(duì)使用,哪怕只是獲取RemoteCallbackList的元素個(gè)數(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)介紹完了,但是還是有幾點(diǎn)需要注意。
  客戶端調(diào)用遠(yuǎn)程服務(wù)的方法,被調(diào)用的方法運(yùn)行在服務(wù)端的Binder線程池中,同時(shí)客戶端線程會(huì)被掛起,這個(gè)時(shí)候如果服務(wù)端方法比較耗時(shí),就會(huì)導(dǎo)致客戶端長(zhǎng)時(shí)間的阻塞在這里,而如果這個(gè)客戶端線程是UI線程的話,就會(huì)導(dǎo)致UI線程ANR。因此如果這個(gè)遠(yuǎn)程方法是耗時(shí)的話,就要避免在客戶端的UI線程去訪問(wèn)遠(yuǎn)程方法。由于客戶端的onServiceConnected和onServiceDisconnected都運(yùn)行在UI線程中,所以也不可以在它們里面直接調(diào)用服務(wù)端的耗時(shí)方法。
  另外,由于服務(wù)端的方法本身就是運(yùn)行在服務(wù)端的Binder線程池中,所以服務(wù)端方法本身就可以執(zhí)行大量耗時(shí)工作,這個(gè)時(shí)候就切記不要在服務(wù)端中開(kāi)線程去執(zhí)行異步任務(wù),除非明確是要干什么,否則不建議。

Binder重連

為了程序的健壯性考慮,Binder是可能會(huì)出現(xiàn)意外死亡的,這往往是由于服務(wù)端進(jìn)程意外終止了,這個(gè)時(shí)候則需要重新連接服務(wù),有兩種辦法。
  第一種方法是給Binder設(shè)置DeathRecipient監(jiān)聽(tīng),當(dāng)Binder死亡時(shí),我們會(huì)收到binderDied方法的回調(diào),在binderDied中重連遠(yuǎn)程服務(wù)。
  另一種方法是在onServiceDisconneced中重連遠(yuǎn)程服務(wù)。
  它們的區(qū)別在于,onServiceDisconneced是在客戶端的UI線程被回調(diào),而bindDied在客戶端的Binder線程池被回調(diào)。

權(quán)限驗(yàn)證

Binder中驗(yàn)證

第一種辦法,我們可以在onBind中驗(yàn)證,驗(yàn)證不通過(guò)就返回null,這樣驗(yàn)證失敗的客戶端就無(wú)法綁定服務(wù),而驗(yàn)證方式可以有多種,比如使用permission進(jìn)行驗(yàn)證。

在onTransact方法中做權(quán)限驗(yàn)證

第二種方法,在onTransact方法中做權(quán)限驗(yàn)證,如果驗(yàn)證失敗就直接返回false,這樣服務(wù)端就不會(huì)終止執(zhí)行AIDL的方法達(dá)到保護(hù)服務(wù)端的掉過(guò),驗(yàn)證的方式也很多,比如permission驗(yàn)證。,還可以通過(guò)getCallingUid和getCallingPid獲取客戶端所屬的Uid和Pid進(jìn)行驗(yàn)證。

其他方法

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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