Volley 上传图片

在开发当中,我们常常需要实现文件上传,比较常见的就是图片上传,比如修改个头像什么的。但是这个功能在Android和iOS中都没有默认的实现类,对于Android我们可以使用Apache提供的HttpClient.jar来实现这个功能,其中依赖的类就是Apache的httpmime.jar中的MultipartEntity这个类。我就是要实现一个文件上传功能,但是我还得下载一个jar包,而这个jar包几十KB,这尼玛仿佛并非人间!今天我们就来自己实现文件上传功能,并且弄懂它们的原理。

在上一篇文章HTTP POST请求报文格式分析与Java实现文件上传中我们介绍了HTTP POST报文格式,如果有对POST报文格式不了解的同学可以先阅读这篇文章。


自定义实现MultipartEntity

我们知道,使用网络协议传输数据无非就是要遵循某个协议,我们在开发移动应用时基本上都是使用HTTP协议。HTTP协议说白了就是基于TCP的一套网络请求协议,你根据该协议规定的格式传输数据,然后服务器返回给你数据。你的协议参数要是传递错了,那么服务器只能给你返回错误。

这跟间谍之间对暗号有点相似,他们有一个规定的暗号,双方见面,A说: 天王盖地虎,B对: 宝塔镇河妖。对上了,说事;对不上,弄死这B。HTTP也是这样的,在HTTP请求时添加header和参数,服务器根据参数进行解析。形如 :

?
1
2
3
4
5
6
7
POST /api/feed/ HTTP/ 1.1
这里是header数据
 
--分隔符
参数 1
--分隔符
参数 2
只要根据格式来向服务器发送请求就万事大吉了!下面我们就来看MultipartEntity的实现:

?
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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
public class MultipartEntity implements HttpEntity {
 
     private final static char [] MULTIPART_CHARS = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
             .toCharArray();
     /**
      * 换行符
      */
     private final String NEW_LINE_STR = "\r\n" ;
     private final String CONTENT_TYPE = "Content-Type: " ;
     private final String CONTENT_DISPOSITION = "Content-Disposition: " ;
     /**
      * 文本参数和字符集
      */
     private final String TYPE_TEXT_CHARSET = "text/plain; charset=UTF-8" ;
 
     /**
      * 字节流参数
      */
     private final String TYPE_OCTET_STREAM = "application/octet-stream" ;
     /**
      * 二进制参数
      */
     private final byte [] BINARY_ENCODING = "Content-Transfer-Encoding: binary\r\n\r\n" .getBytes();
     /**
      * 文本参数
      */
     private final byte [] BIT_ENCODING = "Content-Transfer-Encoding: 8bit\r\n\r\n" .getBytes();
 
     /**
      * 分隔符
      */
     private String mBoundary = null ;
     /**
      * 输出流
      */
     ByteArrayOutputStream mOutputStream = new ByteArrayOutputStream();
 
     public MultipartEntity() {
         this .mBoundary = generateBoundary();
     }
 
