Android对接.net(C#)服务端(二):使用HttpTransportSE发送soap请求调用WCF服务获取WebService数据的网络框架封装

〇、前言

Android开发目前我们最常见最主流的网络访问方式是使用OkHttp/Retrofit在Http协议下进行的网络通信,但是如标题所述本篇文章描述的不是常见主流的网络访问方式,而是调用WCF服务获取WebService数据的方式,有些项目就是采用的这种方式,所以我在标题上加了多个定语来进行此种方式的限定。

一、ksoap2-android

不同于HttpURLConnection已在Android系统中,发送soap请求需要借助第三方jar包(ksoap2-android-assembly-3.3.0-jar-with-dependencies.jar)。

二、SoapServer

SoapServer是封装的直接调用服务端的网络访问类,其中主要解释已在注释中描述:

import org.ksoap2.SoapEnvelope;
import org.ksoap2.serialization.SoapObject;
import org.ksoap2.serialization.SoapPrimitive;
import org.ksoap2.serialization.SoapSerializationEnvelope;
import org.ksoap2.transport.HttpTransportSE;

import jdlf_scgl_zp_android.ui.m990_system.BuildConfig;


/**
 * Description: SoapServer 直接调用服务端的网络访问类
 * 依赖ksoap2-android-assembly-3.3.0-jar-with-dependencies.jar包,底层使用HttpTransportSE访问服务端
 * 与原类(SoapService)相比,本类去除了冗余的变量和方法,增加每一步的说明,使逻辑更加清晰,并针对可能的异常直接进行捕获
 * Copyright  : Copyright (c) 2021
 * Author     : mliuxb
 * Date       : 2021-03-12
 */

class SoapServer {
    //private static final String TAG = "SoapServer";

    /**
     * InvokeWCF类使用严格的单例设计模式,因此访问服务端时只会有一个InvokeWCF对象,
     * 所以SoapServer的对象也是唯一的,从而transport的对象也是唯一的。
     */
    private final HttpTransportSE transport;

    SoapServer() {
        final String url = "http://" + BuildConfig.ipConfig + ":7090/JDLF_SCGL_ZP_WCFServices_ForAndroid.svc";
        //final String url = "http://172.18.20.89:7090/JDLF_SCGL_ZP_WCFServices_ForAndroid.svc";
        //设置服务器地址和超时时间(底层默认20秒)
        transport = new HttpTransportSE(url);
        //打开HttpTransportSE的调试
        //transport.debug = true;
    }

    /**
     * 访问服务端的核心方法
     * @param businessName
     *         业务名称(WCF服务中的方法名)
     * @param funName
     *         指定业务中的获取数据的方法,与CDP层方法对应
     * @param message
     *         传入的信息(参数值)
     * @return 服务端响应的信息(String)
     */
    String requestServer(final String businessName, final String funName, final String message) {
        try {
            //soap的核心对象。参一:服务器命名空间;参二:方法名
            final SoapObject request = new SoapObject("http://tempuri.org/", businessName);
            //请求参数
            request.addProperty("funName", funName);
            request.addProperty("message", message);

            //创建一个请求参数对象(传参为协议版本号,根据导入的jar包选择)(2020-9-1:参数对象每次进行单独创建,避免多线程同时发起请求时多个请求混淆)
            final SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER10);
            //如果设置为false,服务端将无法拿到请求参数
            envelope.dotNet = true;
            //以下两行作用一样
            //envelope.bodyOut = request;
            envelope.setOutputSoapObject(request);

            final String soapAction = "http://tempuri.org/IJDLF_SCGL_ZP_WCFServices_ForAndroid/" + businessName;
            //发送请求(此方法可能抛出异常)
            transport.call(soapAction, envelope);
            //当transport.debug = true时可用transport.responseDump直接查看接收到的xml,否则transport.responseDump为null
            //Log.w(TAG, "InvokeWcfService: Response Dump >> " + transport.responseDump);

            //返回报文是String,所以以下两种解析方法均可
            //SoapObject response = (SoapObject) envelope.bodyIn;
            //return response.getProperty(0).toString();

            final SoapPrimitive primitive = (SoapPrimitive) envelope.getResponse();
            return primitive.toString();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

三、InvokeWCF

InvokeWCF是调用WCF的异步封装类,其中使用了单例设计模式进行对象管理、使用Handler进行线程切换、使用了线程池进行线程管理,如下:

import android.os.Handler;
import android.os.Looper;
import android.util.Log;

import com.google.gson.Gson;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import jdlf_scgl_zp_android.s002_cem.IEnumCem;
import jdlf_scgl_zp_android.ui.m990_system.BuildConfig;


/**
 * Description: InvokeWCF 调用WCF的(异步)封装类
 * 本类采用严格的单例设计模式,保证访问服务端时只有一个InvokeWCF对象,方便管理并节约资源
 * 本类主要作用是进行异步封装(即:在子线程访问网络,并将返回的数据切换到主线程)
 * 其中子线程采用线程池进行管理,避免频繁进行线程的创建和销毁而浪费系统资源
 * 本类中实现了将DataTable数据在子线程进行解析,给业务层直接返回一个限定类型的ArrayList
 * Copyright  : Copyright (c) 2021
 * Author     : mliuxb
 * Date       : 2021-03-12
 */

public class InvokeWCF {
    private static final String TAG = "InvokeWCF";

    // 服务访问对象
    private final SoapServer mSoapServer = new SoapServer();

    // 主线程的Handler对象
    private final Handler mMainHandler = new Handler(Looper.getMainLooper());

    // 线程池
    private final ExecutorService mThreadPool = Executors.newFixedThreadPool(4);

    //单例对象(选择懒汉模式)
    private static volatile InvokeWCF mObject;

    /**
     * 私有构造函数
     */
    private InvokeWCF() {
    }

    /**
     * 公开方法,获取单例对象
     */
    public static InvokeWCF getObject() {
        //懒汉: 考虑线程安全问题,给创建对象的代码块加同步锁
        if (mObject == null) {
            synchronized (InvokeWCF.class) {
                if (mObject == null) {
                    mObject = new InvokeWCF();
                }
            }
        }
        return mObject;
    }

    /**
     * 服务端响应信息包装类
     */
    private static class ResponseInfo {
        //返回结果是否正常
        private boolean Result;
        //返回的结果内容
        private String Message;
    }

    /**
     * 从WCF服务获取数据 此方法在子线程执行
     * @param businessName
     *         业务名称(WCF服务中的方法名)
     * @param funName
     *         指定业务中的获取数据的方法,与CDP层方法对应
     * @param message
     *         传入的信息(参数值)
     * @return 响应结果
     */
    @Nullable
    @WorkerThread
    private ResponseInfo InvokeServer(final String businessName, final String funName, final String message) {
        try {
            final String response = mSoapServer.requestServer(businessName, funName, message);
            if (BuildConfig.DEBUG) {
                Log.i(TAG, "InvokeServer: business = " + businessName);
                Log.i(TAG, "InvokeServer: funName  = " + funName);
                Log.i(TAG, "InvokeServer: message  = " + message);
                Log.i(TAG, "InvokeServer: response = " + response);
            }
            //2020-9-2:response为null、""(空字符串)、" "(空格或tab)时Gson解析都会返回null(不报异常),所以此处可不进行判空。
            return new Gson().fromJson(response, ResponseInfo.class);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /*-------------------------------------------无回调结果start-------------------------------------------*/

    /**
     * 从WCF服务获取数据:两参,适合只需往服务端发送请求不需要响应的情况。
     * @param enumCem
     *         业务中对应方法的枚举值
     * @param message
     *         传入的信息(参数值)
     */
    @MainThread
    public void GetResultInfo(@NonNull final IEnumCem enumCem, @NonNull final String message) {
        mThreadPool.execute(() -> InvokeServer(enumCem.BusinessName(), enumCem.toString(), message));
    }

    /*-------------------------------------------无回调结果end-------------------------------------------*/

    /*---------------------------------------message为回调结果start---------------------------------------*/

    /**
     * Message数据回调的接口
     */
    public interface OnMessageListener {
        void onResult(boolean result, @NonNull String message);
    }

    /**
     * 切换到主线程进行回调:message不可能为空
     */
    @WorkerThread
    private void callbackResult(@Nullable final OnMessageListener listener, final boolean result, @NonNull final String message) {
        if (listener == null)
            return;
        //主线程回调函数
        mMainHandler.post(() -> listener.onResult(result, message));
    }

    /**
     * 从WCF服务获取数据:三参,第三参为OnMessageListener,适合不需要在底层进行解析的情况。
     * @param enumCem
     *         业务中对应方法的枚举值
     * @param message
     *         传入的信息(参数值)
     * @param listener
     *         数据回调的接口
     */
    @MainThread
    public void GetResultInfo(@NonNull final IEnumCem enumCem, @NonNull final String message, @Nullable final OnMessageListener listener) {
        //线程池中执行,在子线程访问网络
        mThreadPool.execute(() -> {
            final ResponseInfo resInfo = InvokeServer(enumCem.BusinessName(), enumCem.toString(), message);
            if (resInfo == null) {//异常情况
                callbackResult(listener, false, "");

            } else if (resInfo.Message == null) {
                callbackResult(listener, resInfo.Result, "");

            } else {//正常情况
                callbackResult(listener, resInfo.Result, resInfo.Message);
            }
        });
    }

    /*---------------------------------------message为回调结果end---------------------------------------*/

    /*-------------------------------------DataTable为回调结果start-------------------------------------*/

    /**
     * DataTable数据回调的接口
     */
    public interface OnDataTableListener<T> {
        void onResult(boolean result, @NonNull ArrayList<T> list);
    }

    @WorkerThread
    private <T> void callbackResult(@Nullable final OnDataTableListener<T> listener, final boolean result, @NonNull final ArrayList<T> list) {
        if (listener == null)
            return;
        //主线程回调函数
        mMainHandler.post(() -> listener.onResult(result, list));
    }

    /**
     * 从WCF服务获取数据
     * @param enumCem
     *         业务中对应方法的枚举值
     * @param message
     *         传入的信息(参数值)
     * @param cls
     *         Model类
     * @param listener
     *         数据回调的接口
     *         回调直接
     */
    @MainThread
    public <T> void GetResultInfo(@NonNull final IEnumCem enumCem, @NonNull final String message, @NonNull Class<T> cls, @Nullable final OnDataTableListener<T> listener) {
        //线程池中执行,在子线程访问网络
        mThreadPool.execute(() -> {
            final ResponseInfo resInfo = InvokeServer(enumCem.BusinessName(), enumCem.toString(), message);

            final ArrayList<T> list = new ArrayList<>();

            if (resInfo == null) {//异常情况
                callbackResult(listener, false, list);

            } else if (!resInfo.Result) {//返回结果为false
                callbackResult(listener, resInfo.Result, list);

            } else if (resInfo.Message == null || resInfo.Message.isEmpty() || resInfo.Message.contains("NoneRow")) {//返回的Message为空,或DataTable是空行
                callbackResult(listener, resInfo.Result, list);

            } else {
                if (BuildConfig.DEBUG) {
                    Log.w(TAG, "GetResultInfo:  Result = " + resInfo.Result);
                    Log.w(TAG, "GetResultInfo: Message = " + resInfo.Message);
                }
                final ArrayList<T> jsonToList = DataTable.fromJsonToList(resInfo.Message, cls);
                callbackResult(listener, resInfo.Result, jsonToList);

                /*try {
                    //对返回的Message在子线程进行解析
                    //final Gson gson = new Gson();
                    //JsonObject jo = new JsonParser().parse(resInfo.Message).getAsJsonObject();

                    //JsonArray array = jo.getAsJsonArray("Table");
                    //2020-10-13:获取到JsonArray后,也可生成对应的Type直接解析
                    //for (final JsonElement jsonElement : array) {
                    //    list.add(gson.fromJson(jsonElement, cls));
                    //}
                    DataTable<T> dataTable = DataTable.fromJson(resInfo.Message, cls);
                    callbackResult(listener, resInfo.Result, dataTable.Table);
                } catch (Exception e) {
                    e.printStackTrace();
                    //防止解析异常
                    callbackResult(listener, resInfo.Result, list);
                }*/
            }
        });
    }

    /*-------------------------------------DataTable为回调结果end-------------------------------------*/

    /*-------------------------------------DataSet为回调结果start-------------------------------------*/

    /**
     * DataSet 数据回调的接口
     */
    public interface OnDataSetListener {
        void onResult(boolean result, HashMap<String, ArrayList<?>> hashMap);
    }


    /**
     * 从WCF服务获取数据
     * @param //enumCem
     *         业务中对应方法的枚举值
     * @param //message
     *         传入的信息(参数值)
     * @param //map
     *         Model类
     * @param //listener
     *         数据回调的接口
     *         回调直接
     */
    /*@MainThread
    public <T> void GetResultInfo2(@NonNull final IEnumCem enumCem, @NonNull final String message, @NonNull HashMap<String, Class<?>> map, @Nullable final OnDataSetListener listener) {
        //线程池中执行,在子线程访问网络
        mThreadPool.execute(() -> {
            final ResponseInfo resInfo = InvokeServer(enumCem.BusinessName(), enumCem.toString(), message);
            HashMap<String, ArrayList<?>> hashMap = new HashMap<>();


            if (resInfo == null) {//异常情况
                //callbackResult(listener, false, list);

            } else if (!resInfo.Result) {//返回结果为false
                //callbackResult(listener, resInfo.Result, list);

            } else if (TextUtils.isEmpty(resInfo.Message) || resInfo.Message.contains("NoneRow")) {//返回的Message为空,或DataTable是空行
                //callbackResult(listener, resInfo.Result, list);

            } else {

                Log.w(TAG, "GetResultInfo2: resInfo.Message = " + resInfo.Message);
                final Gson gson = new Gson();

                //对返回的Message(DataSet)在子线程进行解析
                //对返回的进行解析
                JsonArray jsonSet = new JsonParser().parse(resInfo.Message).getAsJsonArray();
                for (final JsonElement tableElement : jsonSet) {

                    //Log.w(TAG, "GetResultInfo2: tableElement = " + tableElement.toString());
                    JsonObject jsonTable = tableElement.getAsJsonObject();
                    String tableName = jsonTable.getAsJsonPrimitive("TableName").getAsString();
                    //Log.e(TAG, "GetResultInfo2: tableName = " + tableName);
                    //JsonArray table = jsonTable.getAsJsonArray("Table");
                    //Log.w(TAG, "GetResultInfo2: table = " + table);
                    Class<?> clazz = map.get(tableName);
                    //Log.w(TAG, "GetResultInfo2: clazz = " + clazz);
                    DataTable<?> dataTable = DataTable.fromJson(tableElement, clazz);

                    hashMap.put(dataTable.TableName, dataTable.Table);
                    Log.w(TAG, "GetResultInfo2: ");

                    //for (final JsonElement jsonElement : array) {
                    //    list.add(gson.fromJson(jsonElement, cls));
                    //}
                }
                Log.w(TAG, "GetResultInfo2: hashMap = " + hashMap);
                if (listener != null) {
                    listener.onResult(resInfo.Result, hashMap);
                }
                //callbackResult(listener, resInfo.Result, list);
            }
        });
    }*/

    /*-------------------------------------DataSet为回调结果end-------------------------------------*/
}

代码中的注释已经比较清楚,其中本类包含了三个重载的GetResultInfo()方法,第一个只传两个参数,适合只需往服务端发送请求不需要响应的情况,使用场景很少;第二个需传三个参数,第三参为OnMessageListener,即在底层不进行Json数据解析,直接将返回的数据回调到业务层,基本所有场景都可使用但需要数据解析时不是很方便;第三个需传四个参数,第三参为class对象,第四参为OnDataTableListener,适合返回DataTable类型的Json数据直接在本类中进行数据解析,回调给业务层一个带泛型的非空List。

还有一个待完成的GetResultInfo2()方法暂时被注释了,这个方法需要完成对DataSet类型的Json数据进行直接解析,回调给业务层一个HashMap<String, ArrayList<T>>(其中key为TableName,value为解析的DataTable),之所以还未完成是因为此处需要多个不同的泛型去规范List的类型,暂时未调通(或者使用可变参数?),如果你有好的解决方案还请告知...

关于DataTable和DataSet解析请参见上一篇文章:Android对接.net(C#)服务端(一):解析DataTable和DataSet类型的Json数据

猜你喜欢

转载自blog.csdn.net/beita08/article/details/114685999