Android深入理解IPC機(jī)制(三) Android中的幾種IPC方式

上一篇Android深入理解IPC機(jī)制(一)基礎(chǔ)知識(shí)概要中搜立,首先介紹了Android中的多進(jìn)程概念以及使用多進(jìn)程模式需要注意的事項(xiàng)申尤,然后介紹了Android中的序列化機(jī)制,最后詳細(xì)分析了Binder的使用以及上層原理。介紹完IPC的基礎(chǔ)知識(shí)躁愿,接下來我們看看如何將IPC機(jī)制應(yīng)用到實(shí)際的開發(fā)中去嫁盲。

  • Android IPC 簡(jiǎn)介
  • Android中的多進(jìn)程模式
  • IPC基礎(chǔ)概念介紹
  • Android中的幾種IPC方式
  • Binder連接池
  • 選擇合適的IPC方式

Android中的幾種IPC方式

1篓叶、使用Bundle

在Android開發(fā)中,我們通常會(huì)使用Bundle在不同的組件中傳遞一些數(shù)據(jù)羞秤,由于Bundle 本身已經(jīng)實(shí)現(xiàn)了Parcelable 接口缸托,所以它可以很方便地在進(jìn)程間傳輸。當(dāng)我們?cè)谝粋€(gè)進(jìn)程中啟動(dòng)了另一個(gè)進(jìn)程的Activity瘾蛋、Service和Receiver俐镐,我們可以將需要傳輸?shù)臄?shù)據(jù)放入Bundle中并通過Intent傳遞出去。使用示例:

private void startMain() {
    Intent intent = new Intent(FirstActivity.this, MainActivity.class);
    Bundle bundle = new Bundle();
    bundle.putString("key", "value");
    ……
    intent.putExtra("bundle", bundle);
}

我們必須要知道瘦黑,我們傳輸?shù)臄?shù)據(jù)必須是可序列化的京革,比如基本類型、實(shí)現(xiàn)了Serializable 或Parcelable接口的對(duì)象以及一些Android支持的特殊對(duì)象幸斥,具體可以查看Bundle 這類中的一系列put 方法匹摇。Bundle 不支持的數(shù)據(jù)類型我們無法通過它在進(jìn)程間傳遞。

2甲葬、使用文件共享

兩個(gè)進(jìn)程可以通過讀/寫同一個(gè)文件來交換數(shù)據(jù)廊勃,也就是說A進(jìn)程把數(shù)據(jù)寫入到共享文件中,B進(jìn)程通過讀取共享文件獲取A進(jìn)程共享的數(shù)據(jù)经窖,這在Android 中也是一個(gè)常見的數(shù)據(jù)共享方式坡垫。

由于Android系統(tǒng)是基于Linux的,使得并發(fā)讀寫可以沒有限制地進(jìn)行画侣,甚至兩個(gè)線程對(duì)同一個(gè)文件進(jìn)行寫操作都是可以的冰悠,盡管這樣可能會(huì)使數(shù)據(jù)變得雜亂。除了交換一些文本信息配乱,我們還可以序列化一個(gè)對(duì)象到文件系統(tǒng)中溉卓,之后在另一個(gè)進(jìn)程中恢復(fù)這個(gè)對(duì)象,雖然兩個(gè)對(duì)象內(nèi)容完全相同搬泥,但是得到的對(duì)象和之前的對(duì)象本質(zhì)上是兩個(gè)不同的對(duì)象桑寨。

private static final String FILE_PATH = "對(duì)象序列化路徑";

/**
 * 序列化對(duì)象到文件系統(tǒng)
 */
