Android调用WebService系列之KSoap2对象解析

在在网络APP中有2个非常重要的节

  1. 客户端请求服务端接口的能力

  2. 客户端,服务端的对接

而我的Android调用WebService系列共四篇这是最后一篇,所要讲述的只仅仅是Android调用WebService这一种比较少用且不推荐用,但是在一些特定的场合下不得不用的调用方式。 

Android调用WebService系列之封装能力,Android调用WebService系列之请求调用是讲的请求服务端的能力主要是介绍APP如何拥有,或者说更好的更方便的拥有这种能力

而Android调用WebService系列之对象构建传递,和本文章《Android调用WebService系列之KSoap2对象解析》就是讲的客户端,服务端的对接,也是我们俗称的握手,但又不完全是对接,只能说是部分帮助对接完成的工具。

(当你的服务端全部由自己开发的时候,你完全可以对接不搞那么复杂的对象,仅用String自定义GSON,JSON握手即可。)

接下来我要讲述的是KSoap2传递对象回来的时候我们该如何处理!

首先我们要了解一下服务端传递回的流被KSoap2解析并保存了什么。

    /**
     * The body object received with this envelope. Will be an KDom Node for
     * literal encoding. For SOAP Serialization, please refer to
     * SoapSerializationEnvelope.
     */
    public Object bodyIn;
    /**
     * The body object to be sent with this envelope. Must be a KDom Node
     * modelling the remote call including all parameters for literal encoding.
     * For SOAP Serialization, please refer to SoapSerializationEnvelope
     */
    public Object bodyOut;


当我们call之后,我们只需要获取bodyIn或者调用getResponse()就可以获得返回值。

我们看看getResponse方法的源代码


   
    /**
     * Response from the soap call. Pulls the object from the wrapper object and
     * returns it.
     * 
     * @since 2.0.3
     * @return response from the soap call.
     * @throws SoapFault
     */
    public Object getResponse() throws SoapFault {
        if (bodyIn instanceof SoapFault) {
            throw (SoapFault) bodyIn;
        }
        KvmSerializable ks = (KvmSerializable) bodyIn;
        return ks.getPropertyCount() == 0 ? null : ks.getProperty(0);
    }

返回的是将bodyIn转换成KvmSerializable获取对象的第一个属性值传回来。这时候我们可能会想,我们可不可以定义一个类型然后直接让bodyIn直接返回该类型呢?很抱歉KSoap2并没提供这样的方式。

那这个返回的对象是什么呢?

我们通过调试跟踪发现,这个值其实要么是SoapFault要么是SoapObject是实现了KvmSerializable接口的。

那么我们能不能直接强制转换成我们要的类呢,很遗憾的发现,我们不能直接这样强转。

我们看看SoapObject的定义是怎么样的。

/* Copyright (c) 2003,2004, Stefan Haustein, Oberhausen, Rhld., Germany
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The  above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE. 
 *
 * Contributor(s): John D. Beatty, Dave Dash, Andre Gerard, F. Hunter,
 * Renaud Tognelli
 *
 * */

package org.ksoap2.serialization;

import java.util.*;

/**
 * A simple dynamic object that can be used to build soap calls without
 * implementing KvmSerializable
 * 
 * Essentially, this is what goes inside the body of a soap envelope - it is the
 * direct subelement of the body and all further subelements
 * 
 * Instead of this this class, custom classes can be used if they implement the
 * KvmSerializable interface.
 */

public class SoapObject implements KvmSerializable {

    String namespace;
    String name;
    Vector info = new Vector();
    Vector data = new Vector();

    /**
     * Creates a new <code>SoapObject</code> instance.
     * 
     * @param namespace
     *            the namespace for the soap object
     * @param name
     *            the name of the soap object
     */

    public SoapObject(String namespace, String name) {
        this.namespace = namespace;
        this.name = name;
    }

    public boolean equals(Object o) {
        if (!(o instanceof SoapObject))
            return false;

        SoapObject so = (SoapObject) o;
        int cnt = data.size();

        if (cnt != so.data.size())
            return false;

        try {
            for (int i = 0; i < cnt; i++)
                if (!data.elementAt(i).equals(so.getProperty(((PropertyInfo) info.elementAt(i)).name)))
                    return false;
        } catch (Exception e) {
            return false;
        }
        return true;
    }

    public String getName() {
        return name;
    }

    public String getNamespace() {
        return namespace;
    }

    /**
     * Returns a specific property at a certain index.
     * 
     * @param index
     *            the index of the desired property
     * @return the desired property
     */
    public Object getProperty(int index) {
        return data.elementAt(index);
    }

    public Object getProperty(String name) {
        for (int i = 0; i < data.size(); i++) {
            if (name.equals(((PropertyInfo) info.elementAt(i)).name))
                return data.elementAt(i);
        }
        throw new RuntimeException("illegal property: " + name);
    }

