进程间的通信——AIDL

2020-08-11  本文已影响0人  132xin

简介

进程之间的通信——Messenger的文章中说到了通过Messenger进行进程间的通信,可以发现Messenger是以串行的方式处理客户端发送来的消息,如果大量的消息同时发送到服务端,服务端依然只能一个个处理,如果有大量的并发请求,那么Messenger就不太实用了。同时,Messenger的作用主要是为了传递消息,很多时候我们可能需要跨进程调用服务端的方法,这个时候Messenger就无法做到了,但是我们可以使用AIDL来实现跨进程的方法调用。

AIDL接口

实现AIDL的跨进程通信,需要创建AIDL接口,ALDL接口支持的数据类型:

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

其中自定义的Parcelable对象和AIDL对象必须要显示import进来,不管它们是否和当前的AIDL文件位于同一个包内。
另外需要注意的是,如果AIDL文件中用到了自定义的Parcelable对象,必须新建一个和它同名得到AIDL文件,比在其中声明是Parcelable
AIDL中除了基本数据类型,其他类型的参数必须标上方向:in、out或者inout。in表示输入型参数,out表示输出型参数,inout表示输入输出型参数。根据实际情况指定。
还需要注意的是AIDL的包结构在服务端和客服端需要保持一致,否则运行会出错,这是因为客服端需要反序列化服务端中和ALDL接口相关的所有类,如果类的完整路径不一样的话,就无法成功反序列化,程序就无法正常运行。如果是先创建了服务端的接口,就将服务端的AIDL文件复制到客户端对应的目录下,不要改动包名。

服务端

服务端首先创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的连接在这个AIDL文件中声明,最后在Service中实现这个AIDL接口即可。

客户端

需要绑定服务端的Service,绑定成功后,将服务顿返回的Binder对象转化成AIDL接口所属于的类型,接着就可以调用AIDL的方法了。

示例

现在有这样一个要求,在服务端中有个书库,客户端可以向书库中添加书本,并且客户端可以获取书库中的所有书本,如果服务端中添加了新的书本,可以发出通知告诉客户端。

服务端

1.创建AIDL接口


image.png

这里有一个Book的Java类,如果这个类放在aidl的包下的,需要在gradle(app)的Android节点中添加这个代码:

    sourceSets {
        main {
            manifest.srcFile 'src/main/AndroidManifest.xml'
            java.srcDirs = ['src/main/java', 'src/main/aidl']
            resources.srcDirs = ['src/main/java', 'src/main/aidl']
            aidl.srcDirs = ['src/main/aidl']
            res.srcDirs = ['src/main/res']
            assets.srcDirs = ['src/main/assets']
        }
    }

或者放在普通的包下,但是这样有个缺点就是在客户端中也要在普通的包下创建一个book的类,而且还需要注意导包的问题。建议上面那种解决方法。接下来看各个类的代码
Book.java
》 这个类要实现 Parcelable的接口


public class Book implements Parcelable {
    public int bookId;
    public  String bookName;
    public Book(int bookId,String bookName){
        this.bookId=bookId;
        this.bookName=bookName;
    }

    protected Book(Parcel in) {
        bookId = in.readInt();
        bookName = in.readString();
    }

    public static final Creator<Book> CREATOR = new Creator<Book>() {
        @Override
        public Book createFromParcel(Parcel in) {
            return new Book(in);
        }

        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };

    /**
     * 当我们写完属性,和构造发现之后再实现Parcelable的时候
     * 可自动生成以下的代码。
     *
     */


    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(bookId);
        dest.writeString(bookName);
    }

    /**
     * 重写equals,如果Id相同,且名字相同则说明是同一本书
     * @param obj
     * @return
     */
    @Override
    public boolean equals(@Nullable Object obj) {
        return this.bookId== ((Book)obj).bookId && this.bookName.equals(((Book)obj).bookName);
    }

}

因为这个Book是自己自定义而定Parcelable对象,所以必须新建一个和它同名的AIDL文件,并在其中声明它为Parcelable,否则会构建失败。

// Book.aidl
package com.example.hx.servicesaidl;