public static void presistToFile(final Serializable serializable) {
    new Thread(new Runnable() {
        @Override
        public void run() {
            File file = new File(FILE_PATH);
            ObjectOutputStream objectOutputStream = null;
            try {
                objectOutputStream = new ObjectOutputStream(new FileOutputStream(file));
                objectOutputStream.writeObject(serializable);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (objectOutputStream != null) {
                    try {
                        objectOutputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }).start();
}

/**
 * 從文件系統(tǒng)反序列化一個(gè)對(duì)象
 */
public static void recoverFromFile() {
    new Thread(new Runnable() {
        @Override
        public void run() {
            Serializable serializable = null;
            File file = new File(FILE_PATH);
            ObjectInputStream objectInputStream = null;
            try {
                objectInputStream = new ObjectInputStream(new FileInputStream(file));
                serializable = (Serializable) objectInputStream.readObject();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } finally {
                if (objectInputStream != null) {
                    try {
                        objectInputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }).start();
}

通過文件共享實(shí)現(xiàn)進(jìn)程間通信的局限性也是比較明顯的:當(dāng)多個(gè)線程并發(fā)讀寫文件,那么我們得到的數(shù)據(jù)就可能不是正確的忿檩。所以在使用這項(xiàng)技術(shù)的時(shí)候尉尾,我們應(yīng)該盡量避免并發(fā)讀寫這種情況的發(fā)生,或者只在對(duì)數(shù)據(jù)同步要求不高的情況下使用文件共享來實(shí)現(xiàn)進(jìn)程間通信燥透,并且妥善處理并發(fā)讀寫問題沙咏。

3辨图、使用Messenger

Messenger是一種輕量級(jí)的IPC方案,它的底層實(shí)現(xiàn)是AIDL芭碍,通過它可以在不同進(jìn)程中傳遞Message對(duì)象徒役,在Message中放入我們需要傳遞的數(shù)據(jù),就能輕松地實(shí)現(xiàn)數(shù)據(jù)在進(jìn)程間的傳遞窖壕。
Messenger的使用方法很簡(jiǎn)單忧勿,它對(duì)AIDL做了封裝,使得我們可以很方便地進(jìn)行進(jìn)程間通信瞻讽。同時(shí)由于它一次處理一個(gè)請(qǐng)求鸳吸,也就是說服務(wù)端中不存在并發(fā)的情形,所以我們不用考慮線程同步的問題速勇。

接下來分別從服務(wù)端和客戶端介紹Messenger 的使用步驟:

  1. 服務(wù)端創(chuàng)建一個(gè)Service 來處理客戶端的連接請(qǐng)求:
    創(chuàng)建一個(gè)靜態(tài)內(nèi)部類MessengerHandler 晌砾,接收并處理客戶端消息,利用MessengerHandler 創(chuàng)建一個(gè)Messenger烦磁, 并在onBind 方法里返回Messenger里面的Binder對(duì)象养匈;
    同時(shí)使用msg.replyTo 的Messenger 給客戶端回復(fù)一個(gè)Message,告知客戶端已接收到消息都伪。
public class MyService extends Service {

    //處理客戶端發(fā)來的消息
    private static class MessengerHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case Constant.MSG_FROMCLIENT:
                    //接受客戶端消息
                    Bundle bundle = msg.getData();
                    LogUtils.d("qianwei", bundle.getString("msg"));
                    //通知客戶端消息已收到(發(fā)送消息給客戶端)
                    Messenger replyTo = msg.replyTo;
                    Message replyMessage = Message.obtain(null, Constant.MSG_FROMSERVICE);
                    Bundle data = new Bundle();
                    data.putString("reply", "Hello client, message received! Thank you.");
                    replyMessage.setData(data);
                    try {
                        replyTo.send(replyMessage);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
            }
            super.handleMessage(msg);
        }
    }

    private Messenger messenger = new Messenger(new MessengerHandler());

    @Override
    public IBinder onBind(Intent intent) {
        //messenger將消息傳遞給MessengerHandler處理
        return messenger.getBinder();
    }
}

注冊(cè)Service并運(yùn)行在單獨(dú)進(jìn)程中:

<service
    android:name=".service.MyService"
    android:process=":remoteservice" />
  1. 接下來看看客戶端的實(shí)現(xiàn):
    客戶端綁定服務(wù)成功會(huì)調(diào)用ServiceConnection 接口的onServiceConnected方法呕乎,根據(jù)服務(wù)端返回的Binder 創(chuàng)建一個(gè)Messenger,通過這個(gè)Messenger 把Message 消息傳輸給服務(wù)端陨晶。
    同時(shí)設(shè)置"message.replyTo = replyMessenger" 接收并處理服務(wù)端回復(fù)的消息猬仁,實(shí)現(xiàn)方法與服務(wù)端處理消息基本相同。
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    bindServiceAction();
}

/**
 * 監(jiān)控Service連接狀態(tài)
 */
private ServiceConnection serviceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        LogUtils.d("onServiceConnected");
            //傳給服務(wù)端的Message
            Message message = Message.obtain(null, Constant.MSG_FROMCLIENT);
            Bundle bundle = new Bundle();
            bundle.putString("msg", "Hello service, I'm client!");
            message.setData(bundle);
            //接收服務(wù)端回復(fù)的消息
            message.replyTo = replyMessenger;
            try {
                Messenger messenger = new Messenger(service);
                messenger.send(message);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        LogUtils.d("onServiceDisconnected");
    }
};

/**
 * 綁定服務(wù)端Service
 */
private void bindServiceAction(){
    Intent intent = new Intent(MainActivity.this, MyService.class);
    bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
}

//處理服務(wù)端回復(fù)的消息***************************
private static class ServiceReplyMessengerHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case Constant.MSG_FROMSERVICE:
                //接受客戶端回復(fù)
                Bundle bundle = msg.getData();
                LogUtils.d("qianwei", bundle.getString("reply"));

        }
        super.handleMessage(msg);
    }
}

private Messenger replyMessenger = new Messenger(new ServiceReplyMessengerHandler());
//*********************************************

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

到此先誉,準(zhǔn)備工作已經(jīng)就緒湿刽,啟動(dòng)MainActivity我們會(huì)看到消息內(nèi)容在兩個(gè)不同的進(jìn)程間完成了傳輸。

image.png

在Messenger中傳遞數(shù)據(jù)必須將數(shù)據(jù)放入Message中褐耳,Messenger 和Message都實(shí)現(xiàn)了Parcelable 接口诈闺,因此可以進(jìn)行跨進(jìn)程通信。也就是說,Message 支持的數(shù)據(jù)類型就是Messenger 支持的傳輸類型。

在Android 2.2以前犁跪,msg.obj字段不支持跨進(jìn)程傳輸摄乒,Android 2.2之后,也只是系統(tǒng)提供的實(shí)現(xiàn)了Parcelable 接口的對(duì)象才能通過它跨進(jìn)程傳輸嗤军,我們自己定義的Parcelable 對(duì)象是無法通過object字段傳輸?shù)淖⒂@導(dǎo)致object 字段的實(shí)用性大大降低,所幸我們還有Bundle 字段叙赚。

