特别声明:本系列文章LiAnLab.org著作权所有,转载请注明出处。作者系LiAnLab.org资深Android技术顾问吴赫老师。本系列文章交流与讨论:@宋宝华Barry
分多次连载,讲述Android Service(JAVA Service、Native Service等)背后的实现原理,透析基于Binder的RPC以及Linux Binder驱动。
Service与Android系统实现(1)-- 应用程序里的Service
Service与Android系统设计(2)-- Parcel
Service与Android系统设计(3)-- ActivityManager的实现
Service与Android系统设计(4)-- ServiceManager
Service与Android系统设计(5)-- libbinder
Service与Android系统设计(6)--- Native Service
Service与Android系统设计(7)--- Binder驱动
Service
Service在Android应用程序里四大实体之一。Android的应用程序不光是需要有图形界面来进行交互,有时也会需要在没有交互的情况下进行的操作,比如下载、更新、监听等。比如目前对我们网络生存影响如此之大的社交网络、或是更老一些聊天工具,总需要这类应用程序可以一直在后台运行,以等待可能过来的消息。:
Service拥有一部分Activity所无法完成的能力。一是后台运行,有时我们并不希望有过多对话框来影响用户体验,开机自动启动,便可默默地在后台运行。另一特性,就是不被Activity生命周期所管理,Activity处于完全活跃的周期是onResume()与onPause()之间,如果这周期之外发生了事件,实际上Activity构成的执行部分也不会被执行到,从而无法响应处理,但Service由于本身过于简单,则会通过一定的辅助手段来达到这个目标。
从Android应用程序的设计原理来看,Service同样也是主线程里执行的(这点尤为重要,Service由于在主线程里执行,于是也会因为执行耗时操作而触发ANR)。一个应用程序既然可以通过拓展Activity得到可交互的代码逻辑,同样也可以通过拓展Service来得到不交互在后台执行的逻辑。如下图加强部分所示:
Activity对应用程序来说是最重要的组件,但从Android系统设计的角度来看,Service对系统层实现来说才最重要的。Service是构建系统的根本,支持整个系统运营的环境framework,本身就是由大量Service来构成的。也就是说,Service反而是构建Activity的基础环境。
Android与其他系统设计最大的不同之处在于,它并不是一种传统的系统环境,而是一种更加开放的系统。传统的图形操作系统里,会有基本环境,会有系统管理组件,应用程序只是作为补充性功能实现。但Android并不如此,Android系统是没有应用程序的,达到了“无边界”系统设计的最高境界,“手里无剑,心中有剑”。整个Android系统的设计使自己扮演着支撑系统的运行环境的角色,不再有基本系统的概念,而变成了一种“有或者无”的应用程序的支撑环境,没有系统组件的概念。而我们所谓的系统应用程序,我们只能称它们为“内置”应用程序,只是服务于黑心移动运营商的一种方式而已。
这种设计的精髓在于,系统本身不会处理交互,而只是提供交互的手段。从前面我们对于应用程序运行环境的分析中,我们可以看到,Android的Framework,提供一部分功能供应用程序调用,而除了这些应用程序直接使用的API实现,其他代码逻辑就会全是由Service构成。当然作为系统实现角度的Service,与应用程序编程里实现的Service是有差别的,更强调共享,但基本构架一样。在过渡到Android系统的解析之前,我们先从应用程序的Service概念入手。
本地简单Service
我们先来在应用程序里写一个简单的Service。打开Eclipse,新建一个Android工程,然后再创建一个新的基于Service基类的类。与Activity的编程方式类似,Service在编程上也是基于回调方式实现的,我们继承基类Service之后所需要做的,就是通过IoC模式替换原来的Service回调的实现:
- import android.app.Service;
- import android.content.Intent;
- import android.os.IBinder;
- import android.util.Log;
- public class LianlabServiceextends Service
- {
- private staticfinal String TAG ="LianlabService";
- @Override
- public void onCreate() {
- super.onCreate();
- Log.v(TAG, "inonCreate()");
- }
- @Override
- public int onStartCommand(Intent intent,int flags,int startId) {
- super.onStartCommand(intent, flags, startId);
- Log.v(TAG, "inonStartCommand()");
- return START_STICKY;
- }
- @Override
- public void onDestroy()
- {
- Log.v(TAG, "inonDestroy().");
- super.onDestroy();
- }
- }
有了Service的具体实现之后,系统并不会自动地识别到这一实现,在Android世界里,一切都通过AndroidManifest.xml来驱动,于是,我们还需要修改AndroidManifest.xml文件,加入Service的定义:
- <applicationandroid:labelapplicationandroid:label="@string/app_name">
- <serviceandroid:nameserviceandroid:name=".LianLabService"/>
- ;/application>
在上面这种方式里实现的Service,可被执行的方式很有限,就是提供一个可执行的线程环境,可以被Intent所驱动,执行onStartCommand()回调。功能有限并不代表无能,在Android系统里,我们可能还经常会碰到这样的需求:比如我们使用GPS里来记录我们行动轨迹时,这时我们很可能需要通过后台的执行的代码来定时检查GPS的定位信息;杀毒或是监控软件可能希望驻留在后台,并可被Intent来驱动开始进行杀毒;我们的聊天或是社交应用,需要在后台定时地与服务发送“心跳”(Heart beat),用来标识自己的在线状态等。这样的例子,大家可以回头到我们画的GPS轨迹跟踪的构成示意图,这样的跟踪软件,必须是通过一个接收启动完成信息的Broadcast
Receiver来监听自己是否应该被执行,而接收到到启动完成的Broadcast Intent之后,则必须触发一直在后台运行的TrackerService的执行。
既然我们在上述方式里实现的Service是由Intent驱动的,于是我们的使用这一Service部分的代码也会很简单。在任何可被执行到的代码里使用startService(Intent)就可以完成,我们可以给某个控件注册点击事件支持onClickListener对象,然后覆盖onClick()回调方法:
- public void onClick(Viewv) {
- Intent intent = new Intent(this,
- LianlabService.class);
- startService(intent);
- }
我们这里既然使用到了Intent,也就是说我们还可以通过extras这个Bundle对象给我们这里实现的LianLabService来传递运行的参数。于是,这时我们的代码貌似有了pthread多线程执行效果,通过传参,然后我们会执行一个在另一线程里运行的函数,只是函数是固定的onStartCommand()回调方法。但这只是貌似,并非实际情况,Service的执行与后台线程方式极大不同,Service只是一种代码逻辑的抽象,实际上它还是运行在Activity同一线程上下文环境。
于是,我们并不能用Service来进行任何耗时操作,否则会阻塞主线程而造成应用程序的无法响应错误,也就是臭名昭著的ANR错误。Service仅能用于不需要界面交互的代码逻辑。
本地 Bounded Service
这种使用Intent来驱动执行的Service,可用性有限,并不能完全满足我们对于后台服务的需求。对于后台执行的代码,我们更多的应用情境不光是希望进行后台操作,我们可能还希望能进行交互,可以随时检查后台操作的结果,并能暂停或是重启后台执行的服务,可以在使用某一Service时保证它并不会退出执行,甚至一些提交一些参数到后台来进行复杂的处理。这时,我们可以使用Service的另一个访问方式,使用Binder接口来访问。我们的Service基类还提供这类应用的回调方式,onBind()、onUnbind()和onRebind()。使用Binder来访问Service的方式比Intent驱动的应用情境更底层,onBind()回调主要用于返回一个IBinder对象,而这一IBinder对象是Service的引用,将会被调用端用于直接调用这一Service里实现的某些方法。
同样的Service实现,如果通过IBinder来驱动,则会变成下面的样子:
- import android.app.Service;
- import android.content.Intent;
- import android.os.IBinder;
- import android.util.Log;
- public class LianlabServiceextends Service
- {
- private staticfinal String TAG ="LianlabService";
- @Override
- public void onCreate() {
- super.onCreate();
- Log.v(TAG, "inonCreate()");
- }
- @Override
- public intonStartCommand(Intent intent,int flags,int startId) {
- super.onStartCommand(intent, flags, startId);
- Log.v(TAG, "in onStartCommand()");
- return START_STICKY;
- }
- @Override
- public void onDestroy()
- {
- Log.v(TAG, "inonDestroy().");
- super.onDestroy();
- }
- finalIService.Stub m_binder =newIService.Stub() {
- ...
- }
- @Override
- public IBinderonBind(Intent intent) {
- Log.v(TAG, "inonBind().");
- return mBinder;
- }
- @Override
- public booleanonUnbind(Intent intent) {
- Log.v(TAG, "inonUnbind().");
- return mAllowRebind;
- }
- @Override
- public void onRebind(Intentintent) {
- Log.v(TAG, "inonRebind().");
- }
- }
使用IBinder对象来触发的Service,在访问时的代码实现则变得完全不样了。比如我们同样通过onClick()来操作后台的某些操作,但这时并非通过Intent来完成,而是直接使用某个引用这一Service的IBinder对象来直接调用Service里实现的方法。
- bindService(intent, m_connection, …);
- private ServiceConnection m_connection =new ServiceConnection() {
- private IService onServiceConnected(…, IBinder service) {
- m_service =IService.Stub.asInterface(service);
- }
- }
如果Service里实现了某些方法,比如kill(),在上述代码之后,我们对Service的驱动则会变成代码上的直接调用。在onServiceConnected()回调方法被触发之后,我们始终都可以通过m_service.kill()来访问Service里的kill()方法。而bindService()这方法的调用,则会触发onServiceConnected()事件。
这样就要让人抓狂了,既然如此麻烦,何不直接调用呢?所以,事实上,这里列举的这种代码实现方式,在现实编程里确实不常用。一般而言,如果Service通过IBinder对象来触发,那只会出于一个理由,提供一种可能性,将来可以更灵活地提供给另一进程来访问,这就是我们稍后会说明的Remote Service。
这两种不同的Service的实现方式,将决定Service的不同被调用方式,或者准确地说,将决定Service的不同生命周期。
如图所示,Service的进程存活期是处理onCreate()与onDestroy()回调方法之间。onStartCommand()回调是不受控的,每次Intent都将触发执行一次, onStartCommand()执行完则会退出;而使用onBind()来驱动的Service,其活跃区间是onBind()与onUnbind()之间,其活跃周期始终在控制范围内。
Remote Service
得益于Android的进程间模型,无论是系统实现端的开发,还是应用程序的开发者,可认为自己的代码都将在一种安全的环境下执行。但对于在需要共享的场合,又带来了不方便之处,我们不再可以在一个进程里很方便地调用到另一进程里的实现。在上面两种Service实现里,基本上只能自已服务自己,而不能将功能共享给更多地使用者。于是,Android在设计初期,便引入了另一个概念,Remote Service。
Remote Service定义了如何从一个进程,直接访问到另一个进程里实现的方法,这样的机制在通用编程领域被称为RPC,Remote Procedure Call,在Android环境里,因为这样的RPC与Service的概念揉合到了一起,所以被称为Remote Service。
如下图所示,Remote Service基本上算是在Bounded Service实现上的一种拓展。调用端(一般是与用户交互的Activity)会通过bindService()发一个Intent到Service进程,而Service进程会在onBind()回调方法里返回自己的Service处理接口。当调用端得到相应的处理接口,也就是下图所示的Stub.Proxy对象之后,就可以调用远端Stub对象里实现的方法。
当然,由于这种方式需要在每一次调用时都进行一次远程调用,于是实现起来并不简单,甚至可以说是会很麻烦。在上图逻辑里要实现一个Remote Service,在这一Remote Service里实现一个方法,就需要加入多个对象。于是,在Android里引入了AIDL,可以通过AIDL来简化通用代码的处理,在上图中Stub对象、Stub.Proxy对象都将由aidl自动管理。
实现一个Remote Service,基本上可以分三步完成:
- 定义aidl
- 实现被调用的Remote Service(提供可实例化的Stub对象)
- 调用Remote Service
定义AIDL
- packageorg.lianlab.service;
- interface ITaskService {
- int getPid ( );
- }
实现Remote Service
有了AIDL定义之后,我们便可实现这这一接口类的定义,主要是提供一个Stub类的实现,在这个Stub对象里,提供getPid(void)的具体实现。
- private finalITaskService.Stub mTaskServiceBinder =new ITaskService.Stub() {
- public int getPid() {
- return Process.myPid();
- }
- };
于是,当应用程序通过某种方式可以取得ITaskServiceStub所对应的IBinder对象之后,就可以在自己的进程里调用IBinder对象的getPid(),但这一方法实际上会是在别一个进程里执行。虽然我们对于执行IBinder的Stub端代码的执行环境并没有严格要求,可以在一个运行中的进程或是线程里创建一个Stub对象就可以了,但出于通用性设计的角度考虑,实际上我们会使用Service类来承载这个Stub对象,通过这样的方式,Stub的运行周期便被Service的生存周期所管理起来。这样实现的原因,我们可以回顾我们前面所描述的功耗控制部分。当然,使用Service带来的另一个好处是代码的通用性更好,我们在后面将感受到这种统一化设计的好处。
于是,对应于.aidl的具体实现,我们一般会使用一个Service对象把这个Stub对象的存活周期“包”起来。我们可以通过在Service里使用一个 final类型的Stub对象,也可以通过在onBind()接口里进行创建。这两种不同实现的结果是,使final类型的Stub对象,类似于我们的Singleton设计模式,客户端访问过来的总会是由同一个Stub对象来处理;而在onBind()接口里创建新的Stub,可以让我们对每个客户端的访问都新建一个Stub对象。出于简化设计的考虑,我们一般会使用final的唯一Stub对象,于是我们得到的完整的Service实现如下:
- package org.lianlab.services;
- import android.app.Service;
- import android.content.Intent;
- import android.os.IBinder;
- import android.os.Process;
- public class TaskService extends Service {1
- @Override
- public IBinderonBind(Intent intent) {
- if (ITaskService.class.getName().equals(intent.getAction())) {2
- return mTaskServiceBinder;
- }
- return null;
- }
- private finalITaskService.Stub mTaskServiceBinder =new ITaskService.Stub() { 3
- public int getPid() { 4
- return Process.myPid();
- }
- };
- }
- 由于实现上的灵活性,我们一般使用Service来承载一个AIDL的Stub对象,于是这个Stub的存活周期,会由Service的编写方式决定。当我们的Stub对象是在onBind()里返回时,Stub对象的存活周期是Service处于Bounded的周期内;如果使用final限定,则Stub对象的存活周期是Service在onCreate()到onDestroy()之间
- 用于处理bindService()发出Intent请求时的Action匹配,这一行一般会在AndroidManifest.xml文件里对Service对Intent filter设置时使用。我们这种写法,则使这一Service只会对Intent里Action属性是“org.lianlab.services.ITaskService”的bindService()请求作出响应。这一部分我们在后面的使用这一Service的Activity里可以看到
- 创建ITaskService.Stub对象,并实现Stub对象所要求的方法,也就是AIDL的实现。Stub对象可以像我们这样静态创建,也可以在onBind()里动态创建,但必须保证创建在onBind()返回之前完成。在onBind()回调之后,实际上在客户端则已经发生了onServiceConnected()回调,会造成执行出错。
- 实现,这时我们最终给客户端提供的远程调用,就可以在Stub对象里实现。我们可以实现超出AIDL定义的部分,但只有AIDL里定义过的方法才会通过Binder暴露出来,而AIDL里定义的接口方法,则必须完整实现。
有了这样的定义之后,我们还需要在AndroidManifest.xml文件里将Service声明出来,让系统里其他部分可以调用到:
- <application
- …
- <service android:name=".TaskService">
- <intent-filter>
- <action android:name="org.lianlab.services.ITaskService"/>
- </intent-filter>
- </service>
- </application>
在AndroidManifest.xml文件里,会在<application>标签里通过<service>标签来申明这一应用程序存在某个Service实现,在service命名里,如果service的名称与application名称的包名不符,我们还可以使用完整的“包名+类名”这样命名方式来向系统里注册特殊的Service。在<service>标签里,我们也可以注册<intent-filter>来标明自己仅接收某一类的Intent请求,比如我们例子里的action会匹配“org.lianlab.services.ITaskService”,如果没有这样的intent-filter,则任何以TaskService(ComponentName的值是”org.lianlab.services.ITaskService”)为目标的Intent请求会触发TaskService的onBind()回调。当然,我们在<service>标签内还可以定义一些权限,像我们例子里的这个TaskService是没有任何权限限制的。有了这个AndroidManifest.xml文件,我们再来看看客户端的写法。
访问Remote Service
在客户端进行访问时,由于它必须也使用同一接口类的定义,于是我们可以直接将同一.aidl文件拷贝到客户端应用程序的源代码里,让这些接口类的定义在客户端代码里也可以被自动生成,然后客户端便可以自动得到Proxy端的代码。因为我们这里使用了Service,又是通过onBind()返回IBinder的对象引用,这时客户端在使用IBinder之前,需要通过bindService()来触发Service端的onBind()回调事件,这时会通过客户端的onServiceConnected()回调将Stub所对应的Binder对象返回。我们在稍后看AIDL的底层实现时会发现,在此时客户端的Binder对象只是底层Binder
IPC的引用,此时我们还需要创建一个基于这一Stub接口的Proxy,于是在客户端会需要调用asInterface()创建Proxy对象,这一Proxy对象被转义成具体的Service,在我们的例子里,客户端此时就得到了ITaskService对象。从这时开始,在客户端里通过ITaskService.getPid()的调用,都会通过Binder IPC将操作请求发送到Service端的Stub实现。于是,我们可以得到客户端的代码,在Android里我们一般用Activity来完成这样的操作,如下代码所示:
- package org.lianlab.hello;
- import android.os.Bundle;
- import android.os.IBinder;
- import android.os.RemoteException;
- import android.app.Activity;
- import android.content.ComponentName;
- import android.content.Context;
- import android.content.Intent;
- import android.content.ServiceConnection;
- import android.util.Log;
- import android.view.View;
- import android.view.View.OnClickListener;
- import android.widget.TextView;
- import org.lianlab.services.ITaskService;1
- import org.lianlab.hello.R;
- public class Helloworld extends Activity
- {
- /** Called when the activity is first created.*/
- ITaskService mTaskService = null; 2
- @Override
- public voidonCreate(Bundle savedInstanceState)
- {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- bindService(new Intent(ITaskService.class.getName()),mTaskConnection,
- Context.BIND_AUTO_CREATE); 3
- ((TextView) findViewById(R.id.textView1)).setOnClickListener(
- new OnClickListener() {
- @Override
- public void onClick(Viewv) {
- if (mTaskService !=null) {
- try { 4
- int mPid = -1;
- mPid = mTaskService.getPid();
- Log.v("get Pid "," = " +mPid);
- ((TextView)findViewById(R.id.textView1)).setText("Servicepid is" + mPid);
- } catch(RemoteException e) {
- e.printStackTrace();
- }
- }
- else {
- ((TextView)findViewById(R.id.textView1)).setText("Noservice connected");
- }
- }
- });
- }
- privateServiceConnectionmTaskConnection = new ServiceConnection() { 5
- public void onServiceConnected(ComponentName className, IBinder service) {
- mTaskService = ITaskService.Stub.asInterface(service); 6
- }
- public void onServiceDisconnected(ComponentName className) {
- mTaskService = null;
- }
- };
- @Override
- public void onDestroy()
- {
- super.onDestroy();
- if (mTaskService !=null) {
- unbindService(mTaskConnection); 7
- }
- }
- }
- 必须导入远端接口类的定义。我们必须先导入类或者函数定义,然后才能使用,对于任何编程语言都是这样。但我们在AIDL编程的环境下,实际这一步骤变得更加简单,我们并非需要将Service实现的源文件拷贝到应用程序源代码里,也是只需要一个AIDL文件即可,这一文件会自动生成我们所需要的接口类定义。所以可以注意,我们导入的并非Service实现的”org.lianlab.services.TaskService”,而AIDL接口类的”org.lianlab.services.ITaskService”。这种方式更灵活,同时我们在AIDL环境下还得到了另一个好处,那就是可以隐藏实现。
- 对于客户端来说,它并不知道TaskService的实现,于是我们统一使用ITaskService来处理对远程对象的引用。跟步骤1对应,这时我们会使用ITaskService来访问远程对象,就是我们的mTaskService。
- 我们必须先创建对象,才能调用对象里的方法,对于AIDL编程而言,所谓的创建对象,就是通过bindService()来触发另一个进程空间的Stub对象被创建。bindService()的第一参数是一个Intent,这一Intent里可以通过ComponentName来指定使用哪个Service,但此时我们会需要Service的定义,于是在AIDL编程里这一Intent会变通为使用ITaskService作为Action值,这种小技巧将使bindService()操作会通过IntentFilter,帮我们找到合适的目标Service并将其绑定。bindService()的第二个参数的类型是ServiceConnection对象,bindService()成功将使用这样一个ServiceConnection对象来管理onServiceConnected()与onServiceDisconnected()两个回调,于是一般我们会定义一个私有的ServiceConnection对象来作为这一参数,见5。最后的第三个参数是一个整形的标志,说明如何处理bindService()请求,我们这里使用Context.BIND_AUTO_CREATE,则发生bindService()操作时,如果目标Service不存在,会触发Service的onCreate()方法创建。
- 我们这里使用onClickListener对象来触发远程操作,当用户点击时,就会尝试去执行mTaskService.getPid()方法。正如我们看到的,getPid()是一个RPC方法,会在另一个进程里执行,而Exception是无法跨进程捕捉的,如果我们希望在进行方法调用时捕捉执行过程里的异常,我们就可以通过一个RemoteException来完成。RemoteException实际上跟方法的执行上下文没有关系,也并非完整的Exception栈,但还是能帮助我们分析出错现场,所以一般在进行远端调用的部分,我们都会try来执行远程方法然后捕捉RemoteException。
- 如3所述,bindService()会使用一个ServiceConnection对象来判断和处理是否连接正常,于是我们创建这么一个对象。因为这个私有ServiceConnection对象是作为属性存在的,所以实际上在HelloActivity对象的初始化方法里便会被创建。
- 在onServiceConnected()这一回调方法里,将返回引用到远程对象的IBinder引用。在Android官方的介绍里,说是这一IBinder对象需要通过asInterface()来进行类型转换,将IBinder再转换成ITaskService。但在实现上并非如此,我们的Proxy在内部是被拆分成Proxy实现与Stub实现的,这两个实现都使用同一IBinder接口,我们在onServiceConnected()里取回的就是这一对象IBinder引用,asInterface()实际上的操作是通过IBinder对象,得到其对应的Proxy实现。
- 通过bindService()方法来访问Service,则Service的生存周期位于bindService()与unbindService()之间的Bounded区域,所以在bindService()之后,如果不调用unbindService()则会造成内存泄漏,Binder相关的资源无法得到回收。所以在合适的点调用unbindService()是一种好习惯。
通过这样的方式,我们就得到了耦合度很低的Service方案,我们的这个Activity它即可以与Service处于同一应用程序进程,也可以从另一个进程进行跨进程调用来访问这一Service里暴露出来的方法。而在这种执行环境的变动过程中,代码完全不需要作Service处理上的变动,或者说Activity本身并不知道AIDL实现上的细节,在同一个进程里还是不在同一进程里。
这种方式很简单易行,实际上在编程上,AIDL在编程上带来的额外编码上的开销非常小,但得到了灵活性设计的RPC调用。
双向Remote Service
在AIDL编程环境里实际上是支持反向调用的,原理跟我们实现一个Remote Service一样,就是通过把Proxy与Stub反过来,就得到了这样的回调式的aidl编程。唯一的区别是,当我们的Stub在Activity时实现时,我们实际上跟后台线程执行也没有区别,Callback并非是在主线程里执行的,于是不能进行重绘界面的工作。于是,我们必须像后台线程编程一样,使用Handler来处理界面显示处理。
定义AIDL
前面我们说过aidl是可以互相引用的,于是我们可以借用这样的机制,通过引用另一个新增的aidl文件来加强我们前面的单向的TaskService版本。我们先增加一个新的ITaskServiceCallback.aidl文件,与ITaskService保持同一目录:
- package org.lianlab.services;
- onewayinterface ITaskServiceCallback {
- void valueCounted(int value);
- }
在这一定义里,我们新增加了一个ITaskServiceCallback的接口类,基本上与我们前面的ITaskService.aidl一样,在这个接口类里,我们新加了一个valueCounted()方法,这一方法将会被Service所使用。
在这个文件里,唯一与ITaskService.aidl不同之处在于,我们使用了一个oneway的标识符,oneway可以使aidl调用具有异步调用的效果。在默认情况下,基于aidl的调用都会等待远端调用完成之后再继续往下执行,但有时我们可能希望在跨进程调用会有异步执行的能力,我们在发出调用请求后会立即返回继续执行,调用请求的结果会通过其他的callback返回,或是我们干脆并不在乎成功与否,此时就可以使用oneway。当然,从我们前面分析aidl底层进行的工作,我们可以知道,所谓的远程调用,只不过是通过Binder发送出去一个命令而已,所以在aidl里面如果使用了oneway限定符,也就是发送了命令就收工。
然后,我们修改一下我们的ITaskService.aidl,使我们可以使用上这个新加入的回调接口:
- package org.lianlab.services;
- import org.lianlab.services.ITaskServiceCallback;
- interface ITaskService {
- intgetPid (ITaskServiceCallback callback);
- }
在Service端调用回调方法
我们会引用前面定义好的ITaskServiceCallback.aidl文件,通过包名+接口的方式进行引用。为了省事,我们直接在原来的getPid()方法里进行修改,将新定义的ITaskServiceCallback接口类作为参数传递给getPid()接口。于是,在Service端Stub对象里实现的getPid()方法,将可以使用这一回调对象:
- package org.lianlab.services;
- import android.app.Service;
- import android.content.Intent;
- import android.os.IBinder;
- import android.os.Process;
- import android.os.RemoteException;
- public class TaskService extends Service {
- static private int mCount = 0;
- @Override
- public IBinder onBind(Intent intent) {
- if (ITaskService.class.getName().equals(intent.getAction())) {
- return mTaskServiceBinder;
- }
- return null;
- }
- private final ITaskService.Stub mTaskServiceBinder = newITaskService.Stub() {
- public int getPid(ITaskServiceCallback callback) { 1
- mCount ++ ; 2
- try { 3
- callback.valueCounted(mCount); 4
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- return Process.myPid();
- }
- };
- }
加入了回调之后的代码结构并没有大变,只增加了3部分的内容,通过这三部分的内容,我们此时便可以记录我们的getPid()总共被调用了多少次。
- getPid()方法,是通过aidl定义来实现的,否则会报错。所以我们这里新的getPid()会按照aidl里的定义加入ITaskServiceCallback对象作为参数,与ITaskService对象相反,这一对象实际上是由客户端提供给Service端调用的。
- 为了记录下getPid()被调用了多少次,我们使用了一个mCount来进行计数,这一int为static类型,于是在Service生存周期里会始终有效。但这部分的改动与我们的回调改进并无直接关系。
- 在使用回调接口ITaskServiceCall之前,因为这是一个远程引用,我们会需要捕捉Remote Exception,由客户端抛出的异常将在这里被捕获处理。
- 调用ITaskServiceCall里定义的回调方法,将处理发送给客户端。此时,因为是oneway,这时很多就会从回调方法里返回,继续执行原来的getPid(),再将处理结果以返回值的形式发送回客户端。
加入了回调之后,对Service端的实现并没有增加多大的工作量,因为作为回调,实现是放在客户端上来完成的。
在Client端加入回调实现
因为我们的aidl接口已经发生了变动,于是需要将新加的ITaskServiceCall.aidl与改变过的ITaskService.aidl文件拷贝到应用程序工程里。我们再来看一下客户端实现代码需要作怎样的调整:
- package org.lianlab.services;
- import android.os.Bundle;
- import android.os.Handler;
- import android.os.IBinder;
- import android.os.RemoteException;
- import android.app.Activity;
- import android.content.ComponentName;
- import android.content.Context;
- import android.content.Intent;
- import android.content.ServiceConnection;
- import android.view.View;
- import android.view.View.OnClickListener;
- import android.widget.TextView;
- import org.lianlab.services.R;
- public class MainActivity extends Activity {
- ITaskService mTaskService = null;
- private TextView mCallbackText;
- private Handler mHandler = new Handler();
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- try {
- bindService(newIntent(ITaskService.class.getName()), mTaskConnection,
- Context.BIND_AUTO_CREATE);
- } catch (SecurityException e) {
- e.printStackTrace();
- }
- setContentView(R.layout.activity_main);
- ((TextView)findViewById(R.id.textView1)).setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- if (mTaskService != null) {
- try {