在项目中使用AIDL绑定,注册AIDL回调时,为了兼容多客户端监听,通常用RemoteCallbackList来进行多客户端回调。理论上的写法如下:
private IFaceAidlCallback iFaceAidlCallback = new IFaceAidlCallback.Stub() {
@Override
public void onFaceStart(String content) throws RemoteException {
try {
int size = mRemoteCallbackList.beginBroadcast();
for (int i = 0; i < size; i++) {
mRemoteCallbackList.getBroadcastItem(i).onFaceStart(content);
}
mRemoteCallbackList.finishBroadcast();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onFaceChange(String content) throws RemoteException {
try {
int size = mRemoteCallbackList.beginBroadcast();
for (int i = 0; i < size; i++) {
mRemoteCallbackList.getBroadcastItem(i).onFaceChange(content);
}
mRemoteCallbackList.finishBroadcast();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onFaceEnd(String content) throws RemoteException {
try {
int size = mRemoteCallbackList.beginBroadcast();
for (int i = 0; i < size; i++) {
mRemoteCallbackList.getBroadcastItem(i).onFaceEnd(content);
}
mRemoteCallbackList.finishBroadcast();
} catch (Exception e) {
e.printStackTrace();
}
}
};
这种写法在正常情况下不会报错,但当传输的数据过大(回调的content数据量多)或者回调的客户端过多时,会出现上个方法为执行完finishBroadcast()而另外的方法右执行了beginBroadcast,这会导致程序抛出IllegalStateException异常。虽然try-catch使程序不会奔溃,当客户端会接收不到回调。beginBroadcast()方法源码如下:
public int beginBroadcast() {
synchronized (mCallbacks) {
if (mBroadcastCount > 0) {
throw new IllegalStateException(
"beginBroadcast() called while already in a broadcast");
}
final int N = mBroadcastCount = mCallbacks.size();
if (N <= 0) {
return 0;
}
Object[] active = mActiveBroadcast;
if (active == null || active.length < N) {
mActiveBroadcast = active = new Object[N];
}
for (int i=0; i<N; i++) {
active[i] = mCallbacks.valueAt(i);
}
return N;
}
}
解决办法如下:
private Lock mLock = new ReentrantLock();
private IFaceAidlCallback iFaceAidlCallback = new IFaceAidlCallback.Stub() {
@Override
public void onFaceStart(String content) throws RemoteException {
mLock.lock();
try {
int size = mRemoteCallbackList.beginBroadcast();
for (int i = 0; i < size; i++) {
mRemoteCallbackList.getBroadcastItem(i).onFaceStart(content);
}
mRemoteCallbackList.finishBroadcast();
} catch (Exception e) {
e.printStackTrace();
} finally {
mLock.unlock();
}
}
@Override
public void onFaceChange(String content) throws RemoteException {
mLock.lock();
try {
int size = mRemoteCallbackList.beginBroadcast();
for (int i = 0; i < size; i++) {
mRemoteCallbackList.getBroadcastItem(i).onFaceChange(content);
}
mRemoteCallbackList.finishBroadcast();
} catch (Exception e) {
e.printStackTrace();
} finally {
mLock.unlock();
}
}
@Override
public void onFaceEnd(String content) throws RemoteException {
mLock.lock();
try {
int size = mRemoteCallbackList.beginBroadcast();
for (int i = 0; i < size; i++) {
mRemoteCallbackList.getBroadcastItem(i).onFaceEnd(content);
}
mRemoteCallbackList.finishBroadcast();
} catch (Exception e) {
e.printStackTrace();
} finally {
mLock.unlock();
}
}
};
采用加锁的方式,当执行方法时进行阻塞,保证方法同步。至于为什么不用synchronized同步的方式来保证方法的同步,需要另外的讨论。之前遇到过 synchronized同步方法还是会报错的情况,故采用ReentrantLock加锁的方式来保证方法同步,ReentrantLock和synchronized的区别需要自行了解,本篇不做过多描述。