Binder在Java层的调用详解之AIDL

Binder是什么?

在开始介绍AIDL之前,我们必须对Binder有一个初步的了解。

Binder就是Android用来跨进程通信的一种方式,这个涉及的知识点太多,我会单独开一篇讲述。

在这里,我们需要知道的是,既然涉及到跨进程通讯,就肯定会有两个不同的进程,以及一条连接两个进程的通道。

这两个进程中,提供服务的进程我们把它叫做服务端,享受服务的进程我们把它叫做客户端,连接服务端和客户端的通道有很多,binder就是其中之一。

AIDL是什么?

Android Interface Definition Language,即Android接口定义语言

可以理解为用来生成binder通信代码的脚本语言

AIDL该怎么用?

我会选用Android的Doze服务来分析AIDL的使用步骤。

涉及到的文件有以下三个:

  • 服务端:DeviceIdleController.java
  • 客户端:DeviceIdleManager.java
  • 通道(Binder):IDeviceIdleController.aidl、ServiceManager.java

如果把客户端比作一个想吃饭的人,服务端比作一家提供很多菜品餐厅,那么AIDL就是餐厅中的菜单,ServiceManager就是指向各个餐厅的地图


一、通道(Binder)

我们看下IDeviceIdleController.aidl的具体代码实现

package android.os;

import android.os.IMaintenanceActivityListener;
import android.os.UserHandle;

/** @hide */
interface IDeviceIdleController {
    void addPowerSaveWhitelistApp(String name);
    ...
    void addPowerSaveTempWhitelistApp(String name, long duration, int userId, String reason);
}

这就是一个AIDL的标准写法,看起来就是这么枯燥无味且平淡,主要组成部分就是在interface中定义所有服务端向外提供的接口,就像是一家餐厅提供的菜单。

相信有些细心的同学,就会有些疑问,服务端的接口通过AIDL抛出来了,但是客户端怎么找到服务端呢?

这就涉及到Binder的核心实现ServiceManager,在本篇中不多介绍,后续会单开一章。

二、服务端

服务端作为服务的提供者,就像是一家餐厅,菜单上写出来的菜,硬着头皮也得烧出来。

我们看下服务端具体代码实现的关键代码

public class DeviceIdleController extends SystemService
        implements AnyMotionDetector.DeviceIdleCallback {

    BinderService mBinderService;

    private final class BinderService extends IDeviceIdleController.Stub {
        @Override public void addPowerSaveWhitelistApp(String name) {
            if (DEBUG) {
                Slog.i(TAG, "addPowerSaveWhitelistApp(name = " + name + ")");
            }
            getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
                    null);
            long ident = Binder.clearCallingIdentity();
            try {
                addPowerSaveWhitelistAppInternal(name);
            } finally {
                Binder.restoreCallingIdentity(ident);
            }
        }

        @Override public void addPowerSaveTempWhitelistApp(String packageName, long duration,
                int userId, String reason) throws RemoteException {
            addPowerSaveTempWhitelistAppChecked(packageName, duration, userId, reason);
        }
        ...
    }

    @Override
    public void onStart() {
        ...
        mBinderService = new BinderService();
        publishBinderService(Context.DEVICE_IDLE_CONTROLLER, mBinderService);
        ...
    }

}

DeviceIdleController作为服务端,定义了一个内部类BinderService继承IDeviceIdleController.Stub,实现了IDeviceIdleController.aidl中定义的所有接口。(疑问1:IDeviceIdleController.Stub哪儿来的?)

服务端中的publishBinderService(),也通过调用ServiceManager的addService方法,将服务端注册到ServiceManager中。(疑问2:为什么BinderService能够被作为addService的参数)

三、客户端

如果把客户端看作是一个想要吃饭的人,那就需要以下几个步骤:找到餐厅,打开菜单,最后点菜吃饭。

我们来通过客户端代码的实例来具体分析以下这几个步骤

import android.os.IDeviceIdleController;

public class UsageStatsService extends SystemService implements
        UserUsageStatsService.StatsUpdatedListener {

    IDeviceIdleController mDeviceIdleController;
    ...
    @Override
    public void onBootPhase(int phase) {
        ...
        mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
        ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
        ...
    }
    @Override
    public void whitelistAppTemporarily(String packageName, long duration, int userId)
            throws RemoteException {
        ...
        mDeviceIdleController.addPowerSaveTempWhitelistApp(packageName, duration, userId,
                    reason.toString());
    }
    ...
}

首先,ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER)这段代码就充当了找到餐厅这个步骤

其次,通过IDeviceIdleController.Stub.asInterface方法,就好比是将餐厅中的菜单打开

最后,mDeviceIdleController.addPowerSaveTempWhitelistApp就相当于点菜吃饭,这样一来,整个从客户端调用服务端的方法的过程就结束了。

AIDL到底怎么实现的?

看到这里,如果是只想要知道怎么用的同学肯定能够愉快的用起来了。但是,对于AIDL的实现逻辑还是一团雾水。

