android上实现multi-part上传
来源:互联网 发布:软件项目实施方案范文 编辑:程序博客网 时间:2024/05/17 02:12
最近做http的图片上传,约定好用http的multipart实现。遇到的一些坑,网上查不到不到解决方案,自己解决了,在这儿记录下来。
android自带的http实现有两种,一个是java的HttpUrlConnection,一个是apache的HttpClient,在项目里用的是后者,因为感觉它的封装程度使用起来更方便些。
之前使用的是一个国人写的基于HttpClient的框架,用着各种坑。最近接入七牛的sdk时发现人家用的是android-async-http,于是去github找到了这个,看起来挺不错的,决定换之。幸好之前觉得国人那个框架不靠谱,自己封装了个中间层,换库代价没那么大。
大坑仍然是multi-part上传图片,因为上传前有时需要压缩图片,所以传的参数是InputStream而不是File。虽然也可以把InputStream转为File或bytes,但感觉那样太蛋疼了,还是想直接传,于是问题来了。
发现,这个库上传图片的InputStream时,进度马上走到100%,像是被直接读进缓存了。这个当然不行,进度条完全就一摆设,上网查到了说法:
https://github.com/loopj/android-async-http/issues/118
原来是这个库的缺陷,有个家伙把文件上传进度失效的问题搞了,但是InputStream的没搞,虽然自己搞完后感觉人家那种搞法并没什么卵用。求人不如求己,决定自己改下这个库。
multipart的实现主要在SimpleMultipartEntity里面,isChunked()直接返回的false,十分怀疑这个能否实现文件的上传进度。学人家解决文件上传的方式处理了一下InputStream,结果进度还是直接到100%,所以这种方法并没用。七牛能实现进度,是因为他们自己把文件分成了多块,用多个请求分别上传,对于我并不适用。
RequestParams:public HttpEntity getEntity(ResponseHandlerInterface progressHandler) throws IOException { if (useJsonStreamer) { return createJsonStreamerEntity(progressHandler); } else if (!forceMultipartEntity && streamParams.isEmpty() && fileParams.isEmpty() && fileArrayParams.isEmpty()) { return createFormEntity(); } else { return createMultipartEntity(progressHandler); } }找到RequestParams,发现其实他的成员变量都是protected,不过createMultipartEntity是private的,只能改为protected再重写。
public class IhpRequestParams extends RequestParams { private static final Charset UTF_8 = Charset.forName("UTF-8"); @Override protected HttpEntity createMultipartEntity(final ResponseHandlerInterface progressHandler) throws IOException { MultipartEntity multipartEntity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE, null, UTF_8) { @Override public void writeTo(OutputStream outstream) throws IOException { super.writeTo(new CountingOutputStream(outstream, getTotalLength(), progressHandler)); } }; for (BasicNameValuePair nameValuePair : getParamsList()) { multipartEntity.addPart(new FormBodyPart(nameValuePair.getName(), new StringBody(nameValuePair.getValue(), UTF_8))); } for (ConcurrentHashMap.Entry<String, StreamWrapper> entry : streamParams.entrySet()) { multipartEntity.addPart(entry.getKey(), new InputStreamBody(entry.getValue().inputStream, entry.getValue().contentType, entry.getValue().name)); } return multipartEntity; } private int getTotalLength() { int length = 0; for (ConcurrentHashMap.Entry<String, StreamWrapper> entry : streamParams.entrySet()) { try { length += entry.getValue().inputStream.available(); } catch (IOException e) { e.printStackTrace(); } } return length; } public class CountingOutputStream extends FilterOutputStream { private long transferred; private long totalLength; private long progress; private ResponseHandlerInterface responseHandler; public CountingOutputStream(final OutputStream out, long totalLength, ResponseHandlerInterface responseHandler) { super(out); this.totalLength = totalLength; this.transferred = 0; this.responseHandler = responseHandler; } @Override public void write(int oneByte) throws IOException { super.write(oneByte); if (null == responseHandler) { return; } this.transferred ++; long progressNew = transferred * 100 / totalLength; if (progressNew > progress || progressNew >= 100) { progress = progressNew; responseHandler.sendProgressMessage(transferred, totalLength); } } }}引用了httpmime-4.1.3的MultipartEntity, 其实比较新的httpmime库已经推荐用MultipartEntityBuilder了,但没怎么去了解。反正用这个有问题还可能通过继承解决,用builder如果有问题的话继承都没得搞了。进度条的问题通过包装OutputStream解决。
本以为这样万事大吉了,没想到9个上传请求发出时进度条都很快走完,然后卡在100%,最后成功几个,挂掉几个。
查log发现,挂掉的都是SocetTimeout。联想进度条瞬间走完的表现,可以推测出,这些请求并没有按预期实现chunk上传。而是都先读进缓存,最后9个几百k的数据抢着上传,抢慢的就超时了。android-async-http默认的socket超时是10秒,改成30秒后果然好了很多。
在grepcode上找到了4.1.3的MultipartEntity的源码,发现其实只要传进了InputStreamPart,他是会使用chunk上传的。带着疑惑用fiddler抓包看了下,这个multipart上传确实是chunk编码的。
难道chunk编码的chunk size太大了?上网查了下,默认是2k,排除这个因素。
看AsynHttpClient代码,HttpConnectionsParams已经setTcpNoDelay为true,setSocketBufferSize为8192,应该也不存在数据留在缓冲区不发出去的问题。
决定看下writeTo的那个OutputStream的情况,断点调试发现是一个ChunkedOutputStream,去grepcode看源码,发现包了个SocketOutputBuffer,层层递进,最后能找到SocketImpl!
有了这个,就能直接确定socket的状况了。于是,通过这个OutputStream层层反射拿到了SocketImpl实例。断点调试一查看,send buffer的大小竟然有1m多。
妹的,HttpConnectionsParams的setSocketBufferSize方法根本不是我以为的效果啊。不过也没那么多精力再去细究了,反正httpclient的库应该是比较固定的,决定hack一下。
public static void setChunkedSocketSendBufferSize(ChunkedOutputStream out, int size) { SocketOutputBuffer socketOutputBuffer = getOutputBuffer(out); if (null == socketOutputBuffer) { return; } SocketImpl socketImpl = getSocket(socketOutputBuffer); if (null != socketImpl) { try { socketImpl.setOption(SocketOptions.SO_SNDBUF, size); } catch (SocketException e) { e.printStackTrace(); } } } public static SocketOutputBuffer getOutputBuffer(ChunkedOutputStream os) { Class c = ChunkedOutputStream.class; try { Field field = c.getDeclaredField("out"); field.setAccessible(true); return (SocketOutputBuffer) field.get(os); } catch (Exception e) { e.printStackTrace(); return null; } } public static SocketImpl getSocket(SocketOutputBuffer socketOutputBuffer) { try { OutputStream outputStream = getSocketOutputStream(socketOutputBuffer); Class c = outputStream.getClass(); Field field = c.getDeclaredField("socketImpl"); field.setAccessible(true); return (SocketImpl) field.get(outputStream); } catch (Exception e) { return null; } } public static OutputStream getSocketOutputStream(SocketOutputBuffer socketOutputBuffer) { try { Class c = AbstractSessionOutputBuffer.class; Field field = c.getDeclaredField("outstream"); field.setAccessible(true); return (OutputStream) field.get(socketOutputBuffer); } catch (Exception e) { return null; } }通过各种反射把socket的send buffer size改为8k, 终于成功实现了正常的上传进度,把自己都感动了。。。。。
- android上实现multi-part上传
- AFNetWorking POST Multi-Part Request 上传图片
- [iOS开发]AFNetWorking3.0 POST 上传 Multi-Part
- Android: Upload image or file using http POST multi-part
- Multi-threading Model: My Interesting Part in Android
- Send multi-part SMS
- Android--实现断点上传
- S3 Multi Part Upload 中断后续传
- 使用Part上传附件
- Android 筆記-Linux Kernel SMP (Symmetric Multi-Processors) 開機流程解析 Part(1) Boot-Rom與UBoot.
- Android 筆記-Linux Kernel SMP (Symmetric Multi-Processors) 開機流程解析 Part(2) Linux Kernel SMP zImage到st
- Android 筆記-Linux Kernel SMP (Symmetric Multi-Processors) 開機流程解析 Part(1) Boot-Rom與UBoot.
- Android 筆記-Linux Kernel SMP (Symmetric Multi-Processors) 開機流程解析 Part(2) Linux Kernel SMP zImage到st
- 在Android上通过模拟HTTP multipart/form-data请求协议信息实现图片上传
- 在 Android 上通过模拟 HTTP multipart/form-data 请求协议信息实现图片上传
- 在 Android 上通过模拟 HTTP multipart/form-data 请求协议信息实现图片上传
- 在 Android 上通过模拟 HTTP multipart/form-data 请求协议信息实现图片上传
- 如何实现Multi-Key Multi-Map
- tar 命令
- nodejs 初步使用以及实现文件上传的功能
- Letter Combinations of a Phone Number
- my_init()
- makefile教程
- android上实现multi-part上传
- [转载]IDEA新建web项目
- Java面试题
- 每日更新博客文章
- xxx cannot be resolved to a type
- VC++6.0调试篇:定位临界区(critical section)导致的死锁
- Codeforces 27E Number With The Given Amount Of Divisors (求约数个数为n的最小数)
- 业火的向日葵 新娘大作战
- 京东支付逻辑存在不安全因素