部分安卓手机微信浏览器中使用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/

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


本文转自Jaxu博客园博客,原文链接:http://www.cnblogs.com/jaxu/p/5498831.html,如需转载请自行联系原作者


相关文章
|
6月前
|
移动开发 小程序 API
微信外部浏览器或短信链接唤起微信小程序的解决方案
微信外部浏览器或短信链接唤起微信小程序的解决方案
1511 1
|
6月前
|
小程序
内网环境中ruoyi若依实现微信小程序授权登录解决办法
内网环境中ruoyi若依实现微信小程序授权登录解决办法
409 0
|
1月前
|
Web App开发 移动开发 前端开发
H5微信外支付(移动端浏览器)
H5微信外支付(移动端浏览器)
40 0
 H5微信外支付(移动端浏览器)
|
2月前
|
Android开发
Android中SurfaceView的双缓冲机制和普通View叠加问题解决办法
本文介绍了 Android 平台上的 SurfaceView,这是一种高效的图形渲染控件,尤其适用于视频播放、游戏和图形动画等场景。文章详细解释了其双缓冲机制,该机制通过前后缓冲区交换来减少图像闪烁,提升视觉体验。然而,SurfaceView 与普通 View 叠加时可能存在 Z-Order 不一致、同步问题及混合渲染难题。文中提供了使用 TextureView、调整 Z-Order 和创建自定义组合控件等多种解决方案。
140 9
|
3月前
|
开发工具 Android开发
|
5月前
|
Android开发
Android studio 出现Plugin [id: ‘com.android.application‘, version: ‘8.1.0‘, apply: false] 问题解决办法
Android studio 出现Plugin [id: ‘com.android.application‘, version: ‘8.1.0‘, apply: false] 问题解决办法
1374 1
|
5月前
|
Android开发 Windows
android studio开发时提示 TLS 握手错误解决办法
在Windows环境下遇到TLS协议版本不支持的错误,Gradle构建失败。解决方案是在build.gradle.kts中设置系统属性`https.protocols`为`TLSv1.2`,而非遵循误导信息设置为TLSv1.1。
|
4月前
|
Web App开发
软件开发常见流程之移动端调试方法,利用Chrome(谷歌浏览器)的模拟手机调试,搭建本地Web服务器,手机和服务器在一个局域网,通过手机访问服务器,使用服务器,利用ip实现域名访问
软件开发常见流程之移动端调试方法,利用Chrome(谷歌浏览器)的模拟手机调试,搭建本地Web服务器,手机和服务器在一个局域网,通过手机访问服务器,使用服务器,利用ip实现域名访问
|
4月前
|
Web App开发 编解码
软件开发常见流程之兼容性和手机屏页面设计,PC端和移动端常见浏览器,国内的UC都是根据Webkit修改过来的内核,开发重点关注尺寸,常见移动端尺寸汇总,移动端,理想视口根据你设别的样式进行修改
软件开发常见流程之兼容性和手机屏页面设计,PC端和移动端常见浏览器,国内的UC都是根据Webkit修改过来的内核,开发重点关注尺寸,常见移动端尺寸汇总,移动端,理想视口根据你设别的样式进行修改
|
4月前
|
移动开发 小程序 前端开发
php + h5使用 scheme页面跳转微信小程序-其他浏览器一键跳转到微信并打开小程序
php + h5使用 scheme页面跳转微信小程序-其他浏览器一键跳转到微信并打开小程序
125 0