前面介绍了service的生命周期和Local Sevice,下面介绍下Remote Service以及AIDL(Android Interface Definition Language)的相关内容;
官方文档特别提醒我们何时使用AIDL是必要的:
只有你允许客户端从不同的应用程序为了进程间的通信而去访问你的service,以及想在你的service处理多线程。
为什么需要用到AIDL,为什么直接用service不行?
在Android中, 每个应用程序都可以有自己的进程. 在写UI应用的时候, 经常要用到Service. 在不同应用之间传递数据,对象,而每个应用都有自己的进程,则在不同的进程中, 怎样传递对象呢? 显然, Java中不允许跨进程内存共享. 因此传递对象, 只能把对象拆分成操作系统能理解的简单形式, 以达到跨界对象访问的目的.在J2EE中,采用RMI的方式, 可以通过序列化传递对象. 在Android中, 则采用AIDL的方式. 理论上AIDL可以传递Bundle,实际上做起来却比较麻烦.
先介绍下AIDL的相关知识:
AIDL(AndRoid接口描述语言)是一种借口描述语言; 编译器可以通过aidl文件生成一段代码,通过预先定义的接口达到两个进程内部通信进程的目的. 如果需要在一个Activity中, 访问另一个Service中的某个对象, 需要先将对象转化成AIDL可识别的参数(可能是多个参数), 然后使用AIDL来传递这些参数, 在消息的接收端, 使用这些参数组装成自己需要的对象.
定义AIDL接口
AIDL接口文件,和普通的接口内容没有什么特别,只是它的扩展名为.aidl。保存在src目录下。如果其他应用程序需要IPC,则那些应用程序的src也要带有这个文件。Android SDK tools就会在gen目录自动生成一个IBinder接口文件。service必须适当地实现这个IBinder接口。那么客户端程序就能绑定这个service并在IPC时从IBinder调用方法。
每个aidl文件只能定义一个接口,而且只能是接口的声明和方法的声明。
创建.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变量。AIDL的IPC(Interprocess Communication)的机制和COM或CORBA类似, 是基于接口的,但它是轻量级的。它使用代理类在客户端和实现层间传递值.
如果要使用AIDL, 需要完成2件事情:
1. 引入AIDL的相关类.; 2. 调用aidl产生的class.
建立AIDL服务要比建立普通的服务复杂一些,具体步骤如下:
(1)在Eclipse Android工程的Java包目录中建立一个扩展名为aidl的文件。该文件的语法类似于Java代码,但会稍有不同。详细介绍见实例的内容。
(2)如果aidl文件的内容是正确的,ADT会自动生成一个Java接口文件(*.java)。
(3)建立一个服务类(Service的子类)。
(4)实现由aidl文件生成的Java接口。
(5)在AndroidManifest.xml文件中配置AIDL服务,尤其要注意的是,<action>标签中android:name的属性值就是客户端要引用该服务的ID,也就是Intent类的参数值。
这个例子作用是,把用户信息加入,显示出来;
其中介绍了aidl和pracable的相关知识;
上代码:
RemoteServiceDemoActivity.java
package com.potato;
import java.util.List;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class RemoteServiceDemoActivity extends Activity {
/** Called when the activity is first created. */
boolean mIsRemoteBound = false; // 注2
EditText mEditInputPerson;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
if (mIsRemoteBound) {
unbindService(mRemoteConnection);
} else {
bindService(new Intent("com.potato.ITestService"), // 注1
mRemoteConnection, Context.BIND_AUTO_CREATE);
}
mIsRemoteBound = !mIsRemoteBound;
Button btnAddPerson = (Button) findViewById(R.id.btn_add_person_info);
btnAddPerson.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();
}
}
});
mEditInputPerson = (EditText) findViewById(R.id.edit_input_person_info);
// 把保存的信息都显示到EditText上
Button btnListPerson = (Button) findViewById(R.id.btn_list_person_info);
btnListPerson.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());
}
mEditInputPerson.setText(text);
} else {
Toast.makeText(RemoteServiceDemoActivity.this,
"get data error", Toast.LENGTH_SHORT).show();
}
}
});
}
protected void onDestroy() {
super.onDestroy();
// 注3 销毁activity时,解除bind
if (mIsRemoteBound) {
unbindService(mRemoteConnection);
}
}
private ITestService mRemoteService;
private ServiceConnection mRemoteConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
mRemoteService = ITestService.Stub.asInterface(service); // 注4
}
public void onServiceDisconnected(ComponentName className) {
mRemoteService = null; // 注4
}
};
}
注1.
这里用到了Intent的相关知识,先不解释,后面的文章会有详解,敬请关注;
注2 ,3
mIsRemoteBound判断service的绑定,与activity生命相同,同生共死;
注4
mRemoteService = ITestService.Stub.asInterface(service);
这是aidl的特有的调用方式;
onServiceConnection方法参数中的Ibinder不是IMyService.Stub类型,不能直接向上转型,只能使用方法IMyService.Stub.asInterface(service)获得一个实现了IMyService接口的类型对象,其类型为IMyService.Stub.Proxy。
RemoteService.java
package com.potato;
import java.util.LinkedList;
import java.util.List;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
public class RemoteService extends Service {
private LinkedList<Person> personList = new LinkedList<Person>();
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
// 实现需要调用的接口
private final ITestService.Stub mBinder = new ITestService.Stub() { // 注5
@Override
public void savePersonInfo(Person person) throws RemoteException {
if (person != null) {
personList.add(person);
}
}
@Override
public List<Person> getAllPerson() throws RemoteException {
return personList;
}
};
}
注5.
实现和增加自己需要的接口,方法;
person.java
package com.potato;
import android.os.Parcel;
import android.os.Parcelable;
public class Person implements Parcelable { // 注6
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) { // 注6
dest.writeString(name);
dest.writeString(telNumber);
dest.writeInt(age);
}
public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>() { // 注6
@Override
public Person createFromParcel(Parcel source) {
return new Person(source);
}
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
}
注6
序列化;
Person类,是一个序列化的类,这里使用Parcelable 接口来序列化,是Android提供的一个比Serializable 效率更高的序列化类。
Parcelable需要实现三个函数:
1) void writeToParcel(Parcel dest, int flags) 将需要序列化存储的数据写入外部提供的Parcel对象dest。而看了网上的代码例子,个人猜测,读取Parcel数据的次序要和这里的write次序一致,否则可能会读错数据。具体情况我没试验过!
2) describeContents() 不知什么用;
3) static final Parcelable.Creator对象CREATOR 这个CREATOR命名是固定的,而它对应的接口有两个方法:
createFromParcel(Parcel source) 实现从source创建出person实例的
newArray(int size) 反放序列化;
下面是aidl文件的生成(只要生成文件扩张名为.aidl的文件即可)
ITestService.aidl
package com.potato;
import com.potato.Person;
/**
编写Aidl文件时,需要注意下面几点:
1.接口名和aidl文件名相同。
2.接口和方法前不用加访问权限修饰符public,private,protected等,也不能用final,static。
3.Aidl默认支持的类型包话java基本类型(int、long、boolean等)和(String、List、Map、CharSequence),使用这些类型时不需要import声明。
对于List和Map中的元素类型必须是Aidl支持的类型。如果使用自定义类型作为参数或返回值,自定义类型必须实现Parcelable接口。
4.自定义类型和AIDL生成的其它接口类型在aidl描述文件中,应该显式import,即便在该类和定义的包在同一个包中。
5.在aidl文件中所有非Java基本类型参数必须加上in、out、inout标记,以指明参数是输入参数、输出参数还是输入输出参数。
6.Java原始类型默认的标记为in,不能为其它标记。
*/
interface ITestService {
void savePersonInfo(in Person person);
List<Person> getAllPerson();
}
person.aidl
package com.potato;
/*
* 注7 这里的parcelable首字母必须小写
**/
parcelable Person;
注7
因为Person不是java的基本类或者String、List、Map、CharSequence,所以需要import声明;
这里的parcelable首字母必须小写
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello" />
<Button
android:id="@+id/btn_add_person_info"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="addPerson" />
<Button
android:id="@+id/btn_list_person_info"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="listPerson" />
<EditText
android:id="@+id/edit_input_person_info"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
androidMainfest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.potato"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="8" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:label="@string/app_name"
android:name=".RemoteServiceDemoActivity" >
<intent-filter >
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".RemoteService"
android:process=":remote">
<intent-filter>
<action android:name="com.potato.ITestService" />
</intent-filter>
</service>
</application>
</manifest>
注意:
<service
android:name=".RemoteService"
android:process=":remote">
<intent-filter>
<action android:name="com.potato.ITestService" />
</intent-filter>
</service>
android:process=":remote",代表在应用程序里,当需要该service时,会自动创建新的进程。
而如果是android:process="remote",没有“:”分号的,则创建全局进程,不同的应用程序共享该进程。
有问题 ,请留言胡发邮件;
联系方式:ligexiao@gmail.com
这里还包括一个播放器的实例,用到AIDL