Android 与 WebServiceAQ 的交互

2019-05-06

目录:

  1. 功能需求–分析
  2. 具体实现–动手
  3. 踩过的坑
  4. 表单类型的请求–造轮子
  5. 总结

1. 功能需求–分析


文字描述:Android 与 WebServiceAQ 的交互,提交用户填写的数据和选择的图片文件到 server , 解析返回的数据做判断是否提交成功。

具体展示:
拿到的 API 文档( 已修改原始数据,这是模拟数据):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
SOAP 1.1
POST /WebServiceAQ.asmx HTTP/1.1
Host: 10.100.101.100 //模拟数据
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "http://tempuri.org/complaint"

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<complaint xmlns="http://tempuri.org/" />
</soap:Body>
</soap:Envelope>
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: length

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<complaintinfoResponse xmlns="http://tempuri.org/" />
</soap:Body>
</soap:Envelope>

POST /WebServiceAQ.asmx/complaint HTTP/1.1
Host: 10.100.101.100 //模拟数据
Content-Type: application/x-www-form-urlencoded
Content-Length: length

HTTP/1.1 200 OK

思考实现方式:
[1] WebServiceAQ 是什么?
根据搜索引擎提供的知识得到的结论:WebServiceAQ 是通过 URL ,指定某一个方法名,发出请求,站点的这个方法,接收请求后,根据传入的参数做一些处理,然后将处理后的结果以 xml 的形式返回,Android 解析数据显示或者做其他操作。
[2] 与 Socket 的区别在哪?
第一, Socket是基于TCP/IP的传输层协议。 Webservice是基于HTTP协议传输数据,采用了基于http的soap协议传输数据。
第二,Socket接口通过流传输,不支持面向对象。 Webservice 接口支持面向对象,最终webservice将对象进行序列化后通过流传输。 Webservice采用soap协议进行通信,不需专门针对数据流的发送和接收进行处理,是一种跨平台的面向对象远程调用技术。
第三,Socket适用于高性能大数据的传输,传输的数据需要手动处理,socket通信的接口协议需要自定义。–此段解释来源

2. 具体实现–动手


[1] postman 测试:
对于与 server 的交互一般可以先用 postman 测试成功了,点击 code ,选择 java okhttp 后查看代码,如果项目刚好是用 okhttp 框架写的,那么真的是太幸运了,如果不是,学会框架间的对应转换也是不错的方法。

image.png

经过 postman 的测试,可以得到以下几点要注意的:
1.content-type : multipart/form-data
2.boundary=—-WebKitFormBoundary7MA4YWxkTrZu0gW
3.url
4.请求体

[2] Android 的实现
首先是请求体部分:

name=\"json\"\r\n\r\n{\"Attachment_1\":\"open_screen_bg_img_1317.png\",\"Attachment_2\":\"open_screen_bg_img_1733.png\",\"Attachment_3\":\"open_screen_bg_img_1325.png\",\"Attachment_4\":\"open_screen_bg_img_1329.png\",\"Attachment_5\":\"open_screen_bg_img_1525.png\",\"CCID\":\"A\",\"CName\":\"测试\",\"DTNuber\":\"9875652358\",\"Demands\":\"testing\",\"EMail\":\"19769556@qq.com\",\"FDP\":\"AAT\",\"FDate\":\"2019-5-16\",\"FName\":\"AQ1056\",\"Message\":\"testing\",\"ODP\":\"AAT\",\"PhoneNo\":\"1597698258\",\"SDP\":\"AMS\",\"CTID\":1,\"DTID\":1,\"FCCID\":1}

这是 json 格式的数据,需要把 Object -> JSONObject,构建 json 表单.

1
2
3
complaintInfo = new ComplaintInfo(FCCID, CCID, CTID, CName, DTID, DTNuber, FName, FDate, PhoneNo, EMail, ODP, SDP, FDP, Message, Demands, Attachment_1, Attachment_2, Attachment_3, Attachment_4, Attachment_5);
Gson gson = new Gson();
final String json = gson.toJson(complaintInfo);

