Service框架---关于aidl的使用介绍
1.2 应用框架解析
除了基本的组件外,为了维护整个UI系统的良好运行,在应用层,Android还设计了诸多的框架来进行相关方面的管理。本节主要介绍Service框架、Activity管理机制、Broadcast机制、对话框框架、标题栏框架、状态栏框架、通知机制和ActionBar框架等。
1.2.1 Service框架
在Android中,作为执行应用后台运算和框架层运算的基本组件,服务扮演着十分重要的角色。根据通信的方式和应用场景,服务有不同的类型。具体而言,从通信的方式来看,服务可分为两种类型,即本地服务和远程服务,其中远程服务根据通信的方式又可分为基于AIDL的服务和基于Message的服务两种。远程服务是Android中跨进程通信的主要形式之一。从应用的场景来看,服务可以分为应用服务和系统服务等。
1. 本地服务
如果服务没有跨进程调用的需求,应将服务设计为本地服务,这样一方面加快了通信的速度,另一方面也简化了设计。如下是实现一个本地服务的最基本形式的示例:
public class HelloService extends Service {
@Override
public IBinder onBind(Intent arg0) {
return null;
}
}
在本地服务中,必须实现onBind()方法,当然,如果不需要绑定服务(即调用bindService()方法),可以返回null。一个具有绑定服务的本地服务实现示例如下:
public class LocalService extends Service {
public class LocalBinder extends Binder {
LocalService getService() {
return LocalService.this;
}
}
public IBinder onBind(Intent intent) {
return mBinder;
}
private final IBinder mBinder = new LocalBinder();
}
注意 在默认情况下,Service依然运行在主线程中,而非另开线程,如果希望有大运算量的后台计算,则在实现本地服务时,必须在Service中创建单独的线程来执行相应运算。
2. 基于AIDL的远程服务
基于AIDL的远程服务本质上沿袭了分布式计算的思想,当然在以往的嵌入式系统中,其框架相对简单,通常并不具备跨语言的能力。但Android实现了真正的分布式计算,能够支持跨语言的调用。实现基于AIDL的远程服务需要经过以下4个步骤:
(a)创建AIDL文件。
(b)将AIDL文件纳入编译系统。
(c)实现接口方法。
(d)绑定服务客户端。
在完成编码工作后,Android会在编译过程中自动为相应的AIDL文件生成对应的桩(Stub),简化了开发的难度。
下面分别介绍实现基于AIDL的远程服务的4个步骤。
(1)创建AIDL文件
AIDL文件的实现非常简单,其本身仅是一个文件名以“I”开头的接口文件。下面是ITestService的一个实现:
interface ITestService
{
boolean getSthEnabled();
void setSthEnabled(boolean on);
}
(2)将AIDL文件纳入编译系统
为了生成相应的桩,必须将AIDL文件纳入编译系统,其在frameworks/base/Android.mk中的实现如下:
...
LOCAL_SRC_FILES += \
...
core/java/android/os/ITestService.aidl \
...
在应用层,以IMediaPlaybackService为例,其在Android.mk中的实现通常如下:
...
LOCAL_SRC_FILES := $(call all-java-files-under, src) \
src/com/android/music/IMediaPlaybackService.aidl
...
在框架层和应用层将AIDL文件纳入编译系统,两者仅在写法习惯上不同而已。
(3)实现接口方法
实现接口方法只需继承相应接口的Stub子类即可,但必须实现接口所定义的所有方法,示例如下:
public class ITestServiceImpl extends ITestService.Stub{
public boolean getSthEnabled() {
...
}
public void setSthEnabled(boolean on){
...
}
}
为了便于客户端绑定,通常会将桩封装到到一个服务中,方法如下:
public class TestService extends Service{
@Override
public IBinder onBind(Intent arg0) {
return new ITestServiceImpl(getApplicationContext());
}
}
(4)绑定服务客户端
为了与服务进行通信,必须在客户端绑定远程服务,在应用层的实现中,如果是跨进程调用的,必须将相应的ITestService文件复制到客户端所在的进程中。假设服务位于com.miaozl.test包中,在客户端实现进程调用时,其方法如下:
private ITestService mTest = null;
...
public void onCreate() {
Intent intent = new Intent();
intent.setComponent(
new ComponentName("com.miaozl.test","com. miaozl.test.service.TestService"));
bindService(intent, mTestConnection,Context.BIND_AUTO_CREATE);
}
如果是在本地进程中,则实现较简单,方法如下:
bindService(new Intent(this, TestService.class),
mTestConnection,BIND_AUTO_CREATE);
而mTestConnection的实现则不区分是本地应用调用还是跨进程调用,具体如下:
private ServiceConnection mTestConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName name, IBinder service) {
//绑定方法
mTest= ITestService.Stub.asInterface(service);
...
}
public void onServiceDisconnected(ComponentName name) {
mTest = null;
}
};
注意 绑定服务是以异步的方式进行的,对于必须为同步的场景,是无法实现绑定服务的。
3. 基于Messenger的远程服务
基于Messenger的远程服务同样是跨进程的,其本质是将本地服务和Messenger结合,以便实现进程间的通信。基于Messenger实现远程服务的示例如下:
public class AlertService extends Service {
final Messenger mMessenger = new Messenger(new ServiceHandler ());
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
processMessage(msg);
}
}
void processMessage(Message msg) {
...
}
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
}
通过Messenger,服务的调用者可以很方便地发送Message,示例如下:
Messenger mService = null;
...
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
mService = new Messenger(service);
Message msg = Message.obtain(null,
MessengerService.MSG_SET_VALUE, this.hashCode(), 0);
mService.send(msg);
}
}
4. 系统服务
系统服务主要由3部分构成: *Service.java 、I*.aidl、*Manager.java。另外还需要在SystemServer.java增加框架层封装,在ContextImpl.java增加应用层接口。
图1-7所示为以AlarmManagerService为例对系统服务进行的描述。
从图1-7所示中可以看出,为了实现一个系统服务,有5部分的内容需要关注。
接口文件;
客户端文件;
桩文件(系统自动实现);
服务端文件;
系统调用接口。
下面对除桩文件以外的其他文件进行较详细的介绍。
(1)接口文件
通常情况下,接口文件中仅用到了基本的数据类型,如果需要用到复杂的数据类型,则需对数据进行序列化,具体实现可以参见5.1.2 节。如下是IAlarmManager.aidl文件的具体实现:
interface IAlarmManager {
void set(int type, long triggerAtTime, in PendingIntent operation);
void setRepeating(int type, long triggerAtTime, long interval, in PendingIntent operation);
void setInexactRepeating(int type, long triggerAtTime, long interval, in PendingIntent operation);
void setTime(long millis);
void setTimeZone(String zone);
void remove(in PendingIntent operation);
}
在AIDL文件中,通常定义了客户端需要调用的接口中的方法。
(2)客户端文件
在Android系统中,客户端文件对系统服务的调用非常简单,其调用方法类似于普通方法调用,只是需要捕获RemoteException异常。下面是AlarmManager的部分实现:
public class AlarmManager
{
private final IAlarmManager mService;
AlarmManager(IAlarmManager service) {
mService = service;
}
public void set(int type, long triggerAtTime, PendingIntent operation) {
try {
mService.set(type, triggerAtTime, operation);
} catch (RemoteException ex) {
}
}
...
}
(3)服务端文件
服务端的实现是实现系统服务的最重要工作,下面是AlarmManagerService的部分实现:
class AlarmManagerService extends IAlarmManager.Stub {
public void set(int type, long triggerAtTime, PendingIntent operation) {
setRepeating(type, triggerAtTime, 0, operation);
}
...
}
(4)系统调用接口
为了方便应用层进行调用,需要在ContextImpl.java中实现统一的接口封装,并在Context.java中定义如下接口:
public static final String ALARM_SERVICE = "alarm";
在ContextImpl.java中实现统一接口封装的代码如下:
class ContextImpl extends Context {
private static AlarmManager sAlarmManager;
...
public Object getSystemService(String name) {
...
if (ALARM_SERVICE.equals(name)) {
return getAlarmManager();
}
...
}
private AlarmManager getAlarmManager() {
synchronized (sSync) {
if (sAlarmManager == null) {
IBinder b = ServiceManager.getService(ALARM_SERVICE);
IAlarmManager service = IAlarmManager.Stub.asInterface(b);
sAlarmManager = new AlarmManager(service);
}
}
return sAlarmManager;
}
}
完成以上工作后,如果需要闹钟服务,可按照下面的方式执行调用:
AlarmManager alarmManager =
(AlarmManager) getSystemService(Context.ALARM_SERVICE);
注意 框架层的变动会导致SDK的变动,故需要根据设计者的需求选择@hide或make update-api来更新current.xml。
5. 服务配置
为了对服务进行编译,必须在AndroidManifest.xml中对服务进行配置,其方法如下:
<service android:name=".service.EmailBroadcastProcessorService" />
对于远程服务,如果希望服务可以被其他进程调用,则必须开放相应的权限,开放权限的方法如下:
<service android:name="com.android.server.LoadAverageService"
android:exported="true" />
如果服务具有一些敏感的信息,需要对其进行权限配置,这在对安全要求较高的应用中十分重要。权限配置方法如下:
<service android:name=
"com.android.internal.os.storage.ExternalStorageFormatter"
android:permission="android.permission.MASTER_CLEAR"
android:exported="true" />
如果希望服务运行在单独的进程中,则可应用如下方法进行配置:
<service android:name="android.os.MessengerService"
android:process=":messengerService" /> //设置进程名