Android:AIDL的全方位深入探索

1,138 阅读19分钟

目录

1、IPC通信介绍

IPCInter-Process-Communication的缩写,含义为进程间通信或者跨进程通信。因为不同进程之间的资源和数据是互相隔离的,无法直接进行访问,所以我们需要使用IPC通信机制,IPC机制其实就是指两个进程之间数据交换的过程。那么我们一般都是在什么情况下才使用多进程开发呢?

1、系统资源紧张:每个应用所能使用的系统资源是有限的,为了避免主进程发生OOM,我们可以通过多进程将内存占用高的功能脱离到子进程。

2、独立功能解耦:可以将一些独立的、单一的功能放到子进程,如WebView、后台任务等。

3、从其他应用获取数据:当前应用需要向其他应用获取数据,由于是两个应用,所以必须采用跨进程的方式来获取所需的数据。

2、AIDL介绍

AIDL : Android 接口定义语言,是一款供用户用来抽象化 IPC 的工具,可以降低我们实现 IPC 的成本。AIDL 实现的 IPC 是基于 B/S 架构的,所以会有一个客户端,一个服务端。在使用时,需要在服务端定义 AIDL 接口,并含有一个包含了实现 AIDL.StubBinderService,以此对外提供能力;在客户端则需要保存一份 AIDL的复件,然后通过绑定服务端提供的 Service来获取 Binder,通过该 Binder来调用服务端的能力,从而实现跨进程的通信。

3、AIDL使用方法

AIDL的使用大概可以分为以下几个步骤:

1、在服务端创建.aidl文件,定义接口

创建aidl文件夹

创建aidl文件

aidl文件创建完成之后开始定义接口内容

interface IRemoteInterface {
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}

2、服务端创建Service,实现.aidl文件中定义的接口

当我们创建完.aidl文件之后,对应用程序进行编译,Android会自动生成一个同.aidl文件名称相同的.java接口文件,例如IRemoteService.aidl生成的文件名是 IRemoteService.java,接口文件中包含一个Stub类,该类是我们声明的.aidl接口的子类。然后我们需要创建一个Service,在Service实现.aidl文件中定义的接口,并通过Service中的onBind()方法将接口暴露给客户端进行调用。

public class RemoteService extends Service {

    //[1]、实现Service中的onBind()方法
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        //[3]、将服务端的接口通过onBind()方法暴露给客户端,便于客户端进行绑定
        return binder.asBinder();
    }
    
    //[2]、实现.aidl文件中定义的接口以及接口中定义的basicTypes()方法
    private final IMyInterface binder = new IMyInterface.Stub() {
        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
            //do something
        }
    };
}

3、客户端绑定服务端,调用AIDL定义的远程接口

1、拷贝服务端创建的.aidl文件到客户端(路径必须和服务端一致)

2、通过bindService绑定远程服务并实现ServiceConnection

3、在 onServiceConnected() 实现中,调用 IServer.Stub.asInterface() 将服务端返回的Binder对象转换成AIDL所属的接口类型。

4、调用服务端的方法。

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

        intent = new Intent();
        //Android5.0之后不允许通过隐示意图来启动服务,绑定服务的时候必须通过明文标识
        intent.setComponent(new ComponentName("com.android.service", "com.android.service.MyService"));
        //[1]、绑定远程服务
        bindService(intent, conn, Context.BIND_AUTO_CREATE);
    }

    //[2]、实现ServiceConnection
    private final ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            Log.d(TAG, "onServiceConnected: " + Thread.currentThread().getName());
            //[3]、IMyInterface.Stub.asInterface(iBinder)获取接口类型的实例
            IMyInterface myInterface = IMyInterface.Stub.asInterface(iBinder);
            //[4]、调用AIDL定义的远程接口
            myInterface.basicTypes(传入参数,我懒,就不传了);
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
        }
    };

4、AIDL数据传递

ADIL中,并不是所有的数据类型都支持,所以我们来了解一下AIDL支持的数据类型有哪些,一共有如下几种:

  • java中的8种基本数据类型。
  • StringCharSequence
  • ListList中的所有元素必须是上述列表中支持的数据类型。
  • MapMap中的所有元素必须是上述列表中支持的数据类型。
  • Parcelable:所有实现了Parcelable接口的对象。
  • AIDL:AIDL接口本身也可以在AIDL文件中使用。

