Android IPC通信之AIDL
2019-07-23 本文已影响0人
Kris_Ni
AIDL( Android Interface Definition Language),安卓接口定义语言,用于定义服务器和客户端通信接口的一种描述语言,主要是解决了android进程间通信的问题。
在AIDL中,不是所有的数据类型都是可以使用的。可用类型如下:
- 基本数据类型(byte char int long boolean float double),short不支持
- String和CharSequence
- List:只支持ArrayList,里面的每个元素都必须被AIDL支持
- Map:只支持HashMap,里面的每个key,value都必须被AIDL支持
- Parcelable:所有实现了Parcelable接口的对象
- AIDL 所有的AIDL接口本身也可以在AIDL文件中使用
需要注意的是: - AIDL中自定义的Parcelable对象和AIDL对象必须要实现import进来,即使是在同一个包下面。
- 如果AIDL中用到了Parcelable对象,那么必须要新建一个和它同名的AIDL文件,并在其中声明它为Parcelable对象。
- AIDL中除了基本数据,其他类型的参数必须标上方向:in、out、inout,in表示输入型参数,out表示输出型参数,inout表示输入输出型参数。
接下来通过Demo来讲解:
//自定义数据类型Book,实现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];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(bookId);
dest.writeString(bookName);
}
}
接着New->AIDL->AIDL file创建AIDL文件,注意要明确导包
// Book.aidl ,可能会出现同名问题,可以先建一个别的名字再改回来
package com.wtwd.aidl;
parcelable Book;
// IBookManager.aidl
package com.wtwd.aidl;
import com.wtwd.aidl.Book;
import com.wtwd.aidl.IOnNewBookArrivedListener;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
void registerListener(IOnNewBookArrivedListener listener);
void unregisterListener(IOnNewBookArrivedListener listener);
}
// IOnNewBookArrivedListener.aidl
package com.wtwd.aidl;
import com.wtwd.aidl.Book;
interface IOnNewBookArrivedListener {
void onNewBookArrived(in Book newbook);
}
接着build一下项目会在app/build/generated/aidl_source_output_dir/debug/compileDebugAidl/out目录下找到对应的AIDL生成文件(具体分析见Android中的IPC机制一文),接下来创建BookManagerService作为服务端核心代码。
public class BookManagerService extends Service {
private static final String TAG = "BookManagerService";
//CopyOnWriteArrayList是为了处理线程同步,但是这里就和之前说的AIDL只能够使用ArrayList,为什么这儿可以
//使用CopyOnWriteArrayList呢?首先CopyOnWriteArrayList不是继承自ArrayList的,且AIDL支持的是抽象的
//List,而List是一个接口,虽然服务端返回的是CopyOnWriteArrayList,但是Binder中会按照List的规范去访
//问数据并最终形成一个新的ArrayList给客户端,同理ConcurrentHashMap也是如此。
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
//private CopyOnWriteArrayList<IOnNewBookArrivedListener> mListenerList = new CopyOnWriteArrayList<>();
//Binder从客户端传过来的对象经过反序列化后和原先是不同的,所以需要采用RemoteCallbackList来实现
//RemoteCallbackList是系统专门用来删除跨进程的listener接口,它是一个泛型,支持管理任意的AIDL接口,这是因为所以AIDL接口继承自IInterface
//public class RemoteCallbackList<E extends IInterface> {}
//内部采用Map结构保存回调,key是IBinder类型,value是CallBack类型
//ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback>();
//在CallBack中封装了真正的远程listener,客户端注册listener的时候会把这个listener信息存到mCallbacks,key和value通过如下获取
//IBinder binder = callback.asBinder();
//Callback cb = new Callback(callback, cookie);
//RemoteCallbackList内部已实现线程同步
private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<>();
//用来记录Service是否销毁的标志位,默认false
private AtomicBoolean mIsServiceDestroyed = new AtomicBoolean();
//服务端的远程方法
private Binder mBinder = new IBookManager.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
return mBookList;
}
@Override
public void addBook(Book book) throws RemoteException {
mBookList.add(book);
}
@Override
public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
// if (!mListenerList.contains(listener)) {
// mListenerList.add(listener);
// } else {
// Log.e(TAG,"already exists.");
// }
// Log.e(TAG,"registerListener,size:"+mListenerList.size());
mListenerList.register(listener);
}
@Override
public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
// if (!mListenerList.contains(listener)) {
// mListenerList.remove(listener);
// Log.e(TAG,"unregister listener success");
// } else {
// Log.e(TAG,"not found,can not unregister.");
// }
// Log.e(TAG,"unregisterListener,size:"+mListenerList.size());
mListenerList.unregister(listener);
}
};
//服务端返回的IBinder对象
@Nullable
@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"));
//开启子线程,执行任务
new Thread(new ServiceWorker()).start();
}
@Override
public void onDestroy() {
mIsServiceDestroyed.set(true);
super.onDestroy();
}
private void onNewBookArrived(Book book) throws RemoteException {
mBookList.add(book);
final int N = mListenerList.beginBroadcast();
// Log.e(TAG,"onNewBookArrived,notify listener:"+mListenerList.size());
for (int i=0;i<N;i++) {
IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i);
Log.e(TAG,"onNewBookArrived,notify listener:"+listener);
if (listener != null) {
listener.onNewBookArrived(book);
}
}
mListenerList.finishBroadcast();
}
private class ServiceWorker implements Runnable {
@Override
public void run() {
while (!mIsServiceDestroyed.get()) {
try {
//每隔5s执行任务,回调给客户端
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int bookId = mBookList.size()+1;
Book newBook = new Book(bookId,"new book#"+bookId);
try {
onNewBookArrived(newBook);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}
}
AndroidManafist.png
以上就是AIDL服务端的代码了,虽然看起来比较复杂,但是细细评味下来还是获益匪浅的。接下来就开始客户端的代码编写了,通常我们接触最多的也是这方面,服务端会把相关的AIDL代码提供过来,我们直接把它复制粘贴到项目中就可以了,需要注意一点的是,如果存在自定义数据类型,需要将JavaBean放到java目录和AIDL相同的包下面,如下所示:
AIDL客户端复制.png
接下来Build一下Project就可以生成对应的AIDL接口文件了
public class MainActivity extends AppCompatActivity {
private static final String TAG = "kris_ni";
private static final int MESSAGE_NEW_BOOK_ARRIVED = 0x1;
private IBookManager iBookManager;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case MESSAGE_NEW_BOOK_ARRIVED:
Log.e(TAG,"receive new book :" + msg.obj);
break;
default:
break;
}
}
};
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
iBookManager = IBookManager.Stub.asInterface(iBinder);
try {
List<Book> bookList = iBookManager.getBookList();
Log.e(TAG,"query book list,list type: "+ bookList.getClass().getCanonicalName());
Log.e(TAG,"query book list: "+bookList.toString());
Book newBook = new Book(3,"Kotlin");
iBookManager.addBook(newBook);
Log.e(TAG,"add new book: "+newBook);
List<Book> newList = iBookManager.getBookList();
Log.e(TAG,"query book list: "+newList.toString());
iBookManager.registerListener(listener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
iBookManager = null;
Log.e(TAG,"binder died");
}
};
private IOnNewBookArrivedListener listener = new IOnNewBookArrivedListener.Stub() {
@Override
public void onNewBookArrived(Book book) throws RemoteException {
mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED,book).sendToTarget();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent("com.example.aidldemo.BookManagerService");
intent.setPackage("com.example.aidldemo");
bindService(intent,serviceConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
if (iBookManager != null && iBookManager.asBinder().isBinderAlive()) {
try {
iBookManager.unregisterListener(listener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
unbindService(serviceConnection);
super.onDestroy();
}
}
总体看下来AIDL也就这样了么,当然不止这些。有没有考虑过AIDL被滥用的情况呢?可以通过权限来限制客户端,简单的写法就是在服务端声明权限
<permission android:name="com.example.aidldemo.permission.ACCESS_BOOK_SERVICE"
android:protectionLevel="normal"/>
<uses-permission android:name="com.example.aidldemo.permission.ACCESS_BOOK_SERVICE"/>
接着就可以在Service里面判断是否将IBinder返回了
@Override
public IBinder onBind(Intent intent) {
int check = checkCallingOrSelfPermission("com.example.aidldemo.permission.ACCESS_BOOK_SERVICE");
if (check == PackageManager.PERMISSION_DENIED) {
Log.e(TAG,"has no permission");
return null;
}
return mBinder;
}
客户端所做的是需要申明权限才可以进行ServiceConnection
<uses-permission android:name="com.example.aidldemo.permission.ACCESS_BOOK_SERVICE"/>
另外一种写法如下:
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
int check = checkCallingOrSelfPermission("com.example.aidldemo.permission.ACCESS_BOOK_SERVICE");
Log.d(TAG, "check=" + check);
if (check == PackageManager.PERMISSION_DENIED) {
return false;
}
String packageName = null;
String[] packages = getPackageManager().getPackagesForUid(
getCallingUid());
if (packages != null && packages.length > 0) {
packageName = packages[0];
}
Log.d(TAG, "onTransact: " + packageName);
if (!packageName.startsWith("com.example")) {
return false;
}
return super.onTransact(code, data, reply, flags);
}
AIDL的介绍到此就差不多结束了,有没有思考过Binder可能会意外被销毁呢?在接下来的Binder连接池里面会重点为大家介绍,感谢您的阅读,记得随手点个赞喔!