其次是用户选择的文件部分(5张图片):

Content-Disposition: form-data; name=\"Attachment_1\"; filename=\"a.jpeg\"\r\nContent-Type: image/jpeg\r\n\r\n\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"Attachment_2\"; filename=\"p.jpg\"\r\nContent-Type: image/jpeg\r\n\r\n\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"Attachment_3\"; filename=\"g.jpeg\"\r\nContent-Type: image/jpeg\r\n\r\n\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"Attachment_4\"; filename=\"h.jpeg\"\r\nContent-Type: image/jpeg\r\n\r\n\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"Attachment_5\"; filename=\"a.jpeg\"\r\nContent-Type: image/jpeg

需要获取文件名,转换成 file -> byte[] ,构建图片表单.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
[1、获取文件名]
private List<String> path = new ArrayList<>();
String pathImage = cursor.getString(cursor
.getColumnIndex(MediaStore.Images.Media.DATA));
path.add(pathImage);
Attachment_1 = path.get(i).substring(path.get(i).lastIndexOf("/") + 1, path.get(i).length());

[2、转换 file -> byte[] ]
/**
* @param fileName 文件名
* @return byte[]
* @throws Exception
*/
public static byte[] readStream(String fileName) throws Exception {
FileInputStream inStream = new FileInputStream(new File(fileName));
byte[] buffer = new byte[1024];
int len = -1;
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
while ((len = inStream.read(buffer)) != -1) {
outStream.write(buffer, 0, len);
}
byte[] data = outStream.toByteArray();
outStream.close();
inStream.close();
return data;
}

[3、构建图片表单]
files[0] = new FormFile(Attachment_1, ImageDispose.readStream(path.get(0)), "Attachment_1", "multipart/form-data");

其中 FormFile 类
/**
* Author : Emily CH
* Date : 2019/5/27 上午9:38
* UpdateUser : XXX
* UpdateDate : 2019/5/27 上午9:38
*/
public class FormFile {

/* 上传文件的数据 */
private byte[] data;
/* 文件名称 */
private String filname;
/* 表单字段名称*/
private String formname;
/* 内容类型 */
private String contentType = "multipart/form-data"; //需要查阅相关的资料

public FormFile(String filname, byte[] data, String formname, String contentType) {
this.data = data;
this.filname = filname;
this.formname = formname;
if(contentType!=null) this.contentType = contentType;
}

public byte[] getData() {
return data;
}

public void setData(byte[] data) {
this.data = data;
}

public String getFilname() {
return filname;
}

public void setFilname(String filname) {
this.filname = filname;
}

public String getFormname() {
return formname;
}

public void setFormname(String formname) {
this.formname = formname;
}

public String getContentType() {
return contentType;
}

public void setContentType(String contentType) {
this.contentType = contentType;
}

}

最后,请求体准备好,即可以发送请求了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103

import com.jiuair.booking.model.FormFile;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;

