跳至主要內容

Android 中 AIDL 的使用

JI,XIAOYONG...大约 6 分钟

AIDL(Android Interface Definition Language,Android 接口定义语言)用于 Android IPC,适用于大量并发请求。

主要分为两部分:

  1. 服务端 创建 Service 监听 Client 的请求,通过创建 AIDL 将接口暴露给客户端
  2. 客户端 绑定到服务端获取 BInder 对象,将其转化为对应 AIDL,并调用接口对应方法。

两者的连线就是 AIDL,因此两个 APP 的 AIDL 必须一致,可以将 AIDL 文件放到一个 Android Library 中,或者打成 aar 文件供二者依赖。

也可以将 AIDL 涉及到的 AIDL 文件、java 都放到 AIDL 文件夹下,然后在 build.gradle 的android{...}中添加

    sourceSets{
         main{
             java.srcDirs = ['src/main/java','src/main/adil']
         }
     }

即添加一个 java 路径

AIDL 文件特点

支持的数据格式

基本数据类型、List(ArrayList)、Map(HashMap)以及实现了 Parcelable 接口的对象、AIDL 接口。

注意事项

  • 自定义的 Parcelable 对象、AIDL 对象必须显示 import。
  • AIDL 中用到的 Parcelable 对象必须新建一个同名 AIDL 接口,声明其为 Parcelable 类型。
// People.aidl
package cf.android666.androidlib;
import cf.android666.androidlib.People;
// Declare any non-default types here with import statements

parcelable People;
  • AIDL 中除了基本数据类型,其他的参数必须标记方向(in,out,inout)。
  • AIDL 不支持方法重载,也就是说不能有两个同名的方法(即使参数类型、个数不同也不行)。
  • AIDL 中只支持方法,不支持静态变量。

AIDL 用法

AIDL

// ManagerAidl.aidl
package cf.android666.androidlib;

import cf.android666.androidlib.People;
import cf.android666.androidlib.TaskCallBack;

interface ManagerAidl {
    //客户端提供的方法
    List<People> getPeopleList();
    void addPeople(in People people);

    //回调接口,用于服务端往客户端通信
    void registerCallBack(in TaskCallBack callback);
    void unregisterCallBack(in TaskCallBack callback);
}
// TaskCallBack.aidl
package cf.android666.androidlib;
import cf.android666.androidlib.People;

// https://blog.csdn.net/woshiwoshiyu/article/details/54266101
//回调的具体方法,供服务端回调
interface TaskCallBack {
    void callBack(in int size);
    void onPeopleChange(in List<People> peoples);
}
//People.java
package cf.android666.androidlib;
public class People implements Parcelable {
    ...
}
//PeopleManager.java
package cf.android666.androidlib;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;

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

/**
 * 一个管理类,封装了客户端绑定服务端的一些方法
 * 属于客户端部分,不过放在 AIDL 中便于多个客户端开发
 * Created by jixiaoyong on 2018/8/6.
 * email:jixiaoyong1995@gmail.com
 */
public class PeopleManager {

    private static PeopleManager mPeopleManager;

    private  Context mContext;
    private  Listener mListener;
    private ManagerAidl managerAidl;

    private List<People> peopleList;

