使用 AIDL 进行 IPC 的流程解析和其他要点

由于笔者未卜先知,知道自己将写一篇关于 AIDL 的技术分析文章

于是两年前笔者写了这个项目 IPC_Sample

(手动滑稽 ~~)

两年后的现在,我在这里把这篇文章补上

AIDL 实现步骤

这里不会介绍具体的编写步骤,如果需要可以参考 Android 接口定义语言 (AIDL)

具体编写好的代码请参考上述项目 IPC_Sample

总体可以分为三步:

  1. 编写.aidl 文件
  2. 实现服务端 RemoteService
  3. 客户端调用服务端的方法

编写.aidl 文件时有以下几点需要注意:

  1. 支持的数据类型有限
  2. 所有非原语参数都需要指示数据走向的方向标记
    可以是 in、out 或 inout
    原语默认为 in,不能是其他方向
    应该将方向限定为真正需要的方向,因为编组参数的开销极大
  3. 只支持方法

AIDL 文件并不是必需的,只是有了这个文件,系统会帮助我们自动生成 IRemoteService.java 这个类

AIDL 的包结构在服务端和客户端要保持一致,否则反序列化将会失败!

onTransact() 方法在 Binder 线程池中执行,如果要进行 UI 操作,可以利用 Handler 切换到主线程执行

客户端挂起直到服务端返回,所以要注意避免 ANR

IRemoteService

下面的分析还是以这个项目为例进行讲解 IPC_Sample

当我们写完 .aidl 文件后,AS 会自动在 build 子目录下为我们生成 IRemoteService ,这个接口的具体结构如下:

public interface IRemoteService extends android.os.IInterface{
	public static abstract class Stub extends android.os.Binder implements com.sbingo.ipc_sample.AIDL.IRemoteService {
		……
		private static class Proxy implements com.sbingo.ipc_sample.AIDL.IRemoteService {
			……
		}
	}
    public int getPid() throws android.os.RemoteException;

    public int add(int a, int b) throws android.os.RemoteException;

	public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException;
}

理解了这个类的结构, AIDL 的原理就基本理解了

Stub

static abstract

抽象内部类,继承了 Binder,实现了 IRemoteService

这是服务端,如果客户端和服务端在同一进程,Stub 对象将被返回给客户端

Proxy

static

内部类的代理类,实现了 IRemoteService

如果客户端和服务端不在同一进程,代理对象将被返回给客户端

流程解析

现在我们来分析整个流程,搞懂背后的原理

客户端持有服务端引用

客户端在成功连接服务端后的代码如下:

@Override
public void onServiceConnected(ComponentName name, IBinder service) {
    mService = IRemoteService.Stub.asInterface(service);	//1
    mCallbackText.setText("已连接服务端");
    mIsBound = true;
}

注释1处调用了 IRemoteService.StubasInterface() 方法,跟进去继续看:

/**
 * Cast an IBinder object into an com.sbingo.ipc_sample.AIDL.IRemoteService interface,
 * generating a proxy if needed.
 */
public static com.sbingo.ipc_sample.AIDL.IRemoteService asInterface(android.os.IBinder obj) {
    if ((obj == null)) {
        return null;
    }
    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    if (((iin != null) && (iin instanceof com.sbingo.ipc_sample.AIDL.IRemoteService))) {
        return ((com.sbingo.ipc_sample.AIDL.IRemoteService) iin);
    }
    return new com.sbingo.ipc_sample.AIDL.IRemoteService.Stub.Proxy(obj);
}

这个方法的作用就是判断客户端和服务端是否为同一进程

是的话返回类 Stub 的实例

否则返回代理类 Proxy 的实例

客户端调用服务端方法

这里以 add() 方法为例讲解调用过程,并且假设客户端和服务端在不同的进程中

