前端JS中使用XMLHttpRequest 2上传图片到服务器,PC端和大部分手机上都正常,但在少部分安卓手机上上传失败,服务器上查看图片,显示字节数为0。下面是上传图片的核心代码:

HTML

<input type="file" id="choose" capture="camera" accept="image/*">

JavaScript

var filechooser = document.getElementById("choose");

filechooser.onchange = function () {
var _this = $(this);
if (!this.files.length) return;
var files = Array.prototype.slice.call(this.files);
if (files.length > 1) {
alert("一次只能上传1张图片");
return;
}
files.forEach(function (file, i) {
if (!/\/(?:jpeg|png|gif)/i.test(file.type)) return;
var reader = new FileReader();
reader.onload = function () {
var result = this.result;
upload(result, file.type);
};
reader.readAsDataURL(file);
});
}; function upload(basestr, type){
var xhr = new XMLHttpRequest();
var text = window.atob(basestr.split(",")[1]);
var buffer = new Uint8Array(text.length);
var pecent = 0;
for (var i = 0; i < text.length; i++) {
buffer[i] = text.charCodeAt(i);
}
var blob = getBlob(buffer, type);
var formdata = new FormData();
formdata.append('imagefile', blob); xhr.open('post', '/uploadtest');
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
var jsonData = JSON.parse(xhr.responseText);
console.log(jsonData);
}
}; //利用progress事件显示数据发送进度
xhr.upload.addEventListener('progress', function (e) {
pecent = ~~(100 * e.loaded / e.total) / 2;
// 利用pecent来显示上传进度
}, false); xhr.send(formdata);
} function getBlob(buffer, format){
var Builder = window.WebKitBlobBuilder || window.MozBlobBuilder;
if(Builder){
var builder = new Builder();
builder.append(buffer);
return builder.getBlob(format);
} else {
return new window.Blob([ buffer ], {type: format});
}
}

  上述代码使用FormData来实现表单数据提交。FormData是一种针对XHR2设计的新型数据类型,使用它我们可以很方便地实时以JavaScript创建HTML <Form>,然后通过AJAX提交该表单。在上述代码中,提交的表单中的字段名为imagefile,值是blob,这是一个通过getBlob函数构造并返回的文件Blob。通过该方法上传文件简单直观。

  然后我们在服务端接收并保存图片,并返回已上传的图片的信息。下面是Node.js代码的示例:

var Q = require('q');
var fs = require('fs');
var path = require('path');
var formidable = require('formidable');
var moment = require('moment');var imageUpload = function (){ }; imageUpload.prototype.useFormParseCallback = function(req){
var deferred = Q.defer(); var form = new formidable.IncomingForm();
form.parse(req, deferred.makeNodeResolver());
return deferred.promise;
}; imageUpload.prototype.uploadImageTest = function(req){
var pathName = 'uploadImgs/dealInfo/';
var uploadPath = path.join(__dirname, '../../public/', pathName); return this.useFormParseCallback(req).then(function(files){
var file = files[1].imagefile;
var fileType = files[1].imagefile.type.split('/')[1];
var newFileName = 'upload_' + moment().format('x') + Math.random().toString().substr(2, 10) + '.' + fileType; var readStream = fs.createReadStream(file.path);
var writeStream = fs.createWriteStream(uploadPath + newFileName); var deferred = Q.defer();
readStream.pipe(writeStream);
readStream.on('end', deferred.makeNodeResolver());
return deferred.promise.then(function() {
fs.unlinkSync(file.path);
return {
fileName: newFileName,
filePath: '/' + pathName + newFileName,
fileSize: file.size/1024 > 1024 ? (~~(10*file.size/1024/1024))/10 + "MB" : ~~(file.size/1024) + "KB"
};
});
});
}; module.exports = imageUpload;

  我们使用formidable这个包来接收上传文件的数据,然后将文件保存到/public/uploadImgs/dealInfo目录下(假定已在express中将public设置为static的根目录),并将图片按照指定的规则重命名,以保证上传图片不会因为名称相同而被覆盖。另外,代码中使用Q来避免直接使用回调函数,以更好地对函数功能进行分离。

  上面的代码在PC端浏览器以及大部分主流移动设备上都能正常工作,但是少部分Android设备上却会出现上传的图片字节数为0的情况。具体的原因大家可以看下面几个网页中的描述:

http://www.oschina.net/question/2502182_2139420?fromerr=qwYwJQK8