     /**
      * 生成分隔符
      *
      * @return
      */
     private final String generateBoundary() {
         final StringBuffer buf = new StringBuffer();
         final Random rand = new Random();
         for ( int i = 0 ; i < 30 ; i++) {
             buf.append(MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)]);
         }
         return buf.toString();
     }
 
     /**
      * 参数开头的分隔符
      *
      * @throws IOException
      */
     private void writeFirstBoundary() throws IOException {
         mOutputStream.write(( "--" + mBoundary + "\r\n" ).getBytes());
     }
 
     /**
      * 添加文本参数
      *
      * @param key
      * @param value
      */
     public void addStringPart( final String paramName, final String value) {
         writeToOutputStream(paramName, value.getBytes(), TYPE_TEXT_CHARSET, BIT_ENCODING, "" );
     }
 
     /**
      * 将数据写入到输出流中
      *
      * @param key
      * @param rawData
      * @param type
      * @param encodingBytes
      * @param fileName
      */
     private void writeToOutputStream(String paramName, byte [] rawData, String type,
             byte [] encodingBytes,
             String fileName) {
         try {
             writeFirstBoundary();
             mOutputStream.write((CONTENT_TYPE + type + NEW_LINE_STR).getBytes());
             mOutputStream
                     .write(getContentDispositionBytes(paramName, fileName));
             mOutputStream.write(encodingBytes);
             mOutputStream.write(rawData);
             mOutputStream.write(NEW_LINE_STR.getBytes());
         } catch ( final IOException e) {
             e.printStackTrace();
         }
     }
 
     /**
      * 添加二进制参数, 例如Bitmap的字节流参数
      *
      * @param key
      * @param rawData
      */
     public void addBinaryPart(String paramName, final byte [] rawData) {
         writeToOutputStream(paramName, rawData, TYPE_OCTET_STREAM, BINARY_ENCODING, "no-file" );
     }
 
     /**
      * 添加文件参数,可以实现文件上传功能
      *
      * @param key
      * @param file
      */
     public void addFilePart( final String key, final File file) {
         InputStream fin = null ;
         try {
             fin = new FileInputStream(file);
             writeFirstBoundary();
             final String type = CONTENT_TYPE + TYPE_OCTET_STREAM + NEW_LINE_STR;
             mOutputStream.write(getContentDispositionBytes(key, file.getName()));
             mOutputStream.write(type.getBytes());
             mOutputStream.write(BINARY_ENCODING);
 
             final byte [] tmp = new byte [ 4096 ];
             int len = 0 ;
             while ((len = fin.read(tmp)) != - 1 ) {
                 mOutputStream.write(tmp, 0 , len);
             }
             mOutputStream.flush();
         } catch ( final IOException e) {
             e.printStackTrace();
         } finally {
             closeSilently(fin);
         }
     }
 
     private void closeSilently(Closeable closeable) {
         try {
             if (closeable != null ) {
                 closeable.close();
             }
         } catch ( final IOException e) {
             e.printStackTrace();
         }
     }
 
     private byte [] getContentDispositionBytes(String paramName, String fileName) {
         StringBuilder stringBuilder = new StringBuilder();
         stringBuilder.append(CONTENT_DISPOSITION + "form-data; name=\"" + paramName + "\"" );
         // 文本参数没有filename参数,设置为空即可
         if (!TextUtils.isEmpty(fileName)) {
             stringBuilder.append( "; filename=\""
                     + fileName + "\"" );
         }
 
         return stringBuilder.append(NEW_LINE_STR).toString().getBytes();
     }
 
     @Override
     public long getContentLength() {
         return mOutputStream.toByteArray().length;
     }
 
     @Override
     public Header getContentType() {
         return new BasicHeader( "Content-Type" , "multipart/form-data; boundary=" + mBoundary);
     }
 
     @Override
     public boolean isChunked() {
         return false ;
     }
 
     @Override
     public boolean isRepeatable() {
         return false ;
     }
 
     @Override
     public boolean isStreaming() {
         return false ;
     }
 
     @Override
     public void writeTo( final OutputStream outstream) throws IOException {
         // 参数最末尾的结束符
         final String endString = "--" + mBoundary + "--\r\n" ;
         // 写入结束符
         mOutputStream.write(endString.getBytes());
         //
         outstream.write(mOutputStream.toByteArray());
     }
 
     @Override
     public Header getContentEncoding() {
         return null ;
     }
 
     @Override
     public void consumeContent() throws IOException,
             UnsupportedOperationException {
         if (isStreaming()) {
             throw new UnsupportedOperationException(
                     "Streaming entity does not implement #consumeContent()" );
         }
     }
 
     @Override
     public InputStream getContent() {
         return new ByteArrayInputStream(mOutputStream.toByteArray());
     }
}

用户可以通过addStringPart、addBinaryPart、addFilePart来添加参数,分别表示添加字符串参数、添加二进制参数、添加文件参数。在MultipartEntity中有一个ByteArrayOutputStream对象,先将这些参数写到这个输出流中,当执行网络请求时,会执行
?
1
writeTo( final OutputStream outstream)
方法将所有参数的字节流数据写入到与服务器建立的TCP连接的输出流中,这样就将我们的参数传递给服务器了。当然在此之前,我们需要按照格式来向ByteArrayOutputStream对象中写数据。
例如我要向服务器发送一个文本、一张bitmap图片、一个文件,即这个请求有三个参数。代码如下 :

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
MultipartEntity multipartEntity = new MultipartEntity();
// 文本参数
multipartEntity.addStringPart( "type" , "我的文本参数" );
Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.thumb);
// 二进制参数
multipartEntity.addBinaryPart( "images" , bitmapToBytes(bmp));
// 文件参数
multipartEntity.addFilePart( "images" , new File( "storage/emulated/0/test.jpg" ));
 
// POST请求
HttpPost post = new HttpPost( "url" ) ;
// 将multipartEntity设置给post
post.setEntity(multipartEntity);
// 使用http client来执行请求
HttpClient httpClient = new DefaultHttpClient() ;
httpClient.execute(post) ;

