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.csdn.net/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包在CSDN上都有下載,讀者可自行選取。
代碼對應的是最終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.csdn.net/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工程中使用我們的勞動成果了。
全文完。