>  기사  >  类库下载  >  Android 프로세스 통신 메커니즘 AIDL

Android 프로세스 통신 메커니즘 AIDL

高洛峰
高洛峰원래의
2016-10-31 11:32:301726검색

AIDL이란

AIDL은 Android Interface Definition Language의 약자로 Android Interface Description Language입니다. 매우 심오하게 들리지만 그 본질은 프로세스 간 통신 인터페이스를 생성하기 위한 보조 도구입니다. 이는 .aidl 파일 형식으로 존재합니다. 개발자가 해야 할 일은 파일에 프로세스 간 통신을 위한 인터페이스를 정의하는 것뿐입니다. 컴파일할 때 IDE는 프로젝트 기반에서 사용할 수 있는 .java 파일을 생성합니다. .aidl 인터페이스 파일에서 이는 "구문적 설탕"이라고 부르는 것과 다소 유사합니다.

AIDL의 구문은 Java의 구문과 동일하지만 패키지 가져오기에는 약간의 차이가 있습니다. Java에서는 두 개의 클래스가 동일한 패키지에 있으면 패키지를 가져올 필요가 없지만 AIDL에서는 패키지 가져오기 선언을 해야 합니다.

AIDL에 대한 자세한 설명

시나리오를 생각해 보세요. CS 모드를 통해 구현되는 도서관 관리 시스템이 있습니다. 특정 관리 기능은 서버 프로세스에 의해 구현되며 클라이언트는 해당 인터페이스만 호출하면 됩니다.

그런 다음 먼저 이 관리 시스템의 ADIL 인터페이스를 정의합니다.

/rc에 새로운aidl 패키지를 생성합니다. 패키지에는 Book.java, Book.aidl 및 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);
}

이 세 파일은 아래에 설명되어 있습니다.

Book.java는 우리가 정의한 엔터티 클래스로, Book 클래스가 프로세스 간에 전송될 수 있도록 Parcelable 인터페이스를 구현합니다.

Book.aidl은 AIDL에서 이 엔터티 클래스를 선언한 것입니다.

IBookManager는 서버와 클라이언트 간의 통신을 위한 인터페이스입니다. (AIDL 인터페이스에서는 기본 유형 외에도 매개변수 앞에 방향을 추가해야 하며, in은 입력 매개변수, out은 출력 매개변수, inout은 입력 및 출력 매개변수를 나타냅니다.)

컴파일러 이후 Android studio 프로젝트에 대한 .java 파일이 자동으로 생성되었습니다. 이 파일에는 IBookManager, Stub 및 Proxy라는 세 가지 클래스가 포함되어 있습니다. 이 세 가지 클래스는 모두 정적 유형입니다. 클래스 정의는 다음과 같습니다.

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;
            }
       }

생성된 세 가지 클래스에 대한 설명은 다음과 같습니다.

IBookManager 이 클래스는 우리가 정의한 인터페이스입니다. Android 스튜디오는 여기에 상위 클래스를 추가하여 android.os.interface 인터페이스에서 상속받게 합니다. 이 인터페이스에는 IBinder asBinder() 메서드가 하나만 있으므로 IBookManager가 있습니다. 에는 세 가지 메소드가 구현되어 있습니다. 서버 프로세스와 클라이언트 프로세스 간의 통신을 위한 창입니다.

Stub은 추상 클래스입니다. 이 클래스는 android.os.Binder 클래스에서 상속되며 IBookManager 인터페이스를 구현합니다. Stub에는 asBinder() 인터페이스 메소드가 구현되었으며 이를 상속받아 구현하는 서브클래스에 대해 정의한 두 개의 AIDL 인터페이스 메소드가 있습니다. 서버 측에서 사용되기 때문에 서버는 이 두 가지 메소드를 구현해야 합니다.

Proxy는 이름에서 알 수 있듯이 클라이언트의 서버에 대한 프록시이며 IBookManager 인터페이스를 구현합니다. 클라이언트 측에서 사용되며 클라이언트 측 서버의 프록시입니다.

이제 이 세 가지 클래스를 하나씩 분석합니다.

IBookManager 클래스에 대해서는 별로 설명할 것이 없습니다. 이는 단순히 asInterface 인터페이스를 상속하며 해당 기능은 IBookManager를 IBinder로 변환하는 것입니다.

Proxy 클래스는 위에서 언급한 Inter-Process 통신 메커니즘의 Encapsulation 클래스로, 내부 구현 메커니즘은 Binder이며, 이는 구축 방법을 통해 쉽게 알 수 있습니다. 해당 생성자는 분명히 서버를 나타내는 원격이라는 IBinder 유형 매개변수를 허용합니다. 이 클래스의 addBook() 및 getBookList() 메소드를 살펴보겠습니다.

@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;
}

