(轉(zhuǎn))Android IPC進(jìn)程間通信昂验,Binder機(jī)制原理及AIDL實(shí)例

前言

我們知道捂敌,Android的底層是使用Linux內(nèi)核運(yùn)行的,而Linux為了保證系統(tǒng)的穩(wěn)定性既琴,使用的是進(jìn)程隔離的機(jī)制占婉,也就是讓不同的進(jìn)程運(yùn)行在不同的虛擬空間,使得不同進(jìn)程之間無法共享數(shù)據(jù)甫恩,防止數(shù)據(jù)的篡改逆济。但是,有時候我們也會遇到不同進(jìn)程間需要通信的情況磺箕,那么奖慌,這時候就需要使用Android系統(tǒng)進(jìn)程間通信IPC(Inter-Process Communication)。

IPC進(jìn)程間通信方式


屏幕快照 2019-10-07 上午11.56.27.png

*RPC(Remote Procedure Call的縮寫) 是遠(yuǎn)程進(jìn)程調(diào)用的意思松靡。

*Binder線程池最大數(shù)為16简僧,超過的請求會被阻塞。在進(jìn)程間通信時處理并發(fā)時雕欺,如ContentProvider的CRUD(增刪改查)操作方法最多有16個線程同時工作岛马。

一棉姐、Binder的產(chǎn)生

在Linux中規(guī)定一個進(jìn)程分為用戶空間和內(nèi)核空間。區(qū)別是啦逆,用戶空間的數(shù)據(jù)不可在進(jìn)程間共享伞矩,為了保證數(shù)據(jù)安全性 、獨(dú)立性夏志,即進(jìn)程隔離乃坤。而內(nèi)核空間可以被共享且所有用戶空間共享一個內(nèi)核空間。用戶空間與內(nèi)核空間的通信是使用系統(tǒng)調(diào)用:

copy_from_user();//將用戶空間的數(shù)據(jù)拷貝到內(nèi)核空間
copy_to_user();//將內(nèi)核空間的數(shù)據(jù)拷貝到用戶空間

傳統(tǒng)跨進(jìn)程通信的基本原理:


image
  • “進(jìn)程1”通過系統(tǒng)調(diào)用copy_form_user() 將需要發(fā)送的數(shù)據(jù)拷貝到Linux進(jìn)程的內(nèi)核空間中的緩沖區(qū)盲镶。(拷貝第一次)
  • 內(nèi)核服務(wù)程序喚醒“進(jìn)程2”的接收線程侥袜,通過系統(tǒng)調(diào)用copy_to_user() 將數(shù)據(jù)發(fā)送到用戶空間蝌诡。(拷貝第二次)

由于傳統(tǒng)的跨進(jìn)程通信需拷貝數(shù)據(jù)2次溉贿,且接收方事先并不知道需要創(chuàng)建多大的緩存空間用來接收數(shù)據(jù),造成了資源浪費(fèi)浦旱。而采用Binder機(jī)制實(shí)現(xiàn)通信的時候宇色,通過內(nèi)存映射調(diào)用Linux下的系統(tǒng)調(diào)用方法mmap(),只需系統(tǒng)調(diào)用copy_from_user()拷貝1次數(shù)據(jù)就可以實(shí)現(xiàn)進(jìn)程間傳遞數(shù)據(jù)颁湖,節(jié)省了內(nèi)存宣蠕,提高了效率。


image

1甥捺、內(nèi)存映射

通過關(guān)聯(lián)一個進(jìn)程中的一個虛擬內(nèi)存區(qū)域和一個磁盤上的對象抢蚀,使得二者存在映射關(guān)系。被映射的對象稱為共享對象镰禾。當(dāng)多個進(jìn)程的虛擬內(nèi)存區(qū)域和同一個共享對象建立映射關(guān)系時皿曲,對進(jìn)程1的虛擬內(nèi)存區(qū)域進(jìn)行寫操作時,也會映射到進(jìn)程2中的虛擬內(nèi)存區(qū)域吴侦。


image

2屋休、 系統(tǒng)調(diào)用mmap()

