Binder的使用及手动实现AIDL

Binder是Android一个十分重要进程间通信机制,Android系统的很多核心服务AMS,PMS,WMS的使用都是建立在Binder之上的。在对Activity启动流程,App安装流程源码梳理过程中,Binder也是我们经常碰到的。因此,在我们阅读这些源码之前,要弄清Binder是如何使用的。

Binder的使用

我们知道,Binder是基于C/S结构的,就像http接口请求调用。
这里我们想想调用接口时,客户端和服务端做了什么:

  • 客户端封装数据,指定某个接口名称,发送数据给服务端
  • 服务端等待接收到某个接口请求时,解析请求参数,查询操作相关服务(数据库等)数据,然后封装数据,返回数据
  • 客户端返回数据,封装数据(将流封装成对象)

客户端实现

Binder和以上流程类似,我们使用AS分别创建Client,Server两个项目。我们模拟一个考试成绩查询场景,即通过学生名称在服务端查询该学生的成绩。
那么在客户端,就有了如下实现:

//学生
public class Student implements Parcelable {
    public String name;
    ...
}
public class ScoreProxy {
    private IBinder mRemote;
    private static final int TRANSACTION_query = 1;

    public ScoreProxy(IBinder mRemote) {//通过IBinder对象想服务端发送数据
        this.mRemote = mRemote;
    }

    public int query(Student student) {
        Parcel _data = android.os.Parcel.obtain();
        Parcel _reply = android.os.Parcel.obtain();
        int result = -1;
        try {
            _data.writeInterfaceToken("ScoreQuery");
            _data.writeParcelable(student, 0);
            mRemote.transact(TRANSACTION_query, _data, _reply, 0);
            result = _reply.readInt();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            _reply.recycle();
            _data.recycle();
        }
        return result;
    }
}

query方法中,我们通过·_data·传入Student参数,通过_reply接收查询结果,通过IBiner对象发送数据。

服务端实现

在服务端,同样创建Student类(包名相同),然后新建一个ScoreQueryService服务,并通过ScoreStubBinder类用于接收处理客户端传递端数据。

public class ScoreQueryService extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new ScoreStub();
    }

    private static class ScoreStub extends Binder {
        private static final int TRANSACTION_query = 1;
        private Map<String, Integer> scoreMap = new HashMap<>();//模拟数据查询

        public ScoreStub() {
            scoreMap.put("张三", 100);
            scoreMap.put("李四", 89);
            scoreMap.put("王五", 60);
        }

        @Override
        protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
            if (code == TRANSACTION_query) {
                data.enforceInterface("ScoreQuery");
                Student student = data.readParcelable(Student.class.getClassLoader());
                int score = query(student);
                Log.e("Server","query:"+student+",result:"+score);
                reply.writeInt(score);
                return true;
            }
            return super.onTransact(code, data, reply, flags);
        }

        private int query(Student s) {
            Integer score = scoreMap.get(s.getName());
            return score != null ? score : -1;
        }
    }
}

在清单文件中注册这个服务

<service
    android:name="com.iamyours.service.ScoreQueryService"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="com.iamyours.score" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</service>

服务调用

在安装完Serverapk后,在Client端调用成绩查询服务,如下

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.btn1).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                query();
            }
        });
        bindServices();
    }

    private ScoreProxy scoreProxy;

    private void bindServices() {
        Intent intent = new Intent();
        intent.setAction("com.iamyours.score");
        intent.setPackage("com.iamyours.server");//Server端applicationId
        bindService(intent, new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                Log.e("Client", "onServiceConnected");
                scoreProxy = new ScoreProxy(service);
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {
                Log.e("Client", "onServiceDisconnected:");

            }
        }, BIND_AUTO_CREATE);

    }

    private void query() {
        Student s = new Student("张三");
        int result = scoreProxy.query(s);
        Log.e("client", "result:" + result);
        s = new Student("李四");
        result = scoreProxy.query(s);
        Log.e("client", "result:" + result);
        s = new Student("马云");
        result = scoreProxy.query(s);
        Log.e("client", "result:" + result);
    }
}