既然开头时候说到过,AIDL只是一个生成Binder实现的脚本语言,那么想要了解AIDL的实现逻辑,就必须从AIDL生成的java文件开始分析。在源码中,AIDL会被编译生成java文件放在out/soong目录下,感兴趣的同学可以通过find命令去目录下发掘以下。

话不多说,先贴上生成的IDeviceIdleController.java代码

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

public interface IDeviceIdleController extends android.os.IInterface
{
	/** Local-side IPC implementation stub class. */
	public static abstract class Stub extends android.os.Binder implements android.os.IDeviceIdleController
	{
		private static final java.lang.String DESCRIPTOR = "android.os.IDeviceIdleController";
		/** Construct the stub at attach it to the interface. */
		public Stub()
		{
			this.attachInterface(this, DESCRIPTOR);
		}
		/**
		 * Cast an IBinder object into an android.os.IDeviceIdleController interface,
		 * generating a proxy if needed.
		 */
		public static android.os.IDeviceIdleController asInterface(android.os.IBinder obj)
		{
			if ((obj==null)) {
				return null;
			}
			android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
			if (((iin!=null)&&(iin instanceof android.os.IDeviceIdleController))) {
				return ((android.os.IDeviceIdleController)iin);
			}
			return new android.os.IDeviceIdleController.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
		{
			java.lang.String descriptor = DESCRIPTOR;
			switch (code)
			{
				case INTERFACE_TRANSACTION:
				{
					reply.writeString(descriptor);
					return true;
				}
				case TRANSACTION_addPowerSaveTempWhitelistApp:
				{
					data.enforceInterface(descriptor);
					java.lang.String _arg0;
					_arg0 = data.readString();
					long _arg1;
					_arg1 = data.readLong();
					int _arg2;
					_arg2 = data.readInt();
					java.lang.String _arg3;
					_arg3 = data.readString();
					this.addPowerSaveTempWhitelistApp(_arg0, _arg1, _arg2, _arg3);
					reply.writeNoException();
					return true;
				}
				default:
				{
					return super.onTransact(code, data, reply, flags);
				}
			}
		}
		private static class Proxy implements android.os.IDeviceIdleController
		{
			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 addPowerSaveTempWhitelistApp(java.lang.String name, long duration, int userId, java.lang.String reason) throws android.os.RemoteException
			{
				android.os.Parcel _data = android.os.Parcel.obtain();
				android.os.Parcel _reply = android.os.Parcel.obtain();
				try {
					_data.writeInterfaceToken(DESCRIPTOR);
					_data.writeString(name);
					_data.writeLong(duration);
					_data.writeInt(userId);
					_data.writeString(reason);
					mRemote.transact(Stub.TRANSACTION_addPowerSaveTempWhitelistApp, _data, _reply, 0);
					_reply.readException();
				}
				finally {
					_reply.recycle();
					_data.recycle();
				}
			}
		}
		static final int TRANSACTION_addPowerSaveTempWhitelistApp = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
	}
	public void addPowerSaveTempWhitelistApp(java.lang.String name, long duration, int userId, java.lang.String reason) throws android.os.RemoteException;
    ...
}

我已经对IDeviceIdleController.java进行了删减,只留下了binder的通信流程以及addPowerSaveTempWhitelistApp的具体实现。

我们从最外层开始一步步剖析,IDeviceIdleController接口生成了一个stub的抽象类,以及服务端能够提供的方法,比如:addPowerSaveTempWhitelistApp。

这也就能解释上文中的第一个疑问,IDeviceIdleController.Stub是通过AIDL生成的。同时,stub类继承了Binder,所以,能够转化成IBinder对象,这也解释了第二个疑问,因为BinderService继承了stub类,也是能转化成IBinder对象的。

接下来,我们开始肢解stub类的具体实现,这也是binder通讯java层实现的关键。

(1)构造函数

构造函数中调用了attachInterface方法,这是父类Binder中的方法,用于将本接口与Binder相关联,在用户通过queryLocalInterface方法进行请求的时候,通过对比DESCRIPTOR,相同的话,返回当前接口

(2)客户端调用时使用的asInterface方法

我们能看到,asInterface中调用了queryLocalInterface方法,而在构造函数中,我们讲过,queryLocalInterface是获取接口对象的,但是前提是DESCRIPTOR相同。所以,当同进程通讯时,asInterface返回的是stub对象,而跨进程通讯时,返回的是Stub.Proxy对象。

(3)Proxy内部类

在上文中,我们知道了跨进程通讯时,我们获取到的是一个Proxy对象,那么,我们的调用也就发生在Proxy类中。

Proxy类中也实现了我们所需要调用的接口addPowerSaveTempWhitelistApp,但我们仔细观察addPowerSaveTempWhitelistApp的具体实现不难发现,他是通过transact方法,将需求抛出,至于服务端如何接到transact的请求并实现,就设计到Binder的底层实现,这部分我们以后再讲。

你可能感兴趣的:(Binder在Java层的调用详解之AIDL)