转载请以连接形式标明出处: 本文出自:103style的博客java
《Android开发艺术探索》 学习记录android
base on AndroidStudio 3.5.1
编程
前面咱们介绍了 进程间通讯基础介绍 、 经过AIDL介绍Binder的工做机制 ,以及 经过 Bundle、文件共享、Messenger实现进程间通讯 , 不了解的能够先看下。安全
经过以前对 Messenger 的介绍,咱们知道 Messenger 是以串行的方式处理消息的,因此当有 大量消息并发请求 时,Messenger 可能就不太合适了。 同时 Messenger 主要是用来传递消息,不少时候咱们可能须要 跨进程调用其余进程的方法 ,这个是 Messenger 作不到的。bash
这时候就轮到 AIDL 展现本身的实力了。 Messenger 也是基于 AIDL 的,是系统对 AIDL 的封装,方便上层调用。并发
咱们在 经过AIDL介绍Binder的工做机制 中介绍了 Binder 的概念,你们对 Binder 应该有了必定的了解。app
这里咱们先介绍下AIDL 来进行进程间通讯的流程,包括 AIDL接口建立、服务端、客户端。ide
tips: 为了方便开发,建议把 AIDL 相关的类和文件放到统一的目录,这样当客户端和服务端是不一样应用时,能够把整个包复制过去。 注意: 客户端和服务端的 AIDL 包结构必须保持一致,不然会运行报错。性能
建立 IBookManager.aidl
:学习
//IBookManager.aidl:
package aidl;
import aidl.Book;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
}
复制代码
而后咱们先来介绍下AIDL支持的数据格式。
AIDL 支持的大部分数据格式,不过也不是全部的数据类型都能使用的,能够用以下类型:
Book
为例://Book.aidl
package aidl;
parcelable Book;
复制代码
除了基本类型以外,其余的类型在做为参数的时候必须标上方向:in、out、inout。
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接口 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 同样去操做它。 遍历其数据 或者 获取其大小,咱们必须配对使用 beginBroadcast 和 finishBroadcast,参考上面代码中回调的注册和解注册的方法。
至此,AIDL 的基本使用方法已经介绍完了,可是还有几点须要再强调如下:
onServiceConnected
和 onServiceDisconnected
都是运行在UI线程的。bookAddListener
,是运行在客户端的 Binder 线程池的,因此不能直接访问UI内容的,如需访问UI,则须要经过 Handler 等切换线程。另外,为了程序的健壮性,咱们还的防止 Binder 意外死亡,这每每是因为服务端进程意外中止了,这是咱们须要重连服务。有两种方法:
默认状况下,咱们的远程服务任何人均可以链接,但这是咱们不想要的,因此咱们要在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 来作验证,经过 getCallingUid 和 getCallingPid 能够获取客户端所属应用的 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
属性等。
咱们再来回顾下本文的内容:
下一节咱们介绍经过 ContentProvider 来进行IPC.
若是以为本文不错的话,请帮忙点个赞呗。
以上
扫描下面的二维码,关注个人公众号 Android1024, 点关注,不迷路。