Service之《远程服务》

前面我们介绍Service的本地服务,今天我们来看看Service的远程服务。首先什么是Service的远程服务呢?由于在android系统中应用程序不能共享内存,并且每个应用程序都运行在不同的进程当中,两个不同的应用程序之间进行通信就稍微会麻烦一点,而Service的远程服务正好可以解决跨进程通信的问题。

Service的的远程服务其实是通过Binder来完成进程间的通信的(详细的原理以后再介绍)。在通信的过程中Binder服务器端和客户端需要解决两个问题:

1.  客户端如何获得服务器端的Binder对象

2.  客户端和服务端必须事先约好参数的顺序

要解决第一个问题,我们可以使用bindService去得到一个远程服务的Binder引用。要解决第二个问题,我们就需要用到AIDLAidl实际上是一种接口定义语言,全程为Android Interface Definition language.我们必须使用aidl语言去定义我们的接口,然后ADT会根据我们定义的aidl文件自动生成一个java文件,并且统一了参数的存入读取顺序。也就是说aidl只是解决了客户端的参数和服务器端参数的一致性问题,底层的通信还是由Binder来实现的。

有了这些理论基础,我们就具体来实现一下,这样能更好的理解Service的远程通信的流程。

我们今天要实现的功能是这样的:

服务器端:提供一个Service,这个Service可以播放音乐,并且放回返回一个字符串。

客户端:提供三个按钮,第一个开启远程通讯,第二个播放,第三个停止。然后通过调用服务器端的方法来播放音乐。

以下为实现效果图:

                服务器端

 

 

                 客户端

我们首先从服务器端开始编写。具体代码我会提供下载地址,在这里我只给出关键的代码。首先我们必须得定义好需要暴露给客户端的接口RemoteService.aidl:

package org.viking.service;
interface RemoteService
{
	String remoteCall(String action);
}


 

在这里我的命名不规范,应该是IRemoteService.aidl我为了省事后来也没改过来。Aidl的语法基本类似于javapackage指定所在的报名,如果该文件需要引用其他的java类,则可以使用import关键字,但需要注意的是,这里面的类型智能是java原始类型或者Binder引用,还有实现了Parcelable的对象。

  建立好这个文件以后,eclipse会在gen目录下为你建立一个RemoteServicejava类。类中的代码是自动生成的,一般不建议去改动。里面的代码我留到后面讲。

  接着在MediaService类中去实现这个remoteCall方法。

Binder binder=new RemoteService.Stub()
	{
		
		@Override
		public String remoteCall(String action) throws RemoteException
		{
			String result="";
			if(action.equals("play"))
			{
				playMusic();//开始播放音乐
				result="开始播放音乐!";
			}
			else
			{
				if(action.equals("stop"))
				{
					stopMusic();//停止播放音乐
					result="停止播放音乐!";
				}
			}
			return result;
		}
	};


 

实现完以后,我们需要到AndroidMenifast当中去注册这个MediaService

 

<service android:name=".service.MediaService" >
   		     <intent-filter >
   		         <!-- 在这里把android:name写成RemoteService是因为在客户端的可以直接用
   		         RemoteService.class.getName()方法启动,而不必暴露MediaService
   		          -->
                <action android:name="org.viking.service.RemoteService" ></action>
            </intent-filter>
   		</service>


 

在这值得注意的是,我们应该把action中的值设置为RemoteService的完整路径名,但这是不是必须的,你可以设置为任何的名称,但是在实际开发中,我们有时候并不知道,这里的值是什么,所以我们需要一个规范,这个规范就是把这个字设置为暴露的远程接口的完整路径名。

到这里服务器端的代码就讲完了,我们来看客户端,客户端的代码更简单。主界面添加三个按钮,然后为这三个按钮添加监听事件。

public void onClick(View v)
	{
		switch (v.getId())
		{
			case R.id.button1://播放音乐按钮
				try
				{
					String reStr=remoteService.remoteCall("play");
					result.setText(reStr);

				}
				catch (RemoteException e)
				{
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
				break;
			case R.id.button2://停止播放按钮
				try
				{
					String reStr=remoteService.remoteCall("stop");
					result.setText(reStr);
					
				}
				catch (RemoteException e)
				{
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
				break;
			case R.id.button3://开启远程通信按钮
				//RemoteService.class.getName打印出来的时RemoteService的包名加上类名
				Intent intent =new Intent(RemoteService.class.getName());
				bindService(intent, conn, Context.BIND_AUTO_CREATE);
				break;
			default:
				break;
		}
	}


 

按照流程,我们应该先开启远程的通信,当我们点击远程通讯按钮时,我们会建立一个Intent对象,并且将RemoteService.class.getName()的值传入进去。这个方法的返回值就是咱们在服务器端设置的action中的org.viking.service.RemoteService

接下来我们用bindServicer去绑定一个Service,绑定成功后会执行下面的代码。

private ServiceConnection conn=new ServiceConnection()
	{
		
		@Override
		public void onServiceDisconnected(ComponentName name)
		{
			// TODO Auto-generated method stub
			
		}
		
		@Override
		public void onServiceConnected(ComponentName name, IBinder service)
		{
			remoteService=RemoteService.Stub.asInterface(service);
			if(remoteService!=null)
			{
				System.out.println("绑定成功!");
			}
		}
	};


 

通过asInterface方法,我们得到了一个RemoteService对象,然后通过这个对象,咱们就可以调用remoteCall方法,传入不同的参数,调用服务器端的播放音乐或者停止音乐的功能。最后我们把返回的值先生在客户端的TextView上面。

到目前为止,整个项目就已经结束了,但是我想具体的讲讲,远程通讯的具体流程。

首先,我们在客户端点击开启远程通信按钮,会执行bindService方法,绑定成功后会回调onServiceConnected方法,在该方法中我们执行了asInterface方法,这个方法的作用是将一个Ibinder转化为RemoteService。我们具体看看这个方法的代码实现。

我们进入客户端gen目录下的RemoteService类中,找到asInterface方法。

public static org.viking.service.RemoteService asInterface(
				android.os.IBinder obj)
		{
			if ((obj == null))
			{
				return null;
			}
			android.os.IInterface iin = (android.os.IInterface) obj
					.queryLocalInterface(DESCRIPTOR);
			if (((iin != null) && (iin instanceof org.viking.service.RemoteService)))
			{
				return ((org.viking.service.RemoteService) iin);
			}
			return new org.viking.service.RemoteService.Stub.Proxy(obj);
		}


 

在这个方法中最关键的是obj.queryLocalInterface(DESCRIPTOR);这个方法会根据输入的字符串去尝试找回Binder对象的一个接口的本地实现,如果本地找不到,需要new一个Proxy来返回。什么个意思呢?通俗来说,就是判断这个binder是本地的还是远程的,如果是原创的话,那么返回的RemoteService将会是Proxy这个类,这个类实现了RemoteService这个接口。而DESCRIPTOR的值正好是org.viking.service.RemoteService

因为我们是在客户端,所以会先new一个Proxy类然后返回。接着我们会点击播放按钮,这个播放按钮,播放按钮会调用remoteCall(“play”)方法。由于我们返回的是Proxy类,所以这个remoteCall方法也是Proxy类中的,我们来看看Proxy类。

private static class Proxy implements org.viking.service.RemoteService
		{
			private android.os.IBinder mRemote;

			Proxy(android.os.IBinder remote)
			{
				mRemote = remote;
			}

			public android.os.IBinder asBinder()
			{
				return mRemote;
			}

			public java.lang.String getInterfaceDescriptor()
			{
				return DESCRIPTOR;
			}

			public java.lang.String remoteCall(java.lang.String action)
					throws android.os.RemoteException
			{
				android.os.Parcel _data = android.os.Parcel.obtain();
				android.os.Parcel _reply = android.os.Parcel.obtain();
				java.lang.String _result;
				try
				{
					_data.writeInterfaceToken(DESCRIPTOR);
					_data.writeString(action);
					mRemote.transact(Stub.TRANSACTION_remoteCall, _data,
							_reply, 0);
					_reply.readException();
					_result = _reply.readString();
				}
				finally
				{
					_reply.recycle();
					_data.recycle();
				}
				return _result;
			}
		}


 

以上代码我们可以看到Proxy类实现了RemoteService接口,并且实现了remoteCall方法。我们具体看看remoteCall方法。Parcel.obtain()方法申请包裹,这跟现实生活中的邮局一样,用户一般只能使用邮局提供的信封。Data是要传到服务器端的包裹,而replay是服务器端要返回的包裹。writeInterfaceToken()方法为了某种检验,它跟服务器端的enforceInterface()方法对应。writeString()方法用于向包裹中添加一个String变量。接着由远程的binder调用transact()方法,调用该方法后,客户端的线程进入了Binder驱动,Binder驱动会挂起当前的线程,并向远程服务发送一个消息,消息中包含了客户端传进来的包裹。服务器端拿到包裹以后,会对包裹进行拆解,然后执行指定的服务函数,执行完毕后,再把执行结果放入客户端提供的replay包裹中,然后服务器端向binder驱动发送一个notify的消息,从而使得客户端线程从bingder驱动代码区返回到客户端代码区。Transact方法的第一个参数是服务器端与客户端约好的代号,最后一个参数表明了IPC的调用模式,常量0表示双向,也就是服务器端执行完以后会返回一定数据。常量1表示单向,其含义是不返回任何数据。然后从replay包裹中读取一个String对象返回。

我们在理一下客户端的远程binder对象调用transact方法后的流程,当执行完mRemote.transact(Stub.TRANSACTION_remoteCall, _data,  _reply, 0);

以后,会执行服务器端gen目录下RemoteService类中的onTransact()方法。

这样就完成了客户端与服务器端的通信。如下图所示:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值