AIDL

选自https://www.cnblogs.com/tangZH/p/10775848.html

  AIDL是Android中的IPC(跨进程通信)的一种,使用AIDL是用于跨进程通信进行数据的传递。通过AIDL,可以在一个进程中获取另一个进程的数据和调用其暴露出来的方法,从而满足进程间通信的需求。

基本用法

支持的数据类型

  • 支持八种基本数据类型:byte、char、short、int、long、float、double、boolean;
  • String、CharSequence;
  • 实现了Parcelable接口的实体类;
  • List类型。List中承载的数据必须是AIDL支持的类型,或者是其它声明的AIDL对象;
  • Map类型。Map中承载的数据必须是AIDL支持的类型,或者是其它声明的AIDL对象;

AIDL文件类型

  AIDL文件可以分为两类。一类是用来声明实现了Parcelable接口的数据类型,以供其它AIDL文件使用那些非默认支持的数据类型。另一类是用来定义接口方法,声明要暴露哪些接口给客户端调用,定向Tag就是用来标注这些方法的参数值。

定向Tag表示在跨进程通信中数据的流向,用于标注方法的参数值,分为in、out、inout三种。其中in表示数据只能由客户端流向服务端;out表示数据只能由服务端流向客户端;inout表示数据可在服务端和客户端之间双向流通;此外,如果AIDL方法接口的参数值类型是:八种基本数据类型、String、CharSequence、其它AIDL文件定义的方法接口,那么这些参数值的定向Tag默认是且只能是in,所以除了这些类型外,其它参数值都需要明确标注使用哪种定向Tag。

  在AIDL文件中需要明确标明引用到的数据类型所在的包名,即使两个文件处在同一个包名下。

代码实现

  下面是实现两个app之间的跨进程通信的代码;

作为服务端

1 . 创建aidl文件Book.aidl

  创建了Book.aidl文件以后,并不会在java这个文件夹中看到,而是在与java文件夹同级中生成一个aidl文件夹,并且文件夹下级的文件夹名字与在哪里创建的Book.aidl文件夹名字一致。

  生成的Book.aidl文件如下:

// Book.aidl
package com.ex.myapplication.com.aidl;

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

interface Book {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,double aDouble, String aString);
}

2 . 创建一个实体类Book。注意Book类的包名必须与Book.aidl包名一样。但是生成的Book.java文件会在java文件夹下的创建Book类的文件夹下。

  Book.java必须继承Parcelable接口,Book.java的代码如下:

package com.ex.myapplication.com.aidl;

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

public class Book implements Parcelable {

    private String bookName;

    public Book(String name) {
        this.bookName = name;
    }

    public String getBookName() {
        return bookName;
    }

    public void setBookName(String bookName) {
        this.bookName = bookName;
    }

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


    public static final Creator<Book> CREATOR = new Creator<Book>() {
        @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(bookName);
    }

    public void readFromParcel(Parcel desc) {
        bookName = desc.readString();
    }

    @Override
    public String toString() {
        return "bookName = " + bookName;
    }
}

3 . 修改刚才生成的Book.aidl文件,将其声明为parcelable(小写的p)类型,并且需要注意的是,原先默认的interface接口需要去掉,否则会编译报错;
  将刚才生成的Book.aidl文件修改为以下:

// Book.aidl
package com.ex.myapplication.com.aidl;

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

parcelable Book;

4 . 创建将服务端暴露给客户端的接口BookManager.aidl文件。
  在与刚才创建Book.aidl文件的相同目录下创建aidl文件BookManager.aidl。

  BookManager.aidl代码如下:

// BookManager.aidl
package com.ex.myapplication.com.aidl;

import com.ex.myapplication.com.aidl.Book;

interface BookManager {
//    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
//            double aDouble, String aString);
    List<Book> getBookList();
    
    void addBook(inout Book book);
}

5 . 把包手动导进来;
  在Build菜单选项中使用make Project,便可以将刚才的BookManager.aidl文件编译成代码文件。

  BookManager.aidl文件会生成一个BookManager.java文件,这个BookManager.java才是真正需要用到的。

