欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android进程通信AIDL之数据监听,监听器移除

程序员文章站 2022-07-13 23:05:33
...

前言:抱着最起码的要求尽力去做好每一件事 ! ——秋不白

前言

Studio生成AIDL的代码编写,就不赘述了,要注意的是.aidl和对应的.java 文件的包名要保持一致。直蹦主题吧

 本博客中有些细节问题忽略了,代码中也注释说明,在不考虑业务耗时的情况下,实现AIDL进程通信,数据更新监听,注册监听器和移除监听器,Binder死亡监听,服务断开连接重连解决方案。实现业务:监听图书新增变化。

支持数据类型

  AIDL间通信比较简单,支持的数据类型除了基础数据类型,String CharSequence,其他定义的对象都要实现Parcelable,List只支持ArrayList,Map只支持HashMap,当然AIDL接口本身也是可以在AIDL文件中使用的。(实现AIDL中的数据更新监听)

实现功能描述

    1. 监听器的添加和移除,数据监听,数据更新监听都是进程间传递数据的过程,对象传递的过程有个共同的特点,当然我们知道,对象是无法直接跨进程传递的,所以实现了Parcelable,进过序列化,反序列化后就不是同一个对象了。那如何实现移除监听器呢?这些对象传递的过程中有什么共同的特点,来实现移除对应的监听器

     答案是:这些对象传递,多次跨进程传输客户端的对象在服务端生成不同的对象,它们底层的Binder对象死同一个,利用这个特性,就可以实现移除监听器。这里就要使用到RemoteCallbackList,这不是List,内部实现了线程同步,可以简单查看源码都加上了同步锁。

    2.另外还使用到的CopyOnWriteArrayList,注意它不是继承自ArrayList,同样支持并发读写。使用这个来存放Book,被监听的数据,前面提到只支持ArrayList,为什么呢,详情看AidlService中代码注释。

    3. 介绍第二种,服务断开重连的解决方案。(未实践,主要是理解这种方法) 使用CountDownLatch这个类,countDownLatch是一个计数器,线程完成一个记录一个,计数器递减,只能只用一次。(常用的是在onServiceDisconnected()中去处理服务重连的问题)。

   文字描述结束

   Activity(部分代码,客户端)

 private ServiceConnection aidlConn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            iBookManager = IBookManager.Stub.asInterface(service);
        }

        /**
         * 服务断开重连解决方案一
         */
        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(aidlConn);
        /** 移除新书到达监听*/
        try {
            if (iBookManager != null) {
                iBookManager.unregisterListener(mOnNewBookArrivedListener);
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    private IOnNewBookArrivedListener mOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() {
        @Override
        public void onNewBookArrived(Book book) throws RemoteException {
            Log.d("RedRose", " 新书已到达");
        }
    };

    public void openAidl(View view) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                connectBinderService();
            }
        }).start();
    }

    /**
     * 服务断开重连解决方案二
     * 这部分代码未实践
     */
    private synchronized void connectBinderService() {
        /** 线程计数器*/
        mConnectBinderCountDownLatch = new CountDownLatch(1);
        Intent intent = new Intent(this, AidlService.class);
        bindService(intent, aidlConn, BIND_AUTO_CREATE);
        try {
            mConnectBinderCountDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private IBinder.DeathRecipient mBinderDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            /** 对Binder 断开 重练回调*/
            Log.d("RedRose", "binder died");
            iBookManager.asBinder().unlinkToDeath(mBinderDeathRecipient, 0);
            iBookManager = null;
            connectBinderService();
        }
    };

    /**
     * 其实这样写是有问题的,因为在服务端注册和取消注册是运行在Binder线程池,如果远程调用方法
     * 存在耗时,会导致主线程ANR
     * 应该放到线程池去执行,这里只是侧重点是数据更新监听和取消注册
     */
    public void unregisterListener(View view) {
        if (iBookManager != null) {
            try {
                iBookManager.unregisterListener(mOnNewBookArrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }

    public void registerListener(View view) {
        if (iBookManager != null) {
            try {
                iBookManager.registerListener(mOnNewBookArrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }

    public void addBook(View view) {
        try {
            if (iBookManager != null) {
                iBookManager.addBook(new Book());
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    public void getBookList(View view) {
        try {
            if (iBookManager != null) {
                iBookManager.getBookList();
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

       Book.aidl和IBookManager.aidl

// Book.aidl
package com.redrose.aidldemo;

parcelable Book;
---------------------------------------------------------------
// IBookManager.aidl
package com.redrose.aidldemo;

// Declare any non-default types here with import statements
import com.redrose.aidldemo.Book;
import com.redrose.aidldemo.IOnNewBookArrivedListener;
interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
    void registerListener(IOnNewBookArrivedListener listener);
    void unregisterListener(IOnNewBookArrivedListener listener);
}
-------------------------------------------------------------------
// IOnNewBookArrivedListener.aidl
package com.redrose.aidldemo;

// Declare any non-default types here with import statements
import com.redrose.aidldemo.Book;
interface IOnNewBookArrivedListener {
   void onNewBookArrived(in Book book);
}

AidlService(服务端  为了更好体现进程通信,请在清单文件指定service的进程),可以查下RemoteCallbackList的使用方法

package com.redrose.aidldemo;

import android.app.Service;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class AidlService extends Service {
    /**
     * Aidl 中使用的List只有ArrayList
     * 但是CopyOnWriteArrayList 并不是继承ArrayList
     * 因为AIDL中支持的是抽象的List,而List只是一个接口
     * 因此服务端返回的是CopyOnWriteArrayList,但是在Binder中会按照
     * List的规范去访问数据并最终形成一个新的ArrayList传递给客户端
     */
    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
    //使用CopyOnWriteArrayList 来存放监听器是无法实现的,进程间通信传递的不是同一个对象,最后导致无法找到,而无法正确移除
    //需要使用RemoteCallbackList,因为多次跨进程传输客户端的同一个对象在服务端生成不同的对象,但是他们底层的Binder对象
    //是同一个,所以就很明了了。 RemoteCallbackList内部同样也实现了线程同步的功能,可以查看源码
//    private CopyOnWriteArrayList<IOnNewBookArrivedListener> mListenerList = new CopyOnWriteArrayList<>();
    private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<>();


    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        return mBookBinder;
    }

    private IBinder mBookBinder = new IBookManager.Stub() {
        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBookList;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            mBookList.add(book);
            onNewBookArray(book);
        }

        @Override
        public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
            Log.d("RedRose", " --registerListener--");
            mListenerList.register(listener);
            final int num = mListenerList.beginBroadcast();
            mListenerList.finishBroadcast();
            Log.d("RedRose", " mListenerList size = " + num);

        }

        @Override
        public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
            Log.d("RedRose", " --unregisterListener--");
            mListenerList.unregister(listener);
            final int num = mListenerList.beginBroadcast();
            mListenerList.finishBroadcast();
            Log.d("RedRose", " mListenerList size = " + num);
        }


        //在Binder被调用 绑定时调用,用作判断是否具有权限
        @Override
        public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {

            //做权限判断,没有权限就返回null

            return super.onTransact(code, data, reply, flags);
        }
    };

    private void onNewBookArray(Book book) throws RemoteException {
        final int num = mListenerList.beginBroadcast();
        for (int i = 0; i < num; i++) {
            IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i);
            if (listener != null) {
                listener.onNewBookArrived(book);
            }
        }
        mListenerList.finishBroadcast();
    }
}

AIDL进程通信之数据监听,监听和移除总结实例就到这里了,写的比较简洁,菜鸟肯定还需要多看下AIDL方面的知识点。老司机,勿喷。