    //实现该回调方法,用于调用客户端的具体方法
    //注意这里是 new TaskCallBack.Stub(),而非 new TaskCallBack(),否则服务器无法接收到 callback
    //TaskCallBack.Stub() 是 TaskCallBack 的子类,当跨进程通信时传递的是 proxy 类
    private TaskCallBack callBack = new TaskCallBack.Stub() {
        @Override
        public void callBack(int size) throws RemoteException {
            mListener.onCallback(size);
        }

        @Override
        public void onPeopleChange(List<People> peoples) throws RemoteException {
            peopleList = peoples;
            mListener.onPeopleListChange(peoples);
        }
    };

    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //在连接上服务端后,客户端从 IBinder 对象中获取到 AIDL 接口对象,并执行其方法
            managerAidl =  ManagerAidl.Stub.asInterface(service);
            try {
                peopleList = managerAidl.getPeopleList();
                managerAidl.registerCallBack(callBack);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            mListener.onCreate(mPeopleManager);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            try {
                managerAidl.unregisterCallBack(callBack);
            } catch (RemoteException e) {
                e.printStackTrace();
            }

        }
    };

    private PeopleManager(Context context,Listener listener) {
        mContext = context;
        mListener = listener;
        peopleList = new ArrayList<>();

        Intent intent = new Intent();
        intent.setComponent(new ComponentName("cf.android666.demo",
                "cf.android666.demo.MService"));//Android5.0 后必须显示的启动服务
        context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
    }

    public static void init(Context context,Listener listener) {
        mPeopleManager = new PeopleManager( context, listener);
    }

    public void addPeople(People people) {
        try {
            managerAidl.addPeople(people);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    public List<People> getPeopleList() {
        return peopleList;
    }

    //子类可以实现该 Listener 的方法,在服务端调用这些方法时执行对应操作
    public interface Listener {
        void onCreate(PeopleManager peopleManager);//服务连接成功
        void onCallback(int size);
        void onPeopleListChange(List<People> peoples);
    }
}

服务端

注意 MService 在 AndroidManife.xml 中配置:

android:exported="true"android:enabled="true"android:process=":people"

//MService.java
package cf.android666.demo;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.text.InputFilter;
import android.util.Log;

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

import cf.android666.androidlib.ManagerAidl;
import cf.android666.androidlib.People;
import cf.android666.androidlib.TaskCallBack;


/**
 * Created by jixiaoyong on 2018/8/6.
 * email:jixiaoyong1995@gmail.com
 */
public class MService extends Service implements ManagerAidl.Stub.DeathRecipient {

    private List<People> mPeopleList;
    private static RemoteCallbackList<TaskCallBack> callbackList = new RemoteCallbackList<>();;
    private TaskCallBack mCallBack;

	//使用 AIDL 接口生成 mIBinder,在服务端实现接口各个方法,供客户端调用
    private IBinder mIBinder = new ManagerAidl.Stub() {

        @Override
        public List<People> getPeopleList() throws RemoteException {
            return mPeopleList;
        }

        @Override
        public void addPeople(People people) throws RemoteException {
            mPeopleList.add(people);
            onPeopleChange(mPeopleList);
        }

        @Override
        public void registerCallBack(TaskCallBack callback) throws RemoteException {
            mCallBack = callback;
            Log.d("TAG", "registerCallBack 注册回调方法 callback == null" + callback);
            if (callback != null) {//注意这里一定要判断非空
                callbackList.register(callback);
            }
        }

        @Override
        public void unregisterCallBack(TaskCallBack callback) throws RemoteException {
            if (callback != null) {
                callbackList.unregister(callback);
            }
        }
    };

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.d("tag", "onBind  MService 开始了" );

        if (mPeopleList == null) {
            mPeopleList = new ArrayList<>();
        }

        for (int i = 0; i < 20; i++) {
            mPeopleList.add(new People("people" + i, i));
        }

        return mIBinder;//返回开始用 AIDL 创建的 IBinder
    }

	//实现 DeathRecipient 接口的方法,在客户端终止后自动调用该方法
    @Override
    public void binderDied() {
        callbackList.unregister(mCallBack);
    }

    //这里时在服务端调用回调方法的写法,是从 callbackList 依次取出来执行
    private void onPeopleChange(List<People> peoples) {
        if (callbackList == null) {
            return;
        }
        int len = callbackList.beginBroadcast();
        try {
            for (int i = 0; i < len; i++) {
                callbackList.getBroadcastItem(i).onPeopleChange(peoples);
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }finally {
            callbackList.finishBroadcast();
        }
    }
}

注意:

  1. 这里用来注册监听的类是 RemoteCallbackList

    我们知道跨进程的两个 listener 是两个不同的对象,那他是怎么保证跨进程注册、注销的是指定的 listener 呢?

    这是因为虽然两个 listener 对象不同,但是他们底层的 Binder 对象是同一个,在 RemoteCallbackList 中有一个以 Binder 对象为 KEY 的 map 来存放这些 listener 对象,当要注销时,只需要按当前待注销的 listener 的 Binder 对象找到已经注册了的 listener 并删除掉即可。

    ArrayMap<IBinder, Callback> mCallbacks
            = new ArrayMap<IBinder, Callback>()
    

    此外,RemoteCallbackList 可以在客户端死亡的时候自动注销掉对应的 listener,这是因为他在注册的同时也对 Binder 的死亡就行了监听。

    public boolean register(E callback, Object cookie) {
        synchronized (mCallbacks) {
            if (mKilled) {
                return false;
            }
            // Flag unusual case that could be caused by a leak. b/36778087
            logExcessiveCallbacks();
            IBinder binder = callback.asBinder();
            try {
                Callback cb = new Callback(callback, cookie);
                binder.linkToDeath(cb, 0);//监听 binder 的死亡事件
                mCallbacks.put(binder, cb);
                return true;
            } catch (RemoteException e) {
                return false;
            }
        }
    }
    ...
    //当 binder 死亡时,会主动移除其注册的 listener
    public void binderDied() {
                synchronized (mCallbacks) {
                    mCallbacks.remove(mCallback.asBinder());
                }
                onCallbackDied(mCallback, mCookie);
            }
    
  2. 方法运行的线程

    如果客户端和服务端运行在同一进程:客户端调用服务端和服务端回调客户端方法(RemoteCallbackList,下同)都会运行在同一线程,即客户端调用服务端时所在的线程,默认为主线程

    如果客户端和服务端运行在不同进程:客户端调用服务端方法,客户端会被挂起,直到服务端方法在 Binder 线程池中运行完毕,这种情况下服务端可以执行耗时操作而无需另建线程;服务端回调客户端方法运行在客户端主线程 (与客户端调用服务端方法在同一线程)

    通过上述分析,可以注意到一个细节:虽然在服务端中回调客户端的方法是在服务端的 Binder 线程,但是在客户端中被回调的方法却是和客户端中主动调用服务端方法的线程一致

客户端

PeopleManager.init(this, this);
//服务连接成功后,可以开始调用服务的一系列方法
@Override
public void onCreate(PeopleManager peopleManager) {
    mPeopleManager = peopleManager;
    peopleList = peopleManager.getPeopleList();

    for (int i = 0; i < peopleList.size(); i++) {
        Log.d("tag", "people list is " + peopleList.get(i));
    }
}

//其他回调方法,等服务端回调时会执行对应方法
@Override
public void onPeopleListChange(List<People> peoples) {
    Log.d("TAG", "demo2 people 变化了" + peoples.size());
}

监听并处理 Binder 死亡事件

当服务端进程意外死亡时,我们可以选择重新连接服务,一般有两种方式:

  1. binderDied 在客户端的 Binder 线程池中
  2. onServiceDisconnected 在客户端 UI 线程

AIDL 的权限验证

可以在服务的 onBind(Intent intent) 或者 onTransact() 方法中做验证

做验证的手段有:1.permission 验证;2.Uid,Pid 等做验证

参考资料

《Android 开发艺术探索》

文章标题:《Android 中 AIDL 的使用》
本文作者: JI,XIAOYONG
发布时间: 2018/08/07 21:15:39 UTC+8
更新时间: 2023/12/30 16:17:02 UTC+8
written by human, not by AI
本文地址: https://jixiaoyong.github.io/blog/posts/f931e8ae.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 许可协议。转载请注明出处!
你认为这篇文章怎么样?
  • 0
  • 0
  • 0
  • 0
  • 0
  • 0
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.15.8