Android docking with .net (C#) server (two): use HttpTransportSE to send soap request, call WCF service to obtain WebService data network framework package

〇 Preface

At present, our most common and mainstream network access method for Android development is to use OkHttp/Retrofit for network communication under the Http protocol, but as the title says, this article describes not the common mainstream network access methods, but calls WCF services to obtain The way of WebService data is used in some projects, so I added multiple attributives to the title to limit this way.

One, ksoap2-android

Unlike HttpURLConnection, which is already in the Android system, a third-party jar package (ksoap2-android-assembly-3.3.0-jar-with-dependencies.jar) is needed to send soap requests.

二 、 SoapServer

SoapServer is a encapsulated network access class that directly calls the server. The main explanation has been described in the comments:

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 is an asynchronous encapsulation class that calls WCF. It uses a singleton design pattern for object management, Handler for thread switching, and thread pool for thread management, as follows:

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-------------------------------------*/
}

The comments in the code are relatively clear. Among them, this class contains three overloaded GetResultInfo() methods. The first one only passes two parameters . It is suitable for situations where you only need to send a request to the server without a response. There are few usage scenarios. ; The second one needs to pass three parameters , the third parameter is OnMessageListener, that is, no Json data analysis is performed at the bottom layer, and the returned data is directly called back to the business layer. Basically all scenarios can be used but it is not very convenient when data analysis is required; The third parameter needs to pass four parameters , the third parameter is the class object, and the fourth parameter is the OnDataTableListener, which is suitable for returning the Json data of the DataTable type to directly analyze the data in this class, and call back to the business layer with a generic non-empty List .

Another GetResultInfo2() method to be completed is temporarily annotated. This method needs to complete the direct analysis of the Json data of the DataSet type, and call back to the business layer a HashMap<String, ArrayList<T>> (where the key is TableName, value For the parsed DataTable), the reason why it has not been completed is that multiple different generics are needed to standardize the type of List, which has not been adjusted temporarily (or use variable parameters?). If you have a good solution, please inform...

For the analysis of DataTable and DataSet, please refer to the previous article: Android docking with .net (C#) server (1): Parsing Json data of DataTable and DataSet types

 

Guess you like

Origin blog.csdn.net/beita08/article/details/114685999