IPC机制<二>AIDL

上次讲到IPC机制的几种通信方式,但是没有讲完,还剩下几种方式没有讲,今天继续。

在上次讲到Service的时候,说过AIDL,但是那种只是非常简单的方式,今天将会详细的讲述AIDL。

一、AIDL 中的数据类型

aidl支持的数据类型:

  1. 基本数据类型(int,long,char,boolean,double等)
  2. String和CharSequence
  3. List:只支持ArrayList,里面的每个元素都必须能够被AIDL支持。
  4. Map:只支持HashMap,里面的每个元素都必须能够被AIDL支持
  5. Parcelable:所有实现了Parcelable接口的对象。
  6. AIDL:所有的AIDL接口本身也可以在AIDL中使用。

注意:

  1. 自定义的Parcelable对象和AIDL对象必须要显示import进来,不管他们是否在同一个包中。

    package cn.demo.zx_aidl_learn;
    
    import cn.demo.zx_aidl_learn.domain.Book;
    
    interface IService {
        List<Book>getBookList();
        void addBook(in Book b);
    }
    
  2. 如果AIDL文件中用到了自定义的Parcelable对象,那么必须新建一个和它同名的AIDL文件,并在其中声明为parcelable类型。

  3. 在AIDL中处理基本数据类型,其他类型的参数必须标上方向:in,out或者inout。in表示输入型参数,out表示输出型参数,inout表示输入输出型参数。
  4. AIDL接口中只支持方法不支持声明静态常量。
  5. AIDL的包结构在服务端和客户端要保持一致,否则运行会出错,这是因为客户端需要反序列化服务端中和AIDL接口相关的所有类,如果类的完整路径不一样的话,就无法成功反序列化。

案例:

服务端:

public class BookManagerService extends Service {

    /**
     * 使用CopyOnWriteArrayList的原因:
     * 因为CopyOnWriteArrayList支持并发读/写,AIDL方法是在服务端的Binder的线程池中执行的,
     * 因此当多个客户端同时连接的时候,会存在多个线程同时访问的情形,所以我们要在AIDL方法中
     * 处理线程同步,而我们这里直接使用CopyOnWriteArrayList来进行自动的线程同步。
     *
     * AIDL中能够使用的List只有ArrayList,但是这里为什么还使用它呢?
     * 因为AIDL中所支持的是抽象的List,而List只是一个接口,因此虽然服务端返回的是CopyOnWriteArrayList,
     * 但是在Binder中会按照List的规范去访问数据并最终返回一个新的ArrayList传递给客户端。还有就是类似的是
     * ConcurrentHashMap.
     */
    CopyOnWriteArrayList<Book>mBookList = new CopyOnWriteArrayList<>();

    public BookManagerService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    @Override
    public void onCreate() {
        super.onCreate();

        mBookList.add(new Book(1,"android"));
        mBookList.add(new Book(2,"ios学习"));

    }

    private Binder mBinder = new IService.Stub(){

        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBookList;
        }

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

客户端:

public class MainActivity extends AppCompatActivity {

    private IService mService = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Intent intent = new Intent(this,BookManagerService.class);
        startService(intent);

        bindService(intent,conn,BIND_AUTO_CREATE);
    }

    public void findBook(View v){
    /**
     * 注意:服务端的方法有可能是耗时的方法,那么下面的调用就有可能早能ANR异常,需要注意。
     */
        try {
            List<Book> bookList = mService.getBookList();
            if(bookList!=null){
                for(Book b : bookList){
                    System.out.println("书籍::"+b.toString());
                }
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mService = IService.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };
}

清单文件AndroidMenifest:

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    <activity android:name=".MainActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN"/>

            <category android:name="android.intent.category.LAUNCHER"/>
        </intent-filter>
    </activity>

    <service
        android:name=".BookManagerService"
        android:enabled="true"
        android:exported="true"
        android:process=":romote">
    </service>
</application>

上述案例只是讲述客户端从服务端获取图书信息,如果是在服务端每添加一本书籍就向客户端发送消息,那么怎么实现呢?

这里就会用到观察者模式。

二、AIDL中的观察者模式

创建一个aidl接口,每个用户都需要实现这个接口并且向图书馆申请新书的提醒功能。

新创建的aidl文件:

import cn.demo.zx_aidl_learn.domain.Book;
interface IOnNewBookArrivedListener {
    void onNewBookArrived(in Book newBook);
}


import cn.demo.zx_aidl_learn.domain.Book;
import cn.demo.zx_aidl_learn.IOnNewBookArrivedListener;

interface IService {
    List<Book>getBookList();
    void addBook(in Book b);

    void registerListener(in IOnNewBookArrivedListener listener);
    void unregisterListener(in IOnNewBookArrivedListener listener);
}

在MainActivity中创建一个IOnNewBookArrivedListener对象,然后通过注册的方式将IOnNewBookArrivedListener对象传递给服务端:

private ServiceConnection conn = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        mService = IService.Stub.asInterface(service);
        try {
            /**
             * 注册对新书的监控
             */
            mService.registerListener(listener);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {

    }
};

private IOnNewBookArrivedListener listener = new IOnNewBookArrivedListener.Stub() {
    @Override
    public void onNewBookArrived(Book newBook) throws RemoteException {
        System.out.println("新到的图书::"+newBook.toString());
        mHandler.obtainMessage(0,newBook).sendToTarget();
    }
};

服务端通过获取传入的IOnNewBookArrivedListener对象来调用客户端中的方法来通知客户端:

/**
 * 发送新书已到的消息到客户端
 * 怎样通知呢?
 * 通过客户端在主持监听的时候传入的IOnNewBookArrivedListener对象来通知客户端
 * @param newBook
 */
private void sendNewBookArrivedToClient(Book newBook){
    mBookList.add(newBook);
    for(int i=0;i<mListenerList.size();i++){
        IOnNewBookArrivedListener listener = mListenerList.get(i);
        try {
            listener.onNewBookArrived(newBook);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
}

详细功能实现请查看源码

三、取消注册功能

当我们实现了注册功能之后,怎样取消注册功能呢?

@Override
protected void onDestroy() {
    super.onDestroy();
    if(mService!=null || mService.asBinder().isBinderAlive()){
        try {
            mService.unregisterListener(listener);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    unbindService(conn);

}

我们通过上述方法无法取消注册。这是为什么呢?

这是因为在客户端传入的IOnNewBookArrivedListener对象是同一个对象,但是在服务端却又变成了另外的一个对象了。因为对象无法跨进程通信,服务端接收的IOnNewBookArrivedListener对象是通过序列化之后得到对象,所以无法取消注册,这种情况怎么处理呢?

这里我们就会用到RemoteCallbackList类,它是系统专门提供的用于删除跨进程listener的接口,它是一个泛型,支持管理任意的AIDL接口,因为它继承了IInterface接口,aidl接口都继承IInterface。

它的工作原理非常简单,就是在它的内部有一个专门用于保存所有的AIDL回调的Map集合:

ArrayMap<IBinder, Callback> mCallbacks
        = new ArrayMap<IBinder, Callback>();

其中Callback中封装了真正的远程listener,当客户端注册listener的时候,它会把这个listener的信息存入mCallback中,其中key和value分别通过下面的方式获得:

private final class Callback implements IBinder.DeathRecipient
。。。。

public boolean register(E callback, Object cookie) {
    synchronized (mCallbacks) {
        if (mKilled) {
            return false;
        }
        IBinder binder = callback.asBinder();
        try {
            Callback cb = new Callback(callback, cookie);
            binder.linkToDeath(cb, 0);
            mCallbacks.put(binder, cb);
            return true;
        } catch (RemoteException e) {
            return false;
        }
    }
}

到这里就应该知道,虽然在客户端传入的同一对象在服务端会生成了不同的对象,但是它的底层的Binder对象却没有变化,利用这个特性就可以实现上面的功能了。当客户端取消注册的时候,我们会便利所有的listener接口,找到与客户端传入的listener具有相同Binder对象的服务端listener,然后把它删除即可。

服务端的代码:

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


@Override
    public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
//        if(!mListenerList.contains(listener)){
//           mListenerList.add(listener);
//        }
        mListenerList.register(listener);
    }

    @Override
    public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
//        if(mListenerList.contains(listener)){
//            mListenerList.remove(listener);
//        }
        mListenerList.unregister(listener);
    }



/**
 * 发送新书已到的消息到客户端
 * 怎样通知呢?
 * 通过客户端在主持监听的时候传入的IOnNewBookArrivedListener对象来通知客户端
 * @param newBook
 */
private void sendNewBookArrivedToClient(Book newBook){
    mBookList.add(newBook);
//        for(int i=0;i<mListenerList.size();i++){
    //            IOnNewBookArrivedListener listener = mListenerList.get(i);
    //            try {
    //                listener.onNewBookArrived(newBook);
    //            } catch (RemoteException e) {
    //                e.printStackTrace();
    //            }
    //        }

    int N = mListenerList.beginBroadcast();
    for(int i=0;i<N;i++){
        IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i);
        try {
            listener.onNewBookArrived(newBook);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
    mListenerList.finishBroadcast();
}

四、设置死亡代理

关于死亡代理,以前讲过,这里在说明一下,当客户端在与服务端进行跨进程通信的时候,服务端因为异常而被销毁,但是服务端却不知道,那么怎样解决这个问题呢,这里就讲到了重新连接服务的两种方法,第一种就是死亡代理:

private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
    @Override
    public void binderDied() {
        mService.asBinder().unlinkToDeath(mDeathRecipient,0);
        mService = null;
        //重新连接
        bindService(intent,conn,BIND_AUTO_CREATE);
    }
};




private ServiceConnection conn = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        mService = IService.Stub.asInterface(service);
        try {
            service.linkToDeath(mDeathRecipient,0);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        try {
            /**
             * 注册对新书的监控
             */
            mService.registerListener(listener);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {

    }
};

还有一种方法就是在onServiceDisconnected中重新连接绑定服务。

五、权限验证

我们不想让所有的客户端访问我们的服务端,我们希望具有某些权限的客户端才能访问,怎样实现?

第一种方法,在onBind中进行验证。验证不通过就返回null,造成无法连接服务端。这种验证很多,这里介绍其中一种permission权限验证,在服务端定义一个权限。

AndroidMenifest:

<permission android:name="cn.demo.zx_aidl_learn.YANZHENG"/>

服务端:

@Override
public IBinder onBind(Intent intent) {
    int check = checkCallingOrSelfPermission("cn.demo.zx_aidl_learn.YANZHENG");
    if(check== PackageManager.PERMISSION_DENIED){
        return null;
    }

    return mBinder;
}

如果想要访问这个服务端的话,那么客户端就需要设置权限:

<uses-permission android:name="cn.demo.zx_aidl_learn.YANZHENG"/>

另一种方法,这种方法就是在服务端的onTransact方法中进行权限验证,如果验证失败返回false。验证的方法也是很多,可以通过permission验证,也可以通过包名验证。permission验证的方法与上述onBind方法中差不多,唯一的区别就是返回false。包名验证就通过getCallinngUidgetCallingPid获取客户端的uid和pid从而获取包名,进行验证。

String[] packageNames = getPackageManager().getPacagesForUid(getCallingUid());
if(!packageNames[0].startsWith("cn.demo")){
    return false;
}

注意

当客户端访问服务端的方法时候,客户端会等待跨进程通信,如果服务端中的方法是一个耗时的方法的话,那么客户端就需要在子线程中去访问,如果在UI线程中,容易造成ANR异常。服务端访问客户端的方法也是一样的。

源码下载:

https://github.com/zhangxun1900/my_ipc_learn/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值