parcelable Book;

IBookManger.aidl
这个AIDL的接口,里面提供了四个方法,将在Service中实现。

// IBookManager.aidl
package com.example.hx.servicesaidl;

// Declare any non-default types here with import statements
import com.example.hx.servicesaidl.Book;
import com.example.hx.servicesaidl.IOnNewBookArrivedListener;
interface IBookManager {
     List<Book>  getBookList();
     void addBook(in Book book);
     void registerListener(IOnNewBookArrivedListener listener);
     void unRegisterListener( IOnNewBookArrivedListener listener);
}

定义这个接口是为了,进行通知客户端的方法,让客户端进行回调。

// IonNewBookArrivedListener.aidl
package com.example.hx.servicesaidl;

// Declare any non-default types here with import statements
import com.example.hx.servicesaidl.Book;
interface IOnNewBookArrivedListener {
    void onNewBookArrived(in Book newBook);
}

接下来就是BooKMangerService,这是一个服务,需要在AndroidManifest.xml,并且设置action。

<service
            android:name=".BookManagerService"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.START_SERVICE"/>
            </intent-filter>

BookManagerService.java

public class BookManagerService extends Service {
    private static final String TAG="BookManagerService";
    private AtomicBoolean atomicBoolean=new AtomicBoolean(false);
    public BookManagerService() {
    }

    //使用RemoteCallbackList来用于删除进程的接口
    private static RemoteCallbackList<IOnNewBookArrivedListener> mOnNewBookArrivedListeners =
            new RemoteCallbackList<>();
    private  static  CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();

    @Override
    public void onCreate() {
        super.onCreate();
        mBookList.add(new Book(1, "第一行代码"));
        mBookList.add(new Book(2, "Android群英传"));
        new Thread(new ServiceWorker()).start();

    }

    static class BookManagerBinder extends IBookManager.Stub {


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

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

        @Override
        public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
            if (listener!=null){
                mOnNewBookArrivedListeners.register(listener);
                Log.e(TAG, "@@@ 注册了监听器 " );
            }
        }

