您的位置:首页 > 移动开发 > Android开发

Android Service学习之AIDL, Parcelable和远程服务

2016-01-04 15:31 756 查看
AIDL的作用

由于每个应用程序都运行在自己的进程空间,并且可以从应用程序UI运行另一个服务进程,而且经常会在不同的进程间传递对象。在Android平台,一个进程通常不能访问另一个进程的内存空间,所以要想对话,需要将对象分解成操作系统可以理解的基本单元,并且有序的通过进程边界。

通过代码来实现这个数据传输过程是冗长乏味的,Android提供了AIDL工具来处理这项工作。

AIDL (Android Interface Definition Language) 是一种IDL 语言,用于生成可以在Android设备上两个进程之间进行进程间通信(interprocess communication, IPC)的代码。如果在一个进程中(例如Activity)要调用另一个进程中(例如Service)对象的操作,就可以使用AIDL生成可序列化的参数。

AIDL IPC机制是面向接口的,像COM或Corba一样,但是更加轻量级。它是使用代理类在客户端和实现端传递数据。

选择AIDL的使用场合

官方文档特别提醒我们何时使用AIDL是必要的:只有你允许客户端从不同的应用程序为了进程间的通信而去访问你的service,以及想在你的service处理多线程。

如果不需要进行不同应用程序间的并发通信(IPC),you should create your interface by implementing a Binder;或者你想进行IPC,但不需要处理多线程的,则implement your interface using a Messenger。无论如何,在使用AIDL前,必须要理解如何绑定service——bindService。

在设计AIDL接口前,要提醒的是,调用AIDL接口是直接的方法调用的,不是我们所想象的调用是发生在线程里。而调用(call)来自local进程或者remote进程,有什么区别呢?尤其是以下情况(引用原文,不作翻译了,以免翻译有误):

Calls made from the local process are executed in the same thread that is making the call. If this is your main UI thread, that thread continues to execute in the AIDL interface. If it is
another thread, that is the one that executes your code in the service. Thus, if only local threads are accessing the service, you can completely control which threads are executing in it (but if that is the case, then you shouldn't be using AIDL at all, but
should instead create the interface byimplementing a Binder).
Calls from a remote process are dispatched from a thread pool the platform maintains inside of your own process. You must be prepared for incoming calls from unknown threads, with multiple
calls happening at the same time. In other words, an implementation of an AIDL interface must be completely thread-safe.
The
oneway
keyword modifies the behavior of remote calls. When used, a remote call
does not block; it simply sends the transaction data and immediately returns. The implementation of the interface eventually receives this as a regular call from the
Binder
thread
pool as a normal remote call. If
oneway
is used with a local call, there is no impact and the call is still synchronous.

定义AIDL接口

AIDL接口文件,和普通的接口内容没有什么特别,只是它的扩展名为.aidl。保存在src目录下。如果其他应用程序需要IPC,则那些应用程序的src也要带有这个文件。Android SDK tools就会在gen目录自动生成一个IBinder接口文件。service必须适当地实现这个IBinder接口。那么客户端程序就能绑定这个service并在IPC时从IBinder调用方法。

每个aidl文件只能定义一个接口,而且只能是接口的声明和方法的声明。

1.创建.aidl文件

AIDL使用简单的语法来声明接口,描述其方法以及方法的参数和返回值。这些参数和返回值可以是任何类型,甚至是其他AIDL生成的接口。

其中对于Java编程语言的基本数据类型 (int, long, char, boolean等),String和CharSequence,集合接口类型List和Map,不需要import 语句。

而如果需要在AIDL中使用其他AIDL接口类型,需要import,即使是在相同包结构下。AIDL允许传递实现Parcelable接口的类,需要import.

需要特别注意的是,对于非基本数据类型,也不是String和CharSequence类型的,需要有方向指示,包括in、out和inout,in表示由客户端设置,out表示由服务端设置,inout是两者均可设置。

AIDL只支持接口方法,不能公开static变量。

例如 (IMyService.aidl):

package com.demo;

