Home  >  Article  >  类库下载  >  Android process communication mechanism AIDL

Android process communication mechanism AIDL

高洛峰
高洛峰Original
2016-10-31 11:32:301725browse

What is AIDL

AIDL stands for Android Interface Definition Language, which is Android Interface Description Language. It sounds very profound, but its essence is an auxiliary tool for generating inter-process communication interfaces. It exists in the form of an .aidl file. All the developer needs to do is to define the interface for inter-process communication in the file. When compiling, the IDE will generate a .java file that can be used by the project based on our .aidl interface file. , which is somewhat similar to what we call "syntactic sugar".

The syntax of AIDL is the same as that of java, but there are some slight differences in the package import. In Java, if two classes are in the same package, there is no need to import the package, but in AIDL, a package import declaration is required.

Detailed explanation of AIDL

Conceive a scenario: We have a library management system, which is implemented through CS mode. Specific management functions are implemented by the server process, and the client only needs to call the corresponding interface.

Then first define the ADIL interface of this management system.

We create a new aidl package in /rc. There are three files in the package: Book.java, Book.aidl, and IBookManager.aidl.

package com.example.aidl book

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

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

  ...
}

package com.example.aidl;

Parcelable Book;

package com.example.aidl;

import com.example.aidl.Book;

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

The following are descriptions of these three files:

Book.java is the entity class we defined, which implements the Parcelable interface, so Book Classes can be transferred between processes.

Book.aidl is the declaration of this entity class in AIDL.

IBookManager is the interface for communication between the server and the client. (Note that in addition to the basic types in the AIDL interface, the direction must be added before the parameters, in represents input parameters, out represents output parameters, and inout represents input and output parameters)

After the compiler is compiled, android studio will be used for our project A .java file is automatically generated. This file contains three classes. These three classes are IBookManager, Stub and Proxy. These three classes are all static types. We can completely separate them. The three classes are defined as follows:

IBookManager

public interface IBookManager extends android.os.IInterface {

    public void addBook(net.bingyan.library.Book book) throws android.os.RemoteException;

    public java.util.List<net.bingyan.library.Book> getBookList() throws android.os.RemoteException;
}

Stub

public static abstract class Stub extends android.os.Binder implements net.bingyan.library.IBookManager {
        private static final java.lang.String DESCRIPTOR = "net.bingyan.library.IBookManager";

        static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an net.bingyan.library.IBookManager interface,
         * generating a proxy if needed.  http://www.manongjc.com/article/1501.html
         */
        public static net.bingyan.library.IBookManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof net.bingyan.library.IBookManager))) {
                return ((net.bingyan.library.IBookManager) iin);
            }
            return new net.bingyan.library.IBookManager.Stub.Proxy(obj);
        }

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

        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_addBook: {
                    data.enforceInterface(DESCRIPTOR);
                    net.bingyan.library.Book _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = net.bingyan.library.Book.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addBook(_arg0);
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_getBookList: {
                    data.enforceInterface(DESCRIPTOR);
                    java.util.List<net.bingyan.library.Book> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }
}

Proxy

private static class Proxy implements net.bingyan.library.IBookManager {
            private android.os.IBinder mRemote;

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

            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            /**
             * Demonstrates some basic types that you can use as parameters
             * and return values in AIDL.   http://www.manongjc.com/article/1500.html
             */
            @Override
            public void addBook(net.bingyan.library.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);
                    }
                    mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

            @Override
            public java.util.List<net.bingyan.library.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<net.bingyan.library.Book> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(net.bingyan.library.Book.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
       }

The description of the three generated classes is as follows:

IBookManager This class is the interface we defined. Android studio adds a parent class to it and lets it inherit from The android.os.interface interface has only one method IBinder asBinder(), so there are three implemented methods in IBookManager. It is the window for communication between the server process and the client process.

Stub is an abstract class. This class inherits from the android.os.Binder class and implements the IBookManager interface. In Stub, the asBinder() interface method has been implemented, and there are two AIDL interface methods we defined for subclasses that inherit it to implement. It is used on the server side, so the server needs to implement these two methods.