最后老客,獻(xiàn)上Messenger工作原理圖僚饭。

Messenger的工作原理

4、使用AIDL

上一節(jié)我們介紹了使用Messenger 實(shí)現(xiàn)進(jìn)程間通信胧砰,我們能看到Messenger 是以串行的方式處理客戶端發(fā)來的請(qǐng)求鳍鸵,如果客戶端同時(shí)發(fā)送大量并發(fā)請(qǐng)求,使用Messenger就不太合適了尉间。同時(shí)偿乖,Messenger主要是為了跨進(jìn)程傳輸數(shù)據(jù),如果想實(shí)現(xiàn)跨進(jìn)程調(diào)用服務(wù)端方法哲嘲,Messenger 無法做到贪薪,我們可以使用AIDL來實(shí)現(xiàn)跨進(jìn)程的方法調(diào)用。同樣眠副,我們從服務(wù)端和客戶端兩個(gè)方面介紹AIDL的使用画切。

  1. 服務(wù)端
    創(chuàng)建Service監(jiān)聽連接請(qǐng)求,創(chuàng)建AIDL文件囱怕,在文件中聲明要暴露給客戶端的接口霍弹,在Service中實(shí)現(xiàn)這些接口。

  2. 客戶端
    首先綁定服務(wù)端的Service娃弓,成功后將服務(wù)端返回的Binder轉(zhuǎn)化為AIDL接口所屬類型典格,然后就可以調(diào)用AIDL中的方法了。

