Android Volley框架支持WebService Volley與Ksoap2-Android的完美融合

Android Volley框架支持WebService

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的例子都是XMLRequestGsonRequest等。

實踐中在Android下調用WebService主要使用的是Ksoap2-Android JAR包。

Ksoap2-Android JAR包有的直接使用HttpURLConnection,有的使用OKHttp框架,直接使用HttpURLConnection顯然失去了網絡異步框架帶來的好處,而使用OKHttp框架就會在一個應用中有兩個框架,這顯然對資源和性能的優化都不利。

能不能讓Volley也支持WebService,從而充分使用Volley。當然也可以自己寫一套序列化和反序列化SOAP的代碼加到Volley中,但工作量比較大,筆者釆用的方法是把VolleyKsoap2-Android融合在一起,對VolleyKsoap2-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.jarKsoap2-Android 多個版本的JAR包在CSDN上都有下載,讀者可自行選取。

 

代碼對應的是最終APKTestCase =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 Studiomodule配置成用maven來構建,但考慮到要和volley一起構建,所以沒有用這種方法,而是通過分析pom.xml找到依賴關係,再把對應源碼拷貝到Android Studio工程中,用gradle構建。通過分析,只需拷貝ksoap2-baseksoap2-j2se下的所有源文件。

 

編譯後發現,Ksoap2-Android依賴kxml,下載一個kxmlJAR包,

下載地址:

 

http://download.csdn.net/download/woshilanbo1205/7131509

 

Ksoap2-Android還需要kobjectsbase64和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 modulebuild.gradleAndroid塊中加入

useLibrary 'org.apache.http.legacy'

 

再次編譯,這個錯誤沒有了,但Lint不通過,在同一個android塊中加入

 

lintOptions {
        abortOnError false
}

 

再次編譯,通過。

 

爲了防止編出來的volley與框架層原生的volley包名衝突,將volleywithksoap module中的voley包名中的volley全部改成volleywithksoap

 

改完後,再次編譯,通過。

 

然後測試一下編出來volley的正確性,這可以有很多網上例子可供使用,筆者也測試了一下,通過。具體測試方法這裏就不列出了。

 

 

 

第四步,編寫修改代碼

 

現在所有的準備工作都已做完,開始整合volleyksoap-android,並編寫修改相應代碼。

從分析源碼可以知道, volleyRequest類提供了高可定製的接口,允許用戶定製自己的請求,所以最合理的方法是從Request類派生出WebService請求子類,命名爲KsoapRequest。在KsoapRequest中子類實現soap所需的邏輯,以覆蓋父類當中的對應方法。

 

所以保留並直接使用Ksoap-Android中的SoapSerialzationEnvelope類,

它提供了Soap序列化和反序列化的接口。而拋棄使用HttpTransportSE類,

因爲它負責網絡傳輸,而這部分我們將通過Volley來實現。

 

但是通過分析源碼可以知道,Ksoap-AndroidHttpTransportSE的基類Transport中包含了序列化和反序列化所需的createRequestDataparseResponse方法和一些參數變量。我們又不想對Ksoap-Android的類庫結構做大的改動,所以把這個Transport.java拷貝到volleywithksoaptoolbox目錄下,去掉其中的抽象方法和不需要的方法和變量,作爲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類,主要參考了HttpTransportSEcall方法,

Call方法中請求發送到網絡之前的邏輯主要在KsoapRequestGetHeadersGetBody方法中實現,而請求發送後的解析邏輯主要在KsoapRequestParseNetworkRespnse方法中實現。這裏有一點需要注意,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也可以解析出來,但賦給了SoapSerialzationEnvelopebodyIn最好把這個錯誤通知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使用。

 

volleywithksoapbuild.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視圖,在volleywithksoaptaskother中找到makeJar任務,雙擊它執行,執行完後,在build/libs目錄下發現導出的volleywithksoap.jar

 

見下圖:

 

 

 

現在我麼可以在其他APK工程中使用我們的勞動成果了。

 

全文完。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章