Android Service更多知识
Posted ITRenj
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Service更多知识相关的知识,希望对你有一定的参考价值。
- 《Android Service基础》
- 《Android Service回调和配置》
- 《Android Service aidl使用及进阶》
- 《Android Service更多知识》
- 《Android 中的 IntentService 类详解》
- 《Android Service aidl分析》
- 《Android Service 流程分析》
这篇文章主要介绍的是一些Service的相关知识,绑定服务中服务器异常中断时处理办法(死亡代理 DeathRecipient)、服务器数据更新后实时通知客户端(回调通知以及RemoteCallbackList)、Service运行线程说明、使用 Messenger 实现 Servie 与 Client 端通信、可以执行耗时操作的Service(IntentService)、怎样选择什么类型的Service
死亡代理 DeathRecipient
在前面的几篇文章中,我么知道了服务的绑定、aidl的使用等,我们可以通过 bindService()
连接服务然后调用服务器的方法。但是在进行进程间通信的过程中,如果服务端进程由于某种原因异常终止,我们的远程调用就会失败,从而影响我们客户端的功能,那么有什么办法能够知道服务端进程是否终止了呢?在android Service中,可以通过给IBinder增加死亡代理的方式,在服务端进程异常中断时,给客户端一个回调通知。具体的方式就是在 ServiceConnection
对象的回调方法 onServiceConnected
(该方法是客户端与服务端连接成功的回调)中调用IBinder的 linkToDeath(DeathRecipient recipient, int flags)
方法,传递 DeathRecipient
的对象,在服务器异常中断时,会回调该对象的 binderDied()
方法,可以在方法内部进行处理,如重新绑定等。
private IBinderPoolBinder iBinderPoolBinder;
private ServiceConnection bindConnection = new ServiceConnection()
@Override
public void onServiceConnected(ComponentName name, IBinder service)
iBinderPoolBinder = IBinderPoolBinder.Stub.asInterface(service);
// 增加死亡代理
try
service.linkToDeath(recipient, 0);
catch (RemoteException e)
e.printStackTrace();
@Override
public void onServiceDisconnected(ComponentName name)
;
// 死亡代理
private IBinder.DeathRecipient recipient = new IBinder.DeathRecipient()
@Override
public void binderDied()
// 当服务器异常中断时,会调用该方法,可以在该方法内部进行处理,如重新绑定等
if (iBinderPoolBinder == null) return;
// 解除死亡通知,如果Binder死亡了,不会在触发binderDied方法
iBinderPoolBinder.asBinder().unlinkToDeath(recipient, 0);
iBinderPoolBinder = null;
bindService();
;
回调通知以及RemoteCallbackList
在前面我们已经知道了aidl的使用,远程服务aidl中的方法是运行在新的线程当中的,是可以执行耗时任务的,当客户端需要服务器执行耗时操作完成之后的结果时,那怎么办了?如果是在同一进程的不同线程中,子线程执行完耗时操作通知主线程,我们可以直接通过设置监听的方式就可以实现了。但是使用aidl时不是同一个进程了,我们是不是也可以使用监听的方式来获取结果了。答案是可以的,只是我们的监听对象不是普通的java接口,而是应该用aidl接口。
使用监听将结果返回给客户端
一般步骤:
-
定义aidl回调接口 ICalculateResultCallBack.aidl
interface ICalculateResultCallBack void result(double result);
-
和普通aidl一样编写,只是将第1点的dial接口也作为一个参数通过aidl接口传递给服务器 ICalculateResultBinder.aidl
interface ICalculateResultBinder void operatorAdd(double num1, double num2, ICalculateResultCallBack iCalculateResultCallBack); void operatorSubtract(double num1, double num2, ICalculateResultCallBack iCalculateResultCallBack); void operatorMultiply(double num1, double num2, ICalculateResultCallBack iCalculateResultCallBack); void operatorDivide(double num1, double num2, ICalculateResultCallBack iCalculateResultCallBack);
-
aidl接口(ICalculateResultBinder.aidl)实现类,重写相关方法 完整代码
class CalculateResultBinderImpl extends ICalculateResultBinder.Stub @Override public void operatorAdd(double num1, double num2, ICalculateResultCallBack iCalculateResultCallBack) throws RemoteException BigDecimal bigDecimal1 = BigDecimal.valueOf(num1); BigDecimal bigDecimal2 = BigDecimal.valueOf(num2); BigDecimal result = bigDecimal1.add(bigDecimal2); // 将结果回调给客户端 if (iCalculateResultCallBack != null) iCalculateResultCallBack.result(result.doubleValue()); // 其他的需要重新的方法...
-
新建Service,返回IBinder(第3点对象) 完整代码
public class CalculateResultService extends Service private CalculateResultBinderImpl iCalculateResultBinder; @Nullable @Override public IBinder onBind(Intent intent) iCalculateResultBinder = new CalculateResultBinderImpl(); return iCalculateResultBinder;
-
客户端调用方法,并且获取回调结果 完整代码
iCalculateResultBinder.operatorAdd(num1, num2, new ICalculateResultCallBack.Stub() @Override public void result(double result) throws RemoteException Logger.i("收到计算结果 加法: " + num1 + " + " + num2 + " = " + result + currentProgressAndThread()); tvResult.setText(result + ""); );
RemoteCallbackList
根据以上的内容,我们知道了客户端怎样可以实时获取到服务器的执行结果;但是现在我们考虑另外一种场景,我有一个专门生产数据的服务,然后客户端可以根据自己需要告诉服务端是否需要在产生新数据时通知自己,那么我们服务端就需要用一个列表保存多个客户端的回调了,当某一个客户端需要接收到通知时,就把回调对象注册到服务端,服务端产生新的数据之后,就通知所有注册了的客户端,客户端不需要接收通知了,就取消和服务器的注册回调。想要完美又方便的实现以上功能,首先需要介绍一个新的类:RemoteCallbackList
RemoteCallbackList 是系统专门提供用于跨进程的 listener 的接口,它是一个泛型 RemoteCallbackList<E extends IInterface>
,支持管理任意的aidl接口。它的原理很简单,主要是用一个Map来保存所有aidl的回调,通过 unregister(E callback)
和 callback.asBinder()
方法来添加和移除回调。
ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback>();
这个Map的key是 IBinder
类型,value是 Callback
类型,Callback
保存了真正的远程回调 mCallback
,同时 Callback
实现了 IBinder.DeathRecipient
接口,在断开连接时自动解除注册。
private final class Callback implements IBinder.DeathRecipient
final E mCallback;
final Object mCookie;
Callback(E callback, Object cookie)
mCallback = callback;
mCookie = cookie;
public void binderDied()
synchronized (mCallbacks)
mCallbacks.remove(mCallback.asBinder());
onCallbackDied(mCallback, mCookie);
当调用 RemoteCallbackList
的 register(E callback, Object cookie)
方法时,实际上是创建了一个新的 Callback
保存到Map中了,调用 unregister(E callback)
时,通过 callback.asBinder()
获取需要移除的Map的key,这样就能保证能找到移除的对象了。
// register()
IBinder binder = callback.asBinder();
Callback cb = new Callback(callback, cookie);
mCallbacks.put(binder, cb);
// unregister()
Callback cb = mCallbacks.remove(callback.asBinder());
为什么使用 RemoteCallbackList
,而不是使用 List
或者 Map
呢?
- 我们在客户端将回调对象传递给服务端时,服务端获取到的并不是客户端的对象,而是重新生成的一个全新的对象(是序列化和反序列化的过程,也就是为什么我们传递对象时都要实现
Parcelable
接口的原因)。基于这点,使用List就完全可以排除了,因为即使我们在注册和解除注册时,客户端传递同一个对象给服务端,服务端会生成两个完全不同的对象,那么解除注册时,根本就不可能移除注册的对象。 - 使用Map的话,我们用一个基本类型对象作为Key,理论上是可以实现这个功能的。但是在实际当中却会遇到很多的问题。比如:
- 这个Key只能是客户端传递,而不能是服务端自动产生,因为只有这样客户端才能正确解除注册。这样一来必须保证每个客户端传递的key都是不同的,要不然就会覆盖其他客户端的回调;同时,在解除注册时,也不能传错,一旦传错了,不但自己的注册没有被解除,反倒把其他客户端的注册解除了,这就直接导致了再实际中不可能使用了。
- HashMap 没有自动实现同步问题,需要自己处理
- 当客户端进程终止后,HashMap不会自动解除绑定
RemoteCallbackList
的好处:
- 内部的Key用的是
IBinder
类型,通过callback.asBinder()
保证Key的唯一性,并且不需要客户端参与 - 当客户端进程终止后,
RemoteCallbackList
能自动移除该客户端所注册的回调(Callback
实现了IBinder.DeathRecipient
) RemoteCallbackList
自动实现的线程的同步,所以在注册和解除注册时不需要额外的线程同步操作
RemoteCallbackList
的使用:
private RemoteCallbackList<IBookCallBack> remoteCallbackList = new RemoteCallbackList<>();
添加数据:register(E callback, Object cookie)
方法
remoteCallbackList.register(callBack);
移除数据:unregister(E callback)
方法
remoteCallbackList.unregister(callBack);
遍历数据
RemoteCallbackList
的遍历需要注意一下,需要配对使用 beginBroadcast()
和 finishBroadcast()
方法来表示开始和结束,其实不只是遍历,哪怕我们仅仅只是获取 RemoteCallbackList
中的元素个数也需要调用这两个方法
remoteCallbackList.beginBroadcast();
int registeredCallbackCount = remoteCallbackList.getRegisteredCallbackCount();
for (int i = 0; i < registeredCallbackCount; i++)
IBookCallBack callBack = remoteCallbackList.getBroadcastItem(i);
try
callBack.callBack(bookBean);
catch (RemoteException e)
e.printStackTrace();
remoteCallbackList.finishBroadcast();
RemoteCallbackList
使用完整代码
Service运行线程说明
- 本地服务运行在其托管进程的主线程
- 远程服务运行在其指定进程的主线程(通过 android:process=“progress name” 指定)
- aidl中的方法
- 本地进程的调用在发起调用的同一线程内执行,但此情况下,不应该使用 AIDL,而应通过继承 Binder 类来创建接口
- 远程进程的调用由线程池分派,且平台会在自己的进程内部维护该线程池
Messenger
Messenger翻译为信使,顾名思义,就是用于传递信息的,通过它可以在不同进程中传递Message对象。在Message中放入我们需要传递的信息,然后通过Messenger将Message传递给对方,就可以轻轻松松实现跨进程数据传递。实际上Messenger是一种轻量级的IPC(跨进程通信)方式,它的底层仍然是实现的AIDL。
Messager 和 AIDL的区别:
- Messenger使用起来比AIDL简洁方便;
- AIDL的客户端接口同时向服务端发送多个请求时,服务端需要用多线程处理。而Messenger会将所有请求排入队列(Handler对应的MessageQueue),让服务器一次处理一个,不用处理多线程问题。大多数情况下,服务端不需要执行多线程处理此时选择Messenger方式更合适,而如果客户端的请求要求服务端执行多线程处理,就应该使用AIDL来实现。
Messenger
有两个构造方法,一个是接收参数 Handler
用来处理数据的,另一个是接收一个原始 IBinder
用来构建成一个用于通讯的 Messenger
。
当客户端绑定成功之后,我们可以通过 Messenger
的构造方法,传递服务返回的 IBinder
获取服务器的 Messenger
,命名为: serviceMessenger
。
private ServiceConnection conn = new ServiceConnection()
@Override
public void onServiceConnected(ComponentName name, IBinder service)
serviceMessenger = new Messenger(service);
@Override
public void onServiceDisconnected(ComponentName name)
serviceMessenger = null;
;
然后通过 Messenger
的 send(Message message)
方法发送 Message
对象给服务器,同时将客户端的 Messenger
通过 Message
的 replyTo
字段发送给服务端,用于服务端向客户端传递数据。
clientMessenger = new Messenger(clientServiceHandler);
Message message = Message.obtain();
// 将客户端的 Messenger 传递给服务端,用于服务端向客户端发送数据
message.replyTo = clientMessenger;
message.what = MessengerWhatConstant.MESSENGER_CLIENT_SEND_WAIT;
message.obj = "我是客户端数据,收到请告知。";
try
serviceMessenger.send(message);
tvContent.setText("客户端发送数据: " + message.obj);
catch (RemoteException e)
e.printStackTrace();
服务端收到客户端的信息之后,就可以在 handleMessage()
方法内进行处理了,这是因为在服务端创建 Messenger 时将Handler对象作为参数传递进去了。
// MessengerService 的 onBind 方法,
@Nullable
@Override
public IBinder onBind(Intent intent)
if (serviceMessenger == null)
serviceMessenger = new Messenger(messageServiceHandler);
return serviceMessenger.getBinder();
public void handleMessage(@NonNull Message msg)
// 获取客户端传递过来的 Messenger
clientMessenger = msg.replyTo;
switch (msg.what)
case MessengerWhatConstant.MESSENGER_CLIENT_SEND_WAIT:
ToastUtils.showToast("收到客户端信息: " + msg.obj);
Message message = Message.obtain();
message.what = MessengerWhatConstant.MESSENGER_SERVICE_SEND;
message.obj = "你的信息 \\"" + msg.obj + "\\" 已收到,现告知于你。";
try
// 通过客户端的 Messenger 向客户端发送信息
clientMessenger.send(message);
catch (RemoteException e)
e.printStackTrace();
break;
IntentService
IntentService
继承 Service
,所以 Service
的特点 IntentService
也全部拥有。相对于 Service
来说,IntentService
主要有两点区别:
- 会创建独立的工作线程来处理
onHandleIntent()
方法实现的代码,无需处理多线程问题; - 所有请求处理完成后,
IntentService
会自动停止,无需调用stopSelf()
方法停止 Service;
查看文章 《Android 中的 IntentService 类详解》
怎样选择使用什么类型的Service
- 只有在需要不同应用的客户端通过 IPC 方式访问服务,并且希望在服务中进行多线程处理时,才有必要使用 AIDL;
- 如果无需跨不同应用执行并发 IPC,则应通过继承 Binder 来创建接口(本地绑定);
- 如果想执行 IPC,但不需要处理多线程,应该使用 Messenger 来实现;
- 如果不需要服务一直在后台运行,只需要处理完所有数据(包括耗时数据的处理)就自动停止,应该使用 IntentService 来实现。
以上是关于Android Service更多知识的主要内容,如果未能解决你的问题,请参考以下文章