該函數(shù)主要是創(chuàng)建虛擬內(nèi)存區(qū)域 與 共享對象建立映射關(guān)系。用內(nèi)存讀寫代替 I/O讀寫备韧。

/**
  * 函數(shù)原型
  */
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);

內(nèi)部原理:

  • 步驟1:創(chuàng)建虛擬內(nèi)存區(qū)域
  • 步驟2:實(shí)現(xiàn)地址映射關(guān)系(進(jìn)程的虛擬地址空間關(guān)聯(lián)到共享對象)
    使用時:

用戶進(jìn)程直接調(diào)用mmap()建立映射劫樟。

/**
  *  MAP_SIZE的接收緩存區(qū)大小 , 關(guān)聯(lián)到共享對象中织堂,即建立映射
  */
  mmap(NULL, MAP_SIZE, PROT_READ, MAP_PRIVATE, fd, 0);

二叠艳、Binder機(jī)制

1. 定義

Binder是Android中的一個類,它實(shí)現(xiàn)了IBinder接口易阳。
從IPC角度來說附较,Binder是Android中的一種跨進(jìn)程通信方式,Binder還可以理解為一種虛擬的物理設(shè)備闽烙,它的設(shè)備驅(qū)動是/dev/binder翅睛,該通信方式在Linux中沒有声搁。
從Android Framework角度來說,Binder是ServiceManager連接各種Manager(ActivityManager捕发、WindowManager等等)和相應(yīng)ManagerService的橋梁疏旨。
從Android應(yīng)用層來說,Binder是客戶端和服務(wù)端進(jìn)行通信的媒介扎酷,當(dāng)bindService的時候檐涝,服務(wù)端會返回一個包含了服務(wù)端業(yè)務(wù)調(diào)用的Binder對象,通過Binder對象法挨,客戶端就可以獲取服務(wù)端提供的服務(wù)或者數(shù)據(jù)谁榜。這里的服務(wù)包括普通服務(wù)和基于AIDL的服務(wù)。

2. 模型

從字面上理解binder是"粘結(jié)劑"的意思凡纳,那么google的工程師為什會以"粘結(jié)劑"來命名binder呢窃植?這是因?yàn)閎inder是基于C-S架構(gòu),而在這個模型中存在著四個角色荐糜,如下:


屏幕快照 2019-10-07 下午12.03.22.png

3. 模式原理

Client巷怜、Server 和 Service Manager 屬于進(jìn)程的用戶空間,不可進(jìn)行進(jìn)程間交互暴氏。Binder驅(qū)動在內(nèi)核空間中延塑,能持有Server服務(wù)端進(jìn)程的Binder實(shí)體,并給Client客戶端提供Binder實(shí)體的引用答渔。


image

Binder驅(qū)動 和 Service Manager進(jìn)程 屬于 Android基礎(chǔ)架構(gòu)关带;而Client進(jìn)程 和 Server進(jìn)程 屬于Android應(yīng)用層。


image

4. 具體說明

屏幕快照 2019-10-07 下午12.04.56.png
image

三沼撕、AIDL實(shí)例

假設(shè)一個場景:在圖書館宋雏,有兩個進(jìn)程。A進(jìn)程是圖書管理員端朵,B進(jìn)程向A進(jìn)程添加圖書好芭。當(dāng)B進(jìn)程收到A進(jìn)程添加的圖書時,通知A進(jìn)程冲呢,圖書已經(jīng)添加成功舍败。了解場景之后,我們通過aidl來實(shí)現(xiàn)這個需求敬拓。

下文貼出binder服務(wù)端與客戶端的代碼邻薯。

1、binder服務(wù)端

使用一個遠(yuǎn)程Service模擬進(jìn)程A乘凸,提供binder服務(wù)端厕诡。

package com.shine.binderdemo;
 
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
 
import java.util.concurrent.CopyOnWriteArrayList;
 
public class MyService extends Service {
 
    private CopyOnWriteArrayList<Book> booklist;
    private IOnBookAddListener mListener;
 