import com.demo.Person;

interface IMyService {

void savePersonInfo(in Person person);

List<Person> getAllPerson();

}

2.实现接口

创建一个类实现刚才那个aidl的接口:

public class RemoteService extends Service {

private LinkedList<Person> personList = new LinkedList<Person>();

@Override

public IBinder onBind(Intent intent) {

return mBinder;

}

private final IMyService.Stub mBinder = new IMyService.Stub(){

@Override

public void savePersonInfo(Person person) throws RemoteException
{

if (person != null){

personList.add(person);

}

}

@Override

public List<Person> getAllPerson() throws RemoteException {

return personList;

}

};

}

这里会看到有一个名为IMyService.Stub类,查看aidl文件生成的Java文件源代码就能发现有这么一段代码:

/** Local-side IPC implementation stub class. */

public static abstract class Stub extends android.os.Binder implements com.demo.IMyService

原来Stub类就是继承于Binder类,也就是说RemoteService类和普通的Service类没什么不同,只是所返回的IBinder对象比较特别,是一个实现了AIDL接口的Binder。

接下来就是关于所传递的数据Bean——Person类,是一个序列化的类,这里使用Parcelable 接口来序列化,是Android提供的一个比Serializable 效率更高的序列化类。

Parcelable需要实现三个函数:

1) void writeToParcel(Parcel dest, int flags) 将需要序列化存储的数据写入外部提供的Parcel对象dest。而看了网上的代码例子,个人猜测,读取Parcel数据的次序要和这里的write次序一致,否则可能会读错数据。具体情况我没试验过!

2) describeContents() 没搞懂有什么用,反正直接返回0也可以

3) static final Parcelable.Creator对象CREATOR 这个CREATOR命名是固定的,而它对应的接口有两个方法:

createFromParcel(Parcel source) 实现从source创建出JavaBean实例的功能

newArray(int size) 创建一个类型为T,长度为size的数组,仅一句话(return new T[size])即可。估计本方法是供外部类反序列化本类数组使用。

仔细观察Person类的代码和上面所说的内容:

public class Person implements Parcelable {

private String name;

private String telNumber;

private int age;

public Person() {}

public Person(Parcel pl){

name = pl.readString();

telNumber = pl.readString();

age = pl.readInt();

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public String getTelNumber() {

return telNumber;

}

public void setTelNumber(String telNumber) {

this.telNumber = telNumber;

}

public int getAge() {

return age;

}

public void setAge(int age) {

this.age = age;

}

@Override

public int describeContents() {

return 0;

}

@Override

public void writeToParcel(Parcel dest, int flags) {

dest.writeString(name);

dest.writeString(telNumber);

dest.writeInt(age);

}

public static final Parcelable.Creator<Person>
CREATOR = new Parcelable.Creator<Person>() {

@Override

public Person createFromParcel(Parcel source) {

return new Person(source);

}

@Override

public Person[] newArray(int size) {

return new Person[size];

}

};

}

然后创建Person.aidl文件,注意这里的parcelable和原来实现的Parcelable 接口,开头的字母p一个小写一个大写:

package com.demo;

parcelable Person;

对于实现AIDL接口,官方还提醒我们:

1. 调用者是不能保证在主线程执行的,所以从一调用的开始就需要考虑多线程处理,以及确保线程安全;

2. IPC调用是同步的。如果你知道一个IPC服务需要超过几毫秒的时间才能完成地话,你应该避免在Activity的主线程中调用。也就是IPC调用会挂起应用程序导致界面失去响应,这种情况应该考虑单独开启一个线程来处理。

3. 抛出的异常是不能返回给调用者(跨进程抛异常处理是不可取的)。

3. 客户端获取接口

客户端如何获取AIDL接口呢?通过IMyService.Stub.asInterface(service)来得到IMyService对象:

private IMyService mRemoteService;

private ServiceConnection mRemoteConnection = new ServiceConnection() {

public void onServiceConnected(ComponentName className, IBinder service) {

mRemoteService = IMyService.Stub.asInterface(service);

}

public void onServiceDisconnected(ComponentName className) {

mRemoteService = null;

}

};

在生成的IMyService.java里面会找到这样的代码:

/**

* Cast an IBinder object into an com.demo.IMyService interface,

* generating a proxy if needed.

*/

public static com.demo.IMyService asInterface(android.os.IBinder obj) {...}

而service的绑定没有什么不同:

if (mIsRemoteBound) {

unbindService(mRemoteConnection);

}else{

bindService(new Intent("com.demo.IMyService"),

mRemoteConnection, Context.BIND_AUTO_CREATE);

}

mIsRemoteBound = !mIsRemoteBound;

通过IPC调用/传递数据

客户端绑定service后就能通过IPC来调用/传递数据了,直接调用service对象的接口方法:

addPersonButton.setOnClickListener(

new View.OnClickListener(){

private int index = 0;

@Override

public void onClick(View view) {

Person person = new Person();

index = index + 1;

person.setName("Person" + index);

person.setAge(20);

person.setTelNumber("123456");

try {

mRemoteService.savePersonInfo(person);

} catch (RemoteException e) {

e.printStackTrace();

}

}

});

listPersonButton.setOnClickListener(

new View.OnClickListener(){

@Override

public void onClick(View view) {

List<Person> list = null;

try {

list = mRemoteService.getAllPerson();

} catch (RemoteException e) {

e.printStackTrace();

}

if (list != null){

StringBuilder text = new StringBuilder();

for(Person person : list){

text.append("\nPerson name:");

text.append(person.getName());

text.append("\n age :");

text.append(person.getAge());

text.append("\n tel number:");

text.append(person.getTelNumber());

}

inputPersonEdit.setText(text);

}else {

Toast.makeText(ServiceActivity.this, "get data error",

Toast.LENGTH_SHORT).show();

}

}

});

Permission权限

如果Service在AndroidManifest.xml中声明了全局的强制的访问权限,其他引用必须声明权限才能来start,stop或bind这个service.

另外,service可以通过权限来保护她的IPC方法调用,通过调用checkCallingPermission(String)方法来确保可以执行这个操作。

AndroidManifest.xml的Service元素

<service android:name=".RemoteService" android:process=":remote">

<intent-filter>

<action android:name="com.demo.IMyService" />

</intent-filter>

</service>

这里的android:process=":remote",一开始我没有添加的,在同一个程序里使用IPC,即同一个程序作为客户端/服务器端,结果运行mRemoteService = IMyService.Stub.asInterface(service);时提示空指针异常。观察了人家的在不同程序里进行IPC的代码,也是没有这个android:process=":remote"的。后来在官方文档http://androidappdocs.appspot.com/guide/topics/manifest/service-element.html里了解到(留意第二段文字):

android:process

The name of the process where the service is to run. Normally, all components of an application run in the default process created for the application. It has the same name as the application package. The <application> element's process attribute can
set a different default for all components. But component can override the default with its own process attribute, allowing you to spread your application across multiple processes.

If the name assigned to this attribute begins with a colon (':'), a new process, private to the application, is created when it's needed and the
service runs in that process. If the process name begins with a lowercase character, the service will run in a global process of that name, provided that it has permission to do so. This allows components
in different applications to share a process, reducing resource usage.

也就是说android:process=":remote",代表在应用程序里,当需要该service时,会自动创建新的进程。而如果是android:process="remote",没有“:”分号的,则创建全局进程,不同的应用程序共享该进程。

////////////////////////


这个service总结比较全面,不错

android编写Service入门

android SDK提供了Service,用于类似*nix守护进程或者windows的服务。

Service有两种类型:

本地服务(Local Service):用于应用程序内部
远程服务(Remote Sercie):用于android系统内部的应用程序之间

前者用于实现应用程序自己的一些耗时任务,比如查询升级信息,并不占用应用程序比如Activity所属线程,而是单开线程后台执行,这样用户体验比较好。

后者可被其他应用程序复用,比如天气预报服务,其他应用程序不需要再写这样的服务,调用已有的即可。


编写不需和Activity交互的本地服务示例

本地服务编写比较简单。首先,要创建一个Service类,该类继承android的Service类。这里写了一个计数服务的类,每秒钟为计数器加一。在服务类的内部,还创建了一个线程,用于实现后台执行上述业务逻辑。

package com.easymorse;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;

public class CountService extends Service {

private boolean threadDisable;

private int count;

@Override
public IBinder onBind(Intent intent) {
return null;
}

@Override
public void onCreate() {
super.onCreate();
new Thread(new Runnable() {

@Override
public void run() {
while (!threadDisable) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
count++;
Log.v("CountService", "Count is " + count);
}
}
}).start();
}

@Override
public void onDestroy() {
super.onDestroy();
this.threadDisable = true;
Log.v("CountService", "on destroy");
}

public int getCount() {
return count;
}

}


