徒手跨進程-Binder的實現與解析

不借助AIDL實現Binder

  1. 第一步 聲明接口
/**
 * 聲明一個接口悍手,該接口繼承IInterface
 * IInterface代表的就是遠程server對象
 * 在接口中聲明需要實現的方法,這些方法將在server中實現微饥,在client中被調用
 */
public interface IBookManager extends IInterface {
    void addBook(Book book)throws android.os.RemoteException;
    List<Book> getBookList()throws android.os.RemoteException;
}
  1. 第二步 實現可跨進程的類
/**
 * 創(chuàng)建類BookManagerImp,繼承Binder類格侯,實現 IBookManager接口吁朦,然后創(chuàng)建內部類Proxy同樣實現IBookManager接口
 * Binder類,代表的其實就是Binder本地對象加勤。BinderProxy類是Binder類的內部類,它代表遠程進程Binder對象的本地代理同波;
 * IBinder是一個接口鳄梅,它代表了一種跨進程傳輸的能力;只要實現了這個接口未檩,就能將這個對象進行跨進程傳遞戴尸;這是驅動底層支持的;
 * 在跨進程數據流驅動的時候冤狡,驅動會識別IBinder類型的數據孙蒙,從而自動完成不同進程Binder本地對象及Binder代理對象的轉換
 */
public abstract class BookManagerImp extends Binder implements IBookManager {
    private static final java.lang.String DESCRIPTOR = "liuhe.com.ipcdemo.BookManagerImp";
    static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);

    public BookManagerImp() {
        /**
         Convenience method for associating a specific interface with the Binder.
         After calling, queryLocalInterface() will be implemented for you
         to return the given owner IInterface when the corresponding
         descriptor is requested
         將特定接口與Binder相關聯的便捷方法。調用后悲雳,將實現queryLocalInterface()挎峦,
         以便在請求相應的描述符時返回給定的所有者IInterface
         */
        this.attachInterface(this, DESCRIPTOR);
    }

    public static IBookManager asInterface(IBinder obj) {
        if (obj == null) {
            return null;
        }

        /**
         Attempt to retrieve a local implementation of an interface
         for this Binder object.  If null is returned, you will need
         to instantiate a proxy class to marshall calls through
         the transact() method.
         嘗試檢索此Binder對象的接口的本地實現。
         如果返回null合瓢,則需要實例化代理類以通過transact()方法編組調用坦胶。
         */
        IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
        if (iin != null && iin instanceof IBookManager) {
            return (IBookManager) iin;
        } else {
            return new Proxy(obj);
        }
    }

    @Override
    protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
        switch (code) {
            case INTERFACE_TRANSACTION: {
                reply.writeString(DESCRIPTOR);
                return true;
            }
            case TRANSACTION_addBook: {
                data.enforceInterface(DESCRIPTOR);
                liuhe.com.ipcdemo.Book _arg0;
                if ((0 != data.readInt())) {
                    _arg0 = liuhe.com.ipcdemo.Book.CREATOR.createFromParcel(data);
                } else {
                    _arg0 = null;
                }
                this.addBook(_arg0);
                reply.writeNoException();
                return true;
            }
            case TRANSACTION_getBookList: {
                data.enforceInterface(DESCRIPTOR);
                java.util.List<liuhe.com.ipcdemo.Book> _result = this.getBookList();
                reply.writeNoException();
                reply.writeTypedList(_result);
                return true;
            }
        }
        return super.onTransact(code, data, reply, flags);
    }

    private static class Proxy implements IBookManager {

        private IBinder mRemote;

        public Proxy(IBinder mRemote) {
            this.mRemote = mRemote;
        }

        @Override
        public IBinder asBinder() {
            return mRemote;
        }

        public java.lang.String getInterfaceDescriptor() {
            return DESCRIPTOR;
        }

        @Override
        public void addBook(liuhe.com.ipcdemo.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);
                }

                /**
                 Perform a generic operation with the object.
                 @param code The action to perform.  This should
                 be a number between {@link #FIRST_CALL_TRANSACTION} and
                 {@link #LAST_CALL_TRANSACTION}.
                 @param data Marshalled data to send to the target.  Must not be null.
                 If you are not sending any data, you must create an empty Parcel
                 that is given here.
                 @param reply Marshalled data to be received from the target.  May be
                 null if you are not interested in the return value.
                 @param flags Additional operation flags.  Either 0 for a normal
                 RPC, or {@link #FLAG_ONEWAY} for a one-way RPC.

                 @return Returns the result from {@link Binder#onTransact}.  A successful call
                 generally returns true; false generally means the transaction code was not
                 understood.

                 對對象執(zhí)行泛型操作。
                 @param code要執(zhí)行的操作晴楔。
                 這應該是{@link #FIRST_CALL_TRANSACTION}和{@link #LAST_CALL_TRANSACTION}之間的數字迁央。
                 @param _data 要發(fā)送到目標的整理后的數據。不能為空滥崩。如果您未發(fā)送任何數據,則必須創(chuàng)建此處給出的空包讹语。
                 @param _reply 要從目標接收的整理后數據钙皮。如果您對返回值不感興趣,則可以為null。
                 @param標志 附加操作標志短条。正常RPC為0导匣,單向RPC為{@link #FLAG_ONEWAY}。
                 @return返回{@link Binder#onTransact}的結果茸时。成功的通話通常會返回true; false通常表示不理解事務代碼贡定。
                 */

                mRemote.transact(TRANSACTION_addBook, _data, _reply, 0);
                _reply.readException();
            } finally {
                _reply.recycle();
                _data.recycle();
            }
        }

        @Override
        public java.util.List<liuhe.com.ipcdemo.Book> getBookList() throws android.os.RemoteException {
            android.os.Parcel _data = android.os.Parcel.obtain();
            android.os.Parcel _reply = android.os.Parcel.obtain();
            java.util.List<liuhe.com.ipcdemo.Book> _result;
            try {
                _data.writeInterfaceToken(DESCRIPTOR);
                mRemote.transact(TRANSACTION_getBookList, _data, _reply, 0);
                _reply.readException();
                _result = _reply.createTypedArrayList(liuhe.com.ipcdemo.Book.CREATOR);
            } finally {
                _reply.recycle();
                _data.recycle();
            }
            return _result;
        }
    }


    @Override
    public IBinder asBinder() {
        return this;
    }
}
  1. 定義一個service并實現server功能
