Volley与Ksoap2-Android的完美融合
1. 为什么要做这个项目
Volley是谷歌的一套网络通信库,发布于2013年的Google I/O大会上。它的出现,填补了安卓SDK 在不影响用户体验的情况下进行网络通信的空白。Volley有许多优点,如:
(1). 扩展性强。Volley 中大多是基于接口的设计,可配置性强。
(2). 性能优越
近日,Google+ 团队进行了一系列的性能测试,来评测安卓中可用的各种网络通信请求方式。在RESTful应用的测试中,Volley获得了比其他方法高十倍的分数。这得益于Volley缓存一切的策略和谷歌优异的设计。
(3). 提供简便的图片加载工具。
这里无意对Volley设计和源码进行分析和深究,读者可参阅网上这方面的博客。
Volley的设计目标就是非常适合去进行数据量不大,但通信频繁的网络操作,而对于大数据量的网络操作,比如说下载文件等,Volley的表现就会非常糟糕。
大部分的WebService调用都是这种数据量不大,但通信频繁的网络操作,但遗憾的是Volley不支持WebService调用,网上的Volley自定义Request的例子都是XMLRequest和GsonRequest等。
实践中在Android下调用WebService主要使用的是Ksoap2-Android JAR包。
Ksoap2-Android JAR包有的直接使用HttpURLConnection,有的使用OKHttp框架,直接使用HttpURLConnection显然失去了网络异步框架带来的好处,而使用OKHttp框架就会在一个应用中有两个框架,这显然对资源和性能的优化都不利。
能不能让Volley也支持WebService,从而充分使用Volley。当然也可以自己写一套序列化和反序列化SOAP的代码加到Volley中,但工作量比较大,笔者釆用的方法是把Volley和Ksoap2-Android融合在一起,对Volley和Ksoap2-Android都做最小的改动,利用Ksoap2-Android的成熟代码。
闲话不多说,开始干活。
2. 第一步,搞一个用于测试的APK
写一个用Ksoap2-Android JAR包来访问WebService的测试APK很简单,但笔者还是从网上找了一个,省了一些时间。
找到的例子在http://blog.youkuaiyun.com/lyq8479/article/details/6428288/下。
现在网上用Ksoap2-Android访问WebService的例子都使用的是网址http://www.webxml.com.cn/zh_cn/index.aspx下的WebService。我选取的是国内手机号码归属地查询WebService。这个例子提供了代码下载,我把代码下载下来后,发现它是Eclipse工程,而我决定用Android Studio开发,所以我把它导入到Android Studio中并编译通过。这个步奏我就不详细介绍了,网上有很多这方面的博客。实际的网址和例子中的网址有变化,所以改为为实际的网址,
如下所示:
private static final String namespaceAddress="http://WebXml.com.cn/";
private static final String urlAddress="http://ws.webxml.com.cn/WebServices/MobileCodeWS.asmx";
private static final String methodName="getMobileCodeInfo";
private static final String soapAction="http://WebXml.com.cn/getMobileCodeInfo";
另外,例子中使用的Ksoap2-Android JAR包版本比较低,我把它更新成了ksoap2-android-assembly-3.0.0-RC.4-jar-with-dependencies.jar。Ksoap2-Android 多个版本的JAR包在优快云上都有下载,读者可自行选取。
代码对应的是最终APK中TestCase =0的情形,后面一并贴出,测试后可以正确得到查询手机号的归属地。
3. 第二步,编译Ksoap2-Android的源码
由于可能要修改Ksoap2-Android的源码,所以从网上下载Ksoap2-Android的源码。
源码的下载地址是:
git clone https://github.com/simpligility/ksoap2-android.git
现在测试APK的工程中new一个module出来,命名为volleywithksoap。
Ksoap2-Android的源码用maven构建,当然也可以把Android Studio的module配置成用maven来构建,但考虑到要和volley一起构建,所以没有用这种方法,而是通过分析pom.xml找到依赖关系,再把对应源码拷贝到Android Studio工程中,用gradle构建。通过分析,只需拷贝ksoap2-base和ksoap2-j2se下的所有源文件。
编译后发现,Ksoap2-Android依赖kxml,下载一个kxml的JAR包,
下载地址:
http://download.youkuaiyun.com/download/woshilanbo1205/7131509。
Ksoap2-Android还需要kobjects的base64和isodate类,从网上找到源码加到volleywithksoap module中。
现贴出这两个类:
/* Copyright (c) 2002,2003, 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. */
package org.kobjects.base64;
import java.io.*;
public class Base64 {
static final char[] charTab =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
.toCharArray();
public static String encode(byte[] data) {
return encode(data, 0, data.length, null).toString();
}
/** Encodes the part of the given byte array denoted by start and
len to the Base64 format. The encoded data is appended to the
given StringBuffer. If no StringBuffer is given, a new one is
created automatically. The StringBuffer is the return value of
this method. */
public static StringBuffer encode(
byte[] data,
int start,
int len,
StringBuffer buf) {
if (buf == null)
buf = new StringBuffer(data.length * 3 / 2);
int end = len - 3;
int i = start;
int n = 0;
while (i <= end) {
int d =
((((int) data[i]) & 0x0ff) << 16)
| ((((int) data[i + 1]) & 0x0ff) << 8)
| (((int) data[i + 2]) & 0x0ff);
buf.append(charTab[(d >> 18) & 63]);
buf.append(charTab[(d >> 12) & 63]);
buf.append(charTab[(d >> 6) & 63]);
buf.append(charTab[d & 63]);
i += 3;
if (n++ >= 14) {
n = 0;
buf.append("\r\n");
}
}
if (i == start + len - 2) {
int d =
((((int) data[i]) & 0x0ff) << 16)
| ((((int) data[i + 1]) & 255) << 8);
buf.append(charTab[(d >> 18) & 63]);
buf.append(charTab[(d >> 12) & 63]);
buf.append(charTab[(d >> 6) & 63]);
buf.append("=");
}
else if (i == start + len - 1) {
int d = (((int) data[i]) & 0x0ff) << 16;
buf.append(charTab[(d >> 18) & 63]);
buf.append(charTab[(d >> 12) & 63]);
buf.append("==");
}
return buf;
}
static int decode(char c) {
if (c >= 'A' && c <= 'Z')
return ((int) c) - 65;
else if (c >= 'a' && c <= 'z')
return ((int) c) - 97 + 26;
else if (c >= '0' && c <= '9')
return ((int) c) - 48 + 26 + 26;
else
switch (c) {
case '+' :
return 62;
case '/' :
return 63;
case '=' :
return 0;
default :
throw new RuntimeException(
"unexpected code: " + c);
}
}
/** Decodes the given Base64 encoded String to a new byte array.
The byte array holding the decoded data is returned. */
public static byte[] decode(String s) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
decode(s, bos);
}
catch (IOException e) {
throw new RuntimeException();
}
return bos.toByteArray();
}
public static void decode(String s, OutputStream os)
throws IOException {
int i = 0;
int len = s.length();
while (true) {
while (i < len && s.charAt(i) <= ' ')
i++;
if (i == len)
break;
int tri =
(decode(s.charAt(i)) << 18)
+ (decode(s.charAt(i + 1)) << 12)
+ (decode(s.charAt(i + 2)) << 6)
+ (decode(s.charAt(i + 3)));
os.write((tri >> 16) & 255);
if (s.charAt(i + 2) == '=')
break;
os.write((tri >> 8) & 255);
if (s.charAt(i + 3) == '=')
break;
os.write(tri & 255);
i += 4;
}
}
/* Copyright (c) 2002,2003, 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. */
package org.kobjects.isodate;
import java.util.*;
public class IsoDate {
public static final int DATE = 1;
public static final int TIME = 2;
public static final int DATE_TIME = 3;
static void dd(StringBuffer buf, int i) {
buf.append((char) (((int) '0') + i / 10));
buf.append((char) (((int) '0') + i % 10));
}
public static String dateToString(Date date, int type) {
Calendar c = Calendar.getInstance();
c.setTimeZone(TimeZone.getTimeZone("GMT"));
c.setTime(date);
StringBuffer buf = new StringBuffer();
if ((type & DATE) != 0) {
int year = c.get(Calendar.YEAR);
dd(buf, year / 100);
dd(buf, year % 100);
buf.append('-');
dd(
buf,
c.get(Calendar.MONTH) - Calendar.JANUARY + 1);
buf.append('-');
dd(buf, c.get(Calendar.DAY_OF_MONTH));
if (type == DATE_TIME)
buf.append("T");
}
if ((type & TIME) != 0) {
dd(buf, c.get(Calendar.HOUR_OF_DAY));
buf.append(':');
dd(buf, c.get(Calendar.MINUTE));
buf.append(':');
dd(buf, c.get(Calendar.SECOND));
buf.append('.');
int ms = c.get(Calendar.MILLISECOND);
buf.append((char) (((int) '0') + (ms / 100)));
dd(buf, ms % 100);
buf.append('Z');
}
return buf.toString();
}
public static Date stringToDate(String text, int type) {
Calendar c = Calendar.getInstance();
if ((type & DATE) != 0) {
c.set(
Calendar.YEAR,
Integer.parseInt(text.substring(0, 4)));
c.set(
Calendar.MONTH,
Integer.parseInt(text.substring(5, 7))
- 1
+ Calendar.JANUARY);
c.set(
Calendar.DAY_OF_MONTH,
Integer.parseInt(text.substring(8, 10)));
if (type != DATE_TIME || text.length () < 11) {
c.set(Calendar.HOUR_OF_DAY, 0);
c.set(Calendar.MINUTE, 0);
c.set(Calendar.SECOND, 0);
c.set(Calendar.MILLISECOND, 0);
return c.getTime();
}
text = text.substring(11);
}
else
c.setTime(new Date(0));
c.set(
Calendar.HOUR_OF_DAY,
Integer.parseInt(text.substring(0, 2)));
// -11
c.set(
Calendar.MINUTE,
Integer.parseInt(text.substring(3, 5)));
c.set(
Calendar.SECOND,
Integer.parseInt(text.substring(6, 8)));
int pos = 8;
if (pos < text.length() && text.charAt(pos) == '.') {
int ms = 0;
int f = 100;
while (true) {
char d = text.charAt(++pos);
if (d < '0' || d > '9')
break;
ms += (d - '0') * f;
f /= 10;
}
c.set(Calendar.MILLISECOND, ms);
}
else
c.set(Calendar.MILLISECOND, 0);
if (pos < text.length()) {
if (text.charAt(pos) == '+'
|| text.charAt(pos) == '-')
c.setTimeZone(
TimeZone.getTimeZone(
"GMT" + text.substring(pos)));
else if (text.charAt(pos) == 'Z')
c.setTimeZone(TimeZone.getTimeZone("GMT"));
else
throw new RuntimeException("illegal time format!");
}
return c.getTime();
}
编译还是不通过,原来是OkHttpServiceConnectionSE.java和OkHttpTransportSE.java两个文件,反正我们也不用OkHttp,所以删掉这两个文件。
现在编译通过,现在把编出来的Ksoap2-Android的volleywithksoap module加到app的dependency中。
打开File寀单下的Project Structure寀单,在Depencendies中加入vooleywithksoap。
见下图:
去掉对ksoap2-android-assembly-3.0.0-RC.4-jar-with-dependencies.jar的引用,编译出app,运行测试,正确得到手机信息。
第三步,编译Volley的源码
Volley源码下载地址
git clone https://android.googlesource.com/platform/frameworks/volley
下载下来后,把所有源文件拷贝到volleywithksoap module的对应源码目录下,
编译不通过,原来android sdk升级了api23,原生sdk不提供
org.apache.http.*的类(仅仅保留几个)。java上最常用的httpClient也没有了。
在volleywithksoap module的build.gradle的Android块中加入
useLibrary 'org.apache.http.legacy'
再次编译,这个错误没有了,但Lint不通过,在同一个android块中加入
lintOptions {
abortOnError false
}
再次编译,通过。
为了防止编出来的volley与框架层原生的volley包名冲突,将volleywithksoap module中的voley包名中的volley全部改成volleywithksoap。
改完后,再次编译,通过。
然后测试一下编出来volley的正确性,这可以有很多网上例子可供使用,笔者也测试了一下,通过。具体测试方法这里就不列出了。
第四步,编写修改代码
现在所有的准备工作都已做完,开始整合volley和ksoap-android,并编写修改相应代码。
从分析源码可以知道, volley的Request类提供了高可定制的接口,允许用户定制自己的请求,所以最合理的方法是从Request类派生出WebService请求子类,命名为KsoapRequest。在KsoapRequest中子类实现soap所需的逻辑,以覆盖父类当中的对应方法。
所以保留并直接使用Ksoap-Android中的SoapSerialzationEnvelope类,
它提供了Soap序列化和反序列化的接口。而抛弃使用HttpTransportSE类,
因为它负责网络传输,而这部分我们将通过Volley来实现。
但是通过分析源码可以知道,Ksoap-Android中HttpTransportSE的基类Transport中包含了序列化和反序列化所需的createRequestData和parseResponse方法和一些参数变量。我们又不想对Ksoap-Android的类库结构做大的改动,所以把这个Transport.java拷贝到volleywithksoap的toolbox目录下,去掉其中的抽象方法和不需要的方法和变量,作为KsoapRequest的工具类,命名为VolleyKsoapTransport.java。
以下贴出这个类的源码:
/**
* Copyright (c) 2006, James Seigel, Calgary, AB., Canada
* 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.
*/
package com.android.volleywithksoap.toolbox;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.io.*;
import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.URL;
import org.ksoap2.*;
import org.kxml2.io.*;
import org.xmlpull.v1.*;
/**
* Abstract class which holds common methods and members that are used by the
* transport layers. This class encapsulates the serialization and
* deserialization of the soap messages, leaving the basic communication
* routines to the subclasses.
*/
public class VolleyKsoapTransport {
/**
* Added to enable web service interactions on the emulator to be debugged
* with Fiddler2 (Windows) but provides utility for other proxy
* requirements.
*/
public static final int DEFAULT_BUFFER_SIZE = 256*1024; // 256 Kb
/** Set to true if debugging */
public boolean debug = false;
/** String dump of request for debugging. */
public String requestDump;
/** String dump of response for debugging */
public String responseDump;
private String xmlVersionTag = "";
public static final String CONTENT_TYPE_XML_CHARSET_UTF_8 = "text/xml;charset=utf-8";
public static final String CONTENT_TYPE_SOAP_XML_CHARSET_UTF_8 = "application/soap+xml;charset=utf-8";
public static final String USER_AGENT = "ksoap2-android/2.6.0+";
private int bufferLength = DEFAULT_BUFFER_SIZE;
private HashMap prefixes = new HashMap();
public HashMap getPrefixes() {
return prefixes;
}
public VolleyKsoapTransport() {
}
public VolleyKsoapTransport(int bufferLength) {
this.bufferLength = bufferLength;
}
/**
* Sets up the parsing to hand over to the envelope to deserialize.
*/
protected void parseResponse(SoapEnvelope envelope, InputStream is)
throws XmlPullParserException, IOException {
XmlPullParser xp = new KXmlParser();
xp.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
xp.setInput(is, null);
envelope.parse(xp);
/*
* Fix memory leak when running on android in strict mode. Issue 133
*/
is.close();
}
/**
* Serializes the request.
*/
protected byte[] createRequestData(SoapEnvelope envelope, String encoding)
throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream(bufferLength);
byte result[] = null;
bos.write(xmlVersionTag.getBytes());
XmlSerializer xw = new KXmlSerializer();
final Iterator keysIter = prefixes.keySet().iterator();
xw.setOutput(bos, encoding);
while (keysIter.hasNext()) {
String key = (String) keysIter.next();
xw.setPrefix(key, (String) prefixes.get(key));
}
envelope.write(xw);
xw.flush();
bos.write('\r');
bos.write('\n');
bos.flush();
result = bos.toByteArray();
xw = null;
bos = null;
return result;
}
/**
* Serializes the request.
*/
protected byte[] createRequestData(SoapEnvelope envelope)
throws IOException {
return createRequestData(envelope, null);
}
/**
* Sets the version tag for the outgoing soap call. Example <?xml
* version=\"1.0\" encoding=\"UTF-8\"?>
*
* @param tag
* the xml string to set at the top of the soap message.
*/
public void setXmlVersionTag(String tag) {
xmlVersionTag = tag;
}
}
下面开始实现KsoapRequest类,主要参考了HttpTransportSE的call方法,
Call方法中请求发送到网络之前的逻辑主要在KsoapRequest的GetHeaders和GetBody方法中实现,而请求发送后的解析逻辑主要在KsoapRequest的ParseNetworkRespnse方法中实现。这里有一点需要注意,Response.Listener<T>的T使用类型用Ksoap-Android的SoapObject最简洁,但编译不通过,我们又不想对Ksoap-Android的类库结构做大的改动,所以后来改用SoapSerialzationEnvelope,编译通过。
现在贴出KsoapRequest.java的源码:
package com.android.volleywithksoap.toolbox;
import com.android.volleywithksoap.NetworkResponse;
import com.android.volleywithksoap.Request;
import com.android.volleywithksoap.Response;
import com.android.volleywithksoap.Response.ErrorListener;
import com.android.volleywithksoap.Response.Listener;
import com.android.volleywithksoap.VolleyLog;
import com.android.volleywithksoap.AuthFailureError;
import com.android.volleywithksoap.NetworkError;
import com.android.volleywithksoap.ParseError;
import com.android.volleywithksoap.toolbox.VolleyKsoapTransport;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.GZIPInputStream;
import org.ksoap2.HeaderProperty;
import org.ksoap2.SoapEnvelope;
import org.ksoap2.SoapFault;
import org.ksoap2.serialization.*;
import org.ksoap2.transport.Transport;
import org.xmlpull.v1.XmlPullParserException;
import org.ksoap2.transport.HttpResponseException;
public class KsoapRequest extends Request<SoapSerializationEnvelope> {
private final Listener<SoapSerializationEnvelope> mListener;
SoapSerializationEnvelope mEnv;
String mSoapAction;
VolleyKsoapTransport mVolleyKsoapTransport;
public KsoapRequest(String soapAction, SoapSerializationEnvelope soap_env, String url, Listener<SoapSerializationEnvelope> listener,
ErrorListener errorListener) {
super(Method.POST, url, errorListener);
mListener = listener;
mEnv = soap_env;
mSoapAction = soapAction;
if (mSoapAction == null) {
mSoapAction = "\"\"";
}
mVolleyKsoapTransport = new VolleyKsoapTransport();
}
@Override
protected void deliverResponse(SoapSerializationEnvelope response) {
mListener.onResponse(response);
}
@Override
public String getPostBodyContentType() {
return getBodyContentType();
}
/**
* @deprecated Use {@link #getBody()}.
*/
@Override
public byte[] getPostBody() {
return getBody();
}
@Override
public String getBodyContentType() {
if (mEnv.version == SoapSerializationEnvelope.VER12) {
return VolleyKsoapTransport.CONTENT_TYPE_SOAP_XML_CHARSET_UTF_8;
} else {
return VolleyKsoapTransport.CONTENT_TYPE_XML_CHARSET_UTF_8;
}
}
@Override
public byte[] getBody() {
byte[] requestData;
try {
requestData = mVolleyKsoapTransport.createRequestData(mEnv, "UTF-8");
mVolleyKsoapTransport.requestDump = mVolleyKsoapTransport.debug ? new String(requestData) : null;
}
catch (IOException e) {
requestData = null;
mVolleyKsoapTransport.requestDump = null;
}
return requestData;
}
@Override
public Map<String, String> getHeaders() throws AuthFailureError
{
Map<String, String> headers = new HashMap<String, String>();
headers.put("User-Agent", VolleyKsoapTransport.USER_AGENT);
if (mEnv.version != SoapSerializationEnvelope.VER12) {
headers.put("SOAPAction", mSoapAction);
}
/*if (mEnv.version == SoapSerializationEnvelope.VER12) {
headers.put(("Content-Type", VolleyKsoapTransport.CONTENT_TYPE_SOAP_XML_CHARSET_UTF_8);
} else {
headers.put(("Content-Type", VolleyKsoapTransport.CONTENT_TYPE_XML_CHARSET_UTF_8);
}*/
headers.put("Accept-Encoding", "gzip");
return headers;
}
private InputStream getUnZippedInputStream(InputStream inputStream) throws IOException {
/* workaround for Android 2.3
(see http://stackoverflow.com/questions/5131016/)
*/
try {
return (GZIPInputStream) inputStream;
} catch (ClassCastException e) {
return new GZIPInputStream(inputStream);
}
}
@Override
protected Response<SoapSerializationEnvelope> parseNetworkResponse(NetworkResponse response) {
InputStream is1 = null;
try {
if( response.statusCode != 200 && response.statusCode != 202) {
//202 is a correct status returned by WCF OneWay operation
String strTmp = "HTTP request failed, HTTP status: " + (response.statusCode);
throw new HttpResponseException(strTmp, response.statusCode);
}
String myStr;
int contentLength = 8192;
boolean xmlContent = false;
boolean gZippedContent = false;
myStr = response.headers.get("Content-Length");
if ( myStr != null ) {
try {
contentLength = Integer.parseInt(myStr);
} catch ( NumberFormatException nfe ) {
contentLength = 8192;
}
}
myStr = response.headers.get("Content-Type");
if ( myStr != null )
{
if(myStr.contains("xml"))
{
xmlContent = true;
}
}
myStr = response.headers.get("Content-Encoding");
if ( myStr != null )
{
if(myStr.equalsIgnoreCase("gzip"))
{
gZippedContent = true;
}
}
InputStream is_new = new ByteArrayInputStream(response.data);
if (contentLength > 0) {
if (gZippedContent) {
is1 = getUnZippedInputStream(is_new);
} else {
is1 = new BufferedInputStream(is_new,contentLength);
}
}
}
catch ( HttpResponseException e1)
{
return Response.error(new NetworkError(e1));
}
catch (IOException e2) {
return Response.error(new ParseError(e2));
}
if( is1 != null)
{
/*Winfred Young add it fr soapfualt*/
mEnv.bIsParsedSoapFault = false;
try {
mVolleyKsoapTransport.parseResponse(mEnv, is1);
}
catch (IOException e3) {
return Response.error(new ParseError(e3));
}
catch (XmlPullParserException e4)
{
return Response.error(new ParseError(e4));
}
if(mEnv.bIsParsedSoapFault)
{
SoapFault fault = (SoapFault)(mEnv.bodyIn);
return Response.error(new ParseError(fault));
}
}
return Response.success(mEnv,HttpHeaderParser.parseCacheHeaders(response));
}
}
代码中还有一点需要说明,在解析时遇到SoapFault也可以解析出来,但赋给了SoapSerialzationEnvelope的bodyIn,最好把这个错误通知Volley框架。但用instanceof运算赋符编译错误,而又不想对Ksoap-Android的类库做大的改动。所以在SoapSerialzationEnvelope中增加了一个boolean变量 bIsParsedSoapFault
,缺省时或开始解析之前,把它置为false,解析出SoapFault时,把它置为true。解析后,当判断到这个变量为true,将错误通知Volley框架,而不作为成功返回。
现在在测试Activity中增加TestCase = 1,作为测试volleywithksoap的情形。经反复测试,手机信息正确。
下面贴出测试Activity的代码:
package com.example.hmcx;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Timer;
import java.util.TimerTask;
import org.ksoap2.SoapEnvelope;
import org.ksoap2.serialization.SoapObject;
import org.ksoap2.serialization.SoapSerializationEnvelope;
import org.ksoap2.transport.HttpTransportSE;
import org.xmlpull.v1.XmlPullParserException;
import com.android.volleywithksoap.Request;
import com.android.volleywithksoap.RequestQueue;
import com.android.volleywithksoap.Response;
import com.android.volleywithksoap.AuthFailureError;
import com.android.volleywithksoap.NetworkError;
import com.android.volleywithksoap.NetworkResponse;
import com.android.volleywithksoap.NoConnectionError;
import com.android.volleywithksoap.ServerError;
import com.android.volleywithksoap.TimeoutError;
import com.android.volleywithksoap.VolleyError;
import com.android.volleywithksoap.VolleyLog;
import com.android.volleywithksoap.toolbox.StringRequest;
import com.android.volleywithksoap.toolbox.Volley;
import com.android.volleywithksoap.toolbox.KsoapRequest;
import android.support.v7.app.ActionBarActivity;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import android.view.View.OnClickListener;
public class MainActivity extends Activity {
private EditText phoneSecEditText;
private TextView resultView;
private Button queryButton;
private String result;
private String phonesec;
ProgressDialog progressDialog1;
Timer timer;
Thread thread;
private static final String namespaceAddress="http://WebXml.com.cn/";
private static final String urlAddress="http://ws.webxml.com.cn/WebServices/MobileCodeWS.asmx";
private static final String methodName="getMobileCodeInfo";
private static final String soapAction="http://WebXml.com.cn/getMobileCodeInfo";
private static final int TIME_LIMIT = 10000;
private static final int TIME_OUT = 1;
private static final int SUCCESS = 0;
private static final int TestCase = 1;
private RequestQueue mRequestQueue;
public String mStrVolleyResult;
private Handler handler = new Handler(){
public void handleMessage(Message msg) {
// TODO 接收消息并且去更新UI线程上的控件内容
if(msg.what==SUCCESS) {
result=String.valueOf(msg.obj);
resultView.setText(result);
progressDialog1.dismiss();
timer.cancel();
}
else if(msg.what==TIME_OUT){
thread.interrupt();
progressDialog1.dismiss();
Toast.makeText(getApplicationContext(), "请求超时!请重新查询!", Toast.LENGTH_SHORT).show();
}
super.handleMessage(msg);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
phoneSecEditText = (EditText) findViewById(R.id.phone_sec);
resultView = (TextView) findViewById(R.id.result_text);
queryButton = (Button) findViewById(R.id.query_btn);
if(TestCase != 0)
{
mRequestQueue = Volley.newRequestQueue(getBaseContext());
}
queryButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (isOpenNetwork() == true){
phonesec = phoneSecEditText.getText().toString().trim();
// 简单判断用户输入的手机号码(段)是否合法
if ("".equals(phonesec) || phonesec.length() < 7) {
Toast.makeText(getApplicationContext(), "您输入的手机号码(段)有误!", Toast.LENGTH_SHORT).show();
phoneSecEditText.requestFocus();
resultView.setText("");
return;
}
else if(phonesec.length() >11){
Toast.makeText(getApplicationContext(), "您输入的手机号码有误(不得超过11位)!", Toast.LENGTH_SHORT).show();
phoneSecEditText.requestFocus();
resultView.setText("");
return;
}
else{
progressDialog1 = ProgressDialog.show(MainActivity.this,null ,"查询中...", true);
if(TestCase == 0) {
thread = new Thread(new Runnable() {
public void run() {
try {
Message msgSuc = new Message();
msgSuc.obj = getRemoteInfo(phonesec);
msgSuc.what = SUCCESS;
handler.sendMessage(msgSuc);
} catch (IOException e) {
e.printStackTrace();
}
}
});
timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
Message timeOutmsg = new Message();
timeOutmsg.what = TIME_OUT;
handler.sendMessage(timeOutmsg);
}
}, TIME_LIMIT);
thread.start();
}
else
{
timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
Message timeOutmsg = new Message();
timeOutmsg.what = TIME_OUT;
handler.sendMessage(timeOutmsg);
}
}, TIME_LIMIT);
getRemoteInfoByVolley(phonesec);
}
}
}
else{
Toast.makeText(getApplicationContext(), "网络异常,请检查网络连接状态!", Toast.LENGTH_SHORT).show();
}
}
});
}
private boolean isOpenNetwork() {
ConnectivityManager connManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
if (connManager.getActiveNetworkInfo() != null) {
return connManager.getActiveNetworkInfo().isAvailable();
}
return false;
}
public void getRemoteInfoByVolley(String phoneSec){
SoapObject soapObject=new SoapObject(namespaceAddress,methodName);
//soapObject添加参数的顺序必须与asmx里面的参数顺序一致,名称保持一致
soapObject.addProperty("mobileCode",phoneSec);
soapObject.addProperty("userID", "");
SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(
SoapEnvelope.VER11);
envelope.bodyOut = soapObject;
envelope.dotNet = true;
envelope.setOutputSoapObject(soapObject);
Response.Listener<SoapSerializationEnvelope> myListener = new Response.Listener<SoapSerializationEnvelope>() {
@Override
public void onResponse(SoapSerializationEnvelope responseEnv)
{
SoapObject object = (SoapObject) responseEnv.bodyIn;
String info=object.getProperty(0).toString();
Message msgSuc1 = new Message();
msgSuc1.obj = info;
msgSuc1.what = SUCCESS;
handler.sendMessage(msgSuc1);
}
};
Response.ErrorListener myErrorListener = new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error)
{
mStrVolleyResult = error.getMessage();
Message msgSuc2 = new Message();
msgSuc2.obj = mStrVolleyResult;
msgSuc2.what = SUCCESS;
handler.sendMessage(msgSuc2);
}
};
KsoapRequest myKsoapRequest = new KsoapRequest(soapAction, envelope, urlAddress, myListener, myErrorListener);
mRequestQueue.add(myKsoapRequest);
}
public static String getRemoteInfo(String phoneSec) throws IOException{
SoapObject soapObject=new SoapObject(namespaceAddress,methodName);
//soapObject添加参数的顺序必须与asmx里面的参数顺序一致,名称保持一致
soapObject.addProperty("mobileCode",phoneSec);
soapObject.addProperty("userID", "");
SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(
SoapEnvelope.VER11);
envelope.bodyOut = soapObject;
envelope.dotNet = true;
envelope.setOutputSoapObject(soapObject);
HttpTransportSE httpTransportSE = new HttpTransportSE(urlAddress);
try {
httpTransportSE.call(soapAction,envelope);
} catch (IOException e) {
e.printStackTrace();
} catch (XmlPullParserException e) {
e.printStackTrace();
}
SoapObject object = (SoapObject) envelope.bodyIn;
String info=object.getProperty(0).toString();
return info;
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK&& event.getRepeatCount() == 0) {
new AlertDialog.Builder(this).setTitle("确定退出程序么")
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
})
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
finish();//
}
}).show();
return true;
}
else{
return super.onKeyDown(keyCode, event);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
第五步,导出JAR包
现在我们已完成所有的编码工作,我们还想把volleywithksoap导出JAR包,供其他我们开发的APK使用。
在volleywithksoap的build.gradle最后增加如下语句:
task makeJar(type: Copy) {
delete 'build/libs/volleywithksoap.jar'
from( 'build/intermediates/bundles/release ')
into( 'build/libs')
include('classes.jar')
rename('classes.jar', 'volleywithksoap.jar')
}
makeJar.dependsOn(build)
定义了一个task,导出volleywithksoap.jar.
打开gradle视图,在volleywithksoap的task的other中找到makeJar任务,双击它执行,执行完后,在build/libs目录下发现导出的volleywithksoap.jar。
见下图:
现在我么可以在其他APK工程中使用我们的劳动成果了。
全文完。