天天看点

《Android开发进阶:从小工到专家》——第1章,第1.2节Service与AIDL

本节书摘来自异步社区《android开发进阶:从小工到专家》一书中的第1章,第1.2节service与aidl,作者 何红辉,更多章节内容可以访问云栖社区“异步社区”公众号查看

1.2 service与aidl

service是android中实现程序后台运行的解决方案,它非常适合用于去执行那些不需要和用户交互而且还要求长期运行的任务。但不要被“后台”二字所迷惑,service默认并不会运行在子线程中,它也不运行在一个独立的进程中,它同样执行在ui线程中,因此,不要在service中执行耗时的操作,除非你在service中创建了子线程来完成耗时操作。

service的运行不依赖于任何用户界面,即使程序被切换到后台或者用户打开了另外一个应用程序,service仍然能够保持正常运行,这也正是service的使用场景。当某个应用程序进程被杀掉时,所有依赖于该进程的service也会停止运行。

1.2.1 普通service

service的生命周期相对activity来说简单得多,只有3个,分别为oncreate、onstartcommand和ondestory。一旦在项目的任何位置调用了context 的startservice()函数,相应的服务就会启动起来,首次创建时会调用oncreate函数,然后回调onstartcommand()函数。服务启动了之后会一直保持运行状态,直到stopservice()或stopself()函数被调用。虽然每调用一次startservice()函数,onstartcommand()就会执行一次,但实际上每个服务都只会存在一个实例。所以不管你调用了多少次startservice()函数, 只需调用一个stopservice()或stopself()函数,服务就会被停止。

通常的service大致如下:

与activity一样,service也需要在androidmanifest.xml中进行注册,示例如下:

上述示例表示注册一个在应用包service目录下的myservice服务,注册之后,当用户调用startservice(new intent(mcontext,myservice.class)) 时会调用onstartcommand函数,我们在该函数中调用domyjob,而在domyjob中我们创建了一个线程来执行耗时操作,以避免阻塞ui线程。当我们的service完成使命时,需要调用stopservice来停止该服务。

1.2.2 intentservice

完成一个简单的后台任务需要这么麻烦,android显然早就“洞察”了这一点。因此,提供了一个intentservice来完成这样的操作,intentservice将用户的请求执行在一个子线程中,用户只需要覆写onhandleintent函数,并且在该函数中完成自己的耗时操作即可。需要注意的是,在任务执行完毕之后intentservice会调用stopself自我销毁,因此,它适用于完成一些短期的耗时任务。示例如下:

1.2.3 运行在前台的service

service默认是运行在后台的,因此,它的优先级相对比较低,当系统出现内存不足的情况时,它就有可能会被回收掉。如果希望service可以一直保持运行状态,而不会由于系统内存不足被回收,可以将service运行在前台。前台服务不仅不会被系统无情地回收,它还会在通知栏显示一条消息,下拉状态栏后可以看到更加详细的信息。例如,墨迹天气在前台运行了一个service,并且在service中定时更新通知栏上的天气信息,如图1-11所示。

《Android开发进阶:从小工到专家》——第1章,第1.2节Service与AIDL

下面我们就来实现一个类似于如图1-11所示的效果,首先我们定义一个服务,代码如下:

我们在oncreate函数中调用了shownotification函数显示通知,并且在最后调用startforeground将服务设置为前台服务。在androidmanifest.xml注册之后我们就可以启动该service了。效果如图1-12所示。

《Android开发进阶:从小工到专家》——第1章,第1.2节Service与AIDL

1.2.4 aidl(android接口描述语言)

aidl(android接口描述语言)是一种接口描述语言,通常用于进程间通信。编译器根据aidl文件生成一个系列对应的java类,通过预先定义的接口以及binder机制达到进程间通信的目的。说白了,aidl就是定义一个接口,客户端(调用端)通过bindservice来与远程服务端建立一个连接,在该连接建立时会返回一个ibinder对象,该对象是服务端binder的binderproxy,在建立连接时,客户端通过asinterface函数将该binderproxy对象包装成本地的proxy,并将远程服务端的binderproxy对象赋值给proxy类的mremote字段,就是通过mremote执行远程函数调用。

在客户端新建一个aidl文件,如图1-13所示。

《Android开发进阶:从小工到专家》——第1章,第1.2节Service与AIDL

在ssoauth.aidl文件中会默认有一个basictypes函数,我们在程序后面添加一个ssoauth的函数用于sso授权。代码如下:

因为客户端是调用端,因此,只需要定义aidl文件,此时rebuild一下工程就会生成一个ssoauth.java类,该类根据ssoauth.aidl文件生成,包含了我们在aidl文件中定义的函数。因为aidl通常用于进程间通信,因此,我们新建一个被调用端的工程,我们命名为aidl_server,然后将客户端的aidl文件夹复制到aidl_server的app/src/main目录下,结构如图1-14所示。