https://github.com/fex-team/webuploader/issues/185

https://code.google.com/p/android/issues/detail?id=39882

  就是说这个是Android的一个bug!

  那如何解决呢?

  其实从上面给出的页面中可以找到答案,就是我们得换一种文件上传方式。在XHR2中,除了以Blob的方式上传文件外,还可以ArrayBuffer的方式上传文件。下面是修改之后的前端JavaScript代码:

var filechooser = document.getElementById("choose");

filechooser.onchange = function () {
var _this = $(this);
if (!this.files.length) return;
var files = Array.prototype.slice.call(this.files);
if (files.length > 1) {
alert("一次只能上传1张图片");
return;
}
files.forEach(function (file, i) {
if (!/\/(?:jpeg|png|gif)/i.test(file.type)) return;
var reader = new FileReader();
reader.onload = function () {
var result = this.result;
upload(result, file.type);
};
reader.readAsDataURL(file);
});
}; function upload(basestr, type){
var xhr = new XMLHttpRequest();
var text = window.atob(basestr.split(",")[1]);
var buffer = new Uint8Array(text.length);
var pecent = 0;
for (var i = 0; i < text.length; i++) {
buffer[i] = text.charCodeAt(i);
} xhr.open('post', '/uploadtest?filetype=' + type.split('/')[1]);
xhr.setRequestHeader('Content-Type', 'application/octet-stream');
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
var jsonData = JSON.parse(xhr.responseText);
console.log(jsonData);
}
};
//利用progress事件显示数据发送进度
xhr.upload.addEventListener('progress', function (e) {
pecent = ~~(100 * e.loaded / e.total) / 2;
// 利用pecent来显示上传进度
}, false); xhr.send(buffer.buffer); // 以ArrayBuffer的方式上传图片
}

  我将有变化的地方加了高亮显示。以ArrayBuffer方式上传图片必须添加'application/octet-stream'的RequestHeader,否则服务器无法响应请求。另外,通过这种方式上传图片我们也无法从表单数据中获取到文件类型,可以将文件类型以query的方式传到服务器,然后服务器根据文件类型来生成对应的文件,以下是经过少量修改之后的服务器代码:

imageUpload.prototype.uploadImageTest = function(req){
var pathName = 'uploadImgs/dealInfo/';
var uploadPath = path.join(__dirname, '../../public/', pathName); return this.useFormParseCallback(req).then(function(files){
var file = files[1].file;
var fileType = req.query.filetype ? ('.' + req.query.filetype) : '.png';
var newFileName = 'upload_' + moment().format('x') + Math.random().toString().substr(2, 10) + '.' + fileType; var readStream = fs.createReadStream(file.path);
var writeStream = fs.createWriteStream(uploadPath + newFileName); var deferred = Q.defer();
readStream.pipe(writeStream);
readStream.on('end', deferred.makeNodeResolver());
return deferred.promise.then(function() {
fs.unlinkSync(file.path);
return {
fileName: newFileName,
filePath: '/' + pathName + newFileName,
fileSize: file.size/1024 > 1024 ? (~~(10*file.size/1024/1024))/10 + "MB" : ~~(file.size/1024) + "KB"
};
});
});
};

  修改之后的代码可以支持Android手机,包括微信浏览器。注意不是所有的Android手机都会存在该问题,如果你发现在Andriod手机上无法上传图片,尤其是在微信浏览器中,则可以尝试下上面的方法。

参考页面:

http://www.html5rocks.com/zh/tutorials/file/xhr2/

http://www.zhangxinxu.com/wordpress/2013/10/understand-domstring-document-formdata-blob-file-arraybuffer/

