Android进程间通讯(四):进程间通讯的方式之AIDL

转载请以连接形式标明出处: 本文出自:103style的博客java

《Android开发艺术探索》 学习记录android

base on AndroidStudio 3.5.1编程


目录

  • 前言
  • AIDL接口建立
  • AIDL支持的数据格式
  • 服务端实现
    • 建立 BookManagerService.java
    • 处理并发状况
  • 客户端实现
    • 建立 BookManagerActivity.java
    • 运行程序查看日志
  • AIDL添加和解除回调
    • 添加服务端新增数据的回调
    • 解除回调失败?RemoteCallbackList ?
  • AIDL添加权限验证
    • permission验证
    • 包名验证
  • 小结

前言

前面咱们介绍了 进程间通讯基础介绍经过AIDL介绍Binder的工做机制 ,以及 经过 Bundle、文件共享、Messenger实现进程间通讯 , 不了解的能够先看下。安全

经过以前对 Messenger 的介绍,咱们知道 Messenger 是以串行的方式处理消息的,因此当有 大量消息并发请求 时,Messenger 可能就不太合适了。 同时 Messenger 主要是用来传递消息,不少时候咱们可能须要 跨进程调用其余进程的方法 ,这个是 Messenger 作不到的。bash

这时候就轮到 AIDL 展现本身的实力了。 Messenger 也是基于 AIDL 的,是系统对 AIDL 的封装,方便上层调用。并发

咱们在 经过AIDL介绍Binder的工做机制 中介绍了 Binder 的概念,你们对 Binder 应该有了必定的了解。app

这里咱们先介绍下AIDL 来进行进程间通讯的流程,包括 AIDL接口建立服务端客户端ide


AIDL接口建立

tips: 为了方便开发,建议把 AIDL 相关的类和文件放到统一的目录,这样当客户端和服务端是不一样应用时,能够把整个包复制过去。 注意: 客户端和服务端的 AIDL 包结构必须保持一致,不然会运行报错。性能

建立 IBookManager.aidl学习

//IBookManager.aidl:
package aidl;
import aidl.Book;
interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
}
复制代码

而后咱们先来介绍下AIDL支持的数据格式。


AIDL支持的数据格式

AIDL 支持的大部分数据格式,不过也不是全部的数据类型都能使用的,能够用以下类型:

  • 基本数据类型(int、long、char、boolean、double 等)
  • StringCharSequence
  • List : 只能是 ArrayList,并且其中的元素的格式都要能被 AIDL 支持。
  • Map : 只能是 HashMap,并且其中的元素的格式都要能被 AIDL 支持。
  • AIDL:全部 AIDL 接口也能够在 AIDL 中使用。须要import导入
  • Parcelable:全部实现该接口的对象。须要import导入,该对象还需建立 类名.aidl 文件,而后添加以下内容,以上述示例中的 Book 为例:
    //Book.aidl
    package aidl;
    parcelable Book;
    复制代码

除了基本类型以外,其余的类型在做为参数的时候必须标上方向:inoutinout

in:表示输入型参数 out:表示输出型参数 inout:表示输入输出型参数

并且不能一律使用 inout,由于底层性能是有开销的,因此要按需使用。 例如上述示例中的 void addBook(in Book book);


服务端实现

首先咱们在服务端建立一个 Service 来处理客户端的链接请求,而后在 Service 中实如今 AIDL 中的声明暴露给客户端的接口。

建立 BookManagerService.java

//BookManagerService.java
public class BookManagerService extends Service {
    private static final String TAG = "BookManagerService";
    private CopyOnWriteArrayList<Book> bookList = new CopyOnWriteArrayList<>();
    private Binder mBinder = new IBookManager.Stub() {
        @Override
        public List<Book> getBookList() throws RemoteException {
            return bookList;
        }
        @Override
        public void addBook(Book book) throws RemoteException {
            bookList.add(book);
        }
    };
    @Override
    public void onCreate() {
        super.onCreate();
        bookList.add(new Book(1, "Android艺术开发探索"));
        bookList.add(new Book(2, "Java并发编程指南"));
    }
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
}
复制代码

上述示例中主要是建立 实现 AIDL 中声明的方法的 BInder 类,并在 Service 的 onBind 中返回。

而后前面提到是在服务端的 Binder 线程池中执行的,因此会存在多个线程同时访问的状况。因此咱们要在 AIDL 方法中处理线程同步,由于 CopyOnWriteArrayList 是支持并发读写的,这里咱们直接用 CopyOnWriteArrayList 来进行线程自动同步。

可是在上面介绍 AIDL支持的数据格式 时,咱们知道 List 只支持 ArrayList,而 CopyOnWriteArrayList 也不是 ArrayList 的子类,那为何能供支持工做呢? 这是由于 AIDL 中所支持的是抽象的 List,而 List 是一个接口,所以虽然服务端返回的是 CopyOnWriteArrayList,可是在 Binder 中会按照 List 的规范去访问数据并最终造成一个新的 ArrayList 传给客户端。

而后在 AndroidManifest.xml 中声明所在的进程 :remote

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.ipc">
    <application>
        ...
        <service
            android:name="test.BookManagerService"
            android:process=":remote" />
    </application>
</manifest>
复制代码

客户端实现

客户端首先要绑定服务端的 Service, 绑定成功后用服务端返回的 Binder 对象转成 AIDL 接口所属的类型,而后就能够调用 AIDL 的方法了。

建立 BookManagerActivity.java

//BookManagerActivity.java
public class BookManagerActivity extends AppCompatActivity {
    private static final String TAG = "BookManagerActivity";
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IBookManager iBookManager = IBookManager.Stub.asInterface(service);
            try {
                List<Book> list = iBookManager.getBookList();
                Log.e(TAG, "query book list, type = " + list.getClass().getCanonicalName());
                Log.e(TAG, list.toString());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_book_manager);
        Intent intent = new Intent(this, BookManagerService.class);
        bindService(intent, connection, Context.BIND_AUTO_CREATE);
    }
    @Override
    protected void onDestroy() {
        unbindService(connection);
        super.onDestroy();
    }
}
复制代码

运行程序,看到日志信息以下:

BookManagerActivity: query book list, type = java.util.ArrayList
BookManagerActivity: [Book{bookId=1, bookName='Android艺术开发探索'}, Book{bookId=2, bookName='Java并发编程指南'}]
复制代码

AIDL添加和解除回调

咱们在上面的代码中实现如下功能,当服务端有新的书添加时,通知客户端。

来,直接开撸。 由于 AIDL 中没法使用普通接口,因此咱们得建立一个 AIDL接口 IBookAddListener.aidl

//IBookAddListener.aidl
package aidl;
import aidl.Book;
interface IBookAddListener{
    void onBookArrived(in Book newBook);
}
复制代码

而后在以前的 IBookManager.aidl 中添加接口的添加和删除方法。

//IBookManager.aidl
package aidl;
import aidl.Book;
import aidl.IBookAddListener;
interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
    void registerListener(IBookAddListener listener);
    void unregisterListener(IBookAddListener listener);
}
复制代码

而后在修改上面的服务端代码 BookManagerService 中的 mBinder 实现 新增的两个方法,而且建立一个 Worker 定时往服务端的 bookList 中添加数据。

//BookManagerService.java
public class BookManagerService extends Service {
    private static final String TAG = "BookManagerService";
    //服务是否已经销毁
    private AtomicBoolean destroyed = new AtomicBoolean(false);
    private CopyOnWriteArrayList<Book> bookList = new CopyOnWriteArrayList<>();
    private CopyOnWriteArrayList<IBookAddListener> listeners = new CopyOnWriteArrayList<>();
    private Binder mBinder = new IBookManager.Stub() {
        ...
        @Override
        public void registerListener(IBookAddListener listener) throws RemoteException {
            if (!listeners.contains(listener)) {
                listeners.add(listener);
            } else {
                Log.e(TAG, "lister is already exist");
            }
            Log.e(TAG, "registerListener: listeners.size = "  + listeners.size());
        }
        @Override
        public void unregisterListener(IBookAddListener listener) throws RemoteException {
            if (listeners.contains(listener)) {
                listeners.remove(listener);
            } else {
                Log.e(TAG, "lister not found, can not unregister");
            }
            Log.e(TAG, "unregisterListener: listeners.size = "  + listeners.size());
        }
    };
    @Override
    public void onCreate() {
        super.onCreate();
        bookList.add(new Book(1, "Android艺术开发探索"));
        bookList.add(new Book(2, "Java并发编程指南"));
        new Thread(new Worker()).start();
    }
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
    @Override
    public void onDestroy() {
        destroyed.set(true);
        super.onDestroy();
    }
    private void onBookAdd(Book book) throws RemoteException {
        bookList.add(book);
        Log.e(TAG, "onBookAdd: notify listeners.size = " + listeners.size());
        for (IBookAddListener listener : listeners) {
            listener.onBookArrived(book);
        }
    }
    private class Worker implements Runnable {
        @Override
        public void run() {
            while (!destroyed.get()) {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                int bookId = bookList.size() + 1;
                Book book = new Book(bookId, "new book#" + bookId);
                try {
                    onBookAdd(book);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
复制代码

而后修改客户端的 BookManagerActivity 添加服务端的监听。

//BookManagerActivity.java
public class BookManagerActivity extends AppCompatActivity {
    private static final String TAG = "BookManagerActivity";
    private static final int BOOK_ADD_MSG = 0x001;
    private IBookManager remoteBookManager;
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case BOOK_ADD_MSG:
                    Log.e(TAG, "a new book add :" + msg.obj);
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    };
    //监听服务端的回调
    private IBookAddListener bookAddListener = new IBookAddListener.Stub() {
        @Override
        public void onBookArrived(Book newBook) throws RemoteException {
            //运行再客户端的Binder线程池,不能执行访问UI
            handler.obtainMessage(BOOK_ADD_MSG, newBook).sendToTarget();
        }
    };
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IBookManager iBookManager = IBookManager.Stub.asInterface(service);
            try {
                remoteBookManager = iBookManager;
                List<Book> list = iBookManager.getBookList();
                Log.e(TAG, "query book list, type = " + list.getClass().getCanonicalName());
                Log.e(TAG, list.toString());
                Book book = new Book(3, "Android软件安全指南");
                remoteBookManager.addBook(book);
                remoteBookManager.registerListener(bookAddListener);//添加回调监听
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
            remoteBookManager = null;
            Log.e(TAG, "binder died ");
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_book_manager);
        Intent intent = new Intent(this, BookManagerService.class);
        bindService(intent, connection, Context.BIND_AUTO_CREATE);//绑定服务
    }
    @Override
    protected void onDestroy() {
        unregisterListener();//解除注册
        unbindService(connection);//解绑服务
        super.onDestroy();
    }
    private void unregisterListener() {
        if (remoteBookManager != null && remoteBookManager.asBinder().isBinderAlive()) {
            try {
                remoteBookManager.unregisterListener(bookAddListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }
}
复制代码

而后运行程序,打印以下日志。

//客户端进程
BookManagerActivity: query book list, type = java.util.ArrayList
BookManagerActivity: [Book{bookId=1, bookName='Android艺术开发探索'}, Book{bookId=2, bookName='Java并发编程指南'}]
BookManagerActivity: a new book add :Book{bookId=4, bookName='new book#4'}
BookManagerActivity: a new book add :Book{bookId=5, bookName='new book#5'}
BookManagerActivity: a new book add :Book{bookId=6, bookName='new book#6'}
//服务端 :remote进程
BookManagerService: registerListener:  listeners.size = 1
BookManagerService: onBookAdd: notify listeners.size = 1
BookManagerService: onBookAdd: notify listeners.size = 1
BookManagerService: onBookAdd: notify listeners.size = 1
→ BookManagerService: lister not found, can not unregister
BookManagerService: unregisterListener:  listeners.size = 1
BookManagerService: onBookAdd: notify listeners.size = 1
复制代码

咱们从日志中能够看到,确实有监听到每隔5s就新增一条数据。 可是咱们发现一个问题: 解除注册的时候提示 lister not found, can not unregister。说明解除注册失败了,这是为何呢?

这是由于 Binder 的机制的问题,Binder会把客户端传递过来的对象从新转化并生成一个新的对象。 由于对象是不能直接跨进程传输的,对象传输的本质都是反序列化的过程,这就是为何 AIDL 中的对象都得实现 Parcelabe 接口的缘由。

那咱们怎么才能解注册呢? 就得使用系统提供的 RemoteCallbackList,专门提供用于删除跨进程的 回调接口,从它的泛型咱们能够看到,它是支持管理任意的 AIDL 接口。 public class RemoteCallbackList<E extends IInterface> {}

接下来咱们来修改咱们以前的 BookManagerService:

//BookManagerService.java  只贴了要修改的地方
public class BookManagerService extends Service {
    ...
    private RemoteCallbackList<IBookAddListener> listeners = new RemoteCallbackList<>();
    private Binder mBinder = new IBookManager.Stub() {
        ...
        @Override
        public void registerListener(IBookAddListener listener) throws RemoteException {
            listeners.register(listener);
            final int N = listeners.beginBroadcast();
            Log.e(TAG, "registerListener: size = " + N);
            listeners.finishBroadcast();
        }
        @Override
        public void unregisterListener(IBookAddListener listener) throws RemoteException {
            listeners.unregister(listener);
            final int N = listeners.beginBroadcast();
            Log.e(TAG, "unregisterListener: size = " + N);
            listeners.finishBroadcast();
        }
    };
    private void onBookAdd(Book book) throws RemoteException {
        bookList.add(book);
        final int N = listeners.beginBroadcast();
        for (int i = 0; i < N; i++) {
            IBookAddListener listener = listeners.getBroadcastItem(i);
            if (listener != null) {
                listener.onBookArrived(book);
            }
        }
        listeners.finishBroadcast();
    }
}
复制代码

运行程序,从日志咱们能够看到解注册成功了。

//客户端进程
BookManagerActivity: query book list, type = java.util.ArrayList
BookManagerActivity: [Book{bookId=1, bookName='Android艺术开发探索'}, Book{bookId=2, bookName='Java并发编程指南'}]
BookManagerActivity: a new book add :Book{bookId=4, bookName='new book#4'}
//服务端:remote进程
E/BookManagerService: registerListener: size = 1
E/BookManagerService: unregisterListener: size = 0
复制代码

使用 RemoteCallbackList,有一点须要注意,虽然名字中有 List,可是咱们不能像 List 同样去操做它。 遍历其数据 或者 获取其大小,咱们必须配对使用 beginBroadcastfinishBroadcast,参考上面代码中回调的注册和解注册的方法。

至此,AIDL 的基本使用方法已经介绍完了,可是还有几点须要再强调如下:

  • 客户端调用远程服务的方法时运行在服务端的 Binder 线程池中的,客户端会被挂起直到方法执行完成,若是方法比较耗时的话,客户端若是在 UI线程 中直接调用则会出现 ANR。因此在知道方法耗时时,咱们不能直接在UI线程中调用,须要经过子线程去处理,如示例中客户端 BookManagerActivity 中的 ServiceConnection 的两个方法 onServiceConnectedonServiceDisconnected 都是运行在UI线程的。
  • 另外就是客服端中的回调,即示例 BookManagerActivity 中的 bookAddListener,是运行在客户端的 Binder 线程池的,因此不能直接访问UI内容的,如需访问UI,则须要经过 Handler 等切换线程。

另外,为了程序的健壮性,咱们还的防止 Binder 意外死亡,这每每是因为服务端进程意外中止了,这是咱们须要重连服务。有两种方法:

  • 给Binder设置 DeathRecipient 监听,当 Binder死亡时,咱们会收到 binderDied 回调,这个咱们已经在 Binder的工做机制 这里介绍过了 。
  • 在 onServiceDisconnected 中去重连远程服务。

AIDL添加权限验证

默认状况下,咱们的远程服务任何人均可以链接,但这是咱们不想要的,因此咱们要在AIDL中添加权限验证。这里介绍两种方法: 1.在 obBinder 中验证 验证不经过时直接返回 null,这样验证失败的客户端直接没法绑定服务。至于验证方式有多种,好比 permission验证,使用这种验证,咱们须要在 AndroidManifest.xml 中声明所须要的权限,示例以下:

// AndroidManifest.xml 
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.okhttptesst">
    //声明权限
    <permission android:name="com.aidl.test.permission.ACCESS_BOOK_SERVICE"
        android:protectionLevel="normal"/>
    ...
</manifest>

//BookManagerService.java
public class BookManagerService extends Service {
    ...
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        int check = checkCallingOrSelfPermission("com.aidl.test.permission.ACCESS_BOOK_SERVICE");
        if (check == PackageManager.PERMISSION_DENIED) {
            return null;
        }
        return mBinder;
    }
    ...
}
复制代码

而后再咱们要绑定服务的应用内声明权限便可。

// AndroidManifest.xml 
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.okhttptesst">
    //注册权限
    <uses-permission android:name="com.aidl.test.permission.ACCESS_BOOK_SERVICE"/>
    ...
</manifest>
复制代码

2.在服务端的 onTransact 中验证 在 onTransact 中验证失败即返回 false,这样服务端就终止执行AIDL中的方法从而达到保护服务端的效果。具体验证方法也有不少。 能够采用第一种验证中的 permission 验证,具体实现也同样。 也能够 Uid 和 Pid 来作验证,经过 getCallingUidgetCallingPid 能够获取客户端所属应用的 Uid 和 Pid,经过这两个参数咱们作 包名验证 等。 示例以下,咱们重写 BookManagerService 中 mBinder 的 onTransact 方法,添加权限和包名验证:

//BookManagerService.java
public class BookManagerService extends Service {
    ...
    private Binder mBinder = new IBookManager.Stub() {
        @Override
        public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
            int check = checkCallingOrSelfPermission("com.aidl.test.permission.ACCESS_BOOK_SERVICE");
            if (check == PackageManager.PERMISSION_DENIED) {
                Log.e(TAG, "PERMISSION_DENIED");
                return false;
            }
            String packageName = null;
            String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
            if (packages != null && packages.length > 0) {
                packageName = packages[0];
            }
            if (!packageName.startsWith("com.aidl")) {
                Log.e(TAG, "packageName is illeagl = " + packageName);
                return false;
            }
            return super.onTransact(code, data, reply, flags);
        }
        ...
    };
    ...
}
复制代码

启动程序,查看日志:

BookManagerService: PERMISSION_DENIED
复制代码

申明权限以后,再运行:

BookManagerService: packageName is illeagl = com.example.aidltest
BookManagerService: packageName is illeagl = com.example.aidltest
BookManagerService: packageName is illeagl = com.example.aidltest
复制代码

除了上面两个验证方法以外,咱们还能够经过 给 Service 指定 android:permission 属性等。


小结

咱们再来回顾下本文的内容:

  • 介绍了 AIDL 的基本使用方法,以及AIDL支持的数据格式。
  • 经过 RemoteCallbackList 给 AIDL 添加和删除回调,遍历数据或者获取大小 必须配对使用 beginBroadcastfinishBroadcast
  • 以及介绍了 经过 permission验证包名验证 给AIDL作权限验证。

下一节咱们介绍经过 ContentProvider 来进行IPC.

若是以为本文不错的话,请帮忙点个赞呗。

以上


扫描下面的二维码,关注个人公众号 Android1024, 点关注,不迷路。

Android1024
相关文章
相关标签/搜索