/**
* Author : Emily CH
* Date : 2019/5/27 上午9:39
* UpdateUser : XXX
* UpdateDate : 2019/5/27 上午9:39
*/
public class HttpService {

public HttpService() {
}

public void postHttpImageRequest(final String netWorkAddress, final Map<String, Object> params, final FormFile[] files,
final HttpCallBackListener listener) {
new Thread(new Runnable() {
@Override
public void run() {
HttpURLConnection connection = null;
try {
String BOUNDARY = "----WebKitFormBoundary7MA4YWxkTrZu0gW"; //数据分隔线
String MULTIPART_FORM_DATA = "multipart/form-data";

URL url = new URL(HttpClientUtil.BASEURL_ADD + netWorkAddress);
connection = (HttpURLConnection) url.openConnection();
connection.setDoInput(true);//允许输入
connection.setDoOutput(true);//允许输出
connection.setUseCaches(false);//不使用Cache
connection.setRequestMethod("POST");
connection.setRequestProperty("Connection", "Keep-Alive");
connection.setRequestProperty("Charset", "UTF-8");
connection.setRequestProperty("Content-Type", MULTIPART_FORM_DATA + "; boundary=" + BOUNDARY);

StringBuilder sb = new StringBuilder();

//上传的表单参数部分
for (Map.Entry<String, Object> entry : params.entrySet()) {//构建表单字段内容
sb.append("--");
sb.append(BOUNDARY);
sb.append("\r\n");
sb.append("Content-Disposition: form-data; name=\"" + entry.getKey() + "\"\r\n\r\n");
sb.append(entry.getValue());
sb.append("\r\n");
}
DataOutputStream outStream = new DataOutputStream(connection.getOutputStream());
outStream.write(sb.toString().getBytes());//发送表单字段数据
//上传的文件部分
for (FormFile file : files) {
StringBuilder split = new StringBuilder();
split.append("--");
split.append(BOUNDARY);
split.append("\r\n");
split.append("Content-Disposition: form-data; name=\"" + file.getFormname() + "\"; filename=\"" + file.getFilname() + "\"\r\n");
split.append("Content-Type: " + file.getContentType() + "\r\n\r\n");
outStream.write(split.toString().getBytes());
outStream.write(file.getData(), 0, file.getData().length);
outStream.write("\r\n".getBytes());
}
byte[] end_data = ("--" + BOUNDARY + "--\r\n").getBytes();//数据结束标志
outStream.write(end_data);
outStream.flush();
int cah = connection.getResponseCode();
if (cah != 200) throw new RuntimeException("请求url失败");
InputStream in = connection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}

if (listener != null) {
listener.onFinish(response.toString());
}
outStream.close();
} catch (Exception e) {
if (listener != null) {
listener.onError(e);
}
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
}).start();
}

public interface HttpCallBackListener {
void onFinish(String response);

void onError(Exception e);
}
}

在 Activity 中调用:

1
2
3
4
5
6
7
8
9
10
11
httpService.postHttpImageRequest("/complaintinfo", params, files, new HttpService.HttpCallBackListener() {

public void onFinish(final String response) {
// showCustomToast("ATG"+response);
}

public void onError(Exception e) {
// showCustomToast("提交失败!");
e.printStackTrace();
}
});

3. 踩过的坑


3.1 在用 postman 的 模版 code 时,复制黏贴的后果是 \ 变成 \\,然后\\r\\n 是不符合原意的,应该就是一个 . 但如果你的本意是想要显示出 \ 的,那么就应该是 triple \

1
2
3
4
斜杠“/”表示地址路径的下一级目录;
反斜杠“\”表示转义字符,例如:要做制表,可以输入:\t;做换行:\n等。
如果要输出反斜杠“\”也需要用转义字符:“\\”
在java中后台给前台传的时候如果我们的字符串中有“\”的话,我们可以通过string中的substring方法将‘\’转化为‘/’

3.2 尝试过 搜索引擎的前20条 android 与 webservice 交互的经验,全部跌入谷底,因为请求的数据类型等各种原因,但每次掉入都让我进一步揭开了 server 端的神秘面纱,很高兴我最终从深渊爬上来了,感谢前辈们的分享。

4. 表单类型的请求–造轮子


轮子 HttpServiceUtil

5. 总结


踩过无数的坑,才知道只有真的懂了,明白了每一句代码的意思,才能做到想改哪改哪,前辈们的知识精华哪是一朝一夕就能拿来用的,关键还不是靠自己?搬砖搬砖!!!

文章是 Android 面向需求开发系列中的一文,更多相关文章请关注。如若有什么问题,也可以通过扫描二维码发消息给我。转载请注明出处,谢谢!

二维码

作者:Emily CH
2019年5月7日