        @Override
        public void unRegisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
            if (listener!=null){
                mOnNewBookArrivedListeners.unregister(listener);
                Log.e(TAG, "@@@ 取消注册了监听器 " );
            }
        }
    }

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

    /**
     * 添加新的书本,调用该方法,通知用户
     * @param book
     */
    private void onNewBookArrived(Book book) throws RemoteException {
        if (mBookList.contains(book)){
            return;
        }
        mBookList.add(book);
        //RemoteCallbackList与List的遍历方式不同
        final  int num=mOnNewBookArrivedListeners.beginBroadcast();
        for (int i=0;i<num;i++){
            IOnNewBookArrivedListener listener=mOnNewBookArrivedListeners.getBroadcastItem(i);
            listener.onNewBookArrived(book);
            Log.e(TAG, "@@@ 通知客户端,服务端添加了新的书本 "+book.bookId+"  "+book.bookName);
        }
        mOnNewBookArrivedListeners.finishBroadcast();
    }
    /**
     * 为了实现在服务端中添加书本的效果,开启一条线程进行添加
     *
     */
    private class ServiceWorker implements Runnable{

        @Override
        public void run() {
            while (!atomicBoolean.get()){
                int id=mBookList.size()+1;
                Book book=new Book(id,"书本1");
                try {
                    //添加书
                    onNewBookArrived(book);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

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

这里的继承的IBookManager.Stub是重点,这个是AIDL自动生成,也可以通过匿名类的方式初始化,然后再onBind()方法中返回,然后再客户端连接中就可以获得IBookManager对象,然后执行方法的时候,就会调用服务端重新的方法,实现了交互。
在上面的代码中用了CopyOnWriteArrayList保存书本,前面说到AIDL中所支持的只有ArrayList,但是我们用了CopyOnWriteArrayList,为什么能正常工作呢?这是因为AIDL中所支持的是抽象的List,而List是一个接口,因此虽然服务端返回CopyOnWriteArrayList,但是Binder中会按照List的规范去访问数据并最终形成一个新得ArrayList传递给客户端,所以在服务端使用CopyOnWriteArrayList是完成可以的。

客户端

将服务端的aidl的文件复制到main下面,不需改动任何东西。


image.png

客户端连接service,通过IBookManager.Stub.asInterface(service)等到对象之后就可以进行交互了。

public class MainActivity extends AppCompatActivity {
    private final static String TAG = "MainActivity";
    private static IBookManager mBookManager;
    public static Handler handler = new MyHandler();
    private MyServiceConnection myServiceConnection;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //通过隐式的方式启动服务
        Intent intent = new Intent("android.intent.action.START_SERVICE");
        myServiceConnection = new MyServiceConnection();
        intent.setComponent(new ComponentName("com.example.hx.servicesaidl", "com.example.hx.servicesaidl.BookManagerService"));
        bindService(intent, myServiceConnection, Context.BIND_AUTO_CREATE);

    }

    private static class MyServiceConnection implements ServiceConnection {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //获取IBookManager
            mBookManager = IBookManager.Stub.asInterface(service);
            try {
                //获取所有的书本
                List<Book> list = mBookManager.getBookList();
                Log.e(TAG, "客户端: 获取服务端的书本 " + list.size());
                //添加书本
                Book book = new Book(3, "Android开发艺术与探索");
                mBookManager.addBook(book);
                Log.e(TAG, "客户端: 添加书本到服务端 " + book.bookId + "  " + book.bookName);
                //重新获取书本
                List<Book> listNew = mBookManager.getBookList();
                Log.e(TAG, "客户端: 获取服务端的书本 " + listNew.size());
                //注册监听者
                mBookManager.registerListener(mIOnNewBookArrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }

    }

    private static IOnNewBookArrivedListener mIOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() {
        @Override
        public void onNewBookArrived(Book newBook) throws RemoteException {

            /**
             * 这个方法的回调是在客户端的Binder线程上执行的
             * 如果要进行Ui的操作,就需要切换到主线程上。
             * @param newBook
             * @throws RemoteException
             */
            //do someThing
            Message message = Message.obtain(handler, 1);
            Bundle bundle = new Bundle();
            bundle.putParcelable("book", newBook);
            message.setData(bundle);
            handler.sendMessage(message);

        }
    };

    static class MyHandler extends Handler {
        @Override
        public void handleMessage(@NonNull Message msg) {
            switch (msg.what) {
                case 1:
                    Bundle bundle = msg.getData();
                    Book book = (Book) bundle.getParcelable("book");
                    assert book != null;
                    Log.e(TAG, "@@@  服务端中添加新的书本 " + book.bookId + "  " + book.bookName);
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }
    //销毁的时候取消注册

    @Override
    protected void onDestroy() {
        if (mBookManager != null && mBookManager.asBinder().isBinderAlive()) {
            try {
                mBookManager.unRegisterListener(mIOnNewBookArrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        unbindService(myServiceConnection);
        super.onDestroy();

    }
}

因为这里的客户端和服务端是两个不同的应用,如果启动Service,可以参考这个链接Android在一个app中启动其他app中的service或者Activity

结果:
服务端:


image.png

客户端:


image.png

以上是实现AIDL的简单例子,但是在实际使用的时候需要注意,客户端调用远程服务的方法,被调用的方法运行在服务端的Binder线程池中,同时客户端会被挂起,这个时候如果服务端方法执行比较耗时的,就会导致客户端线程长时间阻塞在这里,而如果这个客户端线程是UI线程的话,就会导致客户端ANR。因此如果我们明确知道某个远程方法是耗时的,那么就要避免在UI线程上执行。客户端的onServiceConnected和onServiceDisconnected方法都运行在UI线程中,所以也不可以在这两个方法中直接调用远程的方法,本例子是为了方便,在实际开发中不要这样做。另外需要注意的是,由于服务端的方法本身就运行在服务端的Binder线程池中,所以服务端方法本身就可以执行大量耗时操作,这个时候切记不要在服务端方法中开启线程去进行异步任务,除非你明确知道自己在干什么,否则不要这样做。

上一篇下一篇

猜你喜欢

热点阅读