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);
}

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

// IBookManager.aidl
package com.liuguilin.ipcsample;

import com.liuguilin.ipcsample.Book;
import com.liuguilin.ipcsample.IOnNewBookArrivedListener.aidl;

interface IBookManager {

    List<Book>getBookList();
    void addBook(in Book book);
    void registerListener(IOnNewBookArrivedListener listener);
    void unregisterListener(IOnNewBookArrivedListener listener);
}

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

public class BookManagerService extends Service {
    private static final String TAG = BookManagerService.class.getSimpleName();
    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
    private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false);

    private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<>();

    private Binder mBinder = new IBookManager.Stub(){

        @Override
        public List<Book> 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();
    }
}

最后,我们还需要修改一下客户端的代码,主要是两方面,首先是客户端要注册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<Book> 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. 客户端注册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();
    }

另外,由于客户端的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. 在客户端绑定远程服务成功之后,给binder设置死亡代理;
 ### BookManagerActivity.java
 @Override
 public void onServiceConnected(ComponentName name, IBinder service) {
	IBookManager bookManager = IBookManager.Stub.asInterface(service);
	service.linkToDeath(mDeathRecipient,0);
	...
}
  • 在onServiceDisconnected中重连远程服务

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

权限验证

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

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

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

<permission android:name="com.example.zs.ipcdemo.permission.ACCESS_BOOK_SERVIC" android:protectionLevel="normal" />
<uses-permission android:name="com.example.zs.ipcdemo.permission.ACCESS_BOOK_SERVIC" />

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

<uses-permission android:name="com.example.zs.ipcdemo.permission.ACCESS_BOOK_SERVIC" />

第一种方法:

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;
    }

第二种方法:

 // 权限校验
        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);
        }

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值