前面讲述了service的本地通信,也就是跟启动它的进程本身通信,而接下来还有介绍更为深入的内容,Service的远程通信,也就是跟其他进程的通信。
首先我们来看下面一个问题,假设我们使用本地service,我们在onStartCommand()方法了执行了耗时操作,那么主线程将会阻塞,我们这时点击屏幕上的button的时候,就会出现ANR(Android Not Response)。
但是假如我们把这个service设置为远程service,如下
<service android:name=".Myservice"
android:process=":remote"></service>
只需要设置一个process=":romote"属性就可以了
android:process=":remote",代表在应用程序里,当需要该service时,会自动创建新的进程。而如果是android:process="remote",没有“:”分号的,则创建全局进程,不同的应用程序共享该进程。
这时我们再启动这个service,发现不会出现ANR。
原因是使用远程Service后,MyService已经在另外一个进程当中运行了,所以只会阻塞该进程中的主线程,并不会影响到当前的应用程序。
那么我们为什么不全都使用远程service呢?
但我们使用远程service时,加入调用了bindServer()方法,会发现程序崩溃了。
原因是目前MyService已经是一个远程Service了,Activity和Service运行在两个不同的进程当中,这时就不能再使用传统的建立关联的方式,程序也就崩溃了。
所以,要使用service的远程通信,我必须使用AIDL来进行跨进程通(IPC)
下面我们先来看一下AIDL文件到底是什么。
AIDL(Android Interface Definition Language)是Android接口定义语言的意思,它可以用于让某个Service与多个应用程序组件之间进行跨进程通信,从而可以实现多个应用程序共享同一个Service的功能。
我们再包里面建立一个AIDL文件如下
interface MyAIDLService {
void doSomeThing();
}
大家一眼看去,就感觉这个AIDL文件很像接口,其实就是很像,其中doSomeThing();是我自己定义的一个抽象方法,大家也可以定义自己的方法,目的是为调用这个service的客户端进程提供固定的调用接口
但是AIDL与接口存在下面几点差异:
1,AIDL定义的接口源代码必须以.aidl结尾
2,AIDL接口中的数据类型,除了基本类型,String,List,Map,CharSequence之外,其他类型全部需要导包,即时他们在同一个包中也需要导包
3,AIDL允许传递实现Parcelable接口的类,需要import.
4, 需要特别注意的是,对于非基本数据类型,也不是String和CharSequence类型的,需要有方向指示,包括in、out和inout,in表示由客户端设置,out表示由服务端设置,inout是两者均可设置。 AIDL只支持接口方法,不能公开static变量。
创建了这个AIDL文件以后,就会发现gen目录下自动生成了一个同名的.java文件
接下来看一段使用AIDL接口的代码
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
public class MyService extends Service {
Binder myIBinder = new MyAIDLService.Stub(){
@Override
public void doSomeThing() throws RemoteException {
// TODO Auto-generated method stub
}
};
/**
* 抽象方法,必须实现
*/
@Override
public IBinder onBind(Intent intent) {
return myIBinder;
}
}
从上面的代码可以看出,MyAIDLService.java接口里面有一个Stub内部类,准确来说, 所有AIDL接口里面,有一个Stub内部类,该内部类实现了IBinder、MyAIDLService两个接口
这时,我们在service中实现的IBinder对象,要是这个Stub内部类的实例,onBind()方法也要返回这个对象。
然后我们再来看Activity(另外一个进程中的)是怎么调用这个service的
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.os.RemoteException;
public class MyActivity extends Activity{
ServiceConnection connection = new ServiceConnection() {
/**
* 解除绑定
*/
@Override
public void onServiceDisconnected(ComponentName name) {
}
/**
* 绑定服务
* IBinder就是对于服务onBind()方法返回的对象
*/
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
MyAIDLService myAIDLService = MyAIDLService.Stub.asInterface(service);
try {
myAIDLService.doSomeThing();
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//绑定并启动service
Intent bindIntent = new Intent(this,MyService.class);
bindService(bindIntent,connection,BIND_AUTO_CREATE);
//解除绑定
unbindService(connection);
}
}
从上面代码可以看出,我们
调用了MyAIDLService.Stub的一个asInterface来将IBinder转换为MyAIDLService对象,接着就可以调用里面的方法了
所以,实现客户端开发的第一步,就是把AIDL接口文件复制到客户端应用中,ADT工具会自动为AIDL接口生成对应实现
但是我们是通过另外一个进程调用的Service,希望达到service共享的目的,而另外一个进程本身没有MyService这个类,这是我们就需要隐式的Intent了
我们在注册Myservice的时候这样写
<service android:name=".Myservice">
<intent-filter>
<action android:name="default.package.MyService"/>
</intent-filter>
</service>
然后在activity调用时,
使用隐式intent
//绑定并启动service
Intent bindIntent = new Intent("default.package.MyService");
bindService(bindIntent,connection,BIND_AUTO_CREATE);
OK,这样我就实现了远程的进程通信。我们远程通信和本地通信的代码相比较,发现差别不大, 只是获取Service回调对象(IBinder实例)时的方式有所区别而已。绑定本地Service时可以直接获取onBinder的返回值,绑定远程Service时获取的是onBind方法所返回对象的代理,因此需要一些处理
对于实现AIDL接口,官方还提醒我们:
3. 抛出的异常是不能返回给调用者(跨进程抛异常处理是不可取的)。
远程通信还可以传递一些更加复杂的数据,不过我们要使用Parcelable接口来进行序列化。
我们首先要使用AIDL建立一个类,例如Person类(也就是创建一个Person.aidl文件)
parcelable Person;
接下来定
义一个实现了Parcelable接口的Person类
package test;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.style.ParagraphStyle;
public class Person implements Parcelable{
int id = 0;
String info = "perosn";
@Override
public int describeContents() {
return 0;
}
/**
* 改方法用于将对象包含的内容写到Parcel中
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
dest.writeString(info);
}
public static final Parcelable.Creator<Person> CREATOR = new Creator<Person>() {
/**
* 该方法用于在Parcel读取数据,返回Person对象
* 这样读取数据时,要跟传入的顺序相同!
*/
@Override
public Person createFromParcel(Parcel source) {
Person p = new Person();
p.id = source.readInt();
p.info = source.readString();
return p;
}
@Override
public Person[] newArray(int size) {
// TODO Auto-generated method stub
return null;
}
};
}
实现Parcelable接口的类,必须定义一个Parcelable.Creator<Person>、名为CREATOR的静态常量,该常量的值复制从Parcel数据包中恢复Person对象,因此对该对象定义的createFromPerson()方法用于恢复Person对象
另外我们再定义一个Pet类,方法类似Person类的定义,要有AIDL和类本身两个定义
定义好上述类以后,我们就可以使用AIDL来定义通信接口了,通信接口如下
import test.Person;
import test.Pet;
interface IPet{
List<Pet> getPets(in Person owner);
}
从上面代码可以看到,
在AIDL接口中定义方法是,需要制定形参传递的模式,对于java语言来说,一般采用参入参数的方式,因此上面指定为in模式
接下来在Service中的实现就跟一般的AIDL通信一样了
public class MyService extends Service {
Binder myIBinder = new IPer.Stub(){
@Override
public List<Pet> <pre name="code" class="java">ServiceConnection connection = new ServiceConnection() {
/**
* 解除绑定
*/
@Override
public void onServiceDisconnected(ComponentName name) {
}
/**
* 绑定服务
* IBinder就是对于服务onBind()方法返回的对象
*/
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IPet myAIDLService = IPet.Stub.asInterface(service);
try {
IPet.getPets(new Person());
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
throws RemoteException {return pets.get(owner);}};/** * 抽象方法,必须实现 */@Overridepublic IBinder onBind(Intent intent) {return myIBinder;}}
对于客户端,我们要复制IPet.aidl,还要把定义Person类的java文件,aidl文件,定义Pet类的java文件,aidl文件都复制过去
接下来客户端的使用,也跟普通的aidl一样
ServiceConnection connection = new ServiceConnection() {
/**
* 解除绑定
*/
@Override
public void onServiceDisconnected(ComponentName name) {
}
/**
* 绑定服务
* IBinder就是对于服务onBind()方法返回的对象
*/
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IPet myAIDLService = IPet.Stub.asInterface(service);
try {
myAIDLService.getPets(new Person());
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}