    private IBinder mBinder = new IBookManager.Stub() {
 
        @Override
        public void addBook(Book book) throws RemoteException {
            booklist.add(book);
            if (mListener != null) {
                mListener.onBookadd(book);
            }
        }
 
        @Override
        public void registerListener(IOnBookAddListener listener) throws RemoteException {
            mListener = listener;
        }
    };
 
    @Override
    public void onCreate() {
        super.onCreate();
        booklist = new CopyOnWriteArrayList();
    }
 
 
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
}

在AndroidManifest.xml中通過process屬性使MyService 運(yùn)行在其他進(jìn)程。
有一點(diǎn)我們需要格外注意:我們看到在用來存放添加書籍的booklist是一個CopyOnWriteArrayList营勤。為什么在這要用CopyOnWriteArrayList呢灵嫌?這是因?yàn)閎inder服務(wù)端在接收到客戶端的請求訪問時都會在一個單獨(dú)的線程中處理這個請求壹罚,所以會出現(xiàn)線程安全問題,在這個地方通過CopyOnWriteArrayList來避免出現(xiàn)的線程安全問題,這個過程在子線程中完成也可以通過客戶端的代碼來驗(yàn)證寿羞。

2猖凛、binder客戶端

在activity中綁定服務(wù)來模擬進(jìn)程B,通過binder跨進(jìn)程調(diào)用A的添加圖書的方法绪穆,實(shí)現(xiàn)binder客戶端辨泳。

package com.shine.binderdemo;
 
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Toast;
 
public class MainActivity extends AppCompatActivity {
 
    private IBookManager mBookManager;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent(this, MyService.class);
        bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
    }
 
    public void add(View view) {
        Book book = new Book();
        book.setBookId(1);
        book.setBookName("《第一行代碼》");
        try {
            mBookManager.addBook(book);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
 
    }
 
    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(mServiceConnection);
    }
 
    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mBookManager = IBookManager.Stub.asInterface(service);
            try {
                mBookManager.registerListener(mListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
 
        @Override
        public void onServiceDisconnected(ComponentName name) {
 
        }
    };
 
    private IOnBookAddListener mListener = new IOnBookAddListener.Stub() {
        @Override
        public void onBookadd(final Book book) throws RemoteException {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(MainActivity.this, book.toString(), Toast.LENGTH_SHORT).show();
                }
            });
        }
 
    };
}

首先來驗(yàn)證服務(wù)端中mBinder 的addBook(Book book)方法確實(shí)是在一個子線程中進(jìn)行的。

private IOnBookAddListener mListener = new IOnBookAddListener.Stub() {
        @Override
        public void onBookadd(final Book book) throws RemoteException {
            //方法回調(diào)在子線程中
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(MainActivity.this, book.toString(), Toast.LENGTH_SHORT).show();
                }
            });
        }
 
    };

在onBookadd(final Book book) 回調(diào)中通過runOnUiThread(new Runnable())將線程切回主線程然后才能Toast提示玖院。這也從側(cè)面證明了binder服務(wù)端處理客戶端的請求時會在一個子線程中處理菠红。這里我們還有一個地方要注意,雖然binder服務(wù)會在一個子線程中處理客戶端的請求难菌,但是客戶端請求時卻不會新開一個線程试溯,從上面的代碼我們可能還看不出什么,如果將服務(wù)端的添加圖書的代碼設(shè)置為耗時操作扔傅,運(yùn)行程序耍共,點(diǎn)擊添加圖書可能就會出現(xiàn)ANR烫饼。(這里就不驗(yàn)證了)所以在確定服務(wù)端處理請求時是耗時的操作的時候猎塞。有必要新開一個線程去請求。
上面說了這么多杠纵,總結(jié)一句話:binder服務(wù)在處理客戶端的請求時是在一個獨(dú)立的線程中完成的荠耽,而客戶端請求處理,不會新開一個線程比藻,如果是耗時操作铝量,則可能出現(xiàn)ANR。

四银亲、Binder跨進(jìn)程間原理

