AIDL中RemoteCallbackList的使用及权限验证方式

业务场景:
现在要实现每新增一个员工,就通知相应的部门人员
1、提供一个AIDL接口,由于AIDL中无法使用普通接口,所以提供一个AIDL接口

// IOnNewPersonArrivedListener.aidl
package com.wuc.aidltest;

// Declare any non-default types here with import statements
import com.wuc.aidltest.Person;
// 当服务端有新人加入时,就通知每一个已经申请提醒功能的用户,由于AIDL中无法使用普通接口,所以提供一个AIDL接口
interface IOnNewPersonArrivedListener {
    void onNewPersonArrived(in Person person);
}

2、 修改ICalculateInterface代码,新增注册与注销监听器代码

// ICalculateInterface.aidl
package com.wuc.aidltest;

// Declare any non-default types here with import statements
import com.wuc.aidltest.Person;
import com.wuc.aidltest.IOnNewPersonArrivedListener;
interface ICalculateInterface {
     //计算两个数的和
     int addNum(int num1,int num2);
     //除了基本数据类型,其他类型的参数都需要标上方向类型:in(输入), out(输出), inout(输入输出)  
     List<Person> addPerson(in Person person);
     void registerListener(IOnNewPersonArrivedListener listener);
     void unregisterListener(IOnNewPersonArrivedListener listener);
}

IRemoteService代码