上一篇文章Android深入理解IPC機(jī)制(二)淺談Binder中我們已經(jīng)介紹了如何使用AIDL生成Binder忘闻,感興趣的小伙伴可以先了解了解钝计,有助于理解接下來要介紹的內(nèi)容。上面只是簡(jiǎn)單地介紹了使用AIDL進(jìn)行進(jìn)程間通信的流程齐佳,接下來對(duì)其中的難點(diǎn)和實(shí)現(xiàn)細(xì)節(jié)進(jìn)行詳細(xì)介紹私恬。

首先,創(chuàng)建AIDL接口炼吴,接口里面聲明兩個(gè)接口方法:
// IBookManager.aidl
package com.example.qianwei.myapplication.aidl;
//Book必須要顯示地import進(jìn)來本鸣,哪怕是在同一個(gè)package下
import com.example.qianwei.myapplication.aidl.Book;
interface IBookManager {

    /**
     * 獲取圖書列表
     */
    List<Book> getBookList();

    /**
     * 添加圖書
     */
    void addBook(in Book book);
}

在AIDL文件中并不是所有數(shù)據(jù)類型都可以使用的,我們來看看AIDL到底支持哪些類型硅蹦。

  • Java基本數(shù)據(jù)類型
  • String和 CharSequence
  • List:只支持ArrayList荣德,里面的每個(gè)元素必須能夠被AIDL支持
  • Map:只支持HashMap,里面的每個(gè)元素必須能夠被AIDL支持童芹,包括key和value
  • 所有實(shí)現(xiàn)了Parcelable 接口的對(duì)象
  • 所有的AIDL接口本身也可以在AIDL文件中使用

這些就是AIDL支持的所有數(shù)據(jù)類型涮瞻,其中自定義的Parcelable 對(duì)象和AIDL對(duì)象必須顯示地import進(jìn)來,例如IBookManager 中使用Book類必須聲明 "import com.example.qianwei.myapplication.aidl.Book;"假褪。

我們需要注意署咽,如果AIDL文件中使用了自定義的Parcelable 對(duì)象,那么必須創(chuàng)建一個(gè)與該對(duì)象同名的AIDL文件,并在其中聲明它是Parcelable 類型宁否,我們?yōu)锽ook類創(chuàng)建Book.aidl文件:

// Book.aidl
package com.example.qianwei.myapplication.aidl;

// Declare any non-default types here with import statements
parcelable Book;

除此之外窒升,AIDL中除基本類型之外的其他參數(shù)都必須指定方向:in(輸入型參數(shù))、out(輸出型參數(shù))或inout(輸入輸出型參數(shù))慕匠。AIDL不支持聲明靜態(tài)常量饱须,這點(diǎn)有別于傳統(tǒng)接口。

遠(yuǎn)程服務(wù)端Service實(shí)現(xiàn)

我們創(chuàng)建一個(gè)BookManagerService台谊,并在其中實(shí)現(xiàn)我們定義的AIDL接口:

public class BookManagerService extends Service {

    //使用支持并發(fā)讀寫CopyOnWriteArrayList蓉媳,是因?yàn)锳IDL的方法是在服務(wù)
    // 端的Binder池中執(zhí)行的,會(huì)有多線程同時(shí)訪問的情況
    private CopyOnWriteArrayList<Book> bookCopyOnWriteArrayList = new CopyOnWriteArrayList<>();

    private Binder binder = new IBookManager.Stub() {
        @Override
        public List<Book> getBookList() throws RemoteException {
            return bookCopyOnWriteArrayList;
        }

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

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

    @Override
    public void onCreate() {
        super.onCreate();
        bookCopyOnWriteArrayList.add(new Book(10001, "Android開發(fā)藝術(shù)探索"));
    }
}

接著我們?cè)贏ndroidManifest.xml中注冊(cè)BookManagerService 青伤,讓他運(yùn)行在獨(dú)立進(jìn)程中督怜。