public interface BookManager extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.ex.myapplication.com.aidl.BookManager {
        private static final java.lang.String DESCRIPTOR = "com.ex.myapplication.com.aidl.BookManager";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an com.ex.myapplication.com.aidl.BookManager interface,
         * generating a proxy if needed.
         */
        public static com.ex.myapplication.com.aidl.BookManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.ex.myapplication.com.aidl.BookManager))) {
                return ((com.ex.myapplication.com.aidl.BookManager) iin);
            }
            return new com.ex.myapplication.com.aidl.BookManager.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_getBookList: {
                    data.enforceInterface(DESCRIPTOR);
                    java.util.List<com.ex.myapplication.com.aidl.Book> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_addBook: {
                    data.enforceInterface(DESCRIPTOR);
                    com.ex.myapplication.com.aidl.Book _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.ex.myapplication.com.aidl.Book.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addBook(_arg0);
                    reply.writeNoException();
                    if ((_arg0 != null)) {
                        reply.writeInt(1);
                        _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                    } else {
                        reply.writeInt(0);
                    }
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.ex.myapplication.com.aidl.BookManager {
            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.
             *///    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
//            double aDouble, String aString);
            @Override
            public java.util.List<com.ex.myapplication.com.aidl.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.ex.myapplication.com.aidl.Book> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.ex.myapplication.com.aidl.Book.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public void addBook(com.ex.myapplication.com.aidl.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();
                    if ((0 != _reply.readInt())) {
                        book.readFromParcel(_reply);
                    }
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }

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

    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     *///    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
//            double aDouble, String aString);
    public java.util.List<com.ex.myapplication.com.aidl.Book> getBookList() throws android.os.RemoteException;

    public void addBook(com.ex.myapplication.com.aidl.Book book) throws android.os.RemoteException;
}

  可以看到,生成的BookManager.java这个类继承自IInterface这个接口。这个类首先声明了两个方法:getBookList()和addBook()。显示就是我们在BookManager.aidl中声明的方法。同时,这个类中还声明了两个整型的id(TRANSACTION_getBookList和TRANSACTION_addBook)分别用于标识这两个方法,这两个id用于标识在transact过程中客户端所请求的到底是哪个方法。接着,声明了一个内部类Stub,这个Stub就是一个Binder类,当客户端和服务端都位于同一个进程时,方法调用不会走跨进程的transact过程;而当这两者位于不同进程时,方法调用需要走transact过程,这个逻辑由Stub的内部代理类Proxy来完成。这个类的核心实现时它的内部类Stub和Stub的内部代理类Proxy。

  下面是对这两个内部类的每个方法的介绍:

(1) . DESCRIPTOR
  Binder的唯一标识,一般用当前Binder的类名表示,比如本例中的代码如下:

private static final java.lang.String DESCRIPTOR = "com.ex.myapplication.com.aidl.BookManager";

(2) . asInterface(android.os.IBinder obj)
  用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象,这种转换过程是区分进程的,如果客户端和服务端位于同一进程,那么此方法返回的就是服务端的Stub对象本身,否则返回的是系统封装后的Stub.proxy对象。

(3) . asBinder()
  此方法用于返回当前Binder对象。

(4) . onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
  这个方法运行在服务端中的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。该方法的原型为public Boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)。服务端通过code可以确定客户端所请求的目标方法是什么,接着从data中取出目标方法所需的参数(如果目标方法有参数的话),然后执行目标方法。当目标方法执行完毕后,就向reply中写入返回值(如果目标方法有返回值的话),onTransact()方法的执行过程就是这样。需要注意的是,如果此方法返回false,那么客户端的请求会失败,因此我们可以利用这个特性来做权限验证,毕竟我们也不希望随便一个进程都能远程调用我们的服务。

(5) . Proxy#getBookList()
  这个方法运行在客户端,当客户端远程调用此方法时,它的内部实现是这样的:首先创建该方法所需要的输入型Parcel对象_data、输出型Parcel对象_reply和返回值List对象_resule;然后把该方法的参数信息写入_data中(如果有参数的话);接着调用transact()方法来发起RPC(远程过程调用)请求,同时当前线程挂起;然后服务端的onTransact()方法会被调用,直到RPC过程返回后,当前线程继续执行,并从_reply中取出RPC过程的返回结果;然后把_reply的返回值赋值给_result,最后返回_result中的数据。

(6) . Proxy#addBook(com.ex.myapplication.com.aidl.Book book)
  这个方法运行在客户端,它的执行过程和getBookList()是一样的。但是因为addBook()方法没有返回值,所以这个方法不需要从_reply中取出返回值。

  通过上面的分析,已经知道了Binder的工作机制,但是有两点需要说明一下:首先,当客户端发起远程请求时,由于当前线程会被挂起直至服务端进程返回数据,所以如果一个远程方法是很耗时的话,那么不能在UI线程中发起此远程请求;其次,由于服务端的Binder方法运行在Binder的线程池中,所以Binder方法不管 是否耗时都应该采用同步的方式去实现,因为它已经运行在一个线程中了。

  Binder的工作机制,如下图:

  我们可以不提供AIDL文件即可实现Binder,之所以提供AIDL文件,是为了方便系统为我们生成代码。

  Binder还有两个重要的方法linkToDeath()和unlinkToDeath()。Binder运行在服务端进程,如果服务端进程由于某种原因异常终止,这个时候我们到服务端的Binder连接断裂(称之为Binder死亡),会导致我们的远程调用失败。主要是如果我们不知道Binder连接已经断裂,那么客户端的功能就会受到影响。为了解决这个问题,Binder中提供了两个配对的方法linkToDeath()和unlinkToDeath(),通过linkToDeath()我们可以给Binder设置一个死亡代理,当Binder死亡时,我们就会收到通知,这个时候我们就可以重新发起连接请求从而恢复连接。

  首先声明一个DeathRecipient对象。DeathRecipient是一个接口,其内部只有一个方法bindDied(),我们需要实现这个方法,当Binder死亡的时候,系统就会回调binderDied()方法,然后我们就可以移除之前绑定的binder代理并重新绑定远程服务。

//这部分代码写在客户端LaunchActivity.java中;
IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
    @Override
    public void binderDied() {
        if(bookManager == null){
            Log.e(TAG,"bookManager == null...");
            return;
        }
        bookManager.asBinder().unlinkToDeath(mDeathRecipient,0);
        bookManager = null;
        //重新绑定远程Service;

        Intent intent = new Intent(ACTION2);
        // 注意在 Android 5.0以后,不能通过隐式 Intent 启动 service,必须制定包名;
        intent.setPackage("com.ex.app");
        bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
    }
};

  其次,在客户端绑定远程服务成功后,给binder设置死亡代理:

//这部分代码在客户端的ServiceConnection的onServiceConnected()方法中;
public void onServiceConnected(ComponentName name, IBinder service) {
    Log.e(TAG, "onServiceConnected...");
    bookManager = BookManager.Stub.asInterface(service);
    //客户端绑定远程服务成功后,给binder设置;
    try {
        Log.e(TAG,"service.linkToDeath...");
        service.linkToDeath(mDeathRecipient,0);
    } catch (RemoteException e) {
        Log.e(TAG,"service.linkToDeath  RemoteException...");
        e.printStackTrace();
    }
}

  其中,linkToDeath()的第二个参数是一个标记位,我们直接设为0即可。经过上面两个步骤,就给我们的Binder设置了死亡代理。当Binder死亡的时候,我们就可以收到通知了。另外通过Binder的isBinderAlive()方法也可以判断Binder是否死亡。

6 . 创建Service,供客户端绑定;
  创建一个BookService的Service文件,这个文件不需要限定在某个文件夹中。代码如下:

package com.ex.myapplication.service;

import android.app.Service;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;

import com.ex.myapplication.com.aidl.Book;
import com.ex.myapplication.com.aidl.BookManager;

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

public class BookService extends Service {

    private final String TAG = BookService.class.getSimpleName();
    private List<Book> list;