《Android开发进阶:从小工到专家》——第1章,第1.2节Service与AIDL

此时相当于在客户端和被调用端都有同一份ssoauth.aidl文件,它们的包名、类名完全一致,生成的ssoauth.java类也完全一致,这样在远程调用时它们就能够拥有一致的类型。rebuild被调用端工程之后就会生成ssoauth.java文件,该文件中有一个stub类实现了ssoauth接口。我们首先需要定义一个service子类,然后再定义一个继承自stub的子类,并且在service的onbind函数中返回这个stub子类的对象。示例代码如下:

从上述代码中我们看到,实际上完成功能的是继承自stub的sinassoimpl类,service只提供了一个让sinassoimpl依附的外壳。完成sinassoauthservice之后我们需要将它注册在被调用端应用的manifest中,注册代码如下:

然后先运行被调用端(也就是server端)应用,并且在客户端中完成调用server的代码。客户端activity的代码如下:

在上述activity程序中,运行程序后点击登录按钮时会向server端发起连接service请求,在建立连接之后会将binder对象转换为ssoauth对象,然后调用ssoauth对象的ssoauth函数。此时的ssoauth函数实际上调用的就是server端中sinassoimpl类的实现。运行程序后点击登录按钮,如图1-15所示。

《Android开发进阶:从小工到专家》——第1章,第1.2节Service与AIDL

这一切的核心都是通过aidl文件生成的stub类以及其背后的binder机制。首先我们看看生成的ssoauth.java,stub类就是该文件中的内部类。代码如下:

在ssoauth.java中自动生成了ssoauth接口,该接口中有一个ssoauth函数。但最,重要的是生成了一个stub类,该类继承自binder类,并且实现了ssoauth接口。stub里面最重要的就是asinterface()这个函数,在这个函数中会判断obj参数的类型,如果该obj是本地的接口类型,则认为不是进程间调用,此时将该obj转换成ssoauth类型;否则会通过自动生成的另一个内部类proxy来包装obj,将其赋值给proxy中的mremote字段。proxy类也实现了ssoauth接口,不同的是它是通过binder机制来与远程进程进行交互,例如,在ssoauth ()函数中,proxy将通过binder机制向服务端传递请求和数据,它请求的类型为transaction_ssoauth,参数分别是string类型的username和pwd。

对于服务端代码来说,它也有同一份ssoauth.aidli以及ssoauth.java,但不同的是服务端是指令的接收端,客户端的调用会通过binder机制传递到服务端,最终调用stub类中的ontransact函数。可以看到在case transaction_ssoauth处执行了this.ssoauth()函数,意思是当接收到客户端的transaction_ssoauth请求时,执行this.ssoauth()函数,通过客户端的分析我们知道,当我们调用ssoauth()时实际上就是通过mremote向服务端提交了一个transaction_ssoauth请求,因此,这两端通过binder机制就对接上了,我们可以简单地理解为c/s模式。

而在客户端调用bindservice之后,如果绑定成功则会调用onserviceconnected(componentname name,ibinder service),这里的service对象是binderproxy类型,经过asinterface转换后被包装成了proxy类型,但是调用的时候,执行的是服务端sinassoimpl中的ssoauth()函数。因此, sinassoimpl实例mbinder被服务端包装成binderproxy类型,再经过客户端的proxy进行包装,通过binder机制进行数据传输,实现进程间调用。

它们的调用时序图如图1-17所示。

《Android开发进阶:从小工到专家》——第1章,第1.2节Service与AIDL

打个比方说,有两个公司打算进行合作需要进行业务磋商,并且这次合作已经签署了合同,只剩下一些细节没有最终确定。但是由于大boss比较忙,因此各自都派了一个代表进行沟通。由于两家公司相距较远,双方代表都通过电话进行沟通。boss-a跟代表-a交代说,这次合作对方支付的酬劳不能低于十块钱,于是代表-a通过电话与代表b进行沟通,代表-b得到消息之后跑到boss-b的办公室请示,boss-b确认之后又由代表-b回复代表-a,代表-a最终反馈给boss-a。这个例子中的两个boss分别对应客户端和服务端,合同就对应了ssoauth接口,而两个代表则对应了两端的proxy,代表的通信方式则是电话,而代码的通信方式是binder。

总体来说,使用aidl并不是一件困难的事,但是理解aidl的机制确实有一定的难度。也正是如此,android通过aidl这个机制将一些复杂的概念与逻辑通过自动生成类型的方式屏蔽掉,使得开发人员能够更简单地进行进程间通信。

继续阅读