需要将该服务注册到配置文件AndroidManifest.xml中,否则无法找到:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.easymorse" android:versionCode="1" android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".LocalServiceDemoActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name="CountService" />
</application>
<uses-sdk android:minSdkVersion="3" />
</manifest>


在Activity中启动和关闭本地服务。

package com.easymorse;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;

public class LocalServiceDemoActivity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

this.startService(new Intent(this, CountService.class));
}

@Override
protected void onDestroy() {
super.onDestroy();
this.stopService(new Intent(this, CountService.class));
}
}


可通过日志查看到后台线程打印的计数内容。


编写本地服务和Activity交互的示例

上面的示例是通过startService和stopService启动关闭服务的。适用于服务和activity之间没有调用交互的情况。如果之间需要传递参数或者方法调用。需要使用bind和unbind方法。

具体做法是,服务类需要增加接口,比如ICountService,另外,服务类需要有一个内部类,这样可以方便访问外部类的封装数据,这个内部类需要继承Binder类并实现ICountService接口。还有,就是要实现Service的onBind方法,不能只传回一个null了。

这是新建立的接口代码:

package com.easymorse;

public interface ICountService {

public abstract int getCount();
}


修改后的CountService代码:

package com.easymorse;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

public class CountService extends Service implements ICountService {

private boolean threadDisable;

private int count;

private ServiceBinder serviceBinder=new ServiceBinder();

public class ServiceBinder extends Binder implements ICountService{
@Override
public int getCount() {
return count;
}
}

@Override
public IBinder onBind(Intent intent) {
return serviceBinder;
}

@Override
public void onCreate() {
super.onCreate();
new Thread(new Runnable() {

@Override
public void run() {
while (!threadDisable) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
count++;
Log.v("CountService", "Count is " + count);
}
}
}).start();
}

@Override
public void onDestroy() {
super.onDestroy();
this.threadDisable = true;
Log.v("CountService", "on destroy");
}

/* (non-Javadoc)
* @see com.easymorse.ICountService#getCount()
*/
public int getCount() {
return count;
}

}


服务的注册也要做改动,AndroidManifest.xml文件:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.easymorse" android:versionCode="1" android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".LocalServiceDemoActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name="CountService">
<intent-filter>
<action android:name="com.easymorse.CountService"/>
</intent-filter>
</service>
</application>
<uses-sdk android:minSdkVersion="3" />
</manifest>


Acitity代码不再通过startSerivce和stopService启动关闭服务,另外,需要通过ServiceConnection的内部类实现来连接Service和Activity。

package com.easymorse;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;

public class LocalServiceDemoActivity extends Activity {

private ServiceConnection serviceConnection = new ServiceConnection() {

@Override
public void onServiceConnected(ComponentName name, IBinder service) {
countService = (ICountService) service;
Log.v("CountService", "on serivce connected, count is "
+ countService.getCount());
}

@Override
public void onServiceDisconnected(ComponentName name) {
countService = null;
}

};

private ICountService countService;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
this.bindService(new Intent("com.easymorse.CountService"),
this.serviceConnection, BIND_AUTO_CREATE);
}

