〇、前言
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数据