我的Android网络框架之旅(三)
来源:互联网 发布:保险网络推广 编辑:程序博客网 时间:2024/05/17 22:51
在编写网络框架的过程,数据报文的传输是最繁琐难懂的一节,因为它涉及到了较多的网络通信协议的内容。了解其中的通信细节对我们把握整个网络通讯过程至关重要,这也是为什么要自定义网络框架的原因。使用httpUrlConnection搭建网络通讯模块,不仅可以灵活的控制网络参数格式,使用自定义的HTTPS证书,我们还可以自由的截取整个传输过程中的输入输出流,完全不需要使用第三方的网络抓包工具来进行数据联调。
在这里,我们根据业务需求把网络请求分为以下三块
1.普通的get请求
2.表单提交请求
3.上传文件的请求
1.Get请求
网络get请求大家都知道,是在我们的url后面跟上key=value这样的形式进行传统数据传递,使用?与url进行分离。一般的get请求都是向服务器请求一个状态或者索引,故不需要隐藏用户信息,这里我们的get请求也是直接拼接在url地址后面进行传递。
/** * @param params 参数列表 * @return * @date 2015/12/28 13:55 * @author ZhongR * @description 封装Get请求的参数 */ protected String packageTextParamsForGet(HashMap<String, String> params) { if (params == null) { throw new NullPointerException( "packageTextParamsForGet fail,the params 'params' is null "); } StringBuilder stringBuilder = new StringBuilder(); // 依次取出params里面的参数进行拼接 for (Entry<String, String> entry : params.entrySet()) { stringBuilder.append(entry.getKey() + "=" + entry.getValue() + "&"); } String tmp = stringBuilder.toString(); if (tmp.endsWith("&")) { tmp = "?" + tmp; int len = tmp.length(); tmp = tmp.substring(0, len - 1); } LogUtil.d(TAG, "packageTextParamsForGet:" + tmp); return tmp; }
这样把给packageTextParamsForGet返回的参数进行地址拼接就可以算完成啦,很轻松吧!
2.Post提交表单
直接上代码,注释已经写得很清楚了
/** * @param params * @return * @date 2015/12/28 13:55 * @author ZhongR * @description 封装Post请求的参数,(提交表单) */ protected byte[] PackagePostForm(HashMap<String, String> params) { if (params == null) { throw new NullPointerException( "packageTextParamsForPost fail,the params 'params' or 'boundary' is null "); } StringBuffer stringParams = new StringBuffer(); // 表单参数与get形式一样 Iterator it = params.entrySet().iterator(); while (it.hasNext()) { Entry element = (Entry) it.next(); stringParams.append(element.getKey()); stringParams.append("="); stringParams.append(element.getValue()); stringParams.append("&"); } return stringParams.toString().getBytes(); }
上面的Post请求是单纯的表单提交,比如我们最常见的登陆模块,提交的数据是以键值对的形式传输的比如 username=xxx&password=xxx这样的格式,看起来和get请求很像,只不过是存放在了post数据包中。这里别忘了在报头指定你的消息类型
connection.setRequestProperty("Content-Type","application/x-www-form-urlencoded");
3.Post上传文件
重头戏来啦!post请求是支持文件上传,表单提交,文件+表单一起提交这样的形式的。所以在这里,我们的请求头中一定要设置connection.setRequestProperty("ContentType","multipart/form-data"+ ";boundary=" + boundary);
boundary学名叫做“分割边界”,我们在ContentType中指定了随机生成的字符串为boundary,在后面的拼接中,boundary会在中间起到分隔数据的作用。
下面是表单上传
/** * @param params 参数列表 * @param boundary 分隔符 * @return * @date 2015/12/28 13:55 * @author ZhongR * @description 封装Post请求的参数 */ protected byte[] packageTextParamsForPost(HashMap<String, String> params, String boundary) { if (params == null || TextUtils.isEmpty(boundary)) { throw new NullPointerException( "packageTextParamsForPost fail,the params 'params' or 'boundary' is null "); } StringBuilder stringBuilder = new StringBuilder(); // 依次取出params里面的参数进行拼接 for (Entry<String, String> entry : params.entrySet()) { stringBuilder.append(PREFIX); stringBuilder.append(boundary); stringBuilder.append(LINE_END); stringBuilder.append("Content-Disposition: form-data; name=\"" + entry.getKey() + "\"" + LINE_END); stringBuilder.append( "Content-Type: text/plain; charset=" + CHARSET + LINE_END); stringBuilder.append("Content-Transfer-Encoding: 8bit" + LINE_END); stringBuilder.append(LINE_END); stringBuilder.append(entry.getValue()); stringBuilder.append(LINE_END); } LogUtil.d(TAG, "packageTextParamsForPost:" + stringBuilder.toString()); return stringBuilder.toString().getBytes(); }
上面那段代码看晕了?没关系,总结下来,我们传递了一个hashMap,也就是一个表单,输出的body如下:
Content-Type:multipart/form-data; boundary=Op0M2780150KrHh_G0XUkrBL9r8OPsRPmJ7Content-Disposition: form-data;name="userAccount"Content-Type: text/plain; charset=UTF-8Content-Transfer-Encoding: 8bitxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxOp0M2780150KrHh_G0XUkrBL9r8OPsRPmJ7
xxxxx就是你要传递的键值对,数据格式在上一节已经描述过了,boundary是我们在Content-Type中指定的Op0M2780150KrHh_G0XUkrBL9r8OPsRPmJ7,到这里我们就完成了表单数据,下面就是上传文件的
/** * @param dos 输出流 * @param file 文件对象 * @param boundary 边界 * @param key 附件key值 * @throws IOException * @date 2015/12/28 13:55 * @author ZhongR * @description 封装一条带附件的请求参数 */ private void packageFileParam(DataOutputStream dos, File file, String boundary, String key, AbstractLoader loader) throws IOException { if (dos == null || file == null || TextUtils.isEmpty(boundary) || TextUtils.isEmpty(key) || loader == null) { LogUtil.e(TAG, " the params 'dos' 、'file'、'boundary' or 'key' has exist null "); return; } if (!file.exists()) { LogUtil.e(TAG, "packageFileParam fail, the file isn`t exist"); return; } // 增加文件开始结束标识 StringBuffer stringBuilder = new StringBuffer(); stringBuilder.append(PREFIX); stringBuilder.append(boundary); stringBuilder.append(LINE_END); // 这里重点注意: name里面的值为服务器端需要key 只有这个key 才可以得到对应的文件 filename是文件的名字,包含后缀名 stringBuilder.append("Content-Disposition: form-data; name=\"" + key + "\"; filename=\"" + file.getName() + "\"" + LINE_END); stringBuilder.append("Content-Type: application/octet-stream; charset=" + CHARSET + LINE_END); stringBuilder.append(LINE_END); dos.write(stringBuilder.toString().getBytes()); InputStream is = new FileInputStream(file); byte[] bytes = new byte[1024]; // 每次上传1024Byte int len = 0; float total = 0.0f; float length = file.length(); while ((len = is.read(bytes)) != -1) { // 循环写入数据 dos.write(bytes, 0, len); total += len; float tempProgress=total / length; if(tempProgress<0.01) tempProgress=0.01f; // 向UI回调当前上传进度 loader.doProgress(tempProgress); } is.close(); dos.write(LINE_END.getBytes()); }
这就是我们上传文件时候的封装,注意在上传前指定Content-Disposition和Content-Type,分别用于描述我们的上传文件信息和数据类型,结合上面的表单提交,完整输出如下:
User-Agent: Mozilla/5.0 Accept-Language: zh-cn,zh;q=0.5Accept-Charset: GBK,utf-8;q=0.7,*;q=0.7Connection: keep-aliveContent-Length: 51409Content-Type:multipart/form-data; boundary=Op0M2780150KrHh_G0XUkrBL9r8OPsRPmJ7Host: xxxxxxxx--Op0M2780150KrHh_G0XUkrBL9r8OPsRPmJ7Content-Disposition: form-data;name="userAccount"Content-Type: text/plain; charset=UTF-8Content-Transfer-Encoding: 8bitxxxxxxx--Op0M2780150KrHh_G0XUkrBL9r8OPsRPmJ7Content-Disposition: form-data;name="userAvatar"; filename="photo.jpg"Content-Type: application/octet-streamContent-Transfer-Encoding: binaryxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx--Op0M2780150KrHh_G0XUkrBL9r8OPsRPmJ7--
这样就是一次标准的post请求输出啦!对应不同的业务需求,我们还会在报头添加不同的参数,比如X-Requested-With,App-Agent,User-Agent,Cookie等等,具体的参数要求,你得根据你们的后台API文档来定义。
好不容易写完了网络请求的组成,最后肯定有人会问,不是说好了不用网络抓包了吗,对呀,干货在这里我推荐大家看看faceBook团队出品的调试神器stetho,进行网络抓包so esay!妈妈再也不用担心我联调接口的时候找不到问题啦!
- 我的Android网络框架之旅(三)
- 我的Android网络框架之旅(一)
- 我的Android网络框架之旅(二)
- 我的Android网络框架之旅(四)
- Android网络请求框架之Retrofit(三)
- Android网络框架之Http请求的分发与执行(三)
- 我的Android之旅(三)----基本控件复习
- Android之Volley框架加载网络图片的三种方式
- 我的CTF之旅(三)
- Android 之我的开源框架
- 我的Android之旅(二十四)-------数据库ORMLite框架总结
- Android 开发框架之okhttp 网络框架的学习
- Android长连接--网络框架之mina框架的解析
- Android框架学习之RxJava(三)
- 我的Android NDK之旅(三),使用cmake来构建Jni
- android 高级之旅 (三 )picasso、glide、imageloader等几个常用的图片加载框架
- Android之Http网络编程(三)
- 超好用的网络抓包框架(Windivert)之三(实例一)
- 设计模式
- JAVA多线程实现的三种方式
- linux下如何安装tomcat
- ACM--欧拉函数--mdd的烦恼
- Vulkan Device and Queue --2
- 我的Android网络框架之旅(三)
- 约瑟夫问题(循环单链表求解)
- HTTP Status 404错误
- C++预处理命令
- 【重要】本科优秀毕业生应该掌握的知识或技能
- 《剑指offer》笔记(java)
- Java并发:原子变量和非阻塞同步机制
- Activity生命周期
- 二叉树搜索