public class BinderService extends Service {
    private static final String TAG = "BinderService";
    ArrayList<Book> books;

    IBinder iBinder = new BookManagerImp() {
        @Override
        public void addBook(Book book) throws RemoteException {
            Log.i(TAG, "addBook");
            books.add(book);
        }

        @Override
        public List<Book> getBookList() throws RemoteException {
            Log.i(TAG, "getBookList" + books.toString());
            return books;
        }
    };

    @Override
    public IBinder onBind(Intent intent) {
        books = new ArrayList();
        return iBinder;
    }
}
  1. 在Activity中啟動service,調用server的方法
public class MainActivity extends AppCompatActivity {
    int index = 1;
    IBookManager bookManager;

    TextView tv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv = findViewById(R.id.textView);

        findViewById(R.id.add).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                if (bookManager != null) {
                    Book book = new Book("Book" + index);
                    index++;
                    try {
                        //調用server端addBook方法
                        bookManager.addBook(book);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                } else {
                    //使用bindService方式啟動service
                    final Intent intent = new Intent(getApplicationContext(), BinderService.class);
                    bindService(intent, connection, BIND_AUTO_CREATE);
                }
            }
        });

        findViewById(R.id.get_book_list).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    //調用server端getBookList方法浓体,獲取返回數據并顯示
                    tv.setText(bookManager.getBookList().toString());
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            Toast.makeText(getApplicationContext(), "success", Toast.LENGTH_LONG).show();
            bookManager = BookManagerImp.asInterface(iBinder);
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            bookManager = null;
        }
    };

}

ok了备恤,這樣我們就實現了跨進程通信棍丐。

執(zhí)行過程分析

我們先看一下service是怎么定義的

<service
            android:name=".BinderService"
            android:process=":remote"></service>

我們知道只要給service增加了process屬性,那么它就會運行在這個指定的remote進程中旋炒。如果我們要調試程序,IDE就會讓我們選擇要調試的進程签杈,如下圖所示:


選擇進程.png

我們先把process屬性去掉瘫镇,看一下在同一個進程中代碼是怎么執(zhí)行的。

public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            Toast.makeText(getApplicationContext(), "success", Toast.LENGTH_LONG).show();
            bookManager = BookManagerImp.asInterface(iBinder);
        }

當bindService成功之后會返回一個IBinder對象答姥,我們將這個對象當做參數傳到BookManagerImp的asInterface方法中铣除,然后獲取了一個IBookManager對象。來我們看一下慢動作鹦付,debug走起~


asInterface方法.png
Binder本地對象.png

我們可以看到IBinder對象通過調用queryLocalInterface方法返回了一個IInterface對象尚粘,該對象不為空并且等于IBookManager ,直接將該對象轉換為IBookManager睁壁,作為結果返回背苦。
我們看一下queryLocalInterface的注釋:

/**
         Attempt to retrieve a local implementation of an interface
         for this Binder object.  If null is returned, you will need
         to instantiate a proxy class to marshall calls through
         the transact() method.
         嘗試檢索此Binder對象的接口的本地實現。
         如果返回null潘明,則需要實例化代理類以通過transact()方法編組調用行剂。
         */

注釋中說明該方法返回的IInterface是一個本地Binder對象實現,不需要跨進程通信钳降。我們繼續(xù)調試就會發(fā)現在MainActivity中bookManager可以直接調用server中的方法厚宰,不會再走BookManagerImp的其他方法。
好了遂填,分析完在相同進程的通信過程铲觉,我們再來看看跨進程的過程,把android:process=":remote"再個service加上吓坚,走起撵幽。
和之前的步驟一樣,運行調試礁击,走到asInterface方法中


asInterface方法2.png

我們看到iin為空盐杂,最終返回了一個Proxy對象逗载。接下來在MainActivity中調用addBook方法,我們看到系統會調用Proxy的addBook方法链烈,如下圖所示:


Proxy#addBook.png

可以看到在Proxy的addBook方法中調用了mRemote的transact的方法厉斟,并將方法的code值及構造的序列化的參數傳遞進去。我們看一下transact的注釋:
/**@return Returns the result from {@link Binder#onTransact}.  A successful call
 * generally returns true; false generally means the transaction code was not  understood.
 *@return返回{@link Binder#onTransact}的結果强衡。成功的通話通常會返回true; false通常表示不理解事務代碼擦秽。
 */

很明顯,我們傳過去的code值和參數最終會交給 Binder的onTransact方法漩勤,也就是BookManagerImp的onTransact方法感挥。我們可以推測,我們把斷點放在下圖所示的地方應該是可以看到接下來的執(zhí)行過程的锯七。


onTransact.png

皮皮蝦我們走~
走啊链快,走啊,走啊眉尸,怎么不走S蛭稀!T牖霉祸??袱蜡?
可以思考一下為什么斷點沒走到這丝蹭?


我們選擇調試模式的時候IDE會讓我們選擇調試的進程,我們一開始選擇的是主進程坪蚁,現在BookManagerImp是在remote進程中奔穿,所以斷點是無法到達的。好了敏晤,我們重新選擇調試進程試試:


onTansact2.png

果然如此贱田,斷點成功進入!
我們可以看到code值為1嘴脾,所以接下來會走case TRANSACTION_addBook: 最終會調用IBookManager的addBook方法男摧,也就是service中的addBook方法的實現會被調用。
就這樣译打,跨進程通信成功了耗拓!
如果有問題歡迎在評論區(qū)留言,一起交流學習奏司!
參考:
任玉剛《Android 開發(fā)藝術探索》
weishu《Binder學習指南》

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末乔询,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子韵洋,更是在濱河造成了極大的恐慌哥谷,老刑警劉巖岸夯,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異们妥,居然都是意外死亡,警方通過查閱死者的電腦和手機勉吻,發(fā)現死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門监婶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人齿桃,你說我怎么就攤上這事惑惶。” “怎么了短纵?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵带污,是天一觀的道長。 經常有香客問我香到,道長鱼冀,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任悠就,我火速辦了婚禮千绪,結果婚禮上,老公的妹妹穿的比我還像新娘梗脾。我一直安慰自己荸型,他們只是感情好,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布炸茧。 她就那樣靜靜地躺著瑞妇,像睡著了一般。 火紅的嫁衣襯著肌膚如雪梭冠。 梳的紋絲不亂的頭發(fā)上辕狰,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機與錄音妈嘹,去河邊找鬼柳琢。 笑死,一個胖子當著我的面吹牛润脸,可吹牛的內容都是我干的柬脸。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼毙驯,長吁一口氣:“原來是場噩夢啊……” “哼倒堕!你這毒婦竟也來了?” 一聲冷哼從身側響起爆价,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤垦巴,失蹤者是張志新(化名)和其女友劉穎媳搪,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體骤宣,經...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡秦爆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了憔披。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片等限。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖芬膝,靈堂內的尸體忽然破棺而出望门,到底是詐尸還是另有隱情,我是刑警寧澤锰霜,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布筹误,位于F島的核電站,受9級特大地震影響癣缅,放射性物質發(fā)生泄漏厨剪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一所灸、第九天 我趴在偏房一處隱蔽的房頂上張望丽惶。 院中可真熱鬧,春花似錦爬立、人聲如沸钾唬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽抡秆。三九已至,卻和暖如春吟策,著一層夾襖步出監(jiān)牢的瞬間儒士,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工檩坚, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留着撩,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓匾委,卻偏偏與公主長得像拖叙,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子赂乐,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內容