<service
    android:name=".service.BookManagerService"
    android:process=":remotebookmanagerservice"/>
客戶端實(shí)現(xiàn)

客戶端要做的是在綁定遠(yuǎn)程服務(wù)成功后,將服務(wù)端返回的Binder對(duì)象轉(zhuǎn)換成AIDL接口狠角,然后使用這個(gè)接口調(diào)用服務(wù)端遠(yuǎn)程方法号杠。為了節(jié)省篇幅,這里只展示核心代碼:

【其它代碼與上節(jié)實(shí)現(xiàn)類似丰歌,此處省略…】
private ServiceConnection bookManagerServiceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        //將服務(wù)端返回的Binder對(duì)象轉(zhuǎn)換成AIDL(IBookManager)接口類型
        IBookManager bookManager = IBookManager.Stub.asInterface(service);
        try {
            List<Book> bookList = bookManager.getBookList();
            LogUtils.d("bookList = "+bookList.toString());
            bookManager.addBook(new Book(2,"Java編程思想"));
            LogUtils.d("add finish, bookList = "+bookManager.getBookList().toString());
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {

    }
};

啟動(dòng)MainActivity姨蟋,客戶端成功打印出服務(wù)端的Book信息,證明跨進(jìn)程方法getBookList調(diào)用是成功的立帖。


結(jié)果截圖

到此為止眼溶,我們已經(jīng)完整地使用AIDL進(jìn)行一次跨進(jìn)程通信。

假設(shè)用戶提出一個(gè)新的需求:要求有新書增加的時(shí)候服務(wù)端自動(dòng)通知用戶晓勇,而不需要用戶自己去主動(dòng)獲取圖書信息堂飞。這種情形在我們的日常開發(fā)中很常見,我們很容易想到觀察者模式绑咱,接下來我們就試著簡(jiǎn)單實(shí)現(xiàn)一下這個(gè)需求绰筛。

首先我們新建一個(gè)IBookArrivedListener.aidl 用于監(jiān)聽"新增新書",用戶通過注冊(cè)這個(gè)接口來申請(qǐng)新書提醒功能描融。

// IBookArrivedListener.aidl
package com.example.qianwei.myapplication.aidl;

// Declare any non-default types here with import statements
import com.example.qianwei.myapplication.aidl.Book;

interface IBookArrivedListener {
    void onBookArrive(in Book book);
}

接著在IBookManager 中新增注冊(cè)與解除注冊(cè)方法铝噩,并且在服務(wù)端實(shí)現(xiàn)這兩個(gè)方法:

// IBookManager.aidl
package com.example.qianwei.myapplication.aidl;

import com.example.qianwei.myapplication.aidl.Book;
import com.example.qianwei.myapplication.aidl.IBookArrivedListener;

interface IBookManager {

    /**
     * 獲取圖書列表
     */
    List<Book> getBookList();

    /**
     * 添加圖書
     */
    void addBook(in Book book);

     /**
     * 注冊(cè)監(jiān)聽事件
     */
    void registerListener(IBookArrivedListener listener);