部分安卓手机微信浏览器中使用XMLHttpRequest 2上传图片显示字节数为0的解决办法的更多相关文章

  1. java开发过程中,报错Dangling meta character '*' near index 0,解决办法

    1.split方法转化字符串为数组: String[] strPicArr = map.get("hw_pic").toString().split("*"); ...

  2. 解决安卓微信浏览器中location.reload 或者 location.href失效的问题

    在移动wap中,经常会使用window.location.href去跳转页面,这个方法在绝大多数浏览器中都不会 存在问题,但早上测试的同学会提出了一个bug:在安卓手机的微信自带浏览器中,这个是失效的 ...

  3. vue-router 在微信浏览器中操作history URl未改变的解决方案

    在PC端和手机浏览器中router.replace() or router.push()能够正常使用,页面的地址和页面都正常显示:但是在微信中,从/a页面通过router.push('/b')跳转到/ ...

  4. 在iOS微信浏览器中自动播放HTML5 audio(音乐)的2种正确方式

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  5. JS动态修改微信浏览器中的title

    JS动态修改微信浏览器中的title我们的原理是设置一个ifame然后我们再加载一下就可以实现了,具体的例子如下所示. 平时使用JS修改title,直接document.title=新标题就好了 这样 ...

  6. Windows环境中,通过Charles工具,抓取安卓手机、苹果手机中APP应用的http、https请求包信息

    Windows环境中,通过Charles工具,抓取安卓手机.苹果手机中APP应用的http.https请求包信息1.抓取安卓手机中APP应用的http请求包信息1)在电脑上操作,查看Windows机器 ...

  7. 【bug解决】ios微信浏览器中背景音乐无法播放

    我记得之前在一次项目中,出现过浏览报错: 当时的文档链接如右:[解决]HTML5新标签audio的autoplay自动播放属性失效的解决方案 所以在这次H5的制作中,我使用了iframe来加载音频文件 ...

  8. 解决微信浏览器中无法一键拨号问题tel

    公众号中需要在某些页面显示手机号码,并且需要点击后拨号. 原以为 <a href="tel:10086">10086</a> 可以解决了, 没想到在微信浏览 ...

  9. 在微信浏览器中 location.reload() 不刷新解决方案(直接调用方法)

    1.问题 在微信浏览器中,需要时刷新当前页面. 正常情况下我们直接使用 location.reload 方法来刷新. 2.解决方法 function realod(){ var {search,hre ...

随机推荐

  1. SqlLite 基本操作

    1.数据类型 ●  SQLite将数据划分为以下⼏几种存储类型: ●  integer : 整型值 ●  real : 浮点值 ●  text : ⽂文本字符串 ●  blob : ⼆二进制数据(⽐比 ...

  2. 【监听文件 多线程】使用java--WatchService监听文件 开启多线程copy文件

    有一个小需求: 在PC跟前没有人的时候,迅雷下载文件 至PC磁盘上,并且自动移动文件到U盘上,小主只要在走的时候取走U盘即可. 基于这个需求,有了下面这段代码:[JDK  1.8] package c ...

  3. npm 基础

    npm账户 npm adduser npm whoami 初始化项目: npm init --scope=<username> 项目必要文件 README.md pageage.json: ...

  4. 一些Matlab函数的使用

    1.Kron(A,B) 矩阵A中的每个元素都乘以矩阵B.${A_{m \times n}}$,${B_{m \times n}}$,${\bf{A}} \otimes {\bf{B}} = {({a_ ...

  5. openfl使用64位的ndk时,编译报错的问题!

    当使用64位的ndk时,如果使用openfl test android运行android测试,应该会出现 arm-linux-androideabi-g++:找不到这个命令的错误. 原因是,haxel ...

  6. asp.net dataset 判断是否为空 ?

    1,if(ds == null) 这是判断内存中的数据集是否为空,说明DATASET为空,行和列都不存在!! 2,if(ds.Tables.Count == 0) 这应该是在内存中存在一个DATASE ...

  7. iOS 链式编程探索(Masonry)

    看了几篇关于链式编程的文章,还是理解的不透彻,我想这可能是因为我自己对block掌握的不熟练. 我已经明白了,所以,和大家分享一下我的理解!如有问题,麻烦大家指出! 直接看代码吧!关键的注释都有. 我 ...

  8. 【转】Oracle索引HINT的使用

    转自:Oracle索引HINT的使用       存储在数据库中数据的分布情况开发人员或管理员比Oracle优化器更加的清楚,在优化器不能作出最有查询路径选择的情况下,使用HINT(提示)人为的固定查 ...

  9. SVN出现Invalid authz configuration解决方案

    思路: 1.检查是否为不存在的用户组或用户设置了权限(大部分为此问题,调整用户权限或删除账号后,但忘了去掉某个文件夹的权限) 2.检查authz文件的编码: 3.更改权限后是否未重启svn. 4.按照 ...

  10. 20161022 NOIP模拟赛 解题报告

     好元素 [问题描述] 小A一直认为,如果在一个由N个整数组成的数列{An}中,存在以下情况: Am+An+Ap = Ai (1 <= m, n, p < i <= N ,  m,n ...