美文网首页程序员Android学习
徒手跨进程-Binder的实现与解析

徒手跨进程-Binder的实现与解析

作者: 6He | 来源:发表于2018-12-09 23:13 被阅读171次

不借助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学习指南》

相关文章

网友评论

    本文标题:徒手跨进程-Binder的实现与解析

    本文链接:https://www.haomeiwen.com/subject/szokhqtx.html