继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

写给Android应用工程师的Binder原理剖析(实现)

xzhang76
关注TA
已关注
手记 13
粉丝 33
获赞 8

前言

前两篇文章写给Android应用工程师的Binder原理剖析(转载一)
写给Android应用工程师的Binder原理剖析(转载二)
都转载自张磊的对Binder原理剖析的文章。
作者:张磊BARON
这篇文章是自己基于文章中对Binder原理剖析的理解而写的一个例子。

六 手动编码实现跨进程调用

通常我们在做开发时,实现进程间通信用的最多的就是 AIDL。当我们定义好 AIDL 文件,在编译时编译器会帮我们生成代码实现 IPC 通信。借助 AIDL 编译以后的代码能帮助我们进一步理解 Binder IPC 的通信原理。
先看一下代码清单:
代码清单
通过工程结构可以3个组成部分:

  • client中是主进程的MainActivity,它通过 Binder 代理对象传输Book对象到server端,或者说调用server端提供的代理对象的方法。
  • server中实现了另外一个进程的Service,它包含了一个Binder实体对象,Binder实体对象实现了具体提供给client的方法
    server端还包含了IInterface,Stub和Proxy,(在build中生成),它实现了提供给client端的Binder代理对象
  • 传输的Book类

为了还原最初的AIDL在 IPC 通信中的运用,这里创建了IBookManagerInterface.aidl,然后编译生成包含一个IBookManagerInterface接口、一个 Stub 静态的抽象类和一个 Proxy 静态类的IBookManagerInterface.java。Proxy 是 Stub 的静态内部类,Stub 又是 IBookManagerInterface 的静态内部类。

这样编译器生成的代码对开发者并不好理解,但是
>Android 之所以这样设计其实是有道理的,因为当有多个 AIDL 文件的时候把 IBookManagerInterface、Stub、Proxy 放在同一个文件里能有效避免 Stub 和 Proxy 重名的问题。

6.1 各 Java 类职责描述

在正式编码实现跨进程调用之前,先介绍下实现过程中用到的一些类。了解了这些类的职责,有助于我们更好的理解和实现跨进程通信。

  • IBinder : IBinder 是一个接口,代表了一种跨进程通信的能力。只要实现了这个借口,这个对象就能跨进程传输。
  • IInterface : IInterface 代表的就是 Server 进程对象具备什么样的能力(能提供哪些方法,其实对应的就是 AIDL 文件中定义的接口)
  • Binder : Java 层的 Binder 类,代表的其实就是 Binder 本地对象。BinderProxy 类是 Binder 类的一个内部类,它代表远程进程的 Binder 对象的本地代理;这两个类都继承自 IBinder, 因而都具有跨进程传输的能力;实际上,在跨越进程的时候,Binder 驱动会自动完成这两个对象的转换。
  • Stub : AIDL 的时候,编译工具会给我们生成一个名为 Stub 的静态内部类;这个类继承了 Binder, 说明它是一个 Binder 本地对象,它实现了 IInterface 接口,表明它具有 Server 承诺给 Client 的能力;Stub 是一个抽象类,具体的 IInterface 的相关实现需要开发者自己实现。

6.2 IPC通信的Book对象

Book的对象需要在进程间通过Binder进行传输,那么Book类必须要实现Parcelable,就是将其序列化。

package com.demo.xzhangipcdemo;

import android.os.Parcel;
import android.os.Parcelable;

public class Book implements Parcelable {
    public String mBookName = "";
    public String mBookAuthor = "";

    public Book(String name, String author) {
        this.mBookName = name;
        this.mBookAuthor = author;
    }

    protected Book(Parcel in) {
        mBookName = in.readString();
        mBookAuthor = in.readString();
    }

    public static final Creator CREATOR = new Creator() {
        @Override
        public Book createFromParcel(Parcel in) {
            return new Book(in);
        }

        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(mBookName);
        dest.writeString(mBookAuthor);
    }
}

注:关于序列化的知识,可以看《Android中Serializable和Parcelable序列化》

6.3 AIDL

创建AIDL文件并不是最终的目的,目的是通过AIDL生成IInterface,Stub和代理Proxy。IBookManagerInterface.aidl中需要定义提供给client端Binder代理对象的方法。
另外因为Book类需要在进程中进行传输,所以要创建Book对应的AIDL

Book.aidl

package com.demo.xzhangipcdemo;
parcelable Book;

IBookManagerInterface.aidl

// IBookManagerInterface.aidl
package com.demo.xzhangipcdemo;
import com.demo.xzhangipcdemo.Book;

// Declare any non-default types here with import statements

interface IBookManagerInterface {

            void addBook(in Book book);
            List getBooks();
}

6.4 IInterface

有了AIDL文件,build完后会生成对应的IInterface,即IBookManagerInterface.java。如果只是会用,IBookManagerInterface不需要深入理解。
图片描述
可以看出,Proxy 是 Stub 的静态内部类,Stub 又是 IBookManagerInterface 的静态内部类。所以将IBookManagerInterface拆成三部分来看比较好理解:

1. IBookManagerInterface

前面说过IInterface代表的就是服务端进程具体什么样的能力,IBookManagerInterface继承了IInterface,它提供了两个个能力:

addBook(Book book)
List getBooks()

2. Stub

只定义服务端具备什么样的能力是不够的,既然是跨进程调用,那么接下来我们得实现一个跨进程调用对象 Stub。Stub 继承 Binder, 说明它是一个 Binder 本地对象;实现 IInterface 接口,表明具有 Server 承诺给 Client 的能力;Stub 是一个抽象类,具体的 IInterface 的相关实现需要调用方自己实现。

public static abstract class Stub extends Binder implements IBookManagerInterface {
    private static final String DESCRIPTOR = "com.demo.xzhangipcdemo.IBookManagerInterface";

    public Stub() {
        this.attachInterface(this, DESCRIPTOR);
    }

    public static IBookManagerInterface asInterface(android.os.IBinder obj) {
        if ((obj == null)) {
            return null;
        }
        IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
        if (((iin != null) && (iin instanceof IBookManagerInterface))) {
            return ((IBookManagerInterface) iin);
        }
        return new IBookManagerInterface.Stub.Proxy(obj);
    }

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

    @Override
    public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        switch (code) {
            case INTERFACE_TRANSACTION: {
                reply.writeString(DESCRIPTOR);
                return true;
            }
            case TRANSACTION_addBook: {
                data.enforceInterface(DESCRIPTOR);
                Book _arg0;
                if ((0 != data.readInt())) {
                    _arg0 = Book.CREATOR.createFromParcel(data);
                } else {
                    _arg0 = null;
                }
                this.addBook(_arg0);
                reply.writeNoException();
                return true;
            }
            case TRANSACTION_getBooks: {
                data.enforceInterface(DESCRIPTOR);
                List _result = this.getBooks();
                reply.writeNoException();
                reply.writeTypedList(_result);
                return true;
            }
        }
        return super.onTransact(code, data, reply, flags);
    }

    private static class Proxy implements IBookManagerInterface {
        //Proxy
    }

    static final int TRANSACTION_addBook = (IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_getBooks = (IBinder.FIRST_CALL_TRANSACTION + 1);
}

Stub 类中我们重点介绍下 asInterface()onTransact()

asInterface()

先说说 asInterface(),当 Client 端在创建和服务端的连接,调用 bindService 时需要创建一个 ServiceConnection 对象作为入参。在 ServiceConnection 的回调方法 onServiceConnected 中会通过这个 asInterface(IBinder binder) 拿到 IBookManagerInterface对象,这个 IBinder 类型的入参 binder 是驱动传给我们的,正如你在代码中看到的一样,方法中会去调用 binder.queryLocalInterface() 去查找 Binder 本地对象,如果找到了就说明 Client 和 Server 在同一进程,那么这个 binder 本身就是 Binder 本地对象,可以直接使用。否则说明是 binder 是个远程对象,也就是 BinderProxy。因此需要我们创建一个代理对象 Proxy,通过这个代理对象来是实现远程访问。
MainActivity.java

private IBookManagerInterface mBookManagerInterface;
@Override
protected void onResume() {
    super.onResume();
    ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mBookManagerInterface = IBookManagerInterface.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mBookManagerInterface = null;
        }
    };
    this.bindService(new Intent(this, BookManagerService.class), serviceConnection, Context.BIND_AUTO_CREATE);
}

onTransact()

onTransact()从字面来看是一个回调,在理解onTransact()之前需要理解Proxy,因为它是从这里回调的。

3. Proxy

Proxy是个代理类,既然是代理类自然需要实现 IBookManagerInterface 接口。

private static class Proxy implements IBookManagerInterface {
    private IBinder mRemote;

    Proxy(android.os.IBinder remote) {
        mRemote = remote;
    }

    // ...

