• IPC中的AIDL机制(二)


    在了解了AIDL的流程及基本原理之后,我们还需要对AIDL有进一步的了解。
    在上一篇的例子的基础之上,我们考虑另一种情况:
    假设有一种需求:用户不想时不时地去查询图书列表了,太累了,于是,他去问图书馆,“当有新书时能不能把书的信息告诉我呢?”。
    大家应该明白了,这就是一种典型的观察者模式,每个感兴趣的用户都观察新书,当新书到的时候,图书馆就通知每一个对这本书感兴趣的用户,这种模式在实际开发中用得很多,下面我们就来模拟这种情形。

    首先,我们需要提供一个AIDL接口,每个用户都需要实现这个接口并且向图书馆申请新书的提醒功能,当然用户也可以随时取消这种提醒。之所以选择AIDL接口而不是普通接口,是因为AIDL中无法使用普通接口。这里我们创建一个IOnNewBookArrivedListener.aidl文件,我们所期望的情况是:当服务端有新书到来时,就会通知每一个己经申请提醒功能的用户。从程序上来说就是调用所有IOnNewBookArivedListener对象中的onNewBookArived方法,并把新书的对象通过参数传递给客户端,内容如下所示。

    // IOnNewBookArrivedListener.aidl
    package com.liuguilin.ipcsample;
    
    import com.liuguilin.ipcsample.Book;
    
    interface IOnNewBookArrivedListener {
    
       void onNewBookArrived(in Book newBook);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    除了要新增加一个AIDL外,还需要在原有的接口中添加两个新的方法

    // IBookManager.aidl
    package com.liuguilin.ipcsample;
    
    import com.liuguilin.ipcsample.Book;
    import com.liuguilin.ipcsample.IOnNewBookArrivedListener.aidl;
    
    interface IBookManager {
    
        ListgetBookList();
        void addBook(in Book book);
        void registerListener(IOnNewBookArrivedListener listener);
        void unregisterListener(IOnNewBookArrivedListener listener);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    接下来,我们的服务端也是要稍微的修改一下,每隔5s向感兴趣的用户提醒

    public class BookManagerService extends Service {
        private static final String TAG = BookManagerService.class.getSimpleName();
        private CopyOnWriteArrayList mBookList = new CopyOnWriteArrayList<>();
        private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false);
    
        private RemoteCallbackList mListenerList = new RemoteCallbackList<>();
    
        private Binder mBinder = new IBookManager.Stub(){
    
            @Override
            public List getBookList() throws RemoteException {
                return mBookList;
            }
    
            @Override
            public void addBook(Book book) throws RemoteException {
                mBookList.add(book);
            }
    
            @Override
            public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
                mListenerList.register(listener);
            }
    
            @Override
            public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
                mListenerList.unregister(listener);
            }
        };
    
        @Override
        public void onCreate() {
            super.onCreate();
            mBookList.add(new Book(1,"Android"));
            mBookList.add(new Book(2,"Ios"));
            new Thread(new ServiceWorker()).start();
        }
    
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return mBinder;
        }
    
        @Override
        public void onDestroy() {
            super.onDestroy();
        }
    
        private class ServiceWorker implements Runnable{
    
            @Override
            public void run() {
                while (!mIsServiceDestoryed.get()){
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    int bookId = mBookList.size()+1;
                    Book newBook = new Book(bookId,"new book#" + bookId);
                    try {
                        onNewBookArrived(newBook);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        private void onNewBookArrived(Book book) throws RemoteException{
            mBookList.add(book);
            final int N = mListenerList.beginBroadcast();
            for (int i = 0; i < N; i++) {
                IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i);
                if(l != null){
                    l.onNewBookArrived(book);
                }
            }
            mListenerList.finishBroadcast();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82

    最后,我们还需要修改一下客户端的代码,主要是两方面,首先是客户端要注册IOnNewBookArrivedListener到远程的服务器,这样当有新书时服务端才能通知客户端,同时在我们的Activity的onDestory方法里面去取消绑定,,另一方面,当有新书的时候,服务端会会带哦客户端的Binder线程池中执行,因此,为了便于进行UI操作,我们需要有一个Hnadler可以将其切换到客户端的主线程去执行,这个原理在Binder中已经做了分析了,这里不多说,把代码贴上:

    public class BookManagerActivity extends AppCompatActivity {
        private static final String TAG = BookManagerActivity.class.getSimpleName();
        private static final int MESSAGE_NEW_BOOK_ARRIVED = 0x100;
    
        private IBookManager mRemoteBookManager;
        private Handler mHandler = new Handler(){
            @Override
            public void handleMessage(@NonNull Message msg) {
                switch (msg.what){
                    case MESSAGE_NEW_BOOK_ARRIVED:
                        Log.d(TAG,"receive the book:"+msg.obj);
                        break;
                    default:
                        break;
                }
            }
        };
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_bookmanager);
    
            Intent intent = new Intent(this, BookManagerService.class);
            bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            if(mRemoteBookManager!=null && mRemoteBookManager.asBinder().isBinderAlive()){
                try {
                    Log.d(TAG,"unregister listener:" + mOnNew);
                    mRemoteBookManager.unregisterListener(mOnNew);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
            unbindService(mConnection);
        }
    
        private ServiceConnection mConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                IBookManager bookManager = IBookManager.Stub.asInterface(service);
                try {
                    mRemoteBookManager = bookManager;
                    List list = bookManager.getBookList();
                    Log.i(TAG, "list string : " + list.toString());
                    Book newBook = new Book(3,"Android进阶");
                    bookManager.addBook(newBook);
                    bookManager.registerListener(mOnNew);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
                mRemoteBookManager = null;
                Log.e(TAG,"binder died.");
            }
        };
    
        private IOnNewBookArrivedListener mOnNew = new com.cmnit.ipc.IOnNewBookArrivedListener.Stub() {
            @Override
            public void onNewBookArrived(Book newBook) throws RemoteException {
                mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED,newBook).sendToTarget();
            }
        };
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    1. 客户端注册IOnNewBookArrivedListener到远程服务端,这样当有新书时服务端才能通知当前客户端,同时我们要在Activity退出时解除这个注册;另一方面,当有新书时,服务端会回调客户端的IOnNewBookArrivedListener对象中的onNewBookArrived方法,但是这个方法是在客户端的Binder线程池中去执行的,因此便于UI操作,我们需要有一个Handler可以将其切换到主线程中去执行。
    特别说明

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

    同理,当远程服务需要调用客户端的listener中的方法时,被调用的方法也运行在Binder线程池中,只不过是客户端的Binder线程池。所以,我们同样不可以在服务端中调用客户端的耗时方法。比如针对BookManagerService的onNewBookArrived方法,在它内部调用了客户端的IOnNewBookArrivedListener中的onNewBookArrived方法,如果客户端的这个onNewBookArrived方法比较耗时的话,那么请确保BookManagerService中的onNewBookArrived运行在非UI线程中,否则将导致服务端无法响应。

     private void onNewBookArrived(Book book) throws RemoteException{
            mBookList.add(book);
            final int N = mListenerList.beginBroadcast();
            for (int i = 0; i < N; i++) {
                IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i);
                Log.d(TAG,"onNewBookArrived,notify listener:"+l);
                if(l != null){
                    l.onNewBookArrived(book);
                }
            }
            mListenerList.finishBroadcast();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    另外,由于客户端的IOnNewBookArrivedListener中的onNewBookArrived方法运行在客户端的Binder线程池中,所以不能在里面进行UI更新。

    断开重连机制

    Binder是可能意外死亡的,这往往是由于服务端进程意外终止了,这是我们需要重新连接服务。有两种方法:

    • 给Binder设置DeathRecipient监听

    当Binder死亡时,我们会收到binderDied的回调,在binderDied方法中我们可以重连远程服务。

    1. 首先,声明一个Deathleciient对象、Deathleciient是一个接口,其内部只有一个方法binderDied,我们需要实现这个方法,当Binder死亡的时候,系统就会回调binderDied方法,然后我们就可以移除之前绑定的binder代理并重新绑定远程服务;
    ### BookManagerActivity.java
    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
            @Override
            public void binderDied() {
                if(mBookManager == null){
                    return;
                }
                mBookManager.asBinder().unlinkToDeath(mDeathRecipient,0);
                mBookManager = null;
                //这个可以重新绑定远程service
                bindService();
            }
        };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    1. 在客户端绑定远程服务成功之后,给binder设置死亡代理;
     ### BookManagerActivity.java
     @Override
     public void onServiceConnected(ComponentName name, IBinder service) {
    	IBookManager bookManager = IBookManager.Stub.asInterface(service);
    	service.linkToDeath(mDeathRecipient,0);
    	...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 在onServiceDisconnected中重连远程服务

    区别在于,onServiceDisconnected在客户端的UI线程中被回调,而binderDied在服务端的Binder线程池中被回调。

    权限验证

    默认情况下,我们的远程服务任何人都可以接入,但这不是我们想看到的,所以我们必须加入权限验证功能,权限验证失败将无法调用服务中的方法。

    • 在服务端的onBinder中进行验证
    • 在服务端的onTransact方法中进行验证

    首先需要在服务端的 AndroidMenifest 文件中声明所需权限:

    
    
    
    • 1
    • 2

    客户端 AndroidManifest.xml 中添加权限:

    
    
    • 1

    第一种方法:

    public IBinder onBind(Intent t) {
             // 远程调用的验证方式
            int check = checkCallingPermission("com.example.zs.ipcdemo.permission.ACCESS_BOOK_SERVIC"); 
            if (check == PackageManager.PERMISSION_DENIED) {
                // 权限校验失败
                 return null;
            } 
            return mBinder;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    第二种方法:

     // 权限校验
            private boolean checkPermission(Context context, String permName, String pkgName) {
                PackageManager pm = context.getPackageManager();
                if (PackageManager.PERMISSION_GRANTED == pm.checkPermission(permName, pkgName)) {
                    return true;
                } else {
                    return false;
                }
    	}
    
     @Override
            public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
                // 权限校验
                String packageName = null;
                String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
                if (packages != null && packages.length > 0) {
                    packageName = packages[0];
                }
                if (packageName == null) {
                    return false;
                }
                boolean checkPermission = checkPermission(getApplication(),
                        "com.example.zs.ipcdemo.permission.ACCESS_BOOK_SERVIC", packageName);
                return checkPermission && super.onTransact(code, data, reply, flags);
            }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
  • 相关阅读:
    HCIP 实验作业(ppp实验)
    thinkphp6项目使用多应用开发
    Python配置与测试利器:Hydra + pytest的完美结合
    【Rust日报】2023-10-19 使用 Rust 编写编译器
    Taro 小程序开启wxml代码压缩
    SSM保险办理系统毕业设计源码012232
    【每日一题】中缀表达式计算详解(基本计算器 II,表达式计算4)
    Linux环境变量
    vue3总结(未完~)
    Jmeter之连接数据库的使用步骤有哪些?
  • 原文地址:https://blog.csdn.net/jxq1994/article/details/127794317