目录:
- 功能需求–分析
- 具体实现–动手
- 踩过的坑
- 表单类型的请求–造轮子
- 总结
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 框架写的,那么真的是太幸运了,如果不是,学会框架间的对应转换也是不错的方法。
经过 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日