    /**
     * Returns the number of properties
     * 
     * @return the number of properties
     */
    public int getPropertyCount() {
        return data.size();
    }

    /**
     * Places PropertyInfo of desired property into a designated PropertyInfo
     * object
     * 
     * @param index
     *            index of desired property
     * @param propertyInfo
     *            designated retainer of desired property
     */
    public void getPropertyInfo(int index, Hashtable properties, PropertyInfo propertyInfo) {
        PropertyInfo p = (PropertyInfo) info.elementAt(index);
        propertyInfo.name = p.name;
        propertyInfo.namespace = p.namespace;
        propertyInfo.flags = p.flags;
        propertyInfo.type = p.type;
        propertyInfo.elementType = p.elementType;
    }

    /**
     * Creates a new SoapObject based on this, allows usage of SoapObjects as
     * templates. One application is to set the expected return type of a soap
     * call if the server does not send explicit type information.
     * 
     * @return a copy of this.
     */
    public SoapObject newInstance() {
        SoapObject o = new SoapObject(namespace, name);
        for (int i = 0; i < data.size(); i++) {
            PropertyInfo propertyInfo = (PropertyInfo) info.elementAt(i);
            o.addProperty(propertyInfo, data.elementAt(i));
        }
        return o;
    }

    /**
     * Sets a specified property to a certain value.
     * 
     * @param index
     *            the index of the specified property
     * @param value
     *            the new value of the property
     */
    public void setProperty(int index, Object value) {
        data.setElementAt(value, index);
    }

    /**
     * Adds a property (parameter) to the object. This is essentially a sub
     * element.
     * 
     * @param name
     *            The name of the property
     * @param value
     *            the value of the property
     */
    public SoapObject addProperty(String name, Object value) {
        PropertyInfo propertyInfo = new PropertyInfo();
        propertyInfo.name = name;
        propertyInfo.type = value == null ? PropertyInfo.OBJECT_CLASS : value.getClass();
        return addProperty(propertyInfo, value);
    }

    /**
     * Adds a property (parameter) to the object. This is essentially a sub
     * element.
     * 
     * @param propertyInfo
     *            designated retainer of desired property
     * @param value
     *            the value of the property
     */
    public SoapObject addProperty(PropertyInfo propertyInfo, Object value) {
        info.addElement(propertyInfo);
        data.addElement(value);
        return this;
    }

    public String toString() {
        StringBuffer buf = new StringBuffer("" + name + "{");
        for (int i = 0; i < getPropertyCount(); i++) {
            buf.append("" + ((PropertyInfo) info.elementAt(i)).name + "=" + getProperty(i) + "; ");
        }
        buf.append("}");
        return buf.toString();
    }

}

看到这,我们发现他的数据用2个Vector存储info和data。

那么我们怎么取?

网上有很多文章基本上都是直接通过循环取下标值的方式然后依次进行

我们跟踪发现,KSoap2封装后传回的SoapObject是一次嵌套排序所有借点内容。意味着如果你类中有个列表,那么也是依次排列的。而且最糟糕的是你会发现所有name都是anyType。这样意味着我们也将不能直接通过循环下标赋值的方式将他序列化到类。而且就算可以我们每一个地方要写的代码量,做的重复枯燥的工作就要增加很多。

显然这样并不是我们想要的。我们需要的是一个直接传递回来SoapObject然后直接将SoapObject转换成我们所需要的类。

于是我写了下面这个类。

原理很简单,就是通过获取SoapObject字符串,将SoapObject的字符串改变成GSON字符串由Gson转换成我们要的类!

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.ksoap2.serialization.SoapObject;
import org.ksoap2.serialization.SoapPrimitive;

import com.google.gson.Gson;
import android.util.Log;

/**
 * 对象传输基础类
 * 
 * @author 刘亚林
 * @e-mail [email protected]
 * 
 */
public class SoapObjectToClass {
	private static final String CLASSTAG = SoapObjectToClass.class.getName();

	/**
	 * 首字母大写
	 * 
	 * @param str
	 * @return
	 */
	public static String upCase(String str) {
		return String.valueOf(str.charAt(0)).toUpperCase()
				.concat(str.substring(1));
	}

	/**
	 * 片段是否是列表
	 * 
	 * @param cls
	 * @return
	 */
	public static boolean isList(Class cls) {
		if (cls.isAssignableFrom(List.class)
				|| cls.isAssignableFrom(ArrayList.class)) {
			return true;
		}
		return false;
	}

	/**
	 * soapobject转类
	 * 
	 * @param clazz
	 * @param soapObject
	 * @return
	 */
	public static <T extends BaseKvmSerializable> T soapToClass(Class<T> clazz,
			SoapObject soapObject) {
		String result = soapToGson(clazz, soapObject);
		System.out.println("result:" + result);
		T t = getFromGson(result, clazz);
		return t;
	}

	private static final Gson GSON_CONVERTER = new Gson();

