Android调用Web服务

现在大部分应用程序都把业务逻辑处理,数据调用等功能封装成了服务的形式,应用程序只需要调用这些web服务就好了,在这里就不赘述web服务的优点了。本文总结如何在android中调用Web服务,通过传递基类型和复杂类型对比调用.NET平台发布的WCF服务和WebService服务之间的区别。

0 写在前面

以前都是在.NET平台上conding,使用.NET平台发布服务,然后再在.NET的客户端进行调用,非常的方便,最简单的方式就是添加web服务引用,通过添加web服务引用实现像本地调用那样调用web服务,当然我们也可以采用http-post、http-get和基于soap协议的方式去调用服务。

最近在弄andriod的程序,需要调用web服务器上的数据,服务采用C#写的,并部署在iis服务器上。我们可以像.NET那样调用服务那,利用andriod库自带的HttpPost和HttpGet类来调用Web服务。但是wcf服务发布的一些没有添加WebGet或者WebInvoke特性的服务,都只提供基于Soap协议的服务调用方式。虽然soap协议也是基于Http协议,也可以使用HttpPost类来进行调用,但拼凑soap结构体是比较麻烦,好在Ksoap2包提供了调用web服务的方法,而且还比较好的兼容了.NET平台发布的服务。因此本文总结在Andriod中如何使用Ksoap2来调用.NET平台的服务,通过传递基类型和复杂类型对比调用.NET服务发布的WCF服务和WebService服务之间的区别。本文的末尾提供Ksoap2包的下载。

1 WCF服务

我们在服务中提供两个方法,一个计算整数加法,另一个接受People对象并且返回People信息(string)。

1.1 People的数据契约

[DataContract]
public class People
{
  [DataMember]
  public int Age;
  [DataMember]
  public string Name;
}

2.2 WCF服务契约

[ServiceContract(Name = "JuameService", Namespace = "http://www.juame.edu")]
public interface ITest
{
    [OperationContract]
    int Add(int op1, int op2);

    [OperationContract]
    string PostPeopleInfo(People people);

}

上面的服务契约设置了Namespace特性,该特性重要。在后面的wb服务调用中需要用到。

2.3 WCF服务实现

public class TestService : ITest
{
    public int Add(int op1, int op2)
    {
        return op1 + op2;
    }

    public string PostPeopleInfo(People people)
    {
        return "姓名:"+people.Name+"/"+"年龄"+people.Age;
    }
}

我们需要把服务部署到IIS中去,因此需要添加一个svc文件,把服务实现的代码写在svc文件中,发布后,服务调用的地址就是svc文件的地址。

2.4 服务配置

<configuration>

  <system.web>
    <compilation debug="true" targetFramework="4.0" />
  </system.web>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <!-- 为避免泄漏元数据信息,请在部署前将以下值设置为 false -->
          <serviceMetadata httpGetEnabled="true"/>
          <!-- 要接收故障异常详细信息以进行调试,请将以下值设置为 true。在部署前设置为 false 以避免泄漏异常信息 -->
          <serviceDebug includeExceptionDetailInFaults="false"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <services>
       <service name="Juame.Service.TestService">
        <endpoint address="" 
                  binding="basicHttpBinding"
                  contract ="Juame.Service.ITest">          
        </endpoint>
      </service>
    </services>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
  </system.serviceModel>
 
 <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
    <!--
        若要在调试过程中浏览 Web 应用程序根目录,请将下面的值设置为 True。
        在部署之前将该值设置为 False 可避免泄露 Web 应用程序文件夹信息。
      -->
    <directoryBrowse enabled="true"/>
  </system.webServer>

</configuration>

主要是配置好Service节点和serviceBehaviors就行,服务采用BasicHttpBinding类型。在这里多提一点。BasicHttpBinding是针对于Soap Web Service协议,而webHttpBinding支持web service协议,因此在wcf服务上加上WebGet或WebInvoke特性的必须要使用webHttpBinding类型。

在iis中发布web服务非常简单和部署asp.net网站一样,服务发布成功之后,能访问到svc的地址。

2016_10_6798d44e-3b10-4b21-9b40-66d024199b2f

我们提供的服务,一个是传递基类型(string,int,float等),另外一个是传递对象(复杂类型)。

2 Android调用WCF服务

2.1 android布局

界面布局非常简单,两个Button,一个TextView,按钮分别用来调用两个服务,而TextView用来显示服务调用的结果。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.soapprousage.MainActivity" >

	<Button android:id="@+id/btn_jlx"
	    android:layout_width="match_parent"
	    android:layout_height="wrap_content"
	    android:text="基类型调用"/>
    <Button android:id="@+id/btn_obj"
	    android:layout_width="match_parent"
	    android:layout_height="wrap_content"
	    android:text="对象调用"/>
	<TextView android:id="@+id/lbl_result"
	    android:layout_width="match_parent"
	    android:layout_height="match_parent"
	   android:textAlignment="viewStart"/>"
</LinearLayout>

2.2 利用Ksoap2调用wcf服务

首先把下载下来的Jar格式的Ksoap包复制到libs(自己创建)文件夹下。

声明服务调用需要的地址和方法

private String nameSpace="http://www.juame.edu";//和wcf服务契约特性的Namespace是一样的
private String url="http://172.21.212.54:8888/TestService.svc";//svc服务地址
private String soapAction="http://www.juame.edu/JuameService/Add";//操作地址
private String methodName="Add";//方法名称

上面声明的服务地址、命名空间、操作地址和方法名称都可以从服务的wsdl文档中查看,

2016_10_bab1b9f2-cb82-4092-a3e3-e3fc133b4186

下面利用ksoap2对服务进行调用的代码如下。

protected SoapObject getSoapResult(int op1,int op2){
	SoapObject outObject=new SoapObject(nameSpace,methodName);
	//添加输出参数
	outObject.addProperty("op1", op1);
	outObject.addProperty("op2",op2);
	
	SoapSerializationEnvelope serializationEnvelope=new SoapSerializationEnvelope(SoapEnvelope.VER11);//设置soap版本
	
	serializationEnvelope.bodyOut=outObject;
	serializationEnvelope.dotNet=true;//调用.NET的服务
	
	HttpTransportSE transportSE=new HttpTransportSE(url);
	transportSE.debug=true;//采用调试

	try{
		transportSE.call(soapAction, serializationEnvelope);//调用服务
		SoapObject result=(SoapObject)serializationEnvelope.bodyIn;//获取调用结果
        Log.v("happy1", "服务调用成功");
      //把结果封送到消息中去,让ui线程显示
		Bundle bundle=new Bundle();
		bundle.putString("result", result.getProperty(0).toString());//获取结果中的值
		Message message=new Message();
		message.setData(bundle);
		message.what=12;
		hander.sendMessage(message);
		return result;
		
	}catch(IOException ex){
		Log.v("sad", "IO异常");
		ex.printStackTrace();
	}catch(XmlPullParserException ex){
		Log.v("sad", "xml解析异常");
		ex.printStackTrace();
		
	}catch(Exception ex){
		Log.v("sad", "服务调用异常异常");
	}	
	return null;
}

按钮事件代码,采用多线程。在android3.0后,有关网络资源的调用代码都不能直接在主UI线程中调用,否则会出现android.os.NetworkOnMainThreadException异常。关于android中的多线程机制有时间再进行总结。

//绑定按钮事件
btnJlx.setOnClickListener(new OnClickListener() {
	
	@Override
	public void onClick(View arg0) {
		// TODO Auto-generated method stub
		Thread thread=new Thread(getSoapRequest);
		thread.start();
	}
});
//线程
Runnable getSoapRequest=new Runnable() {
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		getSoapResult(10, 20);
	}
};
//消息处理
Handler hander = new Handler() {
	@Override
	public void handleMessage(Message msg) {
		if(msg.what=12){
			lblResult.append(msg.getData().getString("result")+"\r\n");
        }
	}
};

到目前为止,我们已经调用了wcf服务第一个服务,就说明传递基类型是没有问题。但是很遗憾的是,对于传递复杂类型和数组集合参数进行调用,在服务那边总是提示无法对传递进来的数据进行反序列化的错误(希望高手指点)。还好我们可以把所以的服务类型都转为json数据,通过json数据进行传递调用,就可以解决复杂类型传递的问题。

对于有强迫症的我来说,不甘心,因为在网上看了许多的教程,利用Ksoap2是可以直接传递复杂类型的来调用.NET平台的服务的。不过网上大部分教程调用的都是传统的webservice服务(asmx文件),于是我就在wcf服务项目中新建一个传统的asmx文件,提供的服务与wcf服务一样的。结果发现,果然能够利用ksoap2传递一个复杂类型来调用服务。下一节总结利用ksoap2传递复杂对象来调用传统的webservice服务。

3 传统的WebService服务

为了和wcf服务进行对比,webservice提供的服务和wcf一致,代码如下:

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
// 若要允许使用 ASP.NET AJAX 从脚本中调用此 Web 服务,请取消注释以下行。 
// [System.Web.Script.Services.ScriptService]
public class WebService : System.Web.Services.WebService
{
    [WebMethod]
    public int Add(int op1, int op2)
    {
        return op1 + op2;
    }
    [WebMethod]
    public string PostPeopleInfo(People people)
    {
        return "姓名:" + people.Name + "/" + "年龄:" + people.Age; 
    }
}

其中WebService特性中的Namespace属性和wcf的Namespace的作用一样的。

同样的也在iis中进行发布。发布成功之后,能够访问到asmx文件。

4 Android调用WebService服务

不管是调用WCF的服务还是WebService的服务,传递基类型去调用,代码都是一样的,且能够正确的调用。下面利用复杂的People类型来调用WebService的服务。

我们需要传递复杂类型,首先我们要在android中建立一个复杂类型,并且复杂类型包含字段名称和个数一定要与服务上的复杂类型保持一致,对于服务的复杂类型具有哪些字段,我们可以通过查看服务调用的示例得知。如下图所示。

2016_10_485cf694-7bf4-409c-9074-a849cc9cb329

根据上面复杂类型的字段说明,我们在android中建立复杂类型(类型名可以随意),包含两个字段且字段名称必须是Age和Name,数据类型也要一致,上面的这个people代表该复杂类型形参名为people(服务调用的时候必须要保持一样)。需要注意的是,这个复杂类型必须要继承KvmSerializable,这样ksoap2进行服务调用的时候,能够把people对象序列化为服务端能够接受的格式。代码如下:

public class People implements KvmSerializable {  	  
    public int Age;  
    public String Name;  
    @Override  
    public Object getProperty(int arg0) {           
        switch (arg0){  
            case 0:  
                return Age;  
            case 1:  
                return Name;  
            default:  
                return null;  
        }  
    }  
   
    @Override  
    public int getPropertyCount() {  
        return 2;  
    }  
  
    @Override  
    public void getPropertyInfo(int arg0, Hashtable arg1, PropertyInfo arg2) {           
        switch (arg0){  
            case 0:{  
                arg2.type = PropertyInfo.INTEGER_CLASS;  
                arg2.name = "Age";  
                break;
            }  
            case 1:{  
                arg2.type = PropertyInfo.STRING_CLASS;  
                arg2.name = "Name";  
                break;
            }  
        }          
    }  
    
    @Override  
    public void setProperty(int arg0, Object arg1) {           
        switch (arg0){  
            case 0:{  
                Age = Integer.parseInt(arg1.toString()) ;  
                break;
            }  
            case 1:{  
                Name = arg1.toString();  
                break;
            }  
        }   
    }  
}   

下面是传递复杂对象调用web服务,其中服务地址、操作地址、方法名以及命名空间和前面一样,只需要在服务说明wsdl文档中找operation name节点和operation soapAction节点的值即可,其他地方也类似,只是在封装soapobject的时候有一些区别,代码如下:

//地址声明
private String nameSpace="http://tempuri.org/";
private String url="http://172.21.212.54:8888/WebService.asmx";
private String soapAction="http://tempuri.org/PostPeopleInfo";
private String methodName="PostPeopleInfo";

//服务调用
protected SoapObject getSoapResult() {
	SoapObject outObject = new SoapObject(nameSpace, methodName);

	People people = new People();
	// 设置字段值
	people.setProperty(0, 23);
	people.setProperty(1, "Juame");

	// 设置SoapObject对象
	outObject.addProperty("people", people);
	//也可以这样设置SoapObject
    /*PropertyInfo peoInfo = new PropertyInfo();    
    peoInfo.setName("people");    
    peoInfo.setValue(people);
    peoInfo.setType(People.class); 
    outObject.addProperty(peoInfo);*/
  
	SoapSerializationEnvelope serializationEnvelope = new SoapSerializationEnvelope(
			SoapEnvelope.VER11);//设置soap版本
	// 这一步添加映射非常关键
	// 第一个参数为命名空间,第二参数为服务器中复杂类型的名称,第三参数是安卓的复杂类型
	serializationEnvelope.addMapping(nameSpace, "People", People.class);

	serializationEnvelope.bodyOut = outObject;
	serializationEnvelope.dotNet = true;// 调用.NET的服务

	HttpTransportSE transportSE = new HttpTransportSE(url);
	transportSE.debug = true;// 采用调试

	try {
		transportSE.call(soapAction, serializationEnvelope);// 调用服务
		Log.v("happy1", "服务调用成功");
		SoapObject result = (SoapObject) serializationEnvelope.bodyIn;
       //把结果封送到消息中去,让ui线程显示
		Bundle bundle = new Bundle();
		bundle.putString("result",result.getProperty(0).toString());
		Message message = new Message();
		message.setData(bundle);
		message.what = 11;
		hander.sendMessage(message);
		return result;

	} catch (IOException ex) {
		Log.v("sad", "IO异常");
		ex.printStackTrace();
	} catch (XmlPullParserException ex) {
		Log.v("sad", "xml解析异常");
		ex.printStackTrace();

	} catch (Exception ex) {
		Log.v("sad", "服务调用异常异常");
	}

	return null;
}
//按钮事件
btnObj.setOnClickListener(new OnClickListener() {
	@Override
	public void onClick(View arg0) {
		// TODO Auto-generated method stub		 
	    Thread thread = new Thread(getSoapRequest);
	    thread.start();
	}
});
//线程
Runnable getSoapRequest=new Runnable() {
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		getSoapResult();
	}
};
//消息处理
Handler hander = new Handler() {
	@Override
	public void handleMessage(Message msg) {
		if(msg.what=11){
			lblResult.append(msg.getData().getString("result")+"\r\n");
        }
	}
};

上面的代码就能够传递复杂类型去调用WebService的服务,返回结果如下:

姓名:Juame/年龄:23

5 简述Wcf与WebServic的区别

WebService是一个行业标准,也是Web Service的规范,既不是框架,也不是技术,它使用xml扩展标记语言来表示数据,这正是WebService能够跨语言和平台的关键,而微软的Web服务实现称为ASP.NET Web Service.它使用Soap简单对象访问协议来实现分布式环境里应用程序之间的数据交互。

WCF 是一个分布式应用的开发框架,属于特定的技术,或者平台。既不是标准也不是规范。在一定程度上就是WebService,不得不说WCF确实非常方便,提供非常多且好用的特性,可以用来创建各种服务,而且自定义性也高,以后项目的服务搭建都会基于WCF来实现。

6 小结

本文总结了如何使用android调用web服务。在传递复杂类型调用服务的时候纠结的了半天,最后实现了传递复杂类型调用WebService服务,但没有实现对WCF服务的调用,而传递基类型调用服务,两者都可以。在第5小节中还简述了wcf和webservice之间的区别,其实在项目大都是采用wcf框架来发布自己的服务。下面会继续总结如何用javascript来调用wcf发布的服务。

另Ksaop2下载链接:http://download.csdn.net/download/mingge38/9666650

posted @ 2016-10-28 13:49  msay  阅读(3661)  评论(6编辑  收藏  举报