部分安卓手机微信浏览器中使用XMLHttpRequest 2上传图片显示字节数为0的解决办法
前端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/
部分安卓手机微信浏览器中使用XMLHttpRequest 2上传图片显示字节数为0的解决办法的更多相关文章
- java开发过程中,报错Dangling meta character '*' near index 0,解决办法
		
1.split方法转化字符串为数组: String[] strPicArr = map.get("hw_pic").toString().split("*"); ...
 - 解决安卓微信浏览器中location.reload 或者 location.href失效的问题
		
在移动wap中,经常会使用window.location.href去跳转页面,这个方法在绝大多数浏览器中都不会 存在问题,但早上测试的同学会提出了一个bug:在安卓手机的微信自带浏览器中,这个是失效的 ...
 - vue-router 在微信浏览器中操作history URl未改变的解决方案
		
在PC端和手机浏览器中router.replace() or router.push()能够正常使用,页面的地址和页面都正常显示:但是在微信中,从/a页面通过router.push('/b')跳转到/ ...
 - 在iOS微信浏览器中自动播放HTML5 audio(音乐)的2种正确方式
		
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
 - JS动态修改微信浏览器中的title
		
JS动态修改微信浏览器中的title我们的原理是设置一个ifame然后我们再加载一下就可以实现了,具体的例子如下所示. 平时使用JS修改title,直接document.title=新标题就好了 这样 ...
 - Windows环境中,通过Charles工具,抓取安卓手机、苹果手机中APP应用的http、https请求包信息
		
Windows环境中,通过Charles工具,抓取安卓手机.苹果手机中APP应用的http.https请求包信息1.抓取安卓手机中APP应用的http请求包信息1)在电脑上操作,查看Windows机器 ...
 - 【bug解决】ios微信浏览器中背景音乐无法播放
		
我记得之前在一次项目中,出现过浏览报错: 当时的文档链接如右:[解决]HTML5新标签audio的autoplay自动播放属性失效的解决方案 所以在这次H5的制作中,我使用了iframe来加载音频文件 ...
 - 解决微信浏览器中无法一键拨号问题tel
		
公众号中需要在某些页面显示手机号码,并且需要点击后拨号. 原以为 <a href="tel:10086">10086</a> 可以解决了, 没想到在微信浏览 ...
 - 在微信浏览器中 location.reload() 不刷新解决方案(直接调用方法)
		
1.问题 在微信浏览器中,需要时刷新当前页面. 正常情况下我们直接使用 location.reload 方法来刷新. 2.解决方法 function realod(){ var {search,hre ...
 
随机推荐
- 代码在ie9中不能正确执行
			
<!DOCTYPE html> <html> <head lang="zh"> <meta charset="UTF-8&quo ...
 - C++: Perfect Forwarding
			
Link: Rvalue References and Perfect Forwarding in C++0x (https://www.justsoftwaresolutions.co.uk/cpl ...
 - SQLite 的创建与编辑
			
创建数据库语句 -(void)creatData { sqlite3 *sqlite = nil; NSString *filePath = [NSHomeDirectory() stringByAp ...
 - ASCII和16进制对照表
			
十六进制代码 MCS 字符或缩写 DEC 多国字符名 ASCII 控制字符 1 00 NUL 空字符 01 SOH 标题起始 (Ctrl/A) 02 STX 文本起始 (Ctrl/B) 03 ETX ...
 - 如何去掉dede列表推荐时标题被加粗
			
dede在列表推荐文章默认为加粗不清楚的可以看图: 那个加黑的是默认的.如果你不想要被加黑,可以做如下改动.在include里找到文件:arc.listview.class.php查找并删除(注释掉也 ...
 - SQL  查找重复项及批量修改数据成固定格式
			
1.查找表中多余的重复记录,重复记录是根据单个字段(peopleId)来判断select * from peoplewhere peopleId in (select peopleId from ...
 - 《JavaScript高级程序设计(第3版)》阅读总结记录第一章之JavaScript简介
			
前言: 为什么会想到把<JavaScript 高级程序设计(第 3 版)>总结记录呢,之前写过一篇博客,研究的轮播效果,后来又去看了<JavaScript 高级程序设计(第3版)&g ...
 - 通过NFS(nfsroot)启动linux系统
			
Mounting the root filesystem via NFS (nfsroot) 英文原文位于inux内核源代码中的"Documentation/filesystems/nfs/ ...
 - Webform Application传值  ViewState
			
Application:所有的会话共享一个Application空间,任何一个人改变Application的内容,其他人都会发现被改变了.Application中的内容不会被自动释放 存放位置:服务端 ...
 - windows 环境和linux环境下 ping命令的区别:
			
Ping 是Windows自带的一个DOS命令.利用它可以检查网络是否能够连通,用好它可以很好地帮助我们分析判定网络故障.该命令可以加许多参数使用,键入Ping按回车即可看到详细说明.Ping 命令可 ...