美文网首页Android学习之旅Android开发经验谈Android技术知识
Android学习之旅-使用 AIDL 实现进程间通信03[艺术

Android学习之旅-使用 AIDL 实现进程间通信03[艺术

作者: TengFeiGo | 来源:发表于2018-12-11 15:08 被阅读1次

    本篇文章重点在于以下几点:

    1.如何给Binder设置死亡代理
    2.自定义使用 AIDL 实现跨进程通信权限校验
    3.使用观察者模式监听服务端变化

    给Binder设置死亡代理

    由于 Binder 运行在服务端进程,所以当服务端进程因为异常原因终止了就会导致 Binder 连接失败,当出现这种情况时我们的客户端没法在去调用远程服务,所以需要设置一个“死亡代理”,用于在监听到 binder 连接断裂的时候重新绑定服务,这需要用到 unlinkToDeath 和 linkToDeath ,代码几乎是模版代码,不需要刻意的注意细节,会用就可以了。

     private IBookManager mRemoteBookManager;
    
        /**
         * binder死亡重连机制
         */
     private IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
            @Override
            public void binderDied() {
                if (mRemoteBookManager == null) {
                    return;
                }
                mRemoteBookManager.asBinder().unlinkToDeath(deathRecipient, 0);
                mRemoteBookManager = null;
                Intent intent = new Intent(AIDLActivity.this, BookManagerService.class);
                bindService(intent, connection, Context.BIND_AUTO_CREATE);
            }
        };
     private ServiceConnection connection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                IBookManager iBookManager = IBookManager.Stub.asInterface(service);
                try {
                    service.linkToDeath(deathRecipient,0);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
    
            }
        };
    

    在与远程服务绑定成功后也就是回调 onServiceConnected 方法时调用 IBinder 的 linkToDeath 给 binder 设置死亡代理,参数一是定义的 IBinder.DeathRecipient ,参数二是一个标志位,直接写 0 就可以了,重写 DeathRecipient 的 binderDied 方法,在 Binder 连接断裂的时候会回调此方法,在此方法里重新绑定服务就可以了,在此做个记录,以后也许可以用到,就算忘了也能快速的捡起来。

    自定义使用 AIDL 实现跨进程通信权限校验

    客户端与服务端的权限校验主要用到了服务端 binder 的 onTransact 方法,此方法如果返回 false 那么客户端请求就会失败,重写此方法来进行权限校验,步骤如下:
    1.在服务端项目中自定义权限,表明客户端如果需要访问服务端,那么客户端需要被授予这个权限:

     <permission
            android:name="com.example.tengfei.androidcrossprocess.aidldemo.BookManagerService"
            android:protectionLevel="normal" />
    

    2.在客户端项目中定义权限:

     <uses-permission android:name="com.example.tengfei.androidcrossprocess.aidldemo.BookManagerService" />
    

    3:重写 onTransact 方法,如果返回 false 表示连接失败,同样也是模版代码,理解以下就可以了:

     @Override
     public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
            int check = checkCallingOrSelfPermission("com.example.tengfei.androidcrossprocess.aidldemo.BookManagerService");
            if (check == PackageManager.PERMISSION_DENIED) {
                    return false;
                }
                String packageName = null;
                String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
                if (packages != null && packages.length > 0) {
                    packageName = packages[0];
                }
                if (packageName != null && !packageName.startsWith("com.example.tengfei")) {
                    return false;
                }
                return super.onTransact(code, data, reply, flags);
            }
    

    使用观察者模式监听服务端变化

    假设有这么一个需求,服务端每隔 3 秒钟更新一次数据,每次更新的时候通知连接它的客户端更新界面 UI ,注意绑定这个服务端的客户端不止一个,那么这个时候该怎么来呢?
    定义三个 aidl 文件,注意定义的格式,这在前面文章已经讲过,分别为:

    package com.example.tengfei.client;
    import com.example.tengfei.client.NumberEntity;
    import com.example.tengfei.client.IListener;
    
    interface IBindEntity {
    
        void registerListener(IListener listener);
    
        void unRegisterListener(IListener listener);
    }
    
    package com.example.tengfei.client;
    import com.example.tengfei.client.NumberEntity;
    
    interface IListener {
    
        void onNewInFoArrived(in NumberEntity numberEntity);
    }
    
    package com.example.tengfei.client;
    
    
    parcelable NumberEntity;
    

    IBindEntity.aidl 定义了两个方法主要用于客户端的注册和解绑,新定义一个 IListener.aidl 文件用于通知所有绑定的客户端数据的更新,而 NumberEntity.aidl 在之前已经讲过,所有跨进程通信的实体类数据必须定义类似 NumberEntity.aidl 的 aidl 文件,定义完 aidl 文件后将这些类原封不动的复制到服务端,注意类的路径必须与客户端的包路径是一致的,包括所需要用到的实体类的包路径也必须一致,否则会报错。
    客户端代码:

    public class MainActivity extends AppCompatActivity {
    
        private static final String REMOTE_ACTION = "com.example.tengfei.server.MyService";
        private static final String REMOTE_PACKAGE_NAME = "com.example.tengfei.server";
    
        private static final int REMOTE_MESSAGE_TAG = 0x001;
    
        private IBindEntity iBindEntity;
    
        private TextView mTextView;
    
        private static final String TAG = "MainActivityTag";
    
        @SuppressLint("HandlerLeak")
        private Handler handler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                if (msg.what == REMOTE_MESSAGE_TAG) {
                    NumberEntity numberEntity = (NumberEntity) msg.obj;
                    mTextView.setText(String.valueOf(numberEntity.number));
                }
            }
        };
    
        private IListener listener = new IListener.Stub() {
            @Override
            public void onNewInFoArrived(NumberEntity numberEntity) {
                Log.i(TAG,"onNewInFoArrived"+numberEntity.number);
                handler.obtainMessage(REMOTE_MESSAGE_TAG, numberEntity).sendToTarget();
            }
        };
    
        private IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
            @Override
            public void binderDied() {
                if (iBindEntity == null) {
                    return;
                }
                //解除死亡代理
                iBindEntity.asBinder().unlinkToDeath(deathRecipient, 0);
                iBindEntity = null;
                Intent intent = new Intent();
                intent.setAction(REMOTE_ACTION);
                intent.setPackage(REMOTE_PACKAGE_NAME);
                bindService(intent, connection, Context.BIND_AUTO_CREATE);
            }
        };
    
        private ServiceConnection connection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                iBindEntity = IBindEntity.Stub.asInterface(service);
                try {
                    //设置死亡代理
                    service.linkToDeath(deathRecipient, 0);
                    Log.i(TAG,"onServiceConnected");
                    iBindEntity.registerListener(listener);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
    
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mTextView = findViewById(R.id.tv_client);
            findViewById(R.id.bt_client).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent intent = new Intent();
                    intent.setAction(REMOTE_ACTION);
                    intent.setPackage(REMOTE_PACKAGE_NAME);
                    bindService(intent, connection, Context.BIND_AUTO_CREATE);
                }
            });
    
        }
    
        @Override
        protected void onDestroy() {
            unbindService(connection);
            if (iBindEntity != null && iBindEntity.asBinder().isBinderAlive()) {
                try {
                    iBindEntity.registerListener(listener);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
            super.onDestroy();
        }
    }
    
    

    实体类 NumberEntity 只定义了一个 int 值的 number 并且实现了序列化,这里就不再copy代码了,让我来一步步的理清客户端代码,在客户端创建 ServiceConnection 对象,在 onServiceConnected 方法中调用 registerListener 方法进行绑定的操作,创建 IListener 接口类型的对象,每一次服务端数据更新的时候都会回调 onNewInFoArrived 方法,由于 onNewInFoArrived 运行在客户端 binder 线程池中,所以需要 handler 将线程切换到 UI 线程中去进行界面 UI 的更新。
    服务端代码:

    public class MyService extends Service {
    
        private static final String TAG = "MyServiceTag";
        private RemoteCallbackList<IListener> remoteCallbackList = new RemoteCallbackList<>();
        private AtomicBoolean mServiceIsWork = new AtomicBoolean(false);
    
        private IBinder iBinder = new IBindEntity.Stub() {
            @Override
            public void registerListener(IListener listener) throws RemoteException {
                Log.i(TAG, "server binder current thread # " + Thread.currentThread().getName());
                remoteCallbackList.register(listener);
            }
    
            @Override
            public void unRegisterListener(IListener listener) throws RemoteException {
                remoteCallbackList.unregister(listener);
            }
        };
    
        @Override
        public IBinder onBind(Intent intent) {
            return iBinder;
        }
    
        @Override
        public void onCreate() {
            super.onCreate();
            Log.i(TAG, "server current thread # " + Thread.currentThread().getName());
            new Thread(new ServiceWorker()).start();
        }
    
        private class ServiceWorker implements Runnable {
            int j = 0;
    
            @Override
            public void run() {
                while (!mServiceIsWork.get()) {
    //                Log.i(TAG,"ServiceWorker");
                    j++;
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    final int n = remoteCallbackList.beginBroadcast();
                    for (int i = 0; i < n; i++) {
                        NumberEntity numberEntity = new NumberEntity(j);
                        IListener iListener = remoteCallbackList.getBroadcastItem(i);
                        try {
                            iListener.onNewInFoArrived(numberEntity);
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }
                    remoteCallbackList.finishBroadcast();
                }
            }
        }
    
        @Override
        public void onDestroy() {
            mServiceIsWork.set(true);
            super.onDestroy();
        }
    }
    
    

    在服务端中使用 RemoteCallbackList 来维护所有客户端的 IListener ,主要是因为在跨进程传输中,binder会把服务端传输过来的对象重新反序列化为一个新的对象,所以实际上在客户端和服务端操作的对象实际上是两个不同对象,所以在涉及到跨进程传输使用观察者模式将数据反馈给客户端的时候就需要用到 RemoteCallbackList ,因为两个不用进程实际操作的是两个不同对象,所以在使用常规的方法比如说List 来维护 Listener 对象,就会遇到这么一个问题,无论你在服务端进程还是客户端进程,你删除的 Listener 对象仅仅只是当前进程的 Listener 对象,原因在上面已经说清楚了,这时候就需要用到 RemoteCallbackList ,RemoteCallbackList 是系统提供的专门用于删除跨进程的 Listener 对象的接口,尽管跨进程传输在客户端和服务端会生成不同的对象但是它们的底层binder是同一个,当需要从 RemoteCallbackList 中移除 Listener 时只需要遍历所有的 Listener 并将具有相同 binder 对象的 Listener 给移除掉,这就是RemoteCallbackList 帮助开发者所做的事情。
    创建 IBinder 对象,并在 registerListener 和 unRegisterListener 方法中分别执行注册和解绑的操作,在 onCreate 方法中创建一个子线程,实现每隔三秒钟遍历每一个客户端的listener并回调 onNewInFoArrived 方法,将最新的数据回调给客户端,注意通过 RemoteCallbackList 的 beginBroadcast()来获得所有客户端的Listener的,并且必须在与 beginBroadcast 对应的地方执行 finishBroadcast。
    相关代码 github 地址:https://github.com/TengfeiGo/Android-HotYong/tree/master/01_AndroidCrossProcess/client

    参考资料

    1.Android开发艺术探索

    相关文章

      网友评论

        本文标题:Android学习之旅-使用 AIDL 实现进程间通信03[艺术

        本文链接:https://www.haomeiwen.com/subject/pborhqtx.html