这部分来学习Binder和Service的关系。摘自 柯元旦的《Android内核剖析》,因为这本书没得卖,所以摘录到这里方便学习。
使用Service类
编写Binder服务端和客户端的过程中存在两个重要问题:
第一,客户端如何获得服务端的Binder对象的引用。
第二,客户端和服务端必须事先约定好两件事情
1> 服务端函数的参数在包裹中的顺序
2> 服务端不同int型标识
关于第一个问题,请思考为什么要用Binder。答案很简单,即为了提供一个全局服务,所谓的“全局”,是指系统中的任何应用程序都可以访问。很明显,这是一个操作系统应该提供的最基本的功能之一,Android的工程师估计也是这么想的,因此,他们提供了一个更傻瓜的解决方法,那就是Service.它是Android应用程序4大组件(Component)之一。
无论是否使用Service类,都必须要解决以上两个重要问题。因此,下面先介绍如何解决第一个问题。
1. 获取Binder对象
所谓的系统服务是指可以使用getSystemService()方法获取的服务,所谓的客户端服务是指应用程序提供的自定义服务。
首先,AmS提供了startService()函数用于启动客户服务,而对于客户端来讲,可以使用以下两个函数来和一个服务建立连接,其原型在android.app.ContextImpl类中。
public ComponentName startService(Intent intent);
该函数用于启动intent指定的服务,而启动后,客户端暂时还没有服务端的Binder引用,因此,暂时还不能调用任何服务功能。
public boolean bindService(Intent service,ServiceConnection conn,int flags);
该函数用于绑定一个服务,这就是第一个重要问题的关键所在。其中第二个参数是一个interface类,该interface的定义如下:
public interface ServiceConnection{
public void onserviceConnected(ComponentName name,IBinder service);
public void onServiceDisconnected(ComponentName name);
}
请注意该interface中的onserviceConnected()方法的第二个变量service.当客户端请求AmS启动某个Service后,该Service如果正常启动,那么AmS就会远程调用ActivityThread类中的ApplicationTread对象,调用的参数中会包含Service的Binder引用,然后在ApplicationTread中会回调bindService中的conn接口。因此,在客户端中,可以在onServiceConnected()方法中将其参数service保存为一个全局变量,从而在客户端的任何地方都可以随时调用该远程服务。这就解决了第一个重要问题,即客户端如何获取远程服务的Binder引用。
2. 保证包裹内参数顺序 aidl 工具的使用
关于第二个问题,Android的SDK中提供了一个aidl工具,该工具可以把一个aidl文件转换为一个java类文件,在该java类文件中,同时重载了transact和onTransact()方法,统一了存入包裹和读取包裹参数,从而使设计者可以把注意力放到服务代码本身上。
接下来看aidl工具都做了什么。依然假设要编写一个MusicPlayerService服务,服务中包含两个服务函数,分别是start()和stop()。那么,可以首先编写一个IMusicPlayerService.aidl文件,如下代码所示:
aidl文件的语法基本类似于Java.但要注意的是,包裹中只能写入以下3个类型的内容:
1> Java原子类型,如int, long, String等变量。
2> Binder引用。
3> 实现了Parcelable的对象。
interface为关键字,有时会在interface前面加一个oneway,代表该service提供的方法都是没有返回值的,即都是void类型。
书中还把aild生成的IMusicPlayerService.java文件代码 罗列出来了,代码太多,这里就不粘上来了。
这些代码主要完成以下三个任务:
1> 定义一个Java interface,内部包含aidl文件所声明的服务函数,类名称为IMusicPlayerService,并且该类基于IInterface接口,即需要提供一个asBinder()函数。
2> 定义一个Proxy类,该类将作为客户端程序访问服务端的代理。所谓的代理主要就是为了前面所提到的第二个重要问题--统一包裹内写入参数的顺序。
3> 定义一个Stub类,这是一个abstract类,基于Binder类,并且实现了IMusicPlayerService接口,主要由服务端来使用。该类之所以要定义一个abstract类,是因为具体的服务函数必须由程序员实现,因此,IMusicPlayerService接口中定义的函数在Stub类中可以没有具体实现。同时,在Stub类中重载了onTransact()方法,由于transact()方法内部给包裹内写入参数的顺序是由aidl工具定义的,因此,在onTransact()方法中,aidl工具自然知道应该按照何种顺序从包裹中取出相应参数。
在Stub类中还定义了一些int常量,比如TRANSACTION_start,这些常量与服务函数对应,transact()和onTransact()方法的第一个参数code的值即来源于此。
在Stub类中,除了以上所述的任务外,Stub还提供了一个asInterface()函数。提供这个函数的作用是这样的:首先需要明确的是aidl所产生的代码完全可以由应用程序员手工编写,IMusicPlayerService中的函数只是一种编码习惯而已,asInterface即如此,提供这个函数的原因是服务端提供的服务除了其他进程可以使用外,在服务进程内部的其他类也可以使用该服务,对于后者,显然是不需要经过IPC调用,而可以直接在进程内部调用的,而Binder内部有一个queryLocalInterface函数,该函数是根据输入的字符串判断该Binder对象是一个本地的Binder引用。如图所示曾经指出,当创建一个Binder对象时,服务端进程内部创建一个Binder对象,Binder驱动中也会创建一个Binder对象。如果从远程获取服务端的Binder,则只会返回Binder驱动中的Binder对象,而如果从服务端进程内部获取Binder对象,则会获取服务端本身的Binder对象,听起来有点复杂:
![](https://img-blog.csdn.net/20140905152330062?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveW1hbmd1NjY2/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
因此,asInterface()函数正是利用了queryLocalInterface()方法,提供了一个统一的接口。无论是远程客户端还是本地端,当获取Binder对象后,可以把获取的Binder对象作为asInterface()的参数,从而返回一个IMusicPlayerService接口,该接口要么使用Proxy类,要么直接使用Stub所实现的相应服务函数。
使用Service类
编写Binder服务端和客户端的过程中存在两个重要问题:
第一,客户端如何获得服务端的Binder对象的引用。
第二,客户端和服务端必须事先约定好两件事情
1> 服务端函数的参数在包裹中的顺序
2> 服务端不同int型标识
关于第一个问题,请思考为什么要用Binder。答案很简单,即为了提供一个全局服务,所谓的“全局”,是指系统中的任何应用程序都可以访问。很明显,这是一个操作系统应该提供的最基本的功能之一,Android的工程师估计也是这么想的,因此,他们提供了一个更傻瓜的解决方法,那就是Service.它是Android应用程序4大组件(Component)之一。
无论是否使用Service类,都必须要解决以上两个重要问题。因此,下面先介绍如何解决第一个问题。
1. 获取Binder对象
所谓的系统服务是指可以使用getSystemService()方法获取的服务,所谓的客户端服务是指应用程序提供的自定义服务。
首先,AmS提供了startService()函数用于启动客户服务,而对于客户端来讲,可以使用以下两个函数来和一个服务建立连接,其原型在android.app.ContextImpl类中。
public ComponentName startService(Intent intent);
该函数用于启动intent指定的服务,而启动后,客户端暂时还没有服务端的Binder引用,因此,暂时还不能调用任何服务功能。
public boolean bindService(Intent service,ServiceConnection conn,int flags);
该函数用于绑定一个服务,这就是第一个重要问题的关键所在。其中第二个参数是一个interface类,该interface的定义如下:
public interface ServiceConnection{
public void onserviceConnected(ComponentName name,IBinder service);
public void onServiceDisconnected(ComponentName name);
}
请注意该interface中的onserviceConnected()方法的第二个变量service.当客户端请求AmS启动某个Service后,该Service如果正常启动,那么AmS就会远程调用ActivityThread类中的ApplicationTread对象,调用的参数中会包含Service的Binder引用,然后在ApplicationTread中会回调bindService中的conn接口。因此,在客户端中,可以在onServiceConnected()方法中将其参数service保存为一个全局变量,从而在客户端的任何地方都可以随时调用该远程服务。这就解决了第一个重要问题,即客户端如何获取远程服务的Binder引用。
2. 保证包裹内参数顺序 aidl 工具的使用
关于第二个问题,Android的SDK中提供了一个aidl工具,该工具可以把一个aidl文件转换为一个java类文件,在该java类文件中,同时重载了transact和onTransact()方法,统一了存入包裹和读取包裹参数,从而使设计者可以把注意力放到服务代码本身上。
接下来看aidl工具都做了什么。依然假设要编写一个MusicPlayerService服务,服务中包含两个服务函数,分别是start()和stop()。那么,可以首先编写一个IMusicPlayerService.aidl文件,如下代码所示:
package com.haiii.android.client;
interface IMusicPlayerService{
boolean start(String filePath);
void stop();
}
该文件的名称必须遵循一定的规范,第一个字母“I”不是必需的,但是为了程序风格的统一,“I”的含义是IInterface类,即这是一个可以提供访问远程服务的类。后面的命名--MusicPlayerService对应的是服务的类名,aidl工具会以该名称命名输出Java类。这些规则都只是Eclipse下ADT插件的默认规则,aidl本身只是一个命令行程序。aidl文件的语法基本类似于Java.但要注意的是,包裹中只能写入以下3个类型的内容:
1> Java原子类型,如int, long, String等变量。
2> Binder引用。
3> 实现了Parcelable的对象。
interface为关键字,有时会在interface前面加一个oneway,代表该service提供的方法都是没有返回值的,即都是void类型。
书中还把aild生成的IMusicPlayerService.java文件代码 罗列出来了,代码太多,这里就不粘上来了。
这些代码主要完成以下三个任务:
1> 定义一个Java interface,内部包含aidl文件所声明的服务函数,类名称为IMusicPlayerService,并且该类基于IInterface接口,即需要提供一个asBinder()函数。
2> 定义一个Proxy类,该类将作为客户端程序访问服务端的代理。所谓的代理主要就是为了前面所提到的第二个重要问题--统一包裹内写入参数的顺序。
3> 定义一个Stub类,这是一个abstract类,基于Binder类,并且实现了IMusicPlayerService接口,主要由服务端来使用。该类之所以要定义一个abstract类,是因为具体的服务函数必须由程序员实现,因此,IMusicPlayerService接口中定义的函数在Stub类中可以没有具体实现。同时,在Stub类中重载了onTransact()方法,由于transact()方法内部给包裹内写入参数的顺序是由aidl工具定义的,因此,在onTransact()方法中,aidl工具自然知道应该按照何种顺序从包裹中取出相应参数。
在Stub类中还定义了一些int常量,比如TRANSACTION_start,这些常量与服务函数对应,transact()和onTransact()方法的第一个参数code的值即来源于此。
在Stub类中,除了以上所述的任务外,Stub还提供了一个asInterface()函数。提供这个函数的作用是这样的:首先需要明确的是aidl所产生的代码完全可以由应用程序员手工编写,IMusicPlayerService中的函数只是一种编码习惯而已,asInterface即如此,提供这个函数的原因是服务端提供的服务除了其他进程可以使用外,在服务进程内部的其他类也可以使用该服务,对于后者,显然是不需要经过IPC调用,而可以直接在进程内部调用的,而Binder内部有一个queryLocalInterface函数,该函数是根据输入的字符串判断该Binder对象是一个本地的Binder引用。如图所示曾经指出,当创建一个Binder对象时,服务端进程内部创建一个Binder对象,Binder驱动中也会创建一个Binder对象。如果从远程获取服务端的Binder,则只会返回Binder驱动中的Binder对象,而如果从服务端进程内部获取Binder对象,则会获取服务端本身的Binder对象,听起来有点复杂:
因此,asInterface()函数正是利用了queryLocalInterface()方法,提供了一个统一的接口。无论是远程客户端还是本地端,当获取Binder对象后,可以把获取的Binder对象作为asInterface()的参数,从而返回一个IMusicPlayerService接口,该接口要么使用Proxy类,要么直接使用Stub所实现的相应服务函数。