Android浏览器Blob问题

如果你使用canvas导出图片,并将图片存储在uint8Array中,然后想通过blob构造之后上传到诸如aliyun oss服务器,或者个人文件服务器,那么你要注意了。在chrome早先版本和目前的android中,至少是andrid5.1之前的浏览器,包括微信浏览器等,都不支持blob的构造方法,需要使用BlobBuilder。

皓眸大前端开发学习

原因

1
var jpeg = new Blob( [array.buffer], {type : "image/jpeg"});

这个blob的构造方法,在ios手机浏览器是支持的,但是在android手机浏览器不行。
桌面版的chrome浏览器解决了这个blob bug, 但是android手机还是有这个问题,会返回一个type error,因为android浏览器不支持这个构造方法。你必须使用老版本的BlobBuilder API.
解决方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var array = new Int8Array([17, -45.3]);

try{
var jpeg = new Blob( [array], {type : "image/jpeg"});
}
catch(e){
// TypeError old chrome and FF
window.BlobBuilder = window.BlobBuilder ||
window.WebKitBlobBuilder ||
window.MozBlobBuilder ||
window.MSBlobBuilder;
if(e.name == 'TypeError' && window.BlobBuilder){
var bb = new BlobBuilder();
bb.append([array.buffer]);
var jpeg = bb.getBlob("image/jpeg");
}
else if(e.name == "InvalidStateError"){
// InvalidStateError (tested on FF13 WinXP)
var jpeg = new Blob( [array.buffer], {type : "image/jpeg"});
}
else{
// We're screwed, blob constructor unsupported entirely
}
}

但是,这里还没有结束,仅仅是开始。
报错是没有了,但是呢,发现服务端并没有你需要的文件,是空的,怎么回事呢?抓包看看

1
------WebKitFormBoundarysToAVAYMLPFfJF96 Content-Disposition: form-data; name="img"; filename="blob" Content-Type: application/octet-stream ------WebKitFormBoundarysToAVAYMLPFfJF96--

文件没了

发现提交的表单中没有文件内容,我们不能正常采用formdata提交了,所以需要自己封装。这里参考:
http://www.alloyteam.com/2015/04/ru-he-zai-yi-dong-web-shang-shang-chuan-wen-jian/
封装代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
function FormDataShim () {
var o = this,
parts = [],// Data to be sent
boundary = Array(5).join('-') + (+new Date() * (1e16*Math.random())).toString(32),
oldSend = XMLHttpRequest.prototype.send;

this.append = function (name, value, filename) {
parts.push('--' + boundary + '\r\nContent-Disposition: form-data; name="' + name + '"');

if (value instanceof Blob) {
parts.push('; filename="'+ (filename || 'blob') +'"\r\nContent-Type: ' + value.type + '\r\n\r\n');
parts.push(value);
} else {
parts.push('\r\n\r\n' + value);
}
parts.push('\r\n');
};

//把xhr的send方法重写一下.
XMLHttpRequest.prototype.send = function (val) {
var fr,
data,
oXHR = this;

if (val === o) {
// 最后加一下boundary..注意这里一定要在最后加\r\n..否则服务器有可能会解析参数失败..
parts.push('--' + boundary + '--\r\n');

data = new XBlob(parts);
fr = new FileReader();
fr.onload = function () { oldSend.call(oXHR, fr.result); };
fr.onerror = function (err) { throw err; };
fr.readAsArrayBuffer(data);

// 设置content-type
this.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary);
XMLHttpRequest.prototype.send = oldSend;
}
else {
oldSend.call(this, val);
}
};
}

最后代码

下面代码仅供参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
function newBlob(data, datatype){
var out;
try {
out = new Blob([data], {type: datatype});
}
catch (e) {
window.BlobBuilder = window.BlobBuilder ||
window.WebKitBlobBuilder ||
window.MozBlobBuilder ||
window.MSBlobBuilder;

if (e.name == 'TypeError' && window.BlobBuilder) {
var bb = new BlobBuilder();
bb.append(data.buffer);
out = bb.getBlob(datatype);
}
else if (e.name == "InvalidStateError") {
out = new Blob([data], {type: datatype});
}
else {
}
}
return out;
}

// 判断是否需要blobbuilder
var needsFormDataShim = (function () {
var bCheck = ~navigator.userAgent.indexOf('Android')
&& ~navigator.vendor.indexOf('Google')
&& !~navigator.userAgent.indexOf('Chrome');

return bCheck && navigator.userAgent.match(/AppleWebKit\/(\d+)/).pop() <= 534;
})(),
blobConstruct = !!(function () {
try { return new Blob(); } catch (e) {}
})(),
XBlob = blobConstruct ? window.Blob : function (parts, opts) {
var bb = new (window.BlobBuilder || window.WebKitBlobBuilder || window.MSBlobBuilder);
parts.forEach(function (p) {
bb.append(p);
});

return bb.getBlob(opts ? opts.type : undefined);
};