MultipartEntity的输出格式会成为如下的格式 :

?
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
POST /api/feed/ HTTP/ 1.1
Content-Type: multipart/form-data; boundary=o3Fhj53z-oKToduAElfBaNU4pZhp4-
User-Agent: Dalvik/ 1.6 . 0 (Linux; U; Android 4.4 . 4 ; M040 Build/KTU84P)
Host: www.myhost.com
Connection: Keep-Alive
Accept-Encoding: gzip
Content-Length: 168518
 
--o3Fhj53z-oKToduAElfBaNU4pZhp4-
Content-Type: text/plain; charset=UTF- 8
Content-Disposition: form-data; name= "type"
Content-Transfer-Encoding: 8bit
 
This my type
--o3Fhj53z-oKToduAElfBaNU4pZhp4-
Content-Type: application/octet-stream
Content-Disposition: form-data; name= "images" ; filename= "no-file"
Content-Transfer-Encoding: binary
 
这里是bitmap的二进制数据
--o3Fhj53z-oKToduAElfBaNU4pZhp4-
Content-Type: application/octet-stream
Content-Disposition: form-data; name= "file" ; filename= "storage/emulated/0/test.jpg"
Content-Transfer-Encoding: binary
 
这里是图片文件的二进制数据
--o3Fhj53z-oKToduAElfBaNU4pZhp4---

看到很熟悉吧,这就是我们在文章开头时提到的POST报文格式。没错!HttpEntity就是负责将参数构造成HTTP的报文格式,文本参数该是什么格式、文件该是什么格式,什么类型,这些格式都是固定的。构造完之后,在执行请求时会将http请求的输出流通过writeTo(OutputStream) 函数传递进来,然后将这些参数数据全部输出到http输出流中即可。
明白了这些道理,看看代码也就应该明白了吧。


Volley中实现文件上传

Volley是Google官方推出的网络请求库,这个库很精简、优秀,但是他们也没有默认添加文件上传功能的支持。我们今天就来自定义一个Request实现文件上传功能,还是需要借助上面的MultipartEntity类,下面看代码:
?
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
/**
  * MultipartRequest,返回的结果是String格式的
  * @author mrsimple
  */
public class MultipartRequest extends Request<string> {
 
     MultipartEntity mMultiPartEntity = new MultipartEntity();
 
     public MultipartRequest(HttpMethod method, String url,
             Map<string, string= "" > params, RequestListener<string> listener) {
         super (method, url, params, listener);
     }
 
     /**
      * @return
      */
     public MultipartEntity getMultiPartEntity() {
         return mMultiPartEntity;
     }
 
     @Override
     public String getBodyContentType() {
         return mMultiPartEntity.getContentType().getValue();
     }
 
     @Override
     public byte [] getBody() {
 
         ByteArrayOutputStream bos = new ByteArrayOutputStream();
         try
         {
             // 将mMultiPartEntity中的参数写入到bos中
             mMultiPartEntity.writeTo(bos);
         } catch (IOException e) {
             Log.e( "" , "IOException writing to ByteArrayOutputStream" );
         }
         return bos.toByteArray();
     }
 
     @Override
     protected void deliverResponse(String response) {
         mListener.onResponse(response);
     }
 
     @Override
     protected Response<string> parseNetworkResponse(NetworkResponse response) {
         String parsed;
         try {
             parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
         } catch (UnsupportedEncodingException e) {
             parsed = new String(response.data);
         }
         return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
     }
 
}
</string></string></string,></string>

使用示例代码:
?
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
MultipartRequest multipartRequest = new MultipartRequest(HttpMethod.POST,
         "http://服务器地址" ,
         null , new RequestListener<string>() {
 
             @Override
             public void onStart() {
                 // TODO Auto-generated method stub
 
             }
 
             @Override
             public void onComplete( int stCode, String response, String errMsg) {
 
             }
         });
// 获取MultipartEntity对象
MultipartEntity multipartEntity = multipartRequest.getMultiPartEntity();
multipartEntity.addStringPart( "content" , "hello" );
//
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.thumb);
// bitmap参数
multipartEntity.addBinaryPart( "images" , bitmapToBytes(bitmap));
// 文件参数
multipartEntity.addFilePart( "images" , new File( "storage/emulated/0/test.jpg" ));
 
// 构建请求队列
RequestQueue queue = RequestQueue.newRequestQueue(Context);
// 将请求添加到队列中
queue.addRequest(multipartRequest);</string>

效果图

这是我post到我的应用的截图 : 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值