Proxy, as the name suggests, is a proxy class. It is a proxy for the server on the client. It also implements the IBookManager interface and implements all methods in IBookManager. It is used on the client side and is the proxy of the server on the client side.

Now we analyze these three classes one by one:

IBookManager There is nothing to say about this class. It simply inherits the asInterface interface, and its function is to convert IBookManager into IBinder.

Proxy This class has been mentioned above. It is an encapsulation class of the inter-process communication mechanism. Its internal implementation mechanism is Binder, which we can easily see through the construction method. Its constructor accepts an IBinder type parameter named remote, which obviously represents the server. Let’s take a look at the methods addBook() and getBookList() in this class:

@Override
public void addBook(net.bingyan.library.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);
            }
            mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
            _reply.readException();
       } finally {
            _reply.recycle();
            _data.recycle();
       }
}
@Override /* http://www.manongjc.com/article/1547.html */
public java.util.List<net.bingyan.library.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<net.bingyan.library.Book> _result;
       try {
             _data.writeInterfaceToken(DESCRIPTOR);
             mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
             _reply.readException();
             _result = _reply.createTypedArrayList(net.bingyan.library.Book.CREATOR);
       } finally {
            _reply.recycle();
            _data.recycle();
       }
       return _result;
}

They are automatically implemented by the compiler. These two methods have many similarities, which can be revealed here: these two methods are the client The process calls the window of the server process. At the beginning of these two methods, they both define two Parcel (Chinese translation: package) objects. The Parcel class looks familiar to us. Yes, the parameters of writeToParcel() in the Book class and createFromParcel() in the CREATOR are of the Parcel type. The documentation on this class is explained as follows:

Container for a message (data and object references) that can be sent through an IBinder. A Parcel can contain both flattened data that will be unflattened on the other side of the IPC (using the various methods here for writing specific types, or the general {@link Parcelable} interface), and references to live {@link IBinder} objects that will result in the other side receiving a proxy IBinder connected with the original IBinder in the Parcel.

Translation: Proxy is a proxy that can pass IBinder A container for messaging. A Parcel can contain serializable data, which will be deserialized at the other end of the IPC; it can also contain a reference to an IBinder object, which will cause the other end to receive a proxy object of type IBinder. This proxy object Connected to the original IBinder object in Parcel.

The following is a visual explanation with a picture:

Android process communication mechanism AIDL

As shown in the picture, we can intuitively see that the server uses Parcel as the data package and relies on Binder to communicate with the client. The data package is the object after serialization.

As mentioned above, these two methods define two Parcel objects, called _data and _reply respectively. Figuratively speaking, from the perspective of the client, _data is the data package sent by the client to the server, and _reply is the server The data package sent to the client.

After that, we started to use these two objects to communicate with the server. We can observe that both methods have such a method call mRemote.transact(), which has four parameters. The first parameter is We will talk about the meaning later. The second parameter _data is responsible for sending data packages such as the parameters of interface methods to the server, and the third parameter _reply is responsible for receiving data packages such as the return values ​​of interface methods from the server. This line of code only has a simple method call, but it is the core part of AIDL communication. It actually makes a remote method call (the client calls the server Stub method with the same name through the interface method exposed by the local proxy Proxy), so you can think of It is a time consuming operation.

In our example:

void addBook(Book book) needs to use _data to send the parameter Book:book to the server. The way to send it is to package Book into _data through the writeToParcel(Parcel out) method it implements, as As you can imagine, _data is actually the parameter out. Do you remember the implementation of this method in Book? We package the fields of Book into Parcel one by one.

List getBookList() needs to use _reply to receive the return value List:books from the server. The method is to pass the static field CREATOR in Book as a parameter into the createTypedArrayList() method of _reply. Remember that in Book The CREATOR? At that time, were you curious about how to use this static field? Now everything is clear, we need to rely on this object (we can call it "deserializer" for ease of understanding) to deserialize the data on the server side and regenerate a serializable object or object array. Obviously CREATOR generated List:books with the help of _reply.