    @Override
    public void addBook(Book book) throws RemoteException {
        Parcel _data = Parcel.obtain();
        Parcel _reply = 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 List getBooks() throws RemoteException {
        Parcel _data = Parcel.obtain();
        Parcel _reply = Parcel.obtain();
        List _result;
        try {
            _data.writeInterfaceToken(DESCRIPTOR);
            mRemote.transact(Stub.TRANSACTION_getBooks, _data, _reply, 0);
            _reply.readException();
            _result = _reply.createTypedArrayList(Book.CREATOR);
        } finally {
            _reply.recycle();
            _data.recycle();
        }
        return _result;
    }
}

Proxy是直接和client进行通信的,也就是MainActivtiy中调用BookManagerInterface.addBook()或getBooks()是调用的Proxy的相应方法。

if (mBookManagerInterface != null) {
    try {
        mBookManagerInterface.addBook(new Book("test", "xzhang"));           
    } catch (RemoteException e) {
        e.printStackTrace();
    }
}
  • 如果 Client 和 Server 在同一个进程,那么直接就是调用这个方法。
  • 如果是远程调用,Client 想要调用 Server 的方法就需要通过 Binder 代理来完成,也就是上面的 Proxy。

我们这个例子中,当MainActivity调用addBook()和getBooks()需要经过三个步骤:

  1. 进入Proxy,在 Proxy 中的 addBook() 方法中首先通过 Parcel 将数据序列化,然后调用 remote.transact()。正如前文所述 Proxy 是在 Stub 的 asInterface 中创建,能走到创建 Proxy 这一步就说明 Proxy 构造函数的入参是 BinderProxy,即这里的 remote 是个 BinderProxy 对象。
  2. 通过一系列的函数调用,Client 进程通过系统调用陷入内核态,Client 进程中执行 addBook() 的线程挂起等待返回;驱动完成一系列的操作之后唤醒 Server 进程,调用 Server 进程本地对象的 onTransact()。
  3. 最终又走到了 Stub 中的 onTransact() 中,onTransact() 根据函数编号调用相关函数(在 Stub 类中为 BookManagerInterface 接口中的每个函数中定义了一个编号,在跨进程调用的时候,不会传递函数而是传递编号来指明要调用哪个函数)。在这个例子中,调用了 Binder 本地对象的 addBook() 传递Book对象,以及getBooks()将结果返回给驱动,驱动唤醒 Client 进程里刚刚挂起的线程并将结果返回。

这样一次跨进程调用就完成了。

6.5 Service

再回头来看Stub中的onTransact()

@Override
    public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        switch (code) {
            case INTERFACE_TRANSACTION: {
                reply.writeString(DESCRIPTOR);
                return true;
            }
            case TRANSACTION_addBook: {
                data.enforceInterface(DESCRIPTOR);
                Book _arg0;
                if ((0 != data.readInt())) {
                    _arg0 = Book.CREATOR.createFromParcel(data);
                } else {
                    _arg0 = null;
                }
                this.addBook(_arg0);
                reply.writeNoException();
                return true;
            }
            case TRANSACTION_getBooks: {
                data.enforceInterface(DESCRIPTOR);
                List _result = this.getBooks();
                reply.writeNoException();
                reply.writeTypedList(_result);
                return true;
            }
        }
        return super.onTransact(code, data, reply, flags);
    }

通过函数的编号,最终调用到this.addBook(book)和this.getBooks(),this其实是IBookManagerInterface,它的addBook()和getBooks()是抽象方法,具体实现在service中。
BookManagerService.java

package com.demo.xzhangipcdemo.server;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;

import com.demo.xzhangipcdemo.Book;
import com.demo.xzhangipcdemo.IBookManagerInterface;

import java.util.ArrayList;
import java.util.List;

public class BookManagerService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
        if (null == mBooks) {
            mBooks = new ArrayList<>();
        }
    }

    private List mBooks;

    private IBinder mIBinder = new IBookManagerInterface.Stub() {

        @Override
        public void addBook(Book book) throws RemoteException {
            if (null != mBooks) {
                mBooks.add(book);
            }
        }

        @Override
        public List getBooks() throws RemoteException {
            return mBooks;
        }
    };

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

Service的注册,Service运行在独立的demo进程:

<?xml version="1.0" encoding="utf-8"?>

OK,现在基本通了吧!

小结

以上就是通过AIDL实现进程间通信的流程分析,但是如果你只是想停留在会用的程度。只需要在创建AIDL文件后,编译生成IBookManagerInterface接口,接下来实现具体的Service就OK了。
完整的代码我放到 GitHub 上了,有兴趣的小伙伴可以去看看。源码地址:github.com/xzhang76/XzhangIPCDemo
最后建议大家在不借助 AIDL 的情况下手写实现 Client 和 Server 进程的通信,加深对 Binder 通信过程的理解。

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP