徒手跨进程-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. 第二步 实现可跨进程的类
/**
 * 创建类BookManagerImp,继承Binder类,实现 IBookManager接口,然后创建内部类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.

                 对对象执行泛型操作。
                 @param code要执行的操作。
                 这应该是{@link #FIRST_CALL_TRANSACTION}和{@link #LAST_CALL_TRANSACTION}之间的数字。
                 @param _data 要发送到目标的整理后的数据。不能为空。如果您未发送任何数据,则必须创建此处给出的空包。
                 @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了,这样我们就实现了跨进程通信。

执行过程分析

我们先看一下service是怎么定义的

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

我们知道只要给service增加了process属性,那么它就会运行在这个指定的remote进程中。如果我们要调试程序,IDE就会让我们选择要调试的进程,如下图所示:


选择进程.png

我们先把process属性去掉,看一下在同一个进程中代码是怎么执行的。

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对象实现,不需要跨进程通信。我们继续调试就会发现在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方法。我们可以推测,我们把断点放在下图所示的地方应该是可以看到接下来的执行过程的。


onTransact.png

皮皮虾我们走~
走啊,走啊,走啊,怎么不走!!!???
可以思考一下为什么断点没走到这?


我们选择调试模式的时候IDE会让我们选择调试的进程,我们一开始选择的是主进程,现在BookManagerImp是在remote进程中,所以断点是无法到达的。好了,我们重新选择调试进程试试:


onTansact2.png

果然如此,断点成功进入!
我们可以看到code值为1,所以接下来会走case TRANSACTION_addBook: 最终会调用IBookManager的addBook方法,也就是service中的addBook方法的实现会被调用。
就这样,跨进程通信成功了!
如果有问题欢迎在评论区留言,一起交流学习!
参考:
任玉刚《Android 开发艺术探索》
weishu《Binder学习指南》

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容