     /**
     * 反注冊(cè)監(jiān)聽事件
     */
    void unRegisterListener(IBookArrivedListener listener);
}
private Binder binder = new IBookManager.Stub() {
    @Override
    public List<Book> getBookList() throws RemoteException {
        return bookCopyOnWriteArrayList;
    }

    @Override
    public void addBook(Book book) throws RemoteException {
        bookCopyOnWriteArrayList.add(book);
        for (IBookArrivedListener bookArrivedListener : bookArrivedListenerCopyOnWriteArrayList) {
            try {
                //notify registered listener
                LogUtils.d("notify registered listener:");
                bookArrivedListener.onBookArrive(book);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 注冊(cè)監(jiān)聽事件
     *
     * @param listener
     */
    @Override
    public void registerListener(IBookArrivedListener listener) throws RemoteException {
        if (!bookArrivedListenerCopyOnWriteArrayList.contains(listener)) {
            bookArrivedListenerCopyOnWriteArrayList.add(listener);
        } else {
            LogUtils.d("Listener has already registered!");
        }
        LogUtils.d("Listener list size = "+bookArrivedListenerCopyOnWriteArrayList.size());
    }

    /**
     * 反注冊(cè)監(jiān)聽事件
     *
     * @param listener
     */
    @Override
    public void unRegisterListener(IBookArrivedListener listener) throws RemoteException {
        if (!bookArrivedListenerCopyOnWriteArrayList.contains(listener)) {
            LogUtils.d("unregister listener failed!");
        } else {
            bookArrivedListenerCopyOnWriteArrayList.remove(listener);
        }
    }
};

最后我們?cè)诳蛻舳松献远x一個(gè)IBookArrivedListener 接口,并且將它注冊(cè)到服務(wù)端接口窿克。

private IBookArrivedListener bookArrivedListener = new IBookArrivedListener.Stub() {
    @Override
    public void onBookArrive(Book book) throws RemoteException {
        LogUtils.d("New book arrived: "+book.toString());
    }
};
//#######################################
//注冊(cè)bookArrivedListener
bookManager.registerListener(bookArrivedListener);
//反注冊(cè)bookArrivedListener
if (bookManager != null) {
    try {
        bookManager.unRegisterListener(bookArrivedListener);
    } catch (RemoteException e) {
        e.printStackTrace();
    }
}

我們?cè)俅芜\(yùn)行程序骏庸,可以看到如下結(jié)果:


運(yùn)行結(jié)果

正如結(jié)果顯示,客戶端確實(shí)接收到新書到來的通知年叮,但是當(dāng)我們退出MainActivity試圖解除bookArrivedListener通知的時(shí)候具被,告訴我們解除失敗,這顯然不是我們想要的結(jié)果只损。

但是仔細(xì)想想硬猫,好像這種方法確實(shí)無法完成反注冊(cè)功能,因?yàn)閷?duì)象的跨進(jìn)程傳輸本質(zhì)上都是反序列化的過程,對(duì)象通過Binder傳遞到客戶端后啸蜜,得到的對(duì)象將會(huì)是一個(gè)全新的對(duì)象,自然也就無法完成反注冊(cè)過程辈挂。

那么這種情況下我們?cè)撊绾瓮瓿煞醋?cè)呢衬横?答案就是使用RemoteCallbackList,我們接下來就詳細(xì)分析它的用法(暫時(shí)不介紹RemoteCallbackList的工作原理终蒂,以后會(huì)補(bǔ)上)蜂林。

RemoteCallbackList使用起來很方便,我們首先用它代替現(xiàn)有的CopyOnWriteArrayList拇泣,然后直接在相應(yīng)位置調(diào)用它的register和unRegister方法噪叙,我們看看核心代碼。

private RemoteCallbackList<IBookArrivedListener> bookArrivedListenerRemoteCallbackList = new RemoteCallbackList<>();
…
//注冊(cè)
bookArrivedListenerRemoteCallbackList.register(listener);
…
//反注冊(cè)
bookArrivedListenerRemoteCallbackList.unregister(listener);

接著就是給所有注冊(cè)了通知的客戶端發(fā)送通知霉翔,RemoteCallbackList 的遍歷方式很有意思睁蕾,我們必須按照以下方式遍歷RemoteCallbackList ,而且beginBroadcast和finishBroadcast必須成對(duì)使用债朵,否則程序會(huì)出錯(cuò)子眶。

int size = bookArrivedListenerRemoteCallbackList.beginBroadcast();
for (int i = 0; i < size; i++) {
    IBookArrivedListener bookArrivedListener =
            bookArrivedListenerRemoteCallbackList.getBroadcastItem(i);
    if (bookArrivedListener != null) {
        bookArrivedListener.onBookArrive(book);
    }
}
bookArrivedListenerRemoteCallbackList.finishBroadcast();