package com.wuc.aidltest;
public class IRemoteService extends Service {
    private static final String TAG = "IRemoteService";
    //存储注册监听客户端集合
    private final CopyOnWriteArrayList <IOnNewPersonArrivedListener> mListenerList = new CopyOnWriteArrayList <>();
    /**
     * CopyOnWriteArrayList支持并发读写,AIDL方法是在服务端的Binder线程池中执行的,因此当多个客户端同时连接的时候,
     * 会存在多个线程同时访问的情形,所以我们要在AIDL方法中处理线程同步,这里使用CopyOnWriteArrayList来进行自动的线程同步
     * <p>
     * 因为AIDL中所支持的是抽象的List,二List只是一个接口,因此虽然服务端返回的是CopyOnWriteArrayList,但是在Binder中
     * 会按照List的规范去访问数据并最终形成一个新的ArrayList传递给客户端,所以采用CopyOnWriteArrayList是可以的,类似的
     * 还有ConcurrentHashMap
     */
    private CopyOnWriteArrayList<Person> mPersonList;
    private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false);
    private IBinder mIBinder = new ICalculateInterface.Stub() {

        @Override
        public int addNum(int num1, int num2) throws RemoteException {
            return num1 + num2;
        }

        @Override
        public List<Person> addPerson(Person person) throws RemoteException {
            mPersonList.add(person);
            return mPersonList;
        }

        @SuppressLint("NewApi")
        @Override
        public void registerListener(IOnNewPersonArrivedListener listener) throws RemoteException {
           if (!mListenerList.contains(listener)) {
                mListenerList.add(listener);//添加监听
            } else {
                Log.d(TAG, "already exists.");
            }
            Log.d(TAG, "registerListener,size:" + mListenerList.size());
        }

        @SuppressLint("NewApi")
        @Override
        public void unregisterListener(IOnNewPersonArrivedListener listener) throws RemoteException {
           if (mListenerList.contains(listener)) {
                mListenerList.remove(listener);//移除监听
                Log.d(TAG, "unregister listener succeed.");
            } else {
                Log.d(TAG, "not found,can not unregister.");
            }
            Log.d(TAG, "unregisterListener,current size:" + mListenerList.size());
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        new Thread(new ServiceWorker()).start();
    }

    @Override
    public void onDestroy() {
        mIsServiceDestoryed.set(true);
        super.onDestroy();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        mPersonList = new CopyOnWriteArrayList<>();
        return mIBinder;
    }

    private void onNewPersonArrived(Person person) throws RemoteException {
        mPersonList.add(person);
        Log.d(TAG, "onNewPersonArrived, notify listener:" + mListenerList.size());
        for (int i = 0; i < mListenerList.size(); i++) {
            IOnNewPersonArrivedListener listener = mListenerList.get(i);
            Log.d(TAG, "onNewPersonArrived, notify listener:" + listener);
            listener.onNewPersonArrived(person);
        }
    }

    /**
     * 每个5秒增加一个新人,并通知所有感兴趣的员工
     */
    private class ServiceWorker implements Runnable {

        @Override
        public void run() {
            while (!mIsServiceDestoryed.get()) {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Person person = new Person("new person name:" + 2, 22);
                try {
                    onNewPersonArrived(person);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

MainActivity代码

package com.wuc.aidlclient;
public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";
    private static final int MESSAGE_NEW_PERSON_ARRIVED = 1;

    private AppCompatEditText mEdt_num1;
    private AppCompatEditText mEdt_num2;
    private AppCompatButton mBtn_calculate;
    private AppCompatTextView mTxt_result;


    private ICalculateInterface mICalculateInterface;
    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            if (mICalculateInterface == null) {
                return;
            }
            //移除之前绑定的代理并重新绑定远程服务
            mICalculateInterface.asBinder().unlinkToDeath(mDeathRecipient, 0);
            mICalculateInterface = null;
            bindService();
        }
    };
    private ClientHandler mHandler = new ClientHandler();
    /**
     * 当客户端发起远程请求时,由于当前线程会被挂起直至服务端进程返回数据,所以如果一个远程方法是很耗时的,
     * 则不能在UI 线程中发起此远程请求,为了避免阻塞UI 线程出现ANR
     * 由于服务端的Binder 方法运行在 Binder 的线程池中,所以 Binder 方法不管是否耗时都应该采用同步的方式去实现,
     * 因为它已经运行在一个线程中了
     */
    private IOnNewPersonArrivedListener mOnNewPersonArrivedListener = new IOnNewPersonArrivedListener.Stub() {
        @Override
        public void onNewPersonArrived(Person person) throws RemoteException {
            mHandler.obtainMessage(MESSAGE_NEW_PERSON_ARRIVED, person).sendToTarget();
        }
    };
    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //判断Binder是否死忙
            //boolean binderAlive = service.isBinderAlive();
            //用于将服务端的Binder对象转换为客户端需要的AIDL接口类型的对象
            mICalculateInterface = ICalculateInterface.Stub.asInterface(service);
            try {
                mICalculateInterface.registerListener(mOnNewPersonArrivedListener);
                //给binder设置死忙代理,当Binder死忙时就可以收到通知
                service.linkToDeath(mDeathRecipient, 0);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            //连接断开,释放AIDL Binder对象
            mICalculateInterface = null;
            Log.d(TAG, "binder died");
        }
    };

    @SuppressLint("CutPasteId")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mEdt_num1 = findViewById(R.id.edt_num1);
        mEdt_num2 = findViewById(R.id.edt_num2);
        mTxt_result = findViewById(R.id.txt_result);
        mBtn_calculate = findViewById(R.id.btn_calculate);

        mBtn_calculate.setOnClickListener(new View.OnClickListener() {
            @SuppressLint("SetTextI18n")
            @Override
            public void onClick(View v) {
                int num1 = Integer.parseInt(mEdt_num1.getText().toString());
                int num2 = Integer.parseInt(mEdt_num2.getText().toString());
                try {
                    int num = mICalculateInterface.addNum(num1, num2);
                    mTxt_result.setText("结果:" + num);
                } catch (RemoteException e) {
                    e.printStackTrace();
                    mTxt_result.setText("计算错误");
                }
                try {
                    List<Person> personList = mICalculateInterface.addPerson(new Person("wuc", 22));
                    Log.d("aidl", personList.toString());
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });

        bindService();
    }

    private void bindService() {
        Intent intent = new Intent();
        intent.setComponent(new ComponentName("com.wuc.aidltest",
                "com.wuc.aidltest.IRemoteService"));
        bindService(intent, conn, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        //如果连接持续,并且Binder未死亡
        if (mICalculateInterface != null && mICalculateInterface.asBinder().isBinderAlive()) {
            try {
                Log.d(TAG, "unregister listener : " + mOnNewPersonArrivedListener);
                mICalculateInterface.unregisterListener(mOnNewPersonArrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        unbindService(conn);
        super.onDestroy();
    }

    /**
     * 防止Handler泄漏
     */
    private static class ClientHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MESSAGE_NEW_PERSON_ARRIVED:
                    Log.d(TAG, "receive new person : " + msg.obj);
                    break;
                default:
                    super.handleMessage(msg);
                    break;
            }
        }
    }
}

Log日志

服务端(IRemoteService)
service_log.png

客户端
client_log.png

从日志中可以看出,在解注册的过程中,服务端找不到之前注册的listener;
原因
Binder 会把客户端传递过来的对象重新转化并生成一个新的对象,虽然我们在注册和解注册过程中使用的是同一个客户端,但是通过 Binder 传递到服务端后,却会产生两个全新的对象。而对象是不能跨进程传输的,对象的跨进程传输本质上都是反序列化的过程,这就是为什么 AIDL 中的自定义对象都必须要实现 Parcelable 接口的原因
解决办法
用RemoteCallbackList,RemoteCallbackList 是系统专门提供的用于删除跨进程 listener 的类,RemoteCallbackList 是一个泛型,支持管理任意的 AIDL 接口,从它的声明可以看出,因为所有的 AIDL 接口都继承自 IInteface 接口

修改后的IRemoteService

package com.wuc.aidltest;
public class IRemoteService extends Service {
    private static final String TAG = "IRemoteService";
    //存储注册监听客户端集合
    private final RemoteCallbackList<IOnNewPersonArrivedListener> mListenerList = new RemoteCallbackList<>();
    /**
     * CopyOnWriteArrayList支持并发读写,AIDL方法是在服务端的Binder线程池中执行的,因此当多个客户端同时连接的时候,
     * 会存在多个线程同时访问的情形,所以我们要在AIDL方法中处理线程同步,这里使用CopyOnWriteArrayList来进行自动的线程同步
     * <p>
     * 因为AIDL中所支持的是抽象的List,二List只是一个接口,因此虽然服务端返回的是CopyOnWriteArrayList,但是在Binder中
     * 会按照List的规范去访问数据并最终形成一个新的ArrayList传递给客户端,所以采用CopyOnWriteArrayList是可以的,类似的
     * 还有ConcurrentHashMap
     */
    private CopyOnWriteArrayList<Person> mPersonList;
    private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false);
    private IBinder mIBinder = new ICalculateInterface.Stub() {

        @Override
        public int addNum(int num1, int num2) throws RemoteException {
            return num1 + num2;
        }

        @Override
        public List<Person> addPerson(Person person) throws RemoteException {
            mPersonList.add(person);
            return mPersonList;
        }

        @SuppressLint("NewApi")
        @Override
        public void registerListener(IOnNewPersonArrivedListener listener) throws RemoteException {
            mListenerList.register(listener);
           /* if (!mListenerList.contains(listener)) {
                mListenerList.add(listener);
            } else {
                Log.d(TAG, "already exists.");
            }*/
            Log.d(TAG, "registerListener,size:" + mListenerList.getRegisteredCallbackCount());
        }

        @SuppressLint("NewApi")
        @Override
        public void unregisterListener(IOnNewPersonArrivedListener listener) throws RemoteException {
            mListenerList.unregister(listener);
           /* if (mListenerList.contains(listener)) {
                mListenerList.remove(listener);
                Log.d(TAG, "unregister listener succeed.");
            } else {
                Log.d(TAG, "not found,can not unregister.");
            }*/
            Log.d(TAG, "unregisterListener,current size:" + mListenerList.getRegisteredCallbackCount());
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        new Thread(new ServiceWorker()).start();
    }

    @Override
    public void onDestroy() {
        mIsServiceDestoryed.set(true);
        super.onDestroy();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        mPersonList = new CopyOnWriteArrayList<>();
        int check = checkCallingOrSelfPermission("com.wuc.aidlservice.permission.ACCESS_SERVICE");
        if (check == PackageManager.PERMISSION_DENIED) {
            return null;
        }
        return mIBinder;
    }

    private void onNewPersonArrived(Person person) throws RemoteException {
        mPersonList.add(person);
        /*Log.d(TAG, "onNewPersonArrived, notify listener:" + mListenerList.size());
        for (int i = 0; i < mListenerList.size(); i++) {
            IOnNewPersonArrivedListener listener = mListenerList.get(i);
            Log.d(TAG, "onNewPersonArrived, notify listener:" + listener);
            listener.onNewPersonArrived(person);
        }*/
        synchronized (mListenerList) {
            int n = mListenerList.beginBroadcast();
            try {
                for (int i = 0; i < n; i++) {
                    IOnNewPersonArrivedListener listener = mListenerList.getBroadcastItem(i);
                    if (listener != null) {
                        listener.onNewPersonArrived(person);
                    }
                }
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            mListenerList.finishBroadcast();
        }
    }

    /**
     * 每个5秒增加一个新人,并通知所有感兴趣的员工
     */
    private class ServiceWorker implements Runnable {

        @Override
        public void run() {
            while (!mIsServiceDestoryed.get()) {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Person person = new Person("new person name:" + 2, 22);
                try {
                    onNewPersonArrived(person);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

注: RemoteCallbackList 并不是一个List ,不能像 List 一样去操作它,遍历RemoteCallbackList 必须要以下面的方式进行,其中 beginBroadcast 和 finishBroadcast 必须配套使用,哪怕我们仅仅是想要获取 RemoteCallbackList 中的元素个数

 int n = mListenerList.beginBroadcast();
 try {
     for (int i = 0; i < n; i++) {
           IOnNewPersonArrivedListener listener = mListenerList.getBroadcastItem(i);
           if (listener != null) {
                listener.onNewPersonArrived(person);
           }
    }
 } catch (RemoteException e) {
        e.printStackTrace();
 }
 mListenerList.finishBroadcast();

注:
客户端远程调用服务端的方法,被调用的方法运行在服务端的 Binder 线程池中,同时客户端线程会被挂起,此时如果服务端方法执行比较耗时,则会导致客户端线程长时间阻塞在这里,如果此时客户端线程是 UI 线程,则会导致客户端ANR ,因此如果我们明确知道某个远程方法是耗时的,则要避免在客户端的 UI 线程中去访问远程方法。
由于客户端的 onServiceConnected 和 onServiceDisconnected 方法运行在 UI线程中,故也不可以在他们里面直接调用服务端的耗时方法。
服务端方法本身就运行在服务端的Binder 线程池中,故服务端的方法本身就可以进行大量的耗时操作,此时切记不要在服务端开线程去进行异步任务,除非你明确知道自己在干什么,否则不建议这么做

同理,当远程服务端需要调用客户端的 listener 中的方法时,被调用的方法运行在客户端的 Binder 池中,故我们同样不可以在服务端调用客户端耗时方法,比如针对 IRemoteService 的 onNewPersonArrived 方法,在它内部调用了客户端的 IOnNewPersonArrivedListener 中的 onNewPersonArrived 方法,如果客户端的这个 onNewPersonArrived 方法比较耗时的话,确保 IRemoteService 的onNewPersonArrived 运行在非 UI 线程中,否则将导致服务端无法响应

同时由于客户端的 IOnNewBookArrivedListener 中的 onNewBookArrived 方法运行在客户端的 Binder 池中,故不能在里面访问UI相关的内容,如要访问,请用Handler 切换到主线程

Binder是可能意外死亡的,这往往是由于服务端进程意外停止导致的,此时我们需要重新连接服务。
1、给Binder 设置DeathRecipient 监听,当Binder 死亡时,我们会收到 binderDied 方法的回调,在 binderDied 方法中我们可以重新绑定远程服务
2、在onServiceDisconnected 中重连远程服务
这两种方法的区别在于:onServiceDisconnected 在客户端的 UI 线程中被回调,而 binderDied 在客户端的Binder 线程池中被回调,即在binderDied 方法中我们不能访问 UI

如何在 AIDL 中使用权限验证功能?

  1. 在onBind 中进行验证,验证不通过直接返回null ,这样验证失败的客户端直接无法绑定服务,至于验证方式有很多种,比如使用permission 验证,使用这种验证方式,我们需要先在 AndroidMenifest 中声明所需的权限,比如:
<!--定义权限-->
    <permission
        android:name="com.wuc.aidlservice.permission.ACCESS_SERVICE"
        android:protectionLevel="normal"/>

定义权限后,在IRemoteService的onBinder中做权限验证

 @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        int check = checkCallingOrSelfPermission("com.wuc.aidlservice.permission.ACCESS_SERVICE");
        if (check == PackageManager.PERMISSION_DENIED) {
            return null;
        }
        return mIBinder;
    }

一个应用来绑定我们的服务时,会验证这个应用的权限,如果他没有使用这个权限,则onBind 方法就会直接返回 null,最终这个应用无法绑定到我们的服务,这样就达到了权限验证的效果,这种方法同样适用于 Messenger中。
如果我们自己内部的应用想要绑定到我们的服务中,只需在它的 AndroidMenifest 文件中采用如下方式使用 permission 即可

<uses-permission android:name="com.wuc.aidlservice.permission.ACCESS_SERVICE"/>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,924评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,781评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,813评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,264评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,273评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,383评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,800评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,482评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,673评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,497评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,545评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,240评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,802评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,866评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,101评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,673评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,245评论 2 341

推荐阅读更多精彩内容

  • 一、IPC简介 (1)IPC是Inter-Process Communication的缩写,含义为进程间通信或者跨...
    遥遥的远方阅读 7,189评论 0 3
  • Android开发艺术探索 第二章IPC机制 Linux中IPC通信方式?答:命名管道,共享内存,信号量(具体再细...
    方木Rudy阅读 1,075评论 0 2
  • 1.使用Bundle ----> 用于android四大组件间的进程间通信 android的四大组件都可使用B...
    小帝Ele阅读 1,509评论 2 6
  • 你独自一人爬在地上画画,口里喊着我,我走向你,六六也跟着过来,六六提议帮你画画,你说:好,有很多笔……不连贯,却依...
    想木音阅读 154评论 0 0
  • 这是以前刚开始学unity时遇到的一些问题的笔记,现在看来有些幼稚,但是还是发出来给刚入门的同学参考一下。 1. ...
    JervieQin阅读 1,332评论 0 1