    public BookService() {
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.e(TAG, "onCreate...");
        //默认先有五本书;
        list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            Book book = new Book("第" + i + "本书");
            list.add(book);
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.e(TAG, "onBind...");
        return bookManager;
    }

    private BookManager.Stub bookManager = new BookManager.Stub() {
        //获取书的列表;
        @Override
        public List<Book> getBookList() throws RemoteException {
            Log.e(TAG, "getBookList...");
            return list;
        }
        
        //添加书;
        @Override
        public void addBook(Book book) throws RemoteException {
            Log.e(TAG, "addBook...");
            if (book != null) {
                list.add(book);
            }
            Log.e(TAG, book.toString());
        }
    };

    @Override
    public void unbindService(ServiceConnection conn) {
        super.unbindService(conn);
        Log.e(TAG, "unbindService...");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.e(TAG, "onDestroy...");
    }

}

  在BookService文件中,onBind()方法返回的是BookManager.Stub对象,实现里面的两个方法,客户端拿到这个对象后就可以与服务端进行数据通信了。

7 . 在AndroidManifest.xml文件中修改BookService的service标签;

<service
    android:name=".service.BookService"
    android:enabled="true"
    android:exported="true"
    android:process=":remote">
    <intent-filter>
        <action android:name="android.intent.action.BookService"/>
    </intent-filter>
</service>

作为客户端

1 . 将aidl文件与Book实体类拷贝到客户端app的工程中。注意它们的目录需要和在服务端app中一样。我们就将服务端app中的aidl整个文件夹复制,然后粘贴到客户端app与java文件夹同级。Book这个实体类也需要放在与服务端app一样的文件夹路径中,这个在实际项目中,我们会让服务端app与客户端app都依赖同一个module,然后把数据实体类写在那个module中,否则,我们需要把服务端app与客户端app的文件夹路径写成一样的(实际中不会这样来做,我在这个demo中为了偷懒不去写一个module,就这样子来操作了)。

2 . 编写代码,与服务端进行连接;

package com.ex.myapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

import com.ex.myapplication.com.aidl.Book;
import com.ex.myapplication.com.aidl.BookManager;

import java.util.List;

public class LaunchActivity extends AppCompatActivity {

    private final String TAG = LaunchActivity.class.getSimpleName();

    private TextView textView1;
    private TextView textView2;
    private TextView textView3;
    //服务端app的那个BookService的intent-filter;
    private final String ACTION2 = "android.intent.action.BookService";

    private BookManager bookManager;

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

        textView1 = findViewById(R.id.textView1_tv);
        textView2 = findViewById(R.id.textView2_tv);
        textView3 = findViewById(R.id.textView3_tv);

        textView1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e(TAG, "textView1 onClick...");
                Intent intent = new Intent(ACTION2);
                intent.setPackage("com.ex.app");
                bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
            }
        });

        textView2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e(TAG, "textView2 onClick...");
                if (bookManager != null) {
                    try {
                        List<Book> list = bookManager.getBookList();
                        Log.e(TAG, "getBookList:" + list);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                } else {
                    Log.e(TAG, "bookManager is null...");
                }
            }
        });

        textView3.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e(TAG, "textView3 onClick...");
                Book book = new Book("添加的书");
                try {
                    if (bookManager != null) {
                        Log.e(TAG, "addBook...");
                        bookManager.addBook(book);
                    } else {
                        Log.e(TAG, "bookManager is null ---");
                    }
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.e(TAG, "onServiceConnected...");
            bookManager = BookManager.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.e(TAG, "onServiceDisconnected...");
            bookManager = null;
        }
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(serviceConnection);
    }
}

  在这个Activity中有三个TextView,其中textView1用于绑定服务端,textView2用于获取服务端书籍列表,textView3用于向服务端书籍列表中添加书。

  注意,我写的demo是在客户端app中使用textView1绑定服务端的Service,如果我们手动把服务端app从后台列表中清除了,那么客户端app也就没法与服务端app进行数据通信了。

-
-

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-----------------------last line for now----------------------