最终调用结果如下:
服务端

com.iamyours.server E/Server: query:Student{name='张三'},result:100
com.iamyours.server E/Server: query:Student{name='李四'},result:89
com.iamyours.server E/Server: query:Student{name='马云'},result:-1

客户端

com.iamyours.client E/client: result:100
com.iamyours.client E/client: result:89
com.iamyours.client E/client: result:-1

至此我们简单通过Binder实现一个成绩查询服务。

使用APT实现AIDL

会看上面的代码,我们发现很多代码是耦合在一起的,ScoreProxyScoreStub有许多和业务无关的代码,如果一个功能,数据的发送接收处理会产生类似的模版代码。实际业务开发场景下,在客户端我们只需要定义接口方法,参数(就像Retrofit),并使用它。在服务端,我们应该根据接口实现对应的业务逻辑。在它们中间数据如何传输,如何处理却不是我们关心的。
因此,在使用时,只需要定义好接口,并且在服务端实现它即可。而中间的数据传输相关的代码是通用类似的,我们可以通过APT生成。
比如我们定义了一个ISayHello的接口如下,并用自定义注解@AIDL声明它:

@AIDL
public interface ISayHello {
    void sayHello();
    int sayHelloTo(String name);
    int query(Student s);
}

最终我们希望自动生成在客户端的ISayHelloProxy代理类以及服务端的实现类ISayHelloStub实现类,大概是这样的:

public abstract class ISayHelloStub extends Binder implements ISayHello {
  private static final String DESCRIPTOR = "com.iamyours.interfaces.ISayHello";

  private static final int TRANSACTION_sayHello = android.os.IBinder.FIRST_CALL_TRANSACTION + 0;

  private static final int TRANSACTION_sayHelloTo = android.os.IBinder.FIRST_CALL_TRANSACTION + 1;

  private static final int TRANSACTION_query = android.os.IBinder.FIRST_CALL_TRANSACTION + 2;

  public static ISayHello asInterface(IBinder iBinder) {
    return new Proxy(iBinder);
  }

  @Override
  public abstract void sayHello();

  @Override
  public abstract int sayHelloTo(String var0);

  @Override
  public abstract int query(Student var0);

  @Override
  protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws
      RemoteException {
    String descriptor = DESCRIPTOR;
    switch(code){
    case TRANSACTION_sayHello:{
    data.enforceInterface(descriptor);
    this.sayHello();
    reply.writeNoException();
    return true;
    }
    case TRANSACTION_sayHelloTo:{
    data.enforceInterface(descriptor);
    String _arg0  = data.readString();
    int _result = this.sayHelloTo(_arg0);
    reply.writeNoException();
    reply.writeInt(_result);
    return true;
    }
    case TRANSACTION_query:{
    data.enforceInterface(descriptor);
    com.iamyours.bean.Student _arg0 = data.readParcelable(com.iamyours.bean.Student.class.getClassLoader());
    int _result = this.query(_arg0);
    reply.writeNoException();
    reply.writeInt(_result);
    return true;
    }
    default: {
    return super.onTransact(code, data, reply, flags);
    }
    }
  }

  private static class Proxy implements ISayHello {
    private IBinder mRemote;

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

    @Override
    public void sayHello() {
      android.os.Parcel _data = android.os.Parcel.obtain();
      android.os.Parcel _reply = android.os.Parcel.obtain();
      try{
      _data.writeInterfaceToken(DESCRIPTOR);
      mRemote.transact(TRANSACTION_sayHello, _data, _reply, 0);
      _reply.readException();
      }catch(Exception e){e.printStackTrace();}finally{
      _reply.recycle();
      _data.recycle();
      }
    }

    @Override
    public int sayHelloTo(String var0) {
      int _result = 0;
      android.os.Parcel _data = android.os.Parcel.obtain();
      android.os.Parcel _reply = android.os.Parcel.obtain();
      try{
      _data.writeInterfaceToken(DESCRIPTOR);
      _data.writeString(var0);
      mRemote.transact(TRANSACTION_sayHelloTo, _data, _reply, 0);
      _reply.readException();
      _result = _reply.readInt();}catch(Exception e){e.printStackTrace();}finally{
      _reply.recycle();
      _data.recycle();
      }
      return _result;
    }

    @Override
    public int query(Student var0) {
      int _result = 0;
      android.os.Parcel _data = android.os.Parcel.obtain();
      android.os.Parcel _reply = android.os.Parcel.obtain();
      try{
      _data.writeInterfaceToken(DESCRIPTOR);
      _data.writeParcelable(var0,0);
      mRemote.transact(TRANSACTION_query, _data, _reply, 0);
      _reply.readException();
      _result = _reply.readInt();}catch(Exception e){e.printStackTrace();}finally{
      _reply.recycle();
      _data.recycle();
      }
      return _result;
    }
  }
}

可以看到有很大部分是数据传输相关的,我们只需通过APT生成即可,而Stub中的sayHello等方法通过抽象交给要实现最终业务的子类,从而实现代码解耦。
我们可以使用javapoet库生成代码,在AbstractProcessor子类中的process方法遍历找到AIDL注解,获取接口中的方法列表:

@Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        Map<Element, List<AidlMethod>> sources = new HashMap<>();
        for (Element e : roundEnvironment.getElementsAnnotatedWith(AIDL.class)) {
            List<AidlMethod> methods = new ArrayList<>();
            sources.put(e, methods);
            List<? extends Element> list = elementUtils.getAllMembers((TypeElement) e);
            for (Element ee : list) {
                boolean isAbstract = ee.getModifiers().contains(Modifier.ABSTRACT);
                if (isAbstract) {
                    methods.add(createAidlMethod(ee));
                }
            }
        }
        generateAIDL(sources);
        return true;
    }

其中AidlMethod包含了方法名,返回类型,参数列表

public class AidlMethod {
    public Class retCls;//PrimitiveType,基本类型,int,double等
    public ClassName retClsName;
    public List<ParamData> params;
    public int code;
    public String name;
}

然后通过createAidlMethod方法获取接口方法的数据

private AidlMethod createAidlMethod(Element e) {
        AidlMethod aMethod = new AidlMethod();
        Type.MethodType mt = (Type.MethodType) e.asType();
        Type retType = mt.getReturnType();
        aMethod.name = e.getSimpleName() + "";
        if (retType instanceof Type.JCPrimitiveType) {
            aMethod.retCls = getPrimitiveType(retType);
        } else {
            if (!"void".equals(retType + "")) {
                aMethod.retClsName = ClassName.bestGuess(retType + "");
            }
        }

        List<Type> types = mt.getParameterTypes();
        List<ParamData> params = new ArrayList<>();
        for (Type t : types) {
            ParamData p = new ParamData();
            if (t instanceof Type.JCPrimitiveType) {
                p.cls = getPrimitiveType(t);
            } else if (t instanceof Type.ClassType) {
                Type.ClassType ct = (Type.ClassType) t;
                String cname = ct + "";
                if ("java.lang.String".equals(cname) || isParcelable(ct)) {
                    p.clsName = ClassName.bestGuess(cname);
                } else {
                    throw new RuntimeException("--unSupport param:" + t + ",in method:" + mt + " source:" + e);
                }
            } else {
                throw new RuntimeException("unSupport param:" + t + ",in method:" + mt + " source:" + e);
            }
            params.add(p);
        }
        aMethod.params = params;
        System.out.println(aMethod);
        return aMethod;
    }

最后在generateAIDL方法中生成StubProxy类代码(详细代码可以看这里)。

项目地址

https://github.com/iamyours/BinderLeaning

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