private void tryToAdd() {
    if (!mIsBound) {
        Toast.makeText(MainActivity.this, "请先绑定服务端", Toast.LENGTH_SHORT).show();
        return;
    }
    if (TextUtils.isEmpty(num1.getText().toString()) ||
            TextUtils.isEmpty(num2.getText().toString())) {
        Toast.makeText(MainActivity.this, "请输入以上两个数字", Toast.LENGTH_SHORT).show();
    } else {
        try {
            int r = mService.add(Integer.valueOf(num1.getText().toString()), Integer.valueOf(num2.getText().toString()));	
            mCallbackText.setText("相加结果:" + r);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
}

第11行是调用的代码,mService 就是之前保存的代理类 Proxy 的实例

于是我们查看代理类 Proxyadd() 方法

@Override
public int add(int a, int b) throws android.os.RemoteException {
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    int _result;
    try {
        _data.writeInterfaceToken(DESCRIPTOR);
        //写入客户端传入的两个参数
        _data.writeInt(a);
        _data.writeInt(b);
        //这里会阻塞等待 mRemote 的 onTransact() 返回的计算结果
        mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
        //计算结果保存在 _reply 中
        _reply.readException();
        _result = _reply.readInt();
    } finally {
        _reply.recycle();
        _data.recycle();
    }
    //返回结果给客户端
    return _result;
}

配合注释,这段代码很容易理解

第12行的 mRemote 就是之前的 asInterface() 方法传入的 IBinder 对象,在本项目中就是RemoteService.java 中传入的 Stub 实例

于是我们查看 StubonTransact() 方法

@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 TRANSACTION_getPid: {
            data.enforceInterface(descriptor);
            int _result = this.getPid();
            reply.writeNoException();
            reply.writeInt(_result);
            return true;
        }
        case TRANSACTION_add: {
            data.enforceInterface(descriptor);
            //读入传入的两个参数
            int _arg0;
            _arg0 = data.readInt();
            int _arg1;
            _arg1 = data.readInt();
            //调用服务端的具体实现方法
            int _result = this.add(_arg0, _arg1);
            //写入计算结果
            reply.writeNoException();
            reply.writeInt(_result);
            return true;
        }
       	……
        default: {
            return super.onTransact(code, data, reply, flags);
        }
    }
}

第20行的 this 指的是 RemoteService.java 中传入的 Stub 实例

public class RemoteService extends Service {

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
        @Override
        public int getPid() {
            return Process.myPid();
        }

        @Override
        public int add(int a, int b) throws RemoteException {
            return a + b;
        }

        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean,
                               float aFloat, double aDouble, String aString) {
            // Does nothing
        }
    };
}

第17行就是真正执行计算的地方了,自此计算结果一路返回至客户端,调用成功!

系统服务 Binder 示例

系统服务中使用了大量的 Binder

这里以常见的 startActivity() 为例,它涉及到的 Binder相关类如下

interface IActivityManager extends IInterface	//接口
ServiceManager.getService("activity")	//真正的服务提供者
abstract class ActivityManagerNative extends Binder implements IActivityManager	// Binder 子类
class ActivityManagerProxy implements IActivityManager	//代理类

可以看到,这些和之前分析的原理是一样的

有兴趣的可以自行查看源码,这里不再细述了

服务端死亡通知

如果服务端进程因为某种异常而终止了,它给客户端使用的功能就会受到影响

为解决这一问题,接口 IBinder 提供了死亡通知,可以在服务端终止时通知客户端,与此相关的两个方法如下:

/**
* 注册死亡通知
*/
linkToDeath()       

/**
* 移除死亡通知
*/
unlinkToDeath()

服务端死亡时系统会回调客户端的 binderDied() 方法,此时应该移除之前绑定的 Binder 代理,然后重新绑定远程服务

当然,还有更简单的方法可以得知服务端已经挂了,那就是 onServiceDisconnected() 回调

这个方法和上述死亡通知的区别就是:onServiceDisconnected() 方法在主线程被回调,可以操作 UI

RemoteCallbackList

对象的跨进程传输本质上是反序列化的过程,所以 AIDL 中的自定义对象都要实现 Parcelable 接口,对象传输后得到的是不同的对象

正因为如此,如果客户端用常见写法在服务端设置了某种监听器,解注册时会失败,因为客户端传入的解注册对象在服务端并不存在!

为解决这一问题,系统提供了专门用于跨进程解注册的类 RemoteCallbackList<E extends IInterface>

它主要有以下三大功能:

  1. 成员变量 mCallbacksArrayMap 类型,它的 keyIInterface 类型,轻松实现解注册功能
  2. 注册 listener 时同时注册死亡通知,所以客户端进程终止后,会自动移除客户端所注册的 listener
  3. 内部自动实现了线程同步功能

它的使用方式主要涉及以下3个方法:
beginBroadcast()
getBroadcastItem()
finishBroadcast()

权限验证

如果服务端只允许有权限的客户端调用,可以增加权限验证

权限验证失败就无法调用我们的服务

onBind()onTransact() 方法中进行 permission 验证

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值