✓まえがき
現在、Android開発で最も一般的で主流のネットワークアクセス方法は、Httpプロトコルでのネットワーク通信にOkHttp / Retrofitを使用することですが、タイトルが示すように、この記事では一般的な主流のネットワークアクセス方法ではなく、WCFサービスを呼び出しますget WebServiceデータの方法は一部のプロジェクトで使用されているため、この方法を制限するためにタイトルに複数の属性を追加しました。
1つ、ksoap2-android
すでにAndroidシステムにあるHttpURLConnectionとは異なり、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を呼び出す非同期カプセル化クラスであり、次のように、オブジェクト管理にシングルトンデザインパターン、スレッドスイッチングにハンドラー、スレッド管理にスレッドプールを使用します。
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-------------------------------------*/
}
コード内のコメントは比較的明確です。その中で、このクラスには3つのオーバーロードされたGetResultInfo()メソッドが含まれています。最初のメソッドは2つのパラメーターのみを渡します。これは、応答なしでサーバーに要求を送信するだけでよい状況に適しています。使用シナリオはほとんどありません。; 2番目のパラメーターは3つのパラメーターを渡す必要があり、3番目のパラメーターはOnMessageListenerです。つまり、最下層でJsonデータ分析は実行されず、返されたデータはビジネス層に直接コールバックされます。基本的にすべてのシナリオを使用できますが、データ分析が必要な場合はあまり便利ではありません。3番目のパラメーターは4つのパラメーターを渡す必要があり、3番目のパラメーターはクラスオブジェクトであり、4番目のパラメーターはOnDataTableListenerであり、Jsonデータを返すのに適しています。 DataTableタイプのを使用して、このクラスのデータを直接分析し、一般的な空でないリストを使用してビジネスレイヤーにコールバックします。
完了する別のGetResultInfo2()メソッドに一時的に注釈が付けられます。このメソッドは、DataSetタイプのJsonデータの直接分析を完了し、ビジネスレイヤーにHashMap <String、ArrayList <T >>(キーがis TableName、value解析されたDataTableの場合)、完了していない理由は、一時的に調整されていない(または変数パラメーターを使用している)リストのタイプを標準化するために複数の異なるジェネリックが必要なためです。良い解決策、お知らせください...
DataTableとDataSetの分析については、前の記事を参照してください:.net(C#)サーバーとのAndroidドッキング(1):DataTableおよびDataSetタイプのJsonデータの解析