65.9K
CodeProject 正在变化。 阅读更多。
Home

从 Android 应用程序调用 ASP.NET Web 服务 (ASMX),最简单的方法

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (28投票s)

2011年12月22日

CPOL

5分钟阅读

viewsIcon

474480

如何使用 KSOAP 库以简单高效的方式从 Android 调用 ASP.NET Web 服务。

引言

互联网上已经有许多关于这个主题的教程。但在阅读其中一些教程时,我意识到它们要么对普通人来说太复杂,要么没有得到 proper 的解释。这是 Android 应用的一个重要方面,因为它可以轻松地利用现有的业务逻辑,而无需重写它们。此应用程序的另一个重要方面是,您可以轻松使用全局数据库来存储数据,这些数据可以被不同的手机共享,而不是像通常那样使用 Android 内置的 SQLLite 数据库。

Using the Code

首先,让我们来看一个简单的 Web 服务。

<%@ WebService language="C#" class="MyLocal" %>
using System;
using System.Web.Services;
using System.Xml.Serialization;
public class MyLocal {
    [WebMethod]
    public int Add(int a, int b) {
        return a + b;
    } 
}

创建此 Web 服务时,它会要求提供一个命名空间,这里的命名空间是 www.tempura.org(几乎是默认命名空间,我没有更改!)。

当您在浏览器中打开此 Web 服务时,您会看到一个窗口,如下图所示。

Android-asmx/figure-1.png

我已将其标记以显示如何获取命名空间名称。它还将显示方法列表,您无法测试。那么如何知道参数和返回类型呢?单击该方法,然后查看下图。

Android-asmx/Figure2-soap_structure.png

现在您知道要调用哪个函数、其参数、返回类型和命名空间(当然还有 URL)。

要调用此方法,您需要 ksoap 库,您可以在此处下载。

将下载的 zip 文件复制到任何合适的位置。这是一个外部 jar 文件,您需要将其包含在您的 Android 项目中。

启动一个 Android 项目,然后选择 Android API。在工作区中右键单击项目节点,属性 -> Java 构建路径 -> 库 -> 添加外部 jar。

浏览并选择您的 ksoap jar 文件。

现在我们要做的就是编写一个可以调用 Web 服务并返回结果的方法。请记住,如果您尝试从主活动线程执行任何套接字操作,Android 会抛出异常。因此,最好编写一个单独的类并隔离 soap 相关的函数。

现在让我们来理解这个类的逻辑。

package my.MySOAPCallActivity.namespace; 
import org.ksoap2.SoapEnvelope; 
import org.ksoap2.serialization.PropertyInfo; 
import org.ksoap2.serialization.SoapObject;
import org.ksoap2.serialization.SoapSerializationEnvelope;
import org.ksoap2.transport.HttpTransportSE;
public class CallSoap 
{
public final String SOAP_ACTION = "http://tempuri.org/Add";

public  final String OPERATION_NAME = "Add"; 

public  final String WSDL_TARGET_NAMESPACE = "http://tempuri.org/";

public  final String SOAP_ADDRESS = "http://grasshoppernetwork.com/NewFile.asmx";
public CallSoap() 
{ 
}
public String Call(int a,int b)
{
SoapObject request = new SoapObject(WSDL_TARGET_NAMESPACE,OPERATION_NAME);
PropertyInfo pi=new PropertyInfo();
pi.setName("a");
        pi.setValue(a);
        pi.setType(Integer.class);
        request.addProperty(pi);
        pi=new PropertyInfo();
        pi.setName("b");
        pi.setValue(b);
        pi.setType(Integer.class);
        request.addProperty(pi);

SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(
SoapEnvelope.VER11);
envelope.dotNet = true;

envelope.setOutputSoapObject(request);

HttpTransportSE httpTransport = new HttpTransportSE(SOAP_ADDRESS);
Object response=null;
try
{
httpTransport.call(SOAP_ACTION, envelope);
response = envelope.getResponse();
}
catch (Exception exception)
{
response=exception.toString();
}
return response.toString();
}
}
  • 首先 SOAP_ACTION = 命名空间,如数字 1 + 函数名所示;
  • OPERATION_NAME = Web 方法的名称
  • WSDL_TARGET_NAMESPACE = Web 服务的命名空间
  • SOAP_ADDRESS = Web 服务的绝对 URL

SOAP 基于请求-响应对工作。所以首先您需要构建一个可以调用 Web 服务的请求对象。

SoapObject request = new SoapObject(WSDL_TARGET_NAMESPACE,OPERATION_NAME);

现在,您打算调用的操作或方法有一些参数,您需要将它们附加到请求对象。这是通过 PropertyInfo 实例 pi 完成的。这里需要注意的重要一点是,您在 setName() 方法中使用的名称必须是您在上面数字中看到的属性的确切名称,而在 setType() 中,必须指定变量的数据类型。使用 addProperty(),添加所有参数。

使用 setValue() 方法,为属性设置值。

PropertyInfo pi=new PropertyInfo(); 
pi.setName("a"); 
pi.setValue(a); 
pi.setType(Integer.class); 
request.addProperty(pi);

创建一个序列化的信封,该信封将用于承载 SOAP 主体的参数,并通过 HttpTransportSE 方法调用该方法。

现在,您已经掌握了调用 Web 方法并获取结果的技术。为简单起见,我们创建了一个简单的 Android GUI,其中包含两个 EditText 和一个 Button

请参阅下面的 main.xml 代码。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView  
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="@string/hello"
    />
<EditText 
        android:id="@+id/editText1"
    android:layout_width="230dp"
    android:layout_height="wrap_content" >
    <requestFocus />
</EditText> 
<EditText
    android:id="@+id/editText2"
    android:layout_width="232dp"
    android:layout_height="wrap_content" />
<Button
    android:id="@+id/button1"
    android:layout_width="229dp"
    android:layout_height="wrap_content"
    android:text="@string/btnStr" />
</LinearLayout>

我们希望从 activity 类中的按钮单击事件调用该方法。

请看下面的代码

package my.MySOAPCallActivity.namespace;
import android.app.Activity;
import android.app.AlertDialog;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
public class SimpleAsmxSOAPCallActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        Button b1=(Button)findViewById(R.id.button1);
       final  AlertDialog ad=new AlertDialog.Builder(this).create();
         
        b1.setOnClickListener(new OnClickListener() { 
@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
CallSoap cs=new CallSoap();

try
{
EditText ed1=(EditText)findViewById(R.id.editText1);
EditText ed2=(EditText)findViewById(R.id.editText2);
int a=Integer.parseInt(ed1.getText().toString());
int b=Integer.parseInt(ed2.getText().toString());

ad.setTitle("OUTPUT OF ADD of "+a+" and "+b);

String resp=cs.Call(a, b);
ad.setMessage(resp);
}catch(Exception ex)
{
ad.setTitle("Error!");
ad.setMessage(ex.toString());
}
ad.show(); }
});
    }
}

现在您要做的就是从 EditTexts 获取 a b 的值,并将这些值传递给调用方法。调用方法会返回 Web 方法的结果。

String resp=cs.Call(a, b);
ad.setMessage(resp);

一旦您成功运行并启动了它,很可能会遇到诸如 android.os.NetworkOnMainThreadException 之类的错误。

这是因为 Android 不允许您从主线程运行套接字相关操作,正如我们已经讨论过的。所以您需要运行一个线程或创建一个线程,您可以在其中执行这些操作。您可以轻松地将 CallSoap 类建模为实现 runnable 的类并获取相关内容。但我希望调用部分是一个单独的实体。所以我只是创建了一个单独的线程类来调用 CallSoap 方法。它提供了一个良好的分层实现,以便调用执行 SOAP 相关活动的函数的线程与主线程完全不同。

但现在的问题是,您的活动线程和网络线程处于多线程操作中,并且有可能您的主线程在获得 SOAP 操作结果之前就结束了。所以我用了一种原始的方法来等待结果到达然后使用它。

调用者类。

public class Caller  extends Thread  
{
    public CallSoap cs;
    public int a,b; 

    public void run(){
        try{
            cs=new CallSoap();
            String resp=cs.Call(a, b);
            MySOAPCallActivity.rslt=resp;
        }catch(Exception ex)
        {MySOAPCallActivity.rslt=ex.toString();}    
    }
}

修改活动线程中按钮的 OnClick 方法,该方法通过这个简单的调用者类调用 SOAP。

package my.MySOAPCallActivity.namespace;
import android.app.Activity;
import android.os.Bundle;
import android.app.AlertDialog;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;

public class MySOAPCallActivity extends Activity {

public static String rslt="";    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        Button b1=(Button)findViewById(R.id.button1);
        final  AlertDialog ad=new AlertDialog.Builder(this).create();

        b1.setOnClickListener(new OnClickListener() {
  
            @Override public void onClick(View arg0) {
            // TODO Auto-generated method stub 

            try
            { 
                EditText ed1=(EditText)findViewById(R.id.editText1);
                EditText ed2=(EditText)findViewById(R.id.editText2); 
                int a=Integer.parseInt(ed1.getText().toString());
                int b=Integer.parseInt(ed2.getText().toString());
                rslt="START"; 
                Caller c=new Caller(); c.a=a;
                c.b=b; c.ad=ad;
                c.join(); c.start();
                while(rslt=="START") {
                    try {
                        Thread.sleep(10); 
                    }catch(Exception ex) {
                    }
                }
                ad.setTitle("RESULT OF ADD of "+a+" and "+b);
                ad.setMessage(rslt); 
            }catch(Exception ex) {
                ad.setTitle("Error!"); ad.setMessage(ex.toString());
            }
            ad.show(); 
        } });
    }
}

最后,您会看到如下结果。

Android-asmx/fig_4.png

关注点

虽然返回像 Employee Person 这样的 Complex 类并不像这里解释的技术那么简单。它们需要另一个可序列化的类。但如果您喜欢简单,可以将类的属性值嵌入到单个 string 中,例如“Name#Age#Phone”。

其中“#”是分隔符。请记住,DataReader 是不可序列化的。所以您的 Web 方法必须将 DataReader 转换为 string 格式,在收到结果后,您可以使用简单的 string 拆分方法分离字段。

更新:如何本地运行服务  

与我之前认为的“无法实现”的看法不同,我们的朋友 Motaz 提出了一个很好的技巧和解决方案。将其包含在主文章中,以便任何阅读的人都能快速找到答案。

you just need to set the ip address of the service to 10.0.2.2 and the URL of the service would be :
http://10.0.2.2/service/WSGetCustomerCountryWise.asmx[^]
where 
service is your web service alias name in the IIS server
WSGetCustomerCountryWise is the name of the web service 
© . All rights reserved.