Of course, _data and _reply in these two methods not only pass the object, but also pass some verification information. We don’t need to delve into this, but it should be noted that the order of Parcel packaging and unpacking must strictly correspond. For example, if the first packed value is int:i, then the first unpacked value should also be this integer value. That is, if the first call when packaging is Parcel.writeInt(int), the first call when unpacking should be Parcel.readInt().

At this point, the client’s Proxy has been explained. Let’s take a look at the server’s Stub.

Stub implements one of the methods of IBookManager. This is very simple. It simply returns itself, because Stub itself inherits from Binder, and Binder inherits from IBinder, so there is no problem. You may ask: Are there two methods that have not been implemented? These two methods are the interface methods we defined, and they are left to the server process to implement. In other words, we will need to define a Stub implementer in the server process. The following is an analysis of two important methods in Stub:

IBookManager asInterface(IBinder obj)

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

这个方法的作用是将 Stub 类转换成 IBookManager 这个接口,方法中有个判断:如果我们的服务端进程和客户端进程是同一进程,那么就直接将 Stub 类通过类型转换转成 IBookManager;如果不是同一进程,那么就通过代理类 Proxy 将 Stub 转换成 IBookManager。为什么这么做,我们知道如果服务端进程和客户端进程不是同一进程,那么它们的内存就不能共享,就不能通过一般的方式进行通信,但是我们如果自己去实现进程间通信方式,对于普通开发者来说成本太大,因此编译器帮我们生成了一个封装了了进程间通信的工具,也就是这个 Proxy,这个类对底层的进程通信机制进行了封装只同时暴露出接口方法,客户端只需要调用这两个方法实现进程间通信(其实就是方法的远程调用)而不需要了解其中的细节。

有了这个方法,我们在客户端可以借助其将一个 IBinder 类型的变量转换成我们定义的接口 IBookManager,它的使用场景我们会在后面的实例中进行讲解。

onTransact(int code, Parcel data, Parcel reply, int flags)

@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
           switch (code) {
               case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
               }
               case TRANSACTION_addBook: {
                    data.enforceInterface(DESCRIPTOR);
                    net.bingyan.library.Book _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = net.bingyan.library.Book.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addBook(_arg0);
                    reply.writeNoException();
                    return true; /*  http://www.manongjc.com/article/1499.html */
               }
               case TRANSACTION_getBookList: {
                    data.enforceInterface(DESCRIPTOR);
                    java.util.List<net.bingyan.library.Book> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
           }
           return super.onTransact(code, data, reply, flags);
}

这个方法我们是不是也很熟悉呢?我们在 Proxy 中也看到一个类似得方法 transact(int, Parcel, Parcel, int),它们的参数一样,而且它们都是 Binder 中的方法,那么它们有什么联系呢?

前面说了,transact() 执行了一个远程调用,如果说 transact() 是远程调用的发起,那么 onTransact() 就是远程调用的响应。真实过程是客户端发器远程方法调用,android 系统通过底层代码对这个调用进行响应和处理,之后回调服务端的 onTransact() 方法,从数据包裹中取出方法参数,交给服务端实现的同名方法调用,最后将返回值打包返回给客户端。

需要注意的是 onTransact() 是在服务端进程的 Binder 线程池中进行的,这就意味着如果我们的要在 onTransact() 方法的中更新 UI,就必须借助 Handler。

这两个方法的第一个参数的含义是 AIDL 接口方法的标识码,在 Stub 中,定义了两个常量作为这两个方法的标示:

static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);   static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);

如果 code == TRANSACTION_addBook,那么说明客户端调用的是 addBook();如果 code == TRANSACTION_getBookList,那么客户端调用的是 getBookList(),然后交由相应的服务端方法处理。 用一张图来表示整个通信过程:

Android process communication mechanism AIDL

了解了 AIDL 的整个过程,接下来就是 AIDL 在安卓程序中的应用了。

 

AIDL 的使用

相信大家应该都和清楚 Service 的使用了吧,Service 虽然称作“服务”,并且运行于后台,但是它们默认还是运行在默认进程的主线程中。其实让 Service 运行在默认进程中,有点大材小用了。android 的很多系统服务都运行于单独的进程中,供其他应用调用,比如窗口管理服务。这样做的好处是可以多个应用共享同一个服务,节约了资源,也便于集中管理各个客户端,要注意问题的就是线程安全问题。