이 두 메소드 사이에는 많은 유사점이 있으며 여기서 확인할 수 있습니다. 이 두 가지 방법은 클라이언트 프로세스가 서버 프로세스를 호출하는 창입니다. 이 두 메서드의 시작 부분에서는 둘 다 두 개의 Parcel(중국어 번역: 패키지) 객체를 정의합니다. Parcel 클래스는 우리에게 친숙해 보입니다. Book 클래스의 writeToParcel() 및 CREATOR의 createFromParcel() 매개변수는 Parcel 유형입니다.

IBinder를 통해 전송될 수 있는 메시지(데이터 및 개체 참조)에 대한 컨테이너입니다. Parcel에는 IPC 반대편에서 평면화되지 않은 평면화된 데이터가 모두 포함될 수 있습니다(여기서는 특정 유형을 작성하기 위한 다양한 방법을 사용함). 또는 일반 {@link Parcelable} 인터페이스) 및 상대방이 Parcel의 원본 IBinder와 연결된 프록시 IBinder를 수신하게 되는 라이브 {@link IBinder} 개체에 대한 참조입니다.

번역: 프록시 IBinder를 통해 메시지를 전달할 수 있는 컨테이너입니다. Parcel은 IPC의 다른 쪽 끝에서 역직렬화되는 직렬화 가능한 데이터를 포함할 수 있습니다. 또한 IBinder 개체에 대한 참조를 포함할 수 있으며, 이로 인해 다른 쪽 끝은 IBinder 유형의 프록시 개체가 연결됩니다. Parcel의 원래 IBinder 객체.

직관적으로 설명하기 위해 다음 다이어그램을 사용합니다.

Android 프로세스 통신 메커니즘 AIDL

그림과 같이 서버가 Parcel을 데이터 패키지로 사용하는 것을 직관적으로 알 수 있습니다. 바인더와 클라이언트 측에 의존하여 통신합니다. 데이터 패키지는 직렬화 후의 개체입니다.

위에서 언급한 것처럼 이 두 메소드는 각각 _data 및 _reply라는 두 개의 Parcel 객체를 정의합니다. 비유적으로 말하면 클라이언트의 관점에서 _data는 클라이언트가 서버로 보내는 데이터 패키지이고, _reply는 데이터 패키지입니다. 서버에서 클라이언트로 전송됩니다.

예제에서:

void addBook(Book book)은 _data를 사용하여 Book:book 매개변수를 서버에 보내야 합니다. 이를 보내는 방법은 Book을 writeToParcel(을 통해 전달하는 것입니다. Parcel out) 메소드는 _data에 패키지되어 있습니다. 여러분이 상상할 수 있듯이 _data는 실제로 Out 매개변수입니다. Book에서 이 메소드를 구현한 것을 기억하시나요? 우리는 Book의 필드를 하나씩 Parcel로 포장합니다.

List

getBookList()는 서버에서 List

:books 반환 값을 수신하기 위해 _reply를 사용해야 합니다. 이 방법은 Book의 정적 필드 CREATOR를 _reply의 createTypedArrayList(에 매개 변수로 전달하는 것입니다. ) 방법, 책에 나오는 CREATOR를 아직도 기억하시나요? 당시 이 static field를 어떻게 활용하는지 궁금하셨나요? 이제 모든 것이 명확해졌습니다. 서버 측에서 데이터를 역직렬화하고 직렬화 가능한 개체 또는 개체 배열을 재생성하려면 이 개체(이해하기 쉽도록 "역직렬 변환기"라고 부를 수 있음)에 의존해야 합니다. 분명히 CREATOR는 _reply의 도움으로 List:books를 생성했습니다. 물론 이 두 메소드의 _data 및 _reply는 객체를 전달할 뿐만 아니라 일부 확인 정보도 전달합니다. 여기에 들어갈 필요는 없지만 소포 포장 순서와 포장 풀기 순서는 엄격하게 일치해야 합니다. 예를 들어, 첫 번째 팩된 값이 int:i인 경우 첫 번째 팩 해제된 값도 이 정수 값이어야 합니다. 즉, 패키징 시 첫 번째 호출이 Parcel.writeInt(int)라면 언패킹 시 첫 번째 호출은 Parcel.readInt()여야 합니다.

이제 클라이언트의 Proxy에 대해 설명했습니다. 서버의 Stub을 살펴보겠습니다.

Stub은 IBookManager의 메소드 중 하나를 구현합니다. 이는 매우 간단합니다. Stub 자체는 Binder에서 상속되고 Binder는 IBinder에서 상속되므로 문제가 없습니다. 당신은 질문할 수 있습니다: 구현되지 않은 두 가지 방법이 있습니까? 이 두 메소드는 우리가 정의한 인터페이스 메소드이며 구현은 서버 프로세스에 맡겨집니다. 즉, 서버 프로세스에서 Stub 구현자를 정의해야 합니다. 다음은 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 프로세스 통신 메커니즘 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 프로세스 통신 메커니즘 AIDL

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

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.