温故而知新——AIDL

一:aidl与messenger的使用区别

  • aidl与messenger都是使用在进程间通信的方式,底层都是Binder。不是进程间通信的可以直接使用Binder。
  • messenger底层使用的是aidl结构。
  • messenger服务端串行执行任务。
  • aidl可以实现并发操作,但是要注意线程安全。aidl还可以调用远程服务的方法,messenger则不可以。
  • aidl的同步主要是在aidl接口在service中的实现时添加。

二:一个简单的aidl创建流程

(一):创建aidl文件

image.png

(二):创建远程服务

注意aidl接口的方法,是需要线程安全的。


image.png

(三):将远程的aidl包拷贝到本地项目

后面还会介绍如何使用parcelable对象,其中实现parcelable对象的类,也需要写一个aidl文件。


(四):绑定远程服务

在调用aidl接口方法的时候,需要注意这个方法是耗时操作。


image.png

三:远程启动服务的注意事项

aidl一般是用在两个进程中,如果使用在两个app中,那么要怎么从一个app启动另一个app的远程服务呢?在android 5.0之后,系统规定要使用显示启动所有的service,否则就会抛出异常。因此就有下面两种启动方式

(一)设置过滤器,google并不建议我们这么做

java.lang.IllegalArgumentException: Service Intent must be explicit: Intent { act=com.xia }

<service android:name=".MyService"
           >
            <intent-filter>
                <action android:name="com.xia"/>
            </intent-filter>
</service>

 intent.setAction("com.xia");
intent.setPackage("xiaguangcheng.com.local");

由于service设置了过滤器,那么exported就默认为true了。

(二)使用全类名打开service,主要要把允许其他app使用设置为true

java.lang.SecurityException: Not allowed to bind to service Intent { cmp=xiaguangcheng.com.local/.MyService }
 <service android:name=".MyService"
            android:exported="true">
  </service>
  //这里的pkg应该是远程服务的applicationId, 然后是远程服务的全路径名
intent.setComponent(new ComponentName("xiaguangcheng.com.local","xiaguangcheng.com.local.MyService"));

由于没有设置过滤器,因此需要显示指定exproted=true。


四:创建一个略复杂的aidl

(一)aidl支持的数据类型

  • 基本数据类型
  • String和CharSequence
  • ArrayList,元素必须被aidl支持
  • HashMap,元素必须被aidl支持
  • Parcelable,所有实现了Parcelable接口的对象
  • aidl接口本身也能在aidl文件中使用

(二)使用parcelable接口的对象

  • 我们要在java包中创建一个Book.java文件实现parcelable接口
  • 我们还要在aidl包中创建一个同名的aidl文件Book.aidl,并在其中声明parcelable Book;同时手动导入Book.java的包名。

(三)使用RemoteCallbackList删除跨进程接口

public class BookManagerService extends Service {
    public static final String TAG="BMS";
    //读写分离,最终一致性,保障并发安全
    private CopyOnWriteArrayList<Book> mBookList=new CopyOnWriteArrayList<>();
    private AtomicBoolean mIsServiceDestoryed=new AtomicBoolean(false);
    //解决因为aidl的序列化问题,导致传递过来的对象,已非客户端的对象。这个类则解决了这个问题。
    private RemoteCallbackList<IBookManagerNewBookArravied> mListenerList=
        new RemoteCallbackList<>();
    @Override
    public void onCreate() {
        super.onCreate();
        mBookList.add(new Book("西游记",111));
        mBookList.add(new Book("红楼梦",222));
        new Thread(new ServiceWorker()).start();
    }

    private Binder mBinder=new IBookManager.Stub(){

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

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

        @Override
        public void registerListener(IBookManagerNewBookArravied listener) throws RemoteException {
            mListenerList.register(listener);
        }

        @Override
        public void unRegisterListener(IBookManagerNewBookArravied Listener) throws RemoteException {
            mListenerList.unregister(Listener);
        }
    };
    public BookManagerService() {
    }

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

    private class ServiceWorker implements Runnable {
        @Override
        public void run() {
            while(!mIsServiceDestoryed.get()){
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                int bookId= mBookList.size()*111;
                Book newBook=new Book("bookName:"+bookId,bookId);
                try {
                    onNewBookArrrived(newBook);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void onNewBookArrrived (Book newBook)throws RemoteException {
        mBookList.add(newBook);
        final int N=mListenerList.beginBroadcast();
        for(int i=0;i<N;i++){
            IBookManagerNewBookArravied iBookManagerNewBookArravied = mListenerList.getBroadcastItem(i);
            if(iBookManagerNewBookArravied!=null){
                iBookManagerNewBookArravied.onNewBookArrived(newBook);
            }
        }
        mListenerList.finishBroadcast();
    }

    @Override
    public void onDestroy() {
        mIsServiceDestoryed.set(true);
        super.onDestroy();
    }
}

五:如何判断Binder连接是否中断

在绑定服务完成之后,我们要给binder设置一个死亡代理。即通过IBinder.linkToDeath(mDeathRecipient,0)来设置。其中这个死亡接收者有一个binderDied的回调。当binder中断,就需要在这个回调中,取消之前绑定死亡代理的那个binder,重新绑定服务,绑定死亡代理。

    IM im;
    private IBinder.DeathRecipient mDeathRecipient=new IBinder.DeathRecipient(){
        @Override
        public void binderDied() {
            if(im==null){
                return;
            }
            im.asBinder().unlinkToDeath(mDeathRecipient,0);
            im=null;

           connect();

        }
    };
……
 public void connect(){
        Intent intent=new Intent("com.abc");
        intent.setPackage("com.xiaguangcheng.aidl");
        bindService(intent, new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                im=IM.Stub.asInterface(service);
                try {
                    service.linkToDeath(mDeathRecipient,0);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {

            }
        },BIND_AUTO_CREATE);
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容