那么接下来我们就用 AIDL 实现一个简单的 CS 架构的图书管理系统。

首先我们定义服务端:

BookManagerService

public class BookManagerService extends Service {

    private final List<Book> mLibrary = new ArrayList<>();

    private IBookManager mBookManager = new IBookManager.Stub() {
        @Override
        public void addBook(Book book) throws RemoteException {
            synchronized (mLibrary) {
                mLibrary.add(book);
                Log.d("BookManagerService", "now our library has " + mLibrary.size() + " books");
            }

        }

        @Override
        public List<Book> getBookList() throws RemoteException {
            return mLibrary;
        }
    };
 
    @Override  /*  http://www.manongjc.com/article/1496.html  */
    public IBinder onBind(Intent intent) {
        return mBookManager.asBinder();
    }

}

     android:name=".BookManagerService"/>

服务端我们定义了 BookManagerService 这个类,在它里面我们创建了服务端的 Stub 对象,并且实现了需要实现的两个 AIDL 接口方法来定义服务端的图书管理策略。在 onBind() 方法中我们将 IBookManager 对象作为 IBinder 返回。我们知道,当我们绑定一个服务时,系统会调用 onBinder() 方法得到服务端的 IBinder 对象,并将其转换成客户端的 IBinder 对象传给客户端,虽然服务端的 IBinder 和 客户端的 IBinder 是两个 IBinder 对象,但他们在底层都是同一个对象。我们在 xml 中注册 Service 时给它指定了进程名,这样 Service 就能运行在单独的进程中了。

接下来看看客户端的实现:

Client

public class Client extends AppCompatActivity {

    private TextView textView;

    private IBookManager bookManager;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.library_book_manager_system_client);

        Intent i  = new Intent(Client.this, BookManagerService.class);
        bindService(i, conn, BIND_AUTO_CREATE);

        Button addABook = (Button) findViewById(R.id.button);
        addABook.setOnClickListener(v -> {
            if (bookManager == null) return;
            try {
                bookManager.addBook(new Book(0, "book"));
                textView.setText(getString(R.string.book_management_system_book_count, String.valueOf(bookManager.getBookList().size())));
            } catch (RemoteException e) {
                e.printStackTrace();
            }

        });

        textView = (TextView) findViewById(R.id.textView);
    }

    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d("Client -->", service.toString());

            bookManager = IBookManager.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d("Client", name.toString());
        }
    };

}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:weightSum="1"
    android:gravity="center">

    <Button
        android:text="http://www.manongjc.com/article/1495.html"
        android:layout_width="111dp"
        android:layout_height="wrap_content"
        android:id="@+id/button" />

    <TextView
        android:layout_marginTop="10dp"
        android:text="@string/book_management_system_book_count"
        android:layout_width="231dp"
        android:gravity="center"
        android:layout_height="wrap_content"
        android:id="@+id/textView" />
</LinearLayout>

我们的客户端就是一个 Activity,onCreate() 中进行了服务的绑定,bindService() 方法中有一参数 ServiceConnection:conn,因为绑定服务是异步进行的,这个参数的作用就是绑定服务成功后回调的接口,它有两个回调方法:一个是连接服务成功后回调,另一个在与服务端断开连接后回调。我们现在关心的主要是 onServiceConnected() 方法,在这里我们只做了一件事:将服务端转换过来的 IBinder 对象转换成 AIDL 接口,我们定义 IBookManager:bookManager 字段来保持对其的引用。这样的话,我们就可以通过这个 bookManager 来进行方法的远程调用。我们给客户端的 Button 注册事件:每一次点击都会向服务端增加一本书,并且将图书馆现有的图书数量显示出来。

现在我们看看程序的运行效果:

Android process communication mechanism AIDL

每当我们点击按钮,我们就成功的向服务端添加了一本书,说明我们通过 AIDL 跨进程通信成功了。

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn