欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

笔记 - 安卓中的Binder

程序员文章站 2022-12-20 14:39:55
引言学习记录,其实是反复看了好几次Binder了,做个笔记记录一下,学习的内容是在《Android 开发艺术探索》中的梗概Binder是安卓的一种跨进程通信(IPC)的方式。不管是在安卓系统中,还是在日常开发中都可以存在着Binder。试想一下,两个人在不同的地方要进行通信,那么他们就可以用打电话的方式。那两个进程需要通信,那么它们就可以使用Binder来进行。系统中的BinderServiceManager与ActivityManager、ServiceManager与WindowManage...

引言

学习记录,其实是反复看了好几次Binder了,做个笔记记录一下,学习的内容是在《Android 开发艺术探索》中的

梗概

Binder是安卓的一种跨进程通信(IPC)的方式。不管是在安卓系统中,还是在日常开发中都可以存在着Binder。试想一下,两个人在不同的地方要进行通信,那么他们就可以用打电话的方式。那两个进程需要通信,那么它们就可以使用Binder来进行。

  • 系统中的Binder
    ServiceManager与ActivityManager、ServiceManager与WindowManager等等
  • 应用中的Binder
    客户端和服务端进行通信,这里的客户端与服务端是相对的,谁都可以是客户端,谁都可以是服务端。一端既可以是客户端也可以是另一个客户端的服务端。客户端目的是获取服务端的数据或者其提供的服务。

AIDL

aidl是什么?aidl与binder又有什么关系。aidl是一种文件,通过编写aidl文件,IDE会自动为我们生成Binder的代码。这就类似于我们创建menu,我们只是在menu文件夹下敲一敲xml格式的文件,然后IDE就会给我们生成响应的Java代码。

通过aidl来创建binder

  1. 第一步–创建Book.java
    这是一个图书信息的类,并且实现Parcelable接口来进行序列化。
package com.example.aidltest;

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

public class Book implements Parcelable {
    public int bookId;
    public String bookName;

    public Book(int bookId, String bookName) {
        this.bookId = bookId;
        this.bookName = bookName;
    }

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

    @Override
    public void writeToParcel(Parcel out, int flags) {
        out.writeInt(bookId);
        out.writeString(bookName);
    }

    public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>(){

        @Override
        public Book createFromParcel(Parcel in) {
            return new Book(in);
        }

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

    private Book(Parcel in){
        bookId = in.readInt();
        bookName = in.readString();
    }

}
  1. 创建aidl文件
  • Book.aidl
    这里声明来Book.java这个类
package com.example.aidltest;

parcelable Book;
  • IBookManager.aidl
    这里有两个方法,一个是getBookList,用于远程从服务端获取图书列表;另一个是addBook,用于远程向列表中添加书本
package com.example.aidltest;

import com.example.aidltest.Book;

interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
}

整个项目结构

我使用的是Android Studio的开发环境
笔记 - 安卓中的Binder

自动生成的Binder代码

生成的Binder类位置在根目录下,位置如下所示
笔记 - 安卓中的Binder

分析IBookManager类的各部分实现

直接从最外层来看有3个部分:Default类、Stub类、方法声明。关键代码其实都在Stub类中
笔记 - 安卓中的Binder

方法声明

整个IBookManager是一个接口,同时也继承了IInterface这个接口,这里声明了两个我们在aidl文件中定义的方法getBookList和addBook,如下所示
笔记 - 安卓中的Binder

Default类

笔记 - 安卓中的Binder
自动生成的注释也有说明,这个类是接口IBookManger的默认实现,里面默认实现了我们在aidl中声明的两个方法getBookList和addBook,这里默认实现基本是空的,如下。我们就跳过
笔记 - 安卓中的Binder

Stub类

最重要的类,这里分为两块,一块是Stub自己的逻辑,一块是Stub类中的内部类Proxy类。

Stub类本身

  • DESCRIPTOR标识
    它是用于唯一标识本IBookManager类的,相当于身份证,一般来说会默认使用当前这个Binder的类名,如下所示
    笔记 - 安卓中的Binder
  • 整形ID标识
    这两个整形是用来唯一标识我们接口中的方法的,通信时可以知道客户端请求的是哪一个方法,如下所示
static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
  • asInterface()方法
    这个方法的注释是将服务端的Binder对象转换为客户端所需的AIDL接口类型的对象。如果客户端与服务端在同一个进程,则获得的是服务端对象的Stub本身,若不在同一个进程,则会获得一个代理的Stub对象。
public static com.example.aidltest.IBookManager asInterface(android.os.IBinder obj)
    {
      if ((obj==null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin!=null)&&(iin instanceof com.example.aidltest.IBookManager))) {
        return ((com.example.aidltest.IBookManager)iin);
      }
      return new com.example.aidltest.IBookManager.Stub.Proxy(obj);
    }

从代码逻辑来看,伪代码如下

  1. 判空
  2. 尝试获取Binder对象在本地进程的实现
  3. 如果获取到的非空且为IBookManager的实现类,则将获取到的对象转化为IBookManager并返回
  4. 若本地没有实现,则返回其代理对象Proxy,就是下面的内部代理类Proxy
  • asBinder()方法
    用于返回当前的Binder对象,实现如下
 @Override public android.os.IBinder asBinder()
    {
      return this;
    }
  • onTransact()方法
    这个方法会运行在服务端的Binder线程池中,客户端发起请求时,服务端可以通过code来辨别服务端想要请求什么服务,然后再执行不同的逻辑。最后返回true代表客户端请求成功了,false代表客户端请求失败。如下
 @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
    {
      java.lang.String descriptor = DESCRIPTOR;
      switch (code)
      {
        case INTERFACE_TRANSACTION:
        {
          reply.writeString(descriptor);
          return true;
        }
        case TRANSACTION_getBookList:
        {
          data.enforceInterface(descriptor);
          java.util.List<com.example.aidltest.Book> _result = this.getBookList();
          reply.writeNoException();
          reply.writeTypedList(_result);
          return true;
        }
        case TRANSACTION_addBook:
        {
          data.enforceInterface(descriptor);
          com.example.aidltest.Book _arg0;
          if ((0!=data.readInt())) {
            _arg0 = com.example.aidltest.Book.CREATOR.createFromParcel(data);
          }
          else {
            _arg0 = null;
          }
          this.addBook(_arg0);
          reply.writeNoException();
          return true;
        }
        default:
        {
          return super.onTransact(code, data, reply, flags);
        }
      }
    }

根据code执行不同的逻辑,这里的第2、3个代码框的code就是之前定义的整形ID标识
getBookList时会获取到图书列表_result然后写入reply中,再返回true代表获取了请求。
addBook时会创建一个Book实例,将参数的data转换后再调用addBook()方法添加图书,最后返回true代表获取了请求

内部代理类Proxy

上面我们看到,如果客户端与服务端不在一个进程,那么就会调用到Proxy,这里主要看两个方法一个是getBookList(),另一个是addBook(),就是我们声明的并且一直提到那两个方法。

  • getBookList()方法
@Override public java.util.List<com.example.aidltest.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<com.example.aidltest.Book> _result;
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          boolean _status = mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            return getDefaultImpl().getBookList();
          }
          _reply.readException();
          _result = _reply.createTypedArrayList(com.example.aidltest.Book.CREATOR);
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
        return _result;
      }

先创建Parcel类型的_data和_reply,再List类型的结果_result,如果有参数,则将参数写入_data,再调用mRemote的transact方法,传入请求code,_data信息,_reply。这样就发起了RPC(远程过程调用),然后当前线程挂起,服务端的onTransact就会被调用,直到放回了结果,这样当前线程就会继续执行,如果返回值为true,表示服务端接收了我们的请求,就从_reply中取出结果并构造到_result中,最后对Parcel对象进行回收并返回_result。

  • addBook()方法
@Override public void addBook(com.example.aidltest.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);
          }
          boolean _status = mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            getDefaultImpl().addBook(book);
            return;
          }
          _reply.readException();
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
      }

这个方法过程与getBookList()方法的过程类似,这里就不再展开

这里我打一个可能不太恰当的比喻

小时候你妈妈要炒菜,发现家里没有菜了,拿了个菜篮让你去隔壁邻居家借点菜回来炒。自己的家跟邻居的家就是两个进程,妈妈要炒菜,先在家里自己找,如果有,那就直接用,如果没有就有你这个小朋友做代理去邻居家要,并且拿个菜篮子让你把菜放里面包装好带回家里来。在作为小代理的你还没回来时,妈妈没有菜炒,只能一直在家等你回来,直到你回来了,妈妈才继续干接下来的事。

注意事项

  • 因为在客户端发起请求后,会一直等到服务端返回消息,所以如果请求的远程方法很耗时,那么就不可以在UI线程中发起这种远程请求,因为会这样会造成ANR。
  • 服务端的Binder方法已经是在Binder线程池中运行的,所以Binder方法应该使用同步的方式去实现(因为本来就在线程中运行)

我把相关的代码放在github上了,如果有需要的话,大家自取

下一篇是Binder在两个进程的使用

本文地址:https://blog.csdn.net/weixin_42530254/article/details/107409520

相关标签: 笔记