Messenger与AIDL
上一节讲解的Messenger来进行进程间通信的方法,可以发现,Messenger是以串行的方式来处理客户端的请求的,如果大量的消息同时发送给服务端,仍然也只能一个一个的处理。所以如果有大量的并发请求,那么用Messenger是不太合适了。同时,Messenger的作用是用来传递消息的,很多时候我们需要跨进程调用服务端的方法。这种情形Messenger是无法做到的。只能使用AIDL来进行。AIDL也是Messenger的底层实现,因此Messenger本质上也是AIDL。只不过系统为我们做了封装方便调用而已。
AIDL进程间通信的流程,分为服务端和客户端:
1.服务端
首先需要创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,最后在Service中实现这个AIDL接口即可。
2.客户端
首先通过绑定服务端的Service,绑定成功后,将服务端返回的IBinder对象转成AIDL接口所属的类型。接着就可以调用AIDL中的方法了。
AIDL接口的创建
// IBookManager.aidl
package com.thh.ipcdemo2aidl.aidl;
// Declare any non-default types here with import statements
import com.thh.ipcdemo2aidl.aidl.Book;
import com.thh.ipcdemo2aidl.aidl.IOnNewBookArrivedListener;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
void registerListener(IOnNewBookArrivedListener listener);
void unRegisterListener(IOnNewBookArrivedListener listener);
}
AIDL支持的数据类型:
基本数据类型、String和CharSequence、List(只支持ArrayList,并且里面的每个元素都必须能被AIDL支持)、Map(只支持HashMap,里面的每个元素都必须被AIDL支持,包括KEY和Value)、Parcelable、AIDL(所有AIDL接口本身也可以在AIDL文件中使用)
注意:
自定义的Parcelable对象不是与AIDL文件是否在同一个包下,都需要显式的import进来。
如果AIDL文件中用到了自定义的Parcelable对象,必须创建一个和它同名的AIDL文件,并在其中声明它为Parcelable类型。如下:
package com.thh.ipcdemo2aidl.aidl;
parcelable Book;
.
AIDL中除了基本数据类型以外,其他类型的参数必须标上方向:in、out或者inout。
如果client不需要传输数据给server,client只需要处理经过server处理过后的数据,
那么 client 和 server 都为 out
如果client只需要传输数据给server,而不需要处理返回的数据,
那么client和server都为 in
如果client需要传输数据给server,而且需要处理返回的数据,
则client和server都为 inout
AIDL中只支持方法,不支持静态常量。这点区别于传统接口。
示例代码:
package com.thh.ipcdemo2aidl;
import android.annotation.TargetApi;
import android.app.Service;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.util.Log;
import com.thh.ipcdemo2aidl.aidl.Book;
import com.thh.ipcdemo2aidl.aidl.IBookManager;
import com.thh.ipcdemo2aidl.aidl.IOnNewBookArrivedListener;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Created by TangHui on 2015/10/21.
*/
public class IService extends Service {
private AtomicBoolean mIsServiceDestory = new AtomicBoolean(false);
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<>();
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);
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
@Override
public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
mListenerList.register(listener);
Log.i("thhi", "[IService registerListener] registerListener size:"+mListenerList.getRegisteredCallbackCount());
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
@Override
public void unRegisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
mListenerList.unregister(listener);
Log.i("thhi", "[IService unRegisterListener] current size:" + mListenerList.getRegisteredCallbackCount());
}
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
// 第二种远程调用的验证方式
int check = checkCallingOrSelfPermission("com.thh.ipcdemo2aidl.permission.BOOK_SERVICE");
if (check == PackageManager.PERMISSION_DENIED){
Log.i("thhi","[IService onBind] check == PackageManager.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.thh")){
return false;
}
return super.onTransact(code, data, reply, flags);
}
};
@Override
public void onCreate() {
super.onCreate();
mBookList.add(new Book(1, "Android"));
mBookList.add(new Book(2, "IOS"));
new Thread(new ServiceWorker()).start();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
// 第一种远程调用的验证方式
// int check = checkCallingOrSelfPermission("com.thh.ipcdemo2aidl.permission.BOOK_SERVICE");
// if (check == PackageManager.PERMISSION_DENIED){
// Log.i("thhi","[IService onBind] check == PackageManager.PERMISSION_DENIED");
// return null;
// }
return mBinder;
}
private class ServiceWorker implements Runnable {
@Override
public void run() {
while (!mIsServiceDestory.get()){
try {
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();
}
}
}
}
private void onNewBookArrived(Book book) throws RemoteException {
mBookList.add(book);
int N = mListenerList.beginBroadcast();
for (int i = 0; i < N; i++) {
IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i);
Log.i("thhi", "[IService onNewBookArrived] notify listener:"+listener);
listener.onNewBookArrived(book);
}
mListenerList.finishBroadcast();
}
@Override
public void onDestroy() {
super.onDestroy();
mIsServiceDestory.set(true);
}
}
CopyOnWriteArrayList支持并发读/写,其原理是这样的,开始大家共享同一个内容,但如果一个人要去修改,它就会Copy一份新的内容,当修改并添加完毕后,自动将引用指向这个新的内容。在源代码中可以发现,它的add方法是添加的有锁的,get方法没有锁。这种类型对象适合多读少写的操作,因为可能导致数据的不一致性。因为AIDL方法在服务端的Bindler线程池中执行,因此当多个客户端访问时,我们需要做线程同步的处理,所以这里就用CopyOnWriteArrayList。
另外还有一个ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。
package com.thh.ipcdemo2aidl;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import com.thh.ipcdemo2aidl.aidl.Book;
import com.thh.ipcdemo2aidl.aidl.IBookManager;
import com.thh.ipcdemo2aidl.aidl.IOnNewBookArrivedListener;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private static final int MESSAGE_NEW_BOOK_ARRIVED = 0;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_NEW_BOOK_ARRIVED:
Log.i("thhi", "[MainActivity mHandler handlerMessage]: MESSAGE_NEW_BOOK_ARRIVED, msg:"+msg.obj);
break;
}
super.handleMessage(msg);
}
};
private IBookManager iBookManager;
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iBookManager = IBookManager.Stub.asInterface(service);
try {
List<Book> bookList = iBookManager.getBookList();
Log.i("thhi", "bookList:" + bookList);
iBookManager.addBook(new Book(3, "Window Phone"));
Log.i("thhi", "addBook after:" + iBookManager.getBookList());
iBookManager.registerListener(mOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
iBookManager = null;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(this, IService.class);
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
}
private IOnNewBookArrivedListener mOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() {
@Override
public void onNewBookArrived(Book book) throws RemoteException {
mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, book).sendToTarget();
}
};
@Override
protected void onDestroy() {
if (iBookManager != null && iBookManager.asBinder().isBinderAlive()) {
try {
iBookManager.unRegisterListener(mOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
unbindService(mServiceConnection);
super.onDestroy();
}
}
知识点:
AIDL文件中无法使用的普通接口,只能是AIDL接口。
另外通过RemoteCallbackList来删除跨进程listener的接口。用于解决无法删除对应listener对象。因为是客户端传递给服务端的对象在服务端会生成一个不同的对象,但它们底层的Binder对象是同一个,利用这个特点,找出那个和解注册listener具有相同Binder对象的服务端listener并把它删除掉。另外,当客户端进程终止后,它能够自动移除客户端所注册的listener。
客户端调用服务端方法时,尽量使用子线程。服务端AIDL方法调用客户端方法时,因为当前线程是Binder线程池的子线程,所以不得进行UI操作。在上面例子中,服务端需要调用客户端listener的方法时,被调用的方法也运行在Binder线程池中,只不过是客户端的线程池。
.
.
Binderl意外中断
往往是由于服务端进程意外停止了,这时我们需要重新连接服务。有两种方法:
给Binder设置DeathRecipient监听,当Binder中断后,我们会收到binderDeath方法的回调。
另一种方法在onServiceDisconnected中重连。
以上两种方法区别在于:onServiceDisconnected在UI线程中,而DeathRecipient binderDeath方法是在Binder线程池中被回调。
.
.
AIDL权限验证
在服务端Service的onBind方法进行验证,或在onTransact方法进行权限验证。
验证的方法有多种,比如设置Service的permission,或者通过getCallingUid或getCallingPid做验证。
服务端
<uses-permission android:name="com.thh.ipcdemo2aidl.permission.BOOK_SERVICE" />
<service
android:name=".IService"
android:enabled="true"
android:exported="true"
android:permission="com.thh.ipcdemo2aidl.permission.BOOK_SERVICE"
android:process=":remote">
<intent-filter>
<action android:name="com.thh.ipcdemo2aidl.action.BOOK_SERVICE" />
</intent-filter>
</service>
<permission
android:name="com.thh.ipcdemo2aidl.permission.BOOK_SERVICE"
android:protectionLevel="normal" />
@Override
public IBinder onBind(Intent intent) {
// 第一种远程调用的验证方式
int check = checkCallingOrSelfPermission("com.thh.ipcdemo2aidl.permission.BOOK_SERVICE");
if (check == PackageManager.PERMISSION_DENIED){
Log.i("thhi","[IService onBind] check == PackageManager.PERMISSION_DENIED");
return null;
}
return mBinder;
}
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
// 第二种远程调用的验证方式,权限验证
int check = checkCallingOrSelfPermission("com.thh.ipcdemo2aidl.permission.BOOK_SERVICE");
if (check == PackageManager.PERMISSION_DENIED){
Log.i("thhi","[IService onBind] check == PackageManager.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.thh")){
return false;
}
return super.onTransact(code, data, reply, flags);
}
客户端
<uses-permission android:name="com.thh.ipcdemo2aidl.permission.BOOK_SERVICE" />
Intent intent = new Intent();
intent.setAction("com.thh.ipcdemo2aidl.action.BOOK_SERVICE");
Intent explicitIntent = getExplicitIntent(this, intent);
bindService(explicitIntent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBookManager = IBookManager.Stub.asInterface(service);
try {
Log.i("thhi", "[MainActivity onServiceConnected] bookList:" + mBookManager.getBookList());
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}, BIND_AUTO_CREATE);
public static Intent getExplicitIntent(Context context, Intent implicitIntent) {
// Retrieve all services that can match the given intent
PackageManager pm = context.getPackageManager();
List<ResolveInfo> resolveInfo = pm.queryIntentServices(implicitIntent, 0);
// Make sure only one match was found
if (resolveInfo == null || resolveInfo.size() != 1) {
return null;
}
// Get component info and create ComponentName
ResolveInfo serviceInfo = resolveInfo.get(0);
String packageName = serviceInfo.serviceInfo.packageName;
String className = serviceInfo.serviceInfo.name;
ComponentName component = new ComponentName(packageName, className);
// Create a new intent. Use the old one for extras and such reuse
Intent explicitIntent = new Intent(implicitIntent);
// Set the component to be explicit
explicitIntent.setComponent(component);
return explicitIntent;
}