我們可以試試程序修改后的運(yùn)行結(jié)果。



從結(jié)果來看序芦,RemoteCallbackList的確可以完成跨進(jìn)程的解注冊(cè)功能臭杰。

5、使用ContentProvider

ContentProvider 是Android中提供的專門用于不同進(jìn)程間數(shù)據(jù)共享的方式谚中,ContentProvider作為Android中的四大組件之一渴杆,可見它在Android中是比較重要的。它的底層實(shí)現(xiàn)同樣是Binder宪塔,但是它的使用過程比AIDL方便得多磁奖,因?yàn)橄到y(tǒng)為我們做了封裝,使得我們不需要關(guān)心底層細(xì)節(jié)就可以輕松實(shí)現(xiàn)進(jìn)程間通信蝌麸。

系統(tǒng)也為開發(fā)者提供了很多內(nèi)置的ContentProvider 点寥,例如通訊錄、相冊(cè)信息等来吩,要跨進(jìn)程訪問這些數(shù)據(jù)敢辩,我們就必須先了解ContentProvider 的創(chuàng)建以及使用規(guī)則。


推薦閱讀

Android深入理解IPC機(jī)制(一)基礎(chǔ)知識(shí)概要
Android深入理解IPC機(jī)制(二)淺談Binder
Android深入理解IPC機(jī)制(四)Binder連接池

參考書籍

《Android 開發(fā)藝術(shù)探索》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末弟疆,一起剝皮案震驚了整個(gè)濱河市戚长,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌怠苔,老刑警劉巖同廉,帶你破解...
    沈念sama閱讀 222,464評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡迫肖,警方通過查閱死者的電腦和手機(jī)锅劝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,033評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蟆湖,“玉大人故爵,你說我怎么就攤上這事∮缃颍” “怎么了诬垂?”我有些...
    開封第一講書人閱讀 169,078評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)伦仍。 經(jīng)常有香客問我结窘,道長(zhǎng),這世上最難降的妖魔是什么充蓝? 我笑而不...
    開封第一講書人閱讀 59,979評(píng)論 1 299
  • 正文 為了忘掉前任隧枫,我火速辦了婚禮,結(jié)果婚禮上棺克,老公的妹妹穿的比我還像新娘悠垛。我一直安慰自己,他們只是感情好娜谊,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,001評(píng)論 6 398
  • 文/花漫 我一把揭開白布确买。 她就那樣靜靜地躺著,像睡著了一般纱皆。 火紅的嫁衣襯著肌膚如雪湾趾。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,584評(píng)論 1 312
  • 那天派草,我揣著相機(jī)與錄音搀缠,去河邊找鬼。 笑死近迁,一個(gè)胖子當(dāng)著我的面吹牛艺普,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鉴竭,決...
    沈念sama閱讀 41,085評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼歧譬,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了搏存?” 一聲冷哼從身側(cè)響起瑰步,我...
    開封第一講書人閱讀 40,023評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎璧眠,沒想到半個(gè)月后缩焦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體读虏,經(jīng)...
    沈念sama閱讀 46,555評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,626評(píng)論 3 342
  • 正文 我和宋清朗相戀三年袁滥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了盖桥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,769評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡呻拌,死狀恐怖葱轩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情藐握,我是刑警寧澤,帶...
    沈念sama閱讀 36,439評(píng)論 5 351
  • 正文 年R本政府宣布垃喊,位于F島的核電站猾普,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏本谜。R本人自食惡果不足惜初家,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,115評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望乌助。 院中可真熱鬧溜在,春花似錦、人聲如沸他托。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,601評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赏参。三九已至志笼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間把篓,已是汗流浹背纫溃。 一陣腳步聲響...
    開封第一講書人閱讀 33,702評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留韧掩,地道東北人紊浩。 一個(gè)月前我還...
    沈念sama閱讀 49,191評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像疗锐,于是被迫代替她去往敵國(guó)和親坊谁。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,781評(píng)論 2 361