4.1、基本类型传递

首先来看一下java8种基本类型,我们先创建一个.aidl文件,如下所示:

// IAidlInterface.aidl
package com.android.service;

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

interface IAidlInterface {
    /**
     * 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);
}

可以看到文件中自动生成的方法内已经包含了5种基本类型,我们将剩余的3种类型byteshortchar也添加进去,如下所示:

void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString, short aShort, byte aByte ,char aChar);

java8中基本数据类型补全之后,我们对应用程序进行编译,居然报错了,并且提示解析short类型失败,why?

/IAidlInterface.aidl:12.44-50: Failed to resolve 'short'

我们将short类型删除后,再次进行编译发现编译成功并且生成了对应的IAidlInterface.java文件,what?不是说好支持8种基本类型的吗?官方文档的原话是这么描述的,All primitive types in the java programming language(Java 编程语言中的所有原语类型),这其中也并没有提到short类型是不支持的,这是为什么呢?因为Parcel没有办法对short进行序列化,也就没办法通过aidlshort类型在客户端与服务端进行传递。

接下来我们来看一下List类型,对上面的代码进行一下简单的修改,在basicTypes方法中添加一个参数List,代码如下:

interface IAidlInterface {
    /**
     * 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, byte aByte ,char aChar, List<String> aList);
}

然后我们对程序进行编译,发现又双叒叕报错了,Ohshift!!!

/IAidlInterface.aidl:12.81-87: 'List<String>' can be an out type, so you must declare it as in, out, or inout.

错误信息提示我们必须对List<String>添加inout或者inout关键字,先不管三七为啥二十一,随便添加一个再说。

interface IAidlInterface {
    /**
     * 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, byte aByte ,char aChar,in List<String> aList);
}

添加完之后再次对程序进行编译,Ok了,那么这个inoutinout到底是个什么鬼呢?我们后面会进行分析,嘻嘻嘻。。。

4.2、自定义类型传递

说完了基本类型的传递,我们再来说一下自定义类型的传递,在实际的项目中,我们传递的参数类型不会都是基本类型,比如我们想要传递一个StudentBookMessage等等其他自定义类型的数据,该怎么整呢?其实很简单,先这么整,再那么整,最后这么那么整,就好了,都懂了吧!!!

言归正传,首先我们在服务端创建一个Book类,里面包含nameprice两个字段,如下所示:

public class Book {
    String name;
    int price;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }
}

接着我们创建一个.aidl文件,并且在接口中声明一个方法,方法的返回值为List,类型为Book类型。

interface IAidlInterface {

    List<Book> addBook(Book book);

}

编译应用程序,发现报错了,因为在我们之前提到的AIDL支持的数据类型中,并不包括Book类型。

/IAidlInterface.aidl:8.10-14: Failed to resolve 'Book'

这个时候就需要用到Parcelable了,我们需要让Book类实现Parcelable接口

public class Book implements Parcelable {
    String name;
    int price;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    public Book(){
        
    }

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(this.name);
        dest.writeInt(this.price);
    }

    public static final Creator<Book> CREATOR = new Creator<Book>() {
        public Book createFromParcel(Parcel source) {
            return new Book(source);
        }

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

    protected Book(Parcel in) {
        this.name = in.readString();
        this.price = in.readByte();

    }
}

OK,实现了Parcelable接口之后我们再来进行编译,发现依然会报错,为什么呢?为什么命运如此的坎坷?这里是因为使用自定义的Parcelable对象有两个需要注意的地方:

1、自定义的Parcelable对象和AIDL对象必须要显示的improt进来,不管它们是否和当前的AIDL处于同一个包内,什么意思呢?比如我们一开始创建的IAidlInterface这个文件,在这个文件中我们用到了Book类,而Book类又实现了Parcelable对象,我们就需要将Book类显示的导入进来:

  • 导入前:
package com.android.service;

interface IAidlInterface {

    List<Book> addBook(Book book);

}
  • 导入后:
package com.android.service;

import com.android.service.bean.Book;

interface IAidlInterface {

    List<Book> addBook(Book book);

}

2、如果AIDL文件中用到了自定义的Parcelable对象,就必须创建一个和它同名的aidl文件,并在其中声明它为Parcelable类型。我们用到了Book类型,所以需要创建一个同名的Book.aidl文件,并且将Book声明为Parcelable类型,如下所示:

Book.aidl

//创建的Book.aidl文件
package com.android.service.bean;
//声明为parcelable
parcelable Book;

以上的步骤都完成之后,还需要在参数Book的前面添加inoutintout关键字,还是老样子,我们随便添加一个。

package com.android.service;

import com.android.service.bean.Book;

interface IAidlInterface {

    List<Book> addBook(in Book book);

}

到这里,服务端所有的准备工作已经就绪,继续对程序进行编译,程序编译成功并且会生成对应IAidlInterface.java文件。

服务端的工作完成之后,我们继续来看一下客户端需要注意的地方,如果我们的客户端和服务端是在两个工程中编写的话,需要把服务端中的.aidl文件原封不动的拷贝一份到客户端。比如说之前在服务端创建的IAidlInterface.aidlBook.aidl,需要拷贝一份到客户端中。并且包名需要和服务端中的包名保持一致。这是因为客户端需要反序列化服务端中和AIDL接口相关的所有类,如果类的完整路径不一致的话,就无法反序列化成功。

5、AIDL案例

接下来,我们通过一个案例来深刻的掌握一下ADIL,首先来熟悉一下场景,我们需要实现连接和通知服务,其中连接服务包括服务端向客户端提供连接和断连两个方法,通知服务需要在连接服务端之后定时的向客户端推送消息。

连接服务:

  • connectServer:连接服务端
  • disConnectServer:断开和服务端的连接

消息服务:

  • sendMessage:客户端向服务端发送消息
  • registerMessageListener:在客户端监听来自服务端的消息
  • unRegisterMessageListener:注销消息的监听

首先我们来创建客户端工程appClient和服务端工程appServer。 创建完之后,根据我们在第3部分提到的AIDL使用介绍中的步骤,先来实现一下连接服务。

5.1 连接服务

1、创建AIDL文件,定义连接服务的接口:

2、在服务端创建一个Service来监听客户端的请求,并实现IConnectInterface.aidl接口中定义的方法。

public class RemoteService extends Service {

    private static final String TAG = "RemoteService";

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
       //获取一个IBinder对象,客户端通过这个IBinder对象去获取连接服务IConnectInterface。
        return connectInterface.asBinder();
    }
    
    private final IConnectInterface connectInterface = new IConnectInterface.Stub() {
        @Override
        public void connectServer() throws RemoteException {
            try {
                //connect一般都是耗时的方法,这里我们来模拟一下耗时操作
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Log.d(TAG, "service is connect.....");
        } 
        @Override
        public void disConnectServer() throws RemoteException {
            Log.d(TAG, "service is disconnect......");
        }
    };

}

3、目前为止服务端的工作已经完成,接下来我们来完成客户端的内容。在客户端中调用服务端的方法,首先需要将服务端创建的.aidl文件原封不动的拷贝到客户端。这里一定要注意包名路径的一致。

拷贝成功之后,我们就需要在客户端来实现绑定服务以及调用远程方法的代码了,代码如下:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private Button mConnect;
    private Button mDisConnect;
    private IConnectInterface connectInterface;

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

        mConnect = findViewById(R.id.connect);
        mDisConnect = findViewById(R.id.disConnect);
        mConnect.setOnClickListener(this);
        mDisConnect.setOnClickListener(this);

        Intent intent = new Intent();
        //Android5.0之后不允许通过隐示意图来启动服务,绑定服务的时候必须通过明文标识
        intent.setComponent(new ComponentName("com.android.aidltest", "com.android.aidltest.RemoteService"));
        //绑定远程服务
        bindService(intent, conn, BIND_AUTO_CREATE);
    }

    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            //通过IConnectInterface.Stub.asInterface(iBinder)将服务端返回的IBinder转化为AIDL接口所属的类型
            connectInterface = IConnectInterface.Stub.asInterface(iBinder);
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            unbindService(conn);
        }
    };

    @Override
    public void onClick(View view) {

        switch (view.getId()) {
            case R.id.connect:
                try {
                    // 调用远程连接方法
                    connectInterface.connectServer();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                break;
            case R.id.disConnect:
                try {
                    // 调用远程断连方法
                    connectInterface.disConnectServer();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                break;
            default:
                break;
        }
    }
}

启动应用程序之后我们分别点击连接按钮和断开按钮,并查看日志打印的信息。

com.android.aidltest D/RemoteService: service is connect.....
com.android.aidltest D/RemoteService: service is disconnect......

从日志打印的信息我们可以看到服务已经连接成功了。 完成连接服务的IPC过程之后,接下来我们去完成通知服务的IPC过程。

5.2 消息服务

1、定义远程接口

IMessageReceiveInterface.aidl

import com.android.aidltest.bean.Message;
import com.android.aidltest.MessageReceiveListener;
interface IMessageReceiveInterface {
   //发送消息
   void sendMessage(in Message message);
   //注册监听
   void registerMessageListener(MessageReceiveListener receiveListener);
   //取消监听
   void unRegisterMessageListener(MessageReceiveListener receiveListener);
}

Message.aidl

// Message.aidl
package com.android.aidltest;

parcelable Message;

MessageReceiveListener.aidl

package com.android.aidltest;

// Declare any non-default types here with import statements
import com.android.aidltest.bean.Message;

interface MessageReceiveListener {
    void onReceiveStudentMsg(in Message message);
}

定义完.aidl接口之后,我们就来搞一搞服务端的代码:

2、编写服务端代码

public class RemoteService extends Service {

    private static final String TAG = "RemoteService";
    //使用RemoteCallbackList存储MessageReceiverListener
    private RemoteCallbackList<MessageReceiverListener> list = new RemoteCallbackList<>();
    //使用线程池,每5秒发送一个定时任务,回调客户端
    private ScheduledThreadPoolExecutor scheduledThreadPoolExecutor;
    private ScheduledFuture scheduledFuture;


    @Override
    public void onCreate() {
        super.onCreate();
        scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(1);
    }

    @Override
    public IBinder onBind(Intent intent) {
        return serviceManager.asBinder();
    }

    /**
     * 连接服务
     */
    private IConnectInterface remoteInterface = new IConnectInterface.Stub() {
        @Override
        public void connectService() throws RemoteException {
            Log.d(TAG, "service is connect......");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
         
            scheduledFuture = scheduledThreadPoolExecutor.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    int size = list.beginBroadcast();
                    for (int i = 0; i < size; i++) {
                        try {
                            Message message = new Message();
                            message.setContent("this message from remote");
                            //子进程接收到的消息回调给主进程
                            list.getBroadcastItem(i).onReceiveMessage(message);
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }
                    list.finishBroadcast();
                }
            }, 5000, 5000, TimeUnit.MILLISECONDS);
        }

        @Override
        public void disConnectService() throws RemoteException {
            Log.d(TAG, "service is disconnect......");
            scheduledFuture.cancel(true);
        }

    };

    /**
     * 消息服务
     */
    private IMessageInterface messageInterface = new IMessageInterface.Stub() {

        @Override
        public void sendMessage(Message message) throws RemoteException {

            Log.d(TAG, "sendMessage: " + message.getInfo());

        }
        
       /**
        * 注册消息监听
        */
        @Override
        public void registerMessageListener(MessageReceiverListener messageReceiveListener) throws RemoteException {
            if (messageReceiveListener != null) {
                list.register(messageReceiveListener);
            }
        }
       /**
        * 解除消息监听
        */
        @Override
        public void unRegisterMessageListener(MessageReceiverListener messageReceiveListener) throws RemoteException {
            if (messageReceiveListener != null) {
                list.unregister(messageReceiveListener);
            }

        }
    };
    
    /**
     * 管理服务
     */
    private IServiceManager serviceManager = new IServiceManager.Stub() {
        @Override
        public IBinder getService(String serviceName) throws RemoteException {
            if (IRemoteInterface.class.getSimpleName().equals(serviceName)) {
                return remoteInterface.asBinder();
            } else if (IMessageInterface.class.getSimpleName().equals(serviceName)) {
                return messageInterface.asBinder();
            } else {
                return null;
            }
        }
    };

}

从服务端的代码中我们可以看到,我们使用了RemoteCallbackList来存放监听集合,并没有使用我们常用的List<MessageReceiverListener>,这是因为虽然我们在注册和解注册的时候用的是同一个客户端对象,但是跨进程传输客户端的同一对象会在服务端生成不同的对象,所以这里应当使用Android提供的RemoteCallbackListRemoteCallbackList内部是一个mapkeyIBinder类型,因为传递过程中Binder都是同一个。

然后在服务连接成功的时候,我们发送一个定时任务,每3秒通知一次客户端。这里要注意的是我们得通过beginBroadcast()来获取RemoteCallbackList中元素的个数,同时beginBroadcast()finishBroadcast()必须要配对使用。

一开始我们实现连接服务的时候,需要在onBind方法中返回连接服务的IBinder对象,但是后面我们又实现了消息服务,那么这个时候我们应该怎样把消息服务的IBinder对象同时提供给客户端使用呢?这里我们定义了一个管理服务IServiceManager,那么客户端就可以通过IServiceManagerIBinder对象去查询服务端需要的服务。

编写客户端主要代码

private ServiceConnection conn = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            serviceManager = IServiceManager.Stub.asInterface(iBinder);
            try {
                remoteBinder = serviceManager.getService(IRemoteInterface.class.getSimpleName());
                messageBinder = serviceManager.getService(IMessageInterface.class.getSimpleName());
                remoteInterface = IRemoteInterface.Stub.asInterface(AidlTestActivity.this.remoteBinder);
                messageInterface = IMessageInterface.Stub.asInterface(AidlTestActivity.this.messageBinder);

            } catch (RemoteException e) {
                e.printStackTrace();
            }

        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
	        unBindService(conn);
        }
    };
    
    private MessageReceiverListener messageReceiverListener = new MessageReceiverListener.Stub() {
        @Override
        public void onReceiveMessage(Message message) throws RemoteException {
            new Handler(Looper.getMainLooper()).post(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(AidlTestActivity.this, message.getInfo(), Toast.LENGTH_LONG).show();
                }
            });
        }
    };


 @SuppressLint("NonConstantResourceId")
    @Override
    public void onClick(View view) {

        switch (view.getId()) {
            case R.id.connect:
                try {
                    remoteInterface.connectService();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                break;
            case R.id.dis_connect:
                try {
                    remoteInterface.disConnectService();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }

                break;
            case R.id.send:
                try {
                    Message message = new Message();
                    message.setContent("message from main");
                    messageInterface.sendMessage(message);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                break;
            case R.id.register_msg:
                try {
                    messageInterface.registerMessageListener(messageReceiverListener);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                break;
            case R.id.unregister_msg:
                try {
                    messageInterface.unRegisterMessageListener(messageReceiverListener);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                break;
            default:
                break;
        }
    }

在客户端中我们先在onServiceConnected方法中通过IServiceManagerIBinder对象获取到对应的接口类型,然后在点击事件中进行了对应的IPC调用,在onReceiveMessage方法中接收到来自于服务端的回调。我们的小案例到这里就结束了。

6、oneway关键字的使用

oneway 关键字用于修改远程调用的行为。使用此关键字后,远程调用不会阻塞,而只是发送事务数据并立即返回。最终接收该数据时,接口的实现会将其视为来自 Binder 线程池的常规调用(普通的远程调用)。如果 oneway 用于本地调用,则不会有任何影响,且调用仍为同步调用。

在第5个小结的连接服务的调用中,我们可以发现这个过程是一个阻塞的过程,由于我们服务端的connectService方法中阻塞了3s,那么我们的调用端同时也会阻塞3s,它需要等到服务端的方法执行完成,客户端的调用才会执行完毕。我们在客户端的调用是运行在主线程中的,我想要remoteInterface.connectService()这个方法在调用完成之后就立即结束,服务端 的connectService()方法中执行的代码我毫不关心。这时候就可以使用oneway关键字。接下来我们使用代码来做一下对比。

在客户端代码中添加日志:

 case R.id.connect:
      try {
          Log.d("connectOneway", "onClick: 客户端开始连接");
          remoteInterface.connectService();
          Log.d("connectOneway", "onClick: 客户端连接结束");
      } catch (RemoteException e) {
          e.printStackTrace();
      }
      break;

在服务端代码中添加日志:

 @Override
        public void connectService() throws RemoteException {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Log.d(TAG, "service is connect......");
            ......
         }   
  • 不使用oneway

日志打印如下:可以看到客户端的连接结束日志会等到服务端的代码执行完毕之后再打印。

18:55:06.132 10666-10666/com.android.aidldemo D/connectOneway: onClick: 客户端开始连接
18:55:09.135 10411-10423/com.android.service D/connectOneway: service is connect.....
18:55:09.143 10666-10666/com.android.aidldemo D/connectOneway: onClick: 客户端连接结束
  • 使用oneway 接着我们在connectService()方法的前面加上oneway关键字。
// IConnectInterface.aidl
package com.android.aidltest;

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

interface IConnectInterface {

  oneway void connectService();

  void disConnectServer();

}

日志打印如下:可以明显的看到与添加oneway关键字之前不同之处在于客户端的日志在remoteInterface.connectService()方法调用之后立马就打印了,并没有等到客户端的代码执行完毕。

19:13:53.831 10501-10501/com.android.aidldemo D/connectOneway: onClick:: 客户端开始连接
19:13:53.832 10501-10501/com.android.aidldemo D/connectOneway: onClick: 客户端连接结束
19:13:56.834 10411-10423/com.android.service D/connectOneway: service is connect.....

使用oneway需要注意的两点是:

1、使用oneway修饰的方法不能有返回值。

2、不能修饰有定义 out 参数的方法。

因为客户端立马就执行完成了,压根不需要关心服务端有什么结果。

7、in、out、inOut关键字的使用

在上面数据类型传递的过程中,我们了解到,AIDL中除了基本数据类型,其他类型的参数必须标上方向:inout或者inout,那么它们三个到底是什么含义呢?

  • in表示输入型参数:只能由客户端流向服务端,表示服务端修改后的对象并不会同步给客户端。

  • out表示输出型参数:只能由服务端流向客户端,服务端收到该参数的空对象,并且服务端修改后的对象可以同步给客户端。

  • inout表示输入输出型参数:可在客户端与服务端双向流动,服务端接收到该参数对象的完整数据,且服务端对该对象的后续修改将同步改动到客户端的相应参数对象。

有兴趣深入了解的兄弟可以看一下你真的理解AIDL中的in,out,inout么?这篇文章,里面讲的非常的详细。

8、AIDL原理

我们创建一个.aidl文件,里面包含三个方法,如下:

interface IMyAidlInterface {

   oneway void connect(String id);

   void disconnect();

   boolean isConnect();

}

接着我们编译程序,对生成的IMyAidlInterface.java文件进行分析:

/*
 * This file is auto-generated.  DO NOT MODIFY.
 */
package com.android.service;
// Declare any non-default types here with import statements

public interface IMyAidlInterface extends android.os.IInterface {
    /**
     * Default implementation for IMyAidlInterface.
     */
    public static class Default implements com.android.service.IMyAidlInterface {
        @Override
        public void connect(java.lang.String id) throws android.os.RemoteException {
        }

        @Override
        public void disconnect() throws android.os.RemoteException {
        }

        @Override
        public boolean isConnect() throws android.os.RemoteException {
            return false;
        }

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

    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.android.service.IMyAidlInterface {

        // Binder的唯一标识,一般用Binder的当前类名来表示
        private static final java.lang.String DESCRIPTOR = "com.android.service.IMyAidlInterface";

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

        /**
         * Cast an IBinder object into an com.android.service.IMyAidlInterface interface,
         * generating a proxy if needed.
         * 将服务端的的Binder对象转化为客户端需要的AIDL接口类型
         */
        public static com.android.service.IMyAidlInterface asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            // 查找本地对象,返回服务端的Stub对象本身,如果有值说明客户端和服务端在同一个进程
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.android.service.IMyAidlInterface))) {
                return ((com.android.service.IMyAidlInterface) iin);
            }
            // 创建一个远程的Binder代理对象,让这个Binder代理实现对远程对象的访问,返回的是系统封装后的Stub.Proxy对象
            return new com.android.service.IMyAidlInterface.Stub.Proxy(obj);
        }

        // 返回当前的Binder对象
        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        /**
         *
         * 用于服务端接收,运行在服务端的Binder线程池中,客户端发起跨进程请求时,远程服务会通过系统底层封装后交给此方法处理
         * @param code 确定客户端请求的目标方法,AIDL函数都会有一个编号
         * @param data 目标方法所需要的参数
         * @param reply 目标方法的返回值
         * @param flags 0表示正常的IPC调用,1表示添加了oneway关键字的调用
         * @return falseL: 代表客户端请求失败
         * @throws android.os.RemoteException
         */
        @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_connect: {
                    data.enforceInterface(descriptor);
                    java.lang.String _arg0;
                    //读取客户端传入的String类型的参数
                    _arg0 = data.readString();
                    this.connect(_arg0);
                    return true;
                }
                case TRANSACTION_disconnect: {
                    data.enforceInterface(descriptor);
                    this.disconnect();
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_isConnect: {
                    data.enforceInterface(descriptor);
                    boolean _result = this.isConnect();
                    reply.writeNoException();
                    // 将返回值写到reply中
                    reply.writeInt(((_result) ? (1) : (0)));
                    return true;
                }
                default: {
                    return super.onTransact(code, data, reply, flags);
                }
            }
        }

        /**
         * Proxy主要用于客户端的跨进程调用
         */
        private static class Proxy implements com.android.service.IMyAidlInterface {
            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;
            }

            @Override
            public void connect(java.lang.String id) throws android.os.RemoteException {
                // 创建connect方法需要的输入型Parcel对象_data,这里因为我们用了oneway关键字,所以没有输出型对象_reply
                android.os.Parcel _data = android.os.Parcel.obtain();
                try {
                    //把数据序列化
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeString(id);

                    boolean _status = mRemote.transact(Stub.TRANSACTION_connect, _data, null, android.os.IBinder.FLAG_ONEWAY);
                    if (!_status && getDefaultImpl() != null) {
                        getDefaultImpl().connect(id);
                        return;
                    }
                } finally {
                    _data.recycle();
                }
            }

            @Override
            public void disconnect() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    boolean _status = mRemote.transact(Stub.TRANSACTION_disconnect, _data, _reply, 0);
                    if (!_status && getDefaultImpl() != null) {
                        getDefaultImpl().disconnect();
                        return;
                    }
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

            @Override
            public boolean isConnect() throws android.os.RemoteException {
                // 创建输入型Parcel对象_data
                android.os.Parcel _data = android.os.Parcel.obtain();
                // 创建输出型Parcel对象_reply
                android.os.Parcel _reply = android.os.Parcel.obtain();
                // 创建返回值对象
                boolean _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    // 发起RPC(远程过程调用)请求,当前线程挂起,然后会调用到上面的onTransact方法,最后RPC过程返回后,当前线程继续执行
                    boolean _status = mRemote.transact(Stub.TRANSACTION_isConnect, _data, _reply, 0);
                    if (!_status && getDefaultImpl() != null) {
                        return getDefaultImpl().isConnect();
                    }
                    _reply.readException();
                    // 将返回值返回给客户端
                    _result = (0 != _reply.readInt());
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            public static com.android.service.IMyAidlInterface sDefaultImpl;
        }
	// 声明了用于标识方法的id,每个方法各一个id
        static final int TRANSACTION_connect = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_disconnect = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
        static final int TRANSACTION_isConnect = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);

        public static boolean setDefaultImpl(com.android.service.IMyAidlInterface impl) {
            // Only one user of this interface can use this function
            // at a time. This is a heuristic to detect if two different
            // users in the same process use this function.
            if (Stub.Proxy.sDefaultImpl != null) {
                throw new IllegalStateException("setDefaultImpl() called twice");
            }
            if (impl != null) {
                Stub.Proxy.sDefaultImpl = impl;
                return true;
            }
            return false;
        }

        public static com.android.service.IMyAidlInterface getDefaultImpl() {
            return Stub.Proxy.sDefaultImpl;
        }
    }

    public void connect(java.lang.String id) throws android.os.RemoteException;

    public void disconnect() throws android.os.RemoteException;

    public boolean isConnect() throws android.os.RemoteException;
}


代码中有详细的注释,我们主要对Stub类和Stub.Proxy类做一个简单的总结。 Stub类:

  • onTransact中根据code值确定客户端请求的目标方法以及获取目标方法对应的参数。
  • 调用本地对应的方法。
  • 将返回值写入到reply中,用于客户端读取。

Stub.Proxy类:

  • 创建输入型对象、输出流对象以及返回值对象,并且将数据进行序列化。
  • 通过transact方法发起RPC请求。
  • reply中读取到的返回值返回给客户端。 最后通过刚哥的一张图来总结一下整个流程:

AIDL的介绍到这里就结束了,如有需要改正的地方,还请各位BaBa及时指正并多多包涵。

参考资料

Android开发艺术探索

Android 接口定义语言 (AIDL)

你真的理解AIDL中的in,out,inout么?