BindService标准写法
在使用bindservice时,经常会忽略掉死亡回调的作用,下面提供一个标准的bindService的使用流程(客户端),仅供参考
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import ITestBinder;
public class Test extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = new Intent("xxxx");
bindService(intent,serviceConnection, Context.BIND_AUTO_CREATE); //绑定service
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(serviceConnection); //解绑service
}
ITestBinder iBinder;
ServiceConnection serviceConnection = new ServiceConnection() {
// 注意这个函数是在ui线程回调的
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iBinder = ITestBinder.Stub.asInterface(service);
try {
// 注册死亡回调
iBinder.asBinder().linkToDeath(mDeathRecipient, 0);
//TODO 想要进行的主业务
} catch (RemoteException e) {
Log.e("TAG", "onServiceConnected:" + e.getMessage());
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
iBinder = null; //资源释放
}
};
/**
* 死亡回调 service异常结束时 客户端会在这个回调函数里得到通知
*/
IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
iBinder.asBinder().unlinkToDeath(mDeathRecipient, 0); //释放死亡回调
iBinder = null; //资源释放
//TODO 其他处理,例如重新绑定service等
}
};
}
服务端的流程如下:主要是RemoteCallbackList的应用
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;
import common.utils.WLog;
import ITestBinder;
import ITestClientCallback;
import java.util.concurrent.atomic.AtomicInteger;
public class TestService extends Service {
public static final String TAG = "TestService";
/**
* RemoteCallbackList是系统提供的专门用于删除跨进程listener的接口.
* 用RemoteCallbackList,而不用ArrayList的原因是, 客户端的对象注册进来后, 服务端会通过它反序列化出一个新的对象保存一起,
* 所以说已经不是同一个对象了. 在客户端调用解除注册方法时, 在list中根本就找不到它的对象,
* 也就无法从list中删除客户端的对象. 而RemoteCallbackList的内部保存的是客户端对象底层的binder对象,
* 这个binder对象在客户端对象和反序列化的新对象中是同一个对象, RemoteCallbackList的实现原理就是利用的这个特性.
*/
private RemoteCallbackList<ITestClientCallback> mListenerList = new RemoteCallbackList<ITestClientCallback>() {
//回调 注意这个回调 有一个特性是,如果客户端异常死亡了,这个对应的回调会自己消失 所以在这里要对callback 进行死亡监听
@Override
public void onCallbackDied(ITestClientCallback callback, Object cookie) {
Log.v(TAG, "RemoteCallbackList onCallbackDied !!!!!!!!!!!!!");
CONNECTION_COUNT.decrementAndGet();
super.onCallbackDied(callback, cookie);
}
};
/**
* 用来记录当前有多少个callback 连接到 service 上
*/
public static AtomicInteger CONNECTION_COUNT = new AtomicInteger(0);
@Override
public void onCreate() {
super.onCreate();
}
@Override
public void onDestroy() {
mListenerList.kill();
CONNECTION_COUNT.set(0);
Log.i(TAG, "onDestroy !!!!!!");
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent) {
//也可以将onTransact方法中的处理移到该位置
return mBinder;
}
/**
* 这里的方法 不可以阻塞时间过长,否则会影响对下一个client调用的响应。 service本身支持并发访问,所以操作数据时 要保证同步性 client传递到server端的对象是重新被构造出来的
*/
Binder mBinder = new ITestBinder.Stub() {
@Override
public void registerCallback(ITestClientCallback callback) throws RemoteException {
//增加客户端连接计数
CONNECTION_COUNT.incrementAndGet();
mListenerList.register(callback);
final int N = mListenerList.beginBroadcast();
mListenerList.finishBroadcast();
WLog.v(TAG, "getListenerListSize, current size:" + N);
doSomeThing();
}
@Override
public void unregisterCallback(ITestClientCallback callback) throws RemoteException {
//减去客户端连接计数
CONNECTION_COUNT.decrementAndGet();
mListenerList.unregister(callback);
final int N = mListenerList.beginBroadcast();
mListenerList.finishBroadcast();
Log.v(TAG, "unregisterCallback,current size:" + N);
}
// 每次有客户端client 连接进来都会走这个方法
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
//通过getCallingUid()得到客户端的uid, 再通过PackageManager根据uid查到package name进行检查.进行一些相应的限制
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.xxx")) {
return false;
}
return super.onTransact(code, data, reply, flags);
}
};
/**
* RemoteCallbackList 并不是一个List ,不能像 List 一样去操作它,遍历RemoteCallbackList
* 必须要以下面的方式进行,其中 beginBroadcast 和 finishBroadcast 必须配套使用,哪怕我们仅仅是想要获取
* RemoteCallbackList 中的元素个数
*/
private void doSomeThing() {
final int N = mListenerList.beginBroadcast();
for (int i = 0; i < N; i++) {
ITestClientCallback iTestClientCallback = mListenerList.getBroadcastItem(i);
try {
if (iTestClientCallback != null) { //执行客户端的回调
iTestClientCallback.xxx();
}
} catch (RemoteException e) {
// 此异常属于系统异常,编码层面无能力干预,开发者无需过多关注
WLog.e(TAG, " RemoteException ServiceCallback failed:" + e.getMessage());
}
}
mListenerList.finishBroadcast();
}
}
注:
客户端远程调用服务端的方法,被调用的方法运行在服务端的 Binder 线程池中,同时客户端线程会被挂起,此时如果服务端方法执行比较耗时,则会导致客户端线程长时间阻塞在这里,如果此时客户端线程是 UI 线程,则会导致客户端ANR ,因此如果我们明确知道某个远程方法是耗时的,则要避免在客户端的 UI 线程中去访问远程方法。
由于客户端的 onServiceConnected 和 onServiceDisconnected 方法运行在 UI线程中,故也不可以在他们里面直接调用服务端的耗时方法。
服务端方法本身就运行在服务端的Binder 线程池中,故服务端的方法本身就可以进行大量的耗时操作,此时切记不要在服务端开线程去进行异步任务,除非你明确知道自己在干什么,否则不建议这么做
同理,当远程服务端需要调用客户端的 listener 中的方法时,被调用的方法运行在客户端的 Binder 池中,故我们同样不可以在服务端调用客户端耗时方法,
Binder是可能意外死亡的,这往往是由于服务端进程意外停止导致的,此时我们需要重新连接服务。
1、给Binder 设置DeathRecipient 监听,当Binder 死亡时,我们会收到 binderDied 方法的回调,在 binderDied 方法中我们可以重新绑定远程服务
2、在onServiceDisconnected 中重连远程服务
这两种方法的区别在于:onServiceDisconnected 在客户端的 UI 线程中被回调,而 binderDied 在客户端的Binder 线程池中被回调,即在binderDied 方法中我们不能访问 UI