在stackoverflow上有个关于Android大文件上传的讨论。尝试着用Apache HttpComponents来解决。一番折腾,几多挫折,相当有趣。



环境准备

习惯了在Eclipse上开发,于是下载官方插件。早早升级到Eclipse4.3,当然是下载Google Update Site for Eclipse 4.3。这个插件包括GAE和GWT在内,需要挑选出Android的才好。

Java大文件上传(Android亦可)_第1张图片

安装好的插件没有Android的SDK的,只有个SDK Manager,用这个再去下载及安装。在我折腾时4.3刚刚发布,但是没有Intel x86 Atom模拟器,只好4.2.2和4.3都装了。
个人的选择是这样的:

  • SDK Platform是必须的
  • Sample和Source也最好有,本地查资料快些。
  • 由于是在windwos上开发的,所以那个Intel x86 Atom虚拟机是需要的。(怕是只需要这个吧。
    Java大文件上传(Android亦可)_第2张图片

最后Extras里面的选择,Windows的话基本上必选那个Intel x86 Emulator Accelerator(HAXM),不然模拟器会慢到“飘起”。即使装上也没快不到哪里去,我那2010年的笔记本上虽然也能跑起来,但风扇也会不停的尖叫着...据我“耳测”,大抵接近《魔兽世界》或《星际争霸II》的水平。我了去的!
Java大文件上传(Android亦可)_第3张图片

伴随着风扇的尖叫,折腾好那个简陋得不能再简陋的界面后,才发现我这个代码其实绝大部分无须Android的。(等我先去耳鸣一会儿...

Java大文件上传(Android亦可)_第4张图片



主要思路

基本上是将大文件拆成小块,读取,上传。当所有文件块上传完成后,合并。

  1. 读取大文件。考虑到I/O竞争只用单线程;考虑到内存采取分批读取。
    ByteBuffer bb = ByteBuffer.allocate(partSize);int bytesRead = fc.read(bb);if (bytesRead == -1) {break;}byte[] bytes = bb.array();parts.put(new Part(createFileName(fileName, i), bytes));
  2. 将读取的文件块上传。通常多个线程会好些,但是太多又不行,默认用5线程上传。
    Part part = parts.take();if (part == Part.NULL) {parts.add(Part.NULL);// notify others to stop.break;} else {uploader.upload(part);}
  3. 读取和上传用BlockingQueue通信,最通用的生产/消费者模式。
  4. 服务器在接收到全部文件块后,合并。
  5. 还需考虑处理传送失败时,重新尝试上传部分。
  6. 由于Android的问题(参考下面Invocation Exception),在Android上使用时需要用jarjar这个工具先处理下。

JDK5追加的java.util.concurrent功能,一直都是“纸上谈兵”,从来没真正使用过。这次好好折腾了下...

代码(优化中)上传到GitHub,如有兴趣或有需要请移步。



挫折坎坷

Invocation Exception

起手遇到这个问题,在Eclipse本地跑程序还没问题,装进Andorid模拟器后,启动就报错。查下来HttpComponents各种方法找不到。可明明jar包都扔进去的说。网上翻了好久,才搞清楚Android里面有个旧版本的HttpComponents在作怪!就让人有些火大的说!

您弄个老版本扔在那里,能不能想着升级下呢?听说还是beta版!

然后就是Class加载顺序,自然该是source,自带jar包,最后才是系统默认的优先顺序才对。这是多么基本的规矩啊?!

搜索时还看到好像是Android开发组(Dalvik team)的某位跑出来,推荐用HttpURLConnection代替HttpComponents。啥?!大哥你可以再搞笑些吗?虽然HttpComponents 4.x的评价不那么好,但你分别写个文件上传代码看看,差多少?差“老鼻子”了!

最后,既然不推荐,你把老版本去掉好吗?这才叫占茅坑...

解决办法:用jarjar这个工具把最新版的HttpComponents都换下包名,程序中引用指向这个新包。下面是jarjar的rule,只替换org.apache.http,放过其他的比如org.apache.commons等。因为Android里面还藏了大票apache commons的东东!(在我花了九牛二虎之力,企图或是重新打包commons,或是提供‘伪’实现,一通折腾之后才发现!

rule org.apache.http.** [email protected]

Connection refused

解决掉包冲突后可以运行了。首当其冲的就是这个“拒绝连接”的错误。Android的AndroidManifest.xml里permission也加过了。

<uses-permission android:name="android.permission.INTERNET" />

但还是失败。最终找到了问题所在——没把模拟器当成独立的环境,习惯上地址直接写http://localhost/xxx!模拟器里的localhost是模拟器自己,而不是宿主机器。又因为是在Windows上开发的,所以最方便就是IP地址——localhost改成192.168.x.x的局域网地址即可。

Request not MultipartHttpRequest

终于能和服务器通信了,但服务器端不停报错ClassCastException,说Request不是MultipartHttpServletRequest。

解决办法:这个是我自己的问题。临时找来的测试环境,Spring的multipartResolver就根本没配置。

  <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver" p:defaultEncoding="utf-8" />

文件拷贝

这个就不细说了,说多了都是丢人!单单个from和to把自己绕晕了好久...

说结果吧。假如有两个FileChannel,分别是src和dest,需要将src的内容追加到dest的后面,该如何处理?有两种方法:

  1. dest.transferFrom(src, 其他参数);
  2. src.transferTo(其他参数, dest);

效果是一样的,细微的区别在于——作为参数的FileChannel的内部position会被改变!如果你对position有需求,那么就要当心了!(我在这里被绕晕的原因...应该是风扇还在一旁尖叫着...风扇应该怪模拟器...所以你看,最终都是google不好!灭哈哈!

Connection Timeout

这个要怪Apache了。4.2后多了释放连接的方法,如果不显式调用,就不释放连接。导致其他线程在拿连接时timeout。4.2之后的写法如下:

try {HttpResponse response = client.execute(post);// do something.} catch (Exception e) {post.abort();throw new RuntimeException(e);} finally {post.releaseConnection();}



大叔的抱怨

多年前(2009)曾经摸过Android,当时只是觉得它的源代码比较粗糙。4年过去了,现在看来,还是...糙!

更多相关文章

  1. Android模拟器环境中安装和删除应用程序
  2. Android 为模拟器安装其他软件
  3. android 模拟器命令
  4. Android 模拟器几个小问题
  5. adb install将app安装到eclipse Android sdk 模拟器上看效果
  6. android之调用webservice实现图片上传
  7. 模拟器上安装Android Market

随机推荐

  1. 个人简历表格制作与表单制作
  2. laravel环境配置
  3. 商城实战项目
  4. 漫画裤子怎么画?一组裤装的绘画参考!
  5. 收藏|2021年阿里云开源镜像站最热门镜像王
  6. 意派Epub360丨圣诞节营销利器:一键生成微
  7. 标准盒模型和弹性盒模型仿PHP中文网导航
  8. 万能码的轻便快捷独树一帜(安全扫码专业委
  9. md5加密与数组函数
  10. AlmaLinux 与 Rocky Linux