function FormDataShim () {
// Store a reference to this
var o = this,
parts = [],// Data to be sent
boundary = Array(5).join('-') + (+new Date() * (1e16*Math.random())).toString(32),
oldSend = XMLHttpRequest.prototype.send;

this.append = function (name, value, filename) {
parts.push('--' + boundary + '\r\nContent-Disposition: form-data; name="' + name + '"');

if (value instanceof Blob) {
parts.push('; filename="'+ (filename || 'blob') +'"\r\nContent-Type: ' + value.type + '\r\n\r\n');
parts.push(value);
} else {
parts.push('\r\n\r\n' + value);
}
parts.push('\r\n');
};

// Override XHR send()
XMLHttpRequest.prototype.send = function (val) {
var fr,
data,
oXHR = this;

if (val === o) {
//注意不能漏最后的\r\n ,否则有可能服务器解析不到参数.
parts.push('--' + boundary + '--\r\n');
data = new XBlob(parts);
fr = new FileReader();
fr.onload = function () { oldSend.call(oXHR, fr.result); };
fr.onerror = function (err) { throw err; };
fr.readAsArrayBuffer(data);

this.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary);
XMLHttpRequest.prototype.send = oldSend;
}
else {
oldSend.call(this, val);
}
};
}

//把图片转成formdata 可以使用的数据...
//这里要把\s替换掉..要不然atob的时候会出错....
function dataURLtoBlob(data) {
var tmp = data.split(',');

tmp[1] = tmp[1].replace(/\s/g,'');
var binary = atob(tmp[1]);
var array = [];
for(var i = 0; i < binary.length; i++) {
array.push(binary.charCodeAt(i));
}
return new newBlob(new Uint8Array(array), 'image/jpeg');
}

function uploadFile(img){
var fd = needsFormDataShim ? new FormDataShim() : new FormData();
var file = dataURLtoBlob(img);
fd.append('img',file);

var prog = function(e){
/*你的逻辑*/
}
var load = function(e){
/*你的逻辑*/
}
var error = function(e){
/*你的逻辑*/
}
var abort = function(e){
/*你的逻辑*/
}

var xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress',prog,false);
xhr.addEventListener('load',load,false);
xhr.addEventListener('error',error,false);
xhr.addEventListener('abort',abort,false);

xhr.onreadystatechange = function(){
/*你的逻辑*/
}
xhr.open('POST','/upload',true);
xhr.send(fd);
}

我的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
document.addEventListener('DOMContentLoaded', init, false);

function init() {
var u = new UploadPic();
u.init({
input: document.querySelector('#selectFile')
});
}

function UploadPic() {
this.sw = 0;
this.sh = 0;
this.tw = 0;
this.th = 0;
this.scale = 0;
this.maxSize = 0;
this.fileSize = 0;
this.fileDate = null;
this.fileType = '';
this.fileName = '';
this.input = null;
this.canvas = null;
this.mime = {};
this.type = '';
this.callback = function () {
};
this.loading = function () {
};
}

UploadPic.prototype.init = function (options) {
this.input = options.input;
this.mime = {'png': 'image/png', 'jpg': 'image/jpeg', 'jpeg': 'image/jpeg', 'bmp': 'image/bmp'};
this.callback = options.callback || function () {
};
this._addEvent();
};

UploadPic.prototype._addEvent = function () {
var _this = this;

function tmpSelectFile(ev) {
_this._handelSelectFile(ev);
}

this.input.addEventListener('change', tmpSelectFile, false);
};

UploadPic.prototype._handelSelectFile = function (ev) {
var file = ev.target.files[0];

this.type = file.type;

// 如果没有文件类型,则通过后缀名判断(解决微信及360浏览器无法获取图片类型问题)
if (!this.type) {
this.type = this.mime[file.name.match(/\.([^\.]+)$/i)[1]];
}

if (!/image.(png|jpg|jpeg|bmp)/.test(this.type)) {
alert('选择的文件类型不是图片');
return;
}

if (file.size > this.maxSize) {
alert('选择文件大于' + this.maxSize / 1024 / 1024 + 'M,请重新选择');
return;
}

this.fileName = file.name;
this.fileSize = file.size;
this.fileType = this.type;
this.fileDate = file.lastModifiedDate;

this._readImage(file);
};

UploadPic.prototype._readImage = function (file) {
var _this = this;
this._getURI(file, this.callback);
};

UploadPic.prototype._getURI = function (file, callback) {
var reader = new FileReader();
var _this = this;

function tmpLoad() {
// 头不带图片格式,需填写格式
var re = /^data:base64,/;
var ret = this.result + '';

if (re.test(ret)) ret = ret.replace(re, 'data:' + _this.mime[_this.fileType] + ';base64,');

callback && callback(ret);
}

reader.onload = tmpLoad;

reader.readAsDataURL(file);

return false;
};

谢谢!

转载请注明出处:http://www.haomou.net/2016/01/14/2016_android_blob/
有问题请留言。T_T 皓眸大前端开发学习 T_T