android后台服务service全解析(下)--service远程通信

前面讲述了service的本地通信,也就是跟启动它的进程本身通信,而接下来还有介绍更为深入的内容,Service的远程通信,也就是跟其他进程的通信。

首先我们来看下面一个问题,假设我们使用本地service,我们在onStartCommand()方法了执行了耗时操作,那么主线程将会阻塞,我们这时点击屏幕上的button的时候,就会出现ANR(Android Not Response)。

但是假如我们把这个service设置为远程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文件

android后台服务service全解析(下)--service远程通信_第1张图片


接下来看一段使用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的时候这样写


            
                
            
        
然后在activity调用时, 使用隐式intent

//绑定并启动service
		Intent bindIntent = new Intent("default.package.MyService");
		bindService(bindIntent,connection,BIND_AUTO_CREATE);		

OK,这样我就实现了远程的进程通信。我们远程通信和本地通信的代码相比较,发现差别不大, 只是获取Service回调对象(IBinder实例)时的方式有所区别而已。绑定本地Service时可以直接获取onBinder的返回值,绑定远程Service时获取的是onBind方法所返回对象的代理,因此需要一些处理


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

    1. 调用者是不能保证在主线程执行的,所以从一调用的开始就需要考虑多线程处理,以及确保线程安全;
    2. IPC调用是同步的。如果你知道一个IPC服务需要超过几毫秒的时间才能完成地话,你应该避免在Activity的主线程中调用。也就是IPC调用会挂起应用程序导致界面失去响应,这种情况应该考虑单独开启一个线程来处理。
    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 CREATOR = new Creator() {
		/**
		 * 该方法用于在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、名为CREATOR的静态常量,该常量的值复制从Parcel数据包中恢复Person对象,因此对该对象定义的createFromPerson()方法用于恢复Person对象

另外我们再定义一个Pet类,方法类似Person类的定义,要有AIDL和类本身两个定义

定义好上述类以后,我们就可以使用AIDL来定义通信接口了,通信接口如下

import test.Person;
import test.Pet;
interface IPet{
	List getPets(in Person owner);
}
从上面代码可以看到, 在AIDL接口中定义方法是,需要制定形参传递的模式,对于java语言来说,一般采用参入参数的方式,因此上面指定为in模式
接下来在Service中的实现就跟一般的AIDL通信一样了

public class MyService extends Service {
	
	Binder myIBinder = new IPer.Stub(){
		@Override
		public List 
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();
			}
		}


你可能感兴趣的:(android开发)