	/**
	 * 通过GSON
	 * 
	 * @param value
	 * @param cls
	 * @return
	 */
	public static <T> T getFromGson(String value, Class<T> cls) {

		if (value != null && !value.equals("")) {
			try {
				return GSON_CONVERTER.fromJson(value, cls);
			} catch (Exception e) {
				Log.w(CLASSTAG, "JsonSyntaxException " + e.getCause());
			}
		}
		return null;
	}

	/**
	 * 如果里面=的数量大于1则认定为复杂类型
	 * 
	 * @param soStr
	 * @return
	 */
	public static boolean isDiffType(String soStr) {
		String rlstr = soStr.replace(":", "");
		if (soStr.length() - rlstr.length() > 0) {
			return true;
		}
		return false;
	}

	/**
	 * 将soapObject类型向json类型靠
	 * 
	 * @param str
	 * @return
	 */
	public static String replaceStr(String str) {
		return str.replace("=", ":").replace("; }", "}").replace(";", ",")
				.replace("anyType{}", "\"\"").replace("anyType", "");
	}

	/**
	 * 获取已经读取过了的字符起始值
	 * 
	 * @param str
	 * @param value
	 * @return
	 */
	public static int getSubLen(String str, String value) {
		int index = str.indexOf(value);
		return index + value.length();
	}

	/**
	 * 通过复杂对比的形式获取到name
	 * 
	 * @param str
	 * @param value
	 * @return
	 */
	public static String getNameByValue(String str, String value) {
		int index = str.indexOf(value);
		String sbStr = str.substring(0, index);
		int lastdotindex = sbStr.lastIndexOf(",");
		int lastkindex = sbStr.lastIndexOf("{");
		int endindex = sbStr.lastIndexOf(":");
		int startindex = lastdotindex > lastkindex ? lastdotindex : lastkindex;
		String result = sbStr.substring(startindex + 1, endindex);
		return result.trim();
	}

	/**
	 * soap对象转jsonString
	 * 
	 * @param clazz
	 * @param soapObject
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public static <T> String soapToGson(Class<T> clazz, SoapObject soapObject) {
		String result = soapObject.toString();
		result = replaceStr(result);
		int sublen = 0;
		int soapCount = soapObject.getPropertyCount();
		HashMap<String, List<String>> rlmap = new HashMap<String, List<String>>();
		for (int i = 0; i < soapCount; i++) {
			Object childObject = soapObject.getProperty(i);
			if (childObject.getClass().isAssignableFrom(SoapPrimitive.class)) {
				continue;
			} else {
				SoapObject so = (SoapObject) childObject;
				String soStr = replaceStr(so.toString());
				if (isDiffType(soStr)) {
					String name = getNameByValue(result.substring(sublen),
							soStr);
					sublen = getSubLen(result, soStr);
					try {
						Field f = clazz.getDeclaredField(name);
						if (f != null) {
							if (isList(f.getType())) {
								Type fc = f.getGenericType();
								if (fc == null) {
									continue;
								}
								if (fc instanceof ParameterizedType) {
									ParameterizedType pt = (ParameterizedType) fc;
									Class genericClazz = (Class) pt
											.getActualTypeArguments()[0];
									soStr = soapToGson(genericClazz, so);
								}
								List<String> list;
								if (rlmap.containsKey(name)) {
									list = rlmap.get(name);
								} else {
									list = new ArrayList<String>();
								}
								list.add(soStr);
								rlmap.put(name, list);
							}
						}
					} catch (NoSuchFieldException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}
		}
		Iterator iter = rlmap.entrySet().iterator();
		while (iter.hasNext()) {
			Map.Entry entry = (Map.Entry) iter.next();
			String key = (String) entry.getKey();
			List<String> val = (List<String>) entry.getValue();
			String listData = "[";
			String rplstr = "";
			int count = val.size();
			for (int i = 0; i < count; i++) {
				if (i == count - 1) {
					rplstr += key + ":" + val.get(i);
				} else {
					rplstr += key + ":" + val.get(i) + ",";
				}
			}
			result = result
					.trim()
					.replace(" ", "")
					.replace(rplstr.trim().replace(" ", ""),
							key + ":" + val.toString());

		}
		return result;
	}

	// private static boolean hasMethod(String methodName, Method[] method) {
	// for (Method m : method) {
	// if (methodName.equals(m.getName())) {
	// return true;
	// }
	// }
	// return false;
	// }
}

注意:BaseKvmSerializable缺少引用,这个类就是Android调用WebService系列之对象构建传递里面所提到的对象构建传递类。

有了这个类我们只需调用soapToClass就可以获取到我们想要的类了。当然你也可以调用soapToGson然后来获取gson字符串!

到此,我的Android调用WebService系列的四篇文章就结束了。

接下来我将慢慢的整理一套比较基础又带一点特殊的东西,敬请期待吧。

你可能感兴趣的:(android,服务端,客户端,KvmSerializable)