首先來看慢叨,在activity中點(diǎn)擊添加圖書會調(diào)用add(View view),然后調(diào)用mBookManager.addBook(book);就能成功的往MyService的bookList中添加一本書务蝠,這是為什么呢拍谐?我們的activity和service明明處于兩個進(jìn)程,確好像在同一個進(jìn)程中直接持有了服務(wù)端實(shí)現(xiàn)addBook(Book book)方法的mBinder 的引用馏段,通過這個引用就調(diào)用了MyService 所在進(jìn)程的方法轩拨。要解釋這個問題,我們自然而然的要分析activity中的mBookManager是怎么被賦值的:

private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //mBookManager 在這里賦值
            mBookManager = IBookManager.Stub.asInterface(service);
            try {
                mBookManager.registerListener(mListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
 
        @Override
        public void onServiceDisconnected(ComponentName name) {
 
        }
    };

看到這里mBookManager = IBookManager.Stub.asInterface(service);我們可能會糊涂院喜,這個IBookManager.Stub到底是個什么東西亡蓉,為什么調(diào)用它的asInterface(service)方法就能得到mBinder 的引用?接下來我們就來分析IBookManager.Stub是個啥喷舀,它的asInterface(service)到底有什么奧秘砍濒。

package com.shine.binderdemo;
 
public interface IBookManager extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.shine.binderdemo.IBookManager {
        private static final java.lang.String DESCRIPTOR = "com.shine.binderdemo.IBookManager";
 
        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }
 
        /**
         * Cast an IBinder object into an com.shine.binderdemo.IBookManager interface,
         * generating a proxy if needed.
         */
        public static com.shine.binderdemo.IBookManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.shine.binderdemo.IBookManager))) {
                return ((com.shine.binderdemo.IBookManager) iin);
            }
            return new com.shine.binderdemo.IBookManager.Stub.Proxy(obj);
        }
 
        @Override
        public android.os.IBinder asBinder() {
            return this;
        }
 
        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_addBook: {
                    data.enforceInterface(DESCRIPTOR);
                    com.shine.binderdemo.Book _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.shine.binderdemo.Book.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addBook(_arg0);
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_registerListener: {
                    data.enforceInterface(DESCRIPTOR);
                    com.shine.binderdemo.IOnBookAddListener _arg0;
                    _arg0 = com.shine.binderdemo.IOnBookAddListener.Stub.asInterface(data.readStrongBinder());
                    this.registerListener(_arg0);
                    reply.writeNoException();
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }
 
        private static class Proxy implements com.shine.binderdemo.IBookManager {
            private android.os.IBinder mRemote;
 
            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }
 
            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }
 
            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }
 
            @Override
            public void addBook(com.shine.binderdemo.Book book) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((book != null)) {
                        _data.writeInt(1);
                        book.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
 
            @Override
            public void registerListener(com.shine.binderdemo.IOnBookAddListener listener) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeStrongBinder((((listener != null)) ? (listener.asBinder()) : (null)));
                    mRemote.transact(Stub.TRANSACTION_registerListener, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }
 
        static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_registerListener = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }
 
    public void addBook(com.shine.binderdemo.Book book) throws android.os.RemoteException;
 
    public void registerListener(com.shine.binderdemo.IOnBookAddListener listener) throws android.os.RemoteException;
}

上面直接貼出了IBookManager.aidl編譯之后生成的IBookManager.java的代碼淋肾。這個代碼初看之下很長,不容易讀懂爸邢,下面我貼一張as里面關(guān)于這個類的結(jié)構(gòu)圖巫员。
[圖片上傳失敗...(image-7fc006-1570421770459)]
通過這個結(jié)構(gòu)圖大概可以看出Stub是IBookManager的一個內(nèi)部類,有一個asInterface(android.os.IBinder obj)方法甲棍,這也是上面activity中給mBookManager 賦值中用到的一個方法简识,我們待會再來分析。Stub內(nèi)部又有一個內(nèi)部類Proxy 感猛,從名字上來看它應(yīng)該是一個代理類七扰,那么它到底代理的那個類呢?

public static com.shine.binderdemo.IBookManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.shine.binderdemo.IBookManager))) {
                return ((com.shine.binderdemo.IBookManager) iin);
            }
            //將IBinder類型的obj通過構(gòu)造方法傳入Proxy
            return new com.shine.binderdemo.IBookManager.Stub.Proxy(obj);
 }
Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

看完上面兩段代碼應(yīng)該可以看出Proxy代理的就是一個IBinder類型的obj陪白,到這里我們明白了mBookManager原來就是一個Proxy代理類颈走。熟悉代理模式的話,我們知道代理類只是持有一個真實(shí)類的引用咱士,真正功能都是由這個真實(shí)類實(shí)現(xiàn)的立由。在這個IBookManager.Stub.Proxy里面,真實(shí)類是什么呢序厉?

private static class Proxy implements com.shine.binderdemo.IBookManager {
            //mRemote是這個代理中的真實(shí)對象
            private android.os.IBinder mRemote;
 
            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }
 
           ......
 
            @Override
            public void addBook(com.shine.binderdemo.Book book) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((book != null)) {
                        _data.writeInt(1);
                        book.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    //mBookManager的addBook(Book book)方法實(shí)際上是調(diào)用 mRemote.transact(...)方法
                    mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
           ......
}

這段代碼的注釋已經(jīng)很明確的IBinder類型的mRemote是這個類的真實(shí)引用锐膜。
mBookManager.addBook(Book book)方法最后會調(diào)用mRemote.transact(...)方法,那么這個mRemote是個啥呢弛房?mRemote是如何傳入Proxy呢道盏?
我們在之前的binder機(jī)制的概念時說過,binder機(jī)制涉及到4個組件文捶,binder server 荷逞,binder client,binder內(nèi)核驅(qū)動粹排,Service Manager种远。在上面的情景中我們只分析了binder server 和binder client,接下來binder內(nèi)核驅(qū)動(對于binder驅(qū)動顽耳,在下面只會提到它的作用坠敷,不涉及具體的代碼,具體的代碼分析我也不懂)就要出場了斧抱,而對于Service Manager常拓,這個例子中卻不會直接涉及,在activity的啟動過程中會出現(xiàn)辉浦,到時候再分析弄抬,敬請期待。宪郊。掂恕。
有了binder驅(qū)動介入拖陆,就可以解決mRemote到底是個啥了。先看下MyService的onBinder(...)方法

@Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
private IBinder mBinder = new IBookManager.Stub() {
 
        @Override
        public void addBook(Book book) throws RemoteException {
            booklist.add(book);
            if (mListener != null) {
                mListener.onBookadd(book);
            }
        }
 
        @Override
        public void registerListener(IOnBookAddListener listener) throws RemoteException {
            mListener = listener;
        }
    };

這個mBinder是一個IBookManager.Stub()類型的變量懊亡,而IBookManager.Stub()繼承Binder依啰,所以mBinder是一個Binder類型的對象。這個binder類型的對象實(shí)際上就是binder服務(wù)端店枣,在binder服務(wù)端開啟的時候速警,同時會在binder內(nèi)核建一個mRemote的binder對象,我們在上面提到的mRemote其實(shí)就是binder內(nèi)核里面的mRemote binder對象鸯两。實(shí)際在binder進(jìn)程間調(diào)用的時候必須要考慮的問題就是如何獲取binder內(nèi)核mRemote 對象闷旧。這個例子采用的是Service做binder服務(wù)端,而binderService中g(shù)oogle的工程師已經(jīng)為我們實(shí)現(xiàn)好了钧唐。在ServiceConnection里面有個回調(diào)方法可以獲取binder內(nèi)核的mRemote忙灼,如下:

private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //通過獲取到的service(mRemote)生成IBookManager.Stub.Proxy對象
            mBookManager = IBookManager.Stub.asInterface(service);
            try {
                mBookManager.registerListener(mListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
 
        @Override
        public void onServiceDisconnected(ComponentName name) {
 
        }
    };

這樣我們就獲取到了binder內(nèi)核的mRemote對象,同時也傳入了IBookManager.Stub.Proxy钝侠,接著就可以使用mRemote來進(jìn)行跨線程的調(diào)用了该园。
接下來看下mBookManager到底是怎樣實(shí)現(xiàn)addBook(Book,book)方法的帅韧,上面已經(jīng)分析了會調(diào)用到IBookManager.Stub.Proxy.addBook(Book book)里初。

 @Override
    public void addBook(com.shine.binderdemo.Book book) throws android.os.RemoteException {
        //_data表示發(fā)送binder服務(wù)端的數(shù)據(jù),這些數(shù)據(jù)需要通過Parcel (包裹)進(jìn)行傳遞
        android.os.Parcel _data = android.os.Parcel.obtain();
        //_reply 表示binder服務(wù)端相應(yīng)的數(shù)據(jù)弱匪,這些數(shù)據(jù)同樣需要通過Parcel (包裹)進(jìn)行傳遞
        android.os.Parcel _reply = android.os.Parcel.obtain();
        try {
            //用來做安全檢查青瀑,binder服務(wù)端會處理這個數(shù)據(jù)
            _data.writeInterfaceToken(DESCRIPTOR);
            if ((book != null)) {
                _data.writeInt(1);
                //Parcel (包裹)不僅可以傳遞基本類型的數(shù)據(jù)還可以傳遞對象,但是對象必須實(shí)現(xiàn)Parcelable接口
                book.writeToParcel(_data, 0);
            } else {
                _data.writeInt(0);
            }
            //調(diào)用binder內(nèi)核的mRemote對象往binder服務(wù)端發(fā)送信息
            mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
            _reply.readException();
        } finally {
            _reply.recycle();
            _data.recycle();
        }
    }

代碼中注釋已經(jīng)很詳細(xì)了萧诫,就不解釋了,接著看下面枝嘶,我們說過mRemote實(shí)際上是一個binder類型的對象帘饶, mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);就會調(diào)用到Binder的transact(...)方法中

public final boolean transact(int code, Parcel data, Parcel reply,
            int flags) throws RemoteException {
        if (false) Log.v("Binder", "Transact: " + code + " to " + this);
        if (data != null) {
            data.setDataPosition(0);
        }
        //調(diào)用binder服務(wù)端的onTransact(...)中
        boolean r = onTransact(code, data, reply, flags);
        if (reply != null) {
            reply.setDataPosition(0);
        }
        return r;
    }

其中transact(...)有四個參數(shù),分別是 code群扶,data及刻,reply,flag竞阐。
code:整形的一個識別碼缴饭,客戶端傳入,用于區(qū)分服務(wù)端執(zhí)行哪個方法骆莹。
data:客戶端傳入的Parcel類型的參數(shù)颗搂。
reply:服務(wù)端返回的結(jié)果值,也是Parcel類型數(shù)據(jù)幕垦。
flag:整形的一個標(biāo)記丢氢,用于標(biāo)記是否是否有返回值傅联,0表示有返回值,1表示沒有疚察。
接著再次涉及到binder內(nèi)核驅(qū)動蒸走,具體的細(xì)節(jié)我也不太懂,直接的結(jié)論是流程會進(jìn)入到binder服務(wù)端的IBookManager.Stub的onTransact(...)中:

@Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                //根據(jù)transact第一個參數(shù)來確定請求的是哪個方法
                case TRANSACTION_addBook: {
                    data.enforceInterface(DESCRIPTOR);
                    com.shine.binderdemo.Book _arg0;
                    if ((0 != data.readInt())) {//如果客戶端傳遞是有非基本類型的數(shù)據(jù)從data中取出
                        _arg0 = com.shine.binderdemo.Book.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    //取出來的數(shù)據(jù)傳到MyService的mBinder的addBook(...)
                    this.addBook(_arg0);
                    //由于addBook(...)沒有返回值貌嫡,所以不需要通過reply返回結(jié)果
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_registerListener: {
                    data.enforceInterface(DESCRIPTOR);
                    com.shine.binderdemo.IOnBookAddListener _arg0;
                    _arg0 = com.shine.binderdemo.IOnBookAddListener.Stub.asInterface(data.readStrongBinder());
                    this.registerListener(_arg0);
                    reply.writeNoException();
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

通過上面從activity的addBool(Book book)開始比驻,我們一步一步的分析了binder客戶端如何調(diào)用binder服務(wù)端的方法,這個過程在binder內(nèi)核驅(qū)動的基礎(chǔ)上實(shí)現(xiàn)岛抄,讓我們感覺好像是調(diào)用本地(同一個進(jìn)程)方法一樣嫁艇,實(shí)質(zhì)上底層為我們做了大量的工作。這樣基于aidl實(shí)現(xiàn)的Binder跨進(jìn)程調(diào)用就大概談完了弦撩,對binder跨進(jìn)程調(diào)用也應(yīng)該有了一定的了解步咪。
#######binder小結(jié)
跨進(jìn)程調(diào)用的關(guān)鍵點(diǎn)在于如何獲得服務(wù)端的binder對象在內(nèi)核里面的引用(如上面分析的mRemote)。
一般來說有兩種途徑益楼,其一是通過Service Manager猾漫,這篇文章沒有直接涉及Service Manager,但是在底層源碼里面這種情況很常見感凤,在Activity啟動過程中用到的ActivityManagerService就是通過這種方式在客戶端得到服務(wù)端的binder對象在內(nèi)核里面的引用悯周,我們以后再分析。其二是通過已經(jīng)建立好的binder連接來獲取這個引用陪竿。如上面的例子中用到的一樣禽翼。

private IOnBookAddListener mListener = new IOnBookAddListener.Stub() {
        @Override
        public void onBookadd(final Book book) throws RemoteException {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(MainActivity.this, book.toString(), Toast.LENGTH_SHORT).show();
                }
            });
        }
 
    };

這是一個實(shí)現(xiàn)了接口方法的Binder服務(wù),通過已經(jīng)建立好的binde連接mBookManager傳遞給Myservice所在進(jìn)程族跛。

private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mBookManager = IBookManager.Stub.asInterface(service);
            try {
                //通過已經(jīng)建立好的連接傳送服務(wù)端binder的引用
                mBookManager.registerListener(mListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
 
        @Override
        public void onServiceDisconnected(ComponentName name) {
 
        }
    };

有一點(diǎn)需要注意闰挡,在此時activity所在的進(jìn)程就成為了我們通常所說的binder服務(wù)端,而MyService則是binder客戶端礁哄。這就是第二種獲取binder服務(wù)引用的方式长酗。再多談一點(diǎn),其實(shí)在這個例子中桐绒,兩個建立的binder連接都是通過已將建立好的連接來傳遞的夺脾,除了mListener 這個binder引用的獲得,mBinder也是這種情況茉继。這里就不再詳細(xì)討論了咧叭,如果感興趣可以學(xué)習(xí)一下bindService的源碼,就肯定能發(fā)現(xiàn)這一點(diǎn)烁竭。最后貼上一張例子用到的uml類圖:
[圖片上傳失敗...(image-6d3652-1570421770459)]

參考博客

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子生均,更是在濱河造成了極大的恐慌听想,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件马胧,死亡現(xiàn)場離奇詭異汉买,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)佩脊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門蛙粘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人威彰,你說我怎么就攤上這事出牧。” “怎么了歇盼?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵舔痕,是天一觀的道長。 經(jīng)常有香客問我豹缀,道長伯复,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任邢笙,我火速辦了婚禮啸如,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘氮惯。我一直安慰自己叮雳,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布妇汗。 她就那樣靜靜地躺著帘不,像睡著了一般。 火紅的嫁衣襯著肌膚如雪铛纬。 梳的紋絲不亂的頭發(fā)上厌均,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機(jī)與錄音告唆,去河邊找鬼。 笑死晶密,一個胖子當(dāng)著我的面吹牛擒悬,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播稻艰,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼懂牧,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起僧凤,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤畜侦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后躯保,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體旋膳,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年途事,在試婚紗的時候發(fā)現(xiàn)自己被綠了验懊。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡尸变,死狀恐怖义图,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情召烂,我是刑警寧澤碱工,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站奏夫,受9級特大地震影響怕篷,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜桶蛔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一匙头、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧仔雷,春花似錦蹂析、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至竖共,卻和暖如春蝙叛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背公给。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工借帘, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人淌铐。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓肺然,卻偏偏與公主長得像间景,于是被迫代替她去往敵國和親灼捂。 傳聞我的和親對象是個殘疾皇子壮虫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355

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