@Override
protected void onDestroy() {
super.onDestroy();
this.unbindService(serviceConnection);
}
}



编写传递基本型数据的远程服务

上面的示例,可以扩展为,让其他应用程序复用该服务。这样的服务叫远程(remote)服务,实际上是进程间通信(RPC)。

这时需要使用android接口描述语言(AIDL)来定义远程服务的接口,而不是上述那样简单的java接口。扩展名为aidl而不是java。可用上面的ICountService改动而成ICountSerivde.aidl,eclipse会自动生成相关的java文件。

package com.easymorse;

interface ICountService {
int getCount();
}


编写服务(Service)类,稍有差别,主要在binder是通过远程获得的,需要通过桩(Stub)来获取。桩对象是远程对象的本地代理。

package com.easymorse;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;

public class CountService extends Service {

private boolean threadDisable;

private int count;

private ICountService.Stub serviceBinder = new ICountService.Stub() {

@Override
public int getCount() throws RemoteException {
return count;
}
};

@Override
public IBinder onBind(Intent intent) {
return serviceBinder;
}

@Override
public void onCreate() {
super.onCreate();
new Thread(new Runnable() {

@Override
public void run() {
while (!threadDisable) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
count++;
Log.v("CountService", "Count is " + count);
}
}
}).start();
}

@Override
public void onDestroy() {
super.onDestroy();
this.threadDisable = true;
Log.v("CountService", "on destroy");
}
}


配置文件AndroidManifest.xml和上面的类似,没有区别。

在Activity中使用服务的差别不大,只需要对ServiceConnection中的调用远程服务的方法时,要捕获异常。

private ServiceConnection serviceConnection = new ServiceConnection() {

@Override
public void onServiceConnected(ComponentName name, IBinder service) {
countService = (ICountService) service;
try {
Log.v("CountService", "on serivce connected, count is "
+ countService.getCount());
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}

@Override
public void onServiceDisconnected(ComponentName name) {
countService = null;
}

};


这样就可以在同一个应用程序中使用远程服务的方式和自己定义的服务交互了。

如果是另外的应用程序使用远程服务,需要做的是复制上面的aidl文件和相应的包构到应用程序中,其他调用等都一样。


编写传递复杂数据类型的远程服务

远程服务往往不只是传递java基本数据类型。这时需要注意android的一些限制和规定:

android支持String和CharSequence
如果需要在aidl中使用其他aidl接口类型,需要import,即使是在相同包结构下;
android允许传递实现Parcelable接口的类,需要import;
android支持集合接口类型List和Map,但是有一些限制,元素必须是基本型或者上述三种情况,不需要import集合接口类,但是需要对元素涉及到的类型import;
非基本数据类型,也不是String和CharSequence类型的,需要有方向指示,包括in、out和inout,in表示由客户端设置,out表示由服务端设置,inout是两者均可设置。

这里将前面的例子中返回的int数据改为复杂数据类型:

package com.easymorse;

import android.os.Parcel;
import android.os.Parcelable;

public class CountBean implements Parcelable {

public static final Parcelable.Creator<CountBean> CREATOR = new Creator<CountBean>() {

@Override
public CountBean createFromParcel(Parcel source) {
CountBean bean = new CountBean();
bean.count = source.readInt();
return bean;
}

@Override
public CountBean[] newArray(int size) {
return new CountBean[size];
}

};

public int count;

@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.count);
}

@Override
public int describeContents() {
return 0;
}

}


然后,需要在相同包下建一个同名的aidl文件,用于android生成相应的辅助文件:

package com.easymorse;

parcelable CountBean;


这一步是android 1.5后的变化,无法通过adt生成aidl,也不能用一个比如全局的project.aidl文件,具体见:

http://www.anddev.org/viewtopic.php?p=20991

然后,需要在服务的aidl文件中修改如下:

package com.easymorse;

import com.easymorse.CountBean;

interface ICountService {
CountBean getCount();
}


其他的改动很小,只需将CountService和调用CountService的部分修改为使用CountBean即可。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: