【问题标题】:Empty files uploaded in Android Native browserAndroid Native 浏览器上传的空文件
【发布时间】:2013-03-26 14:04:25
【问题描述】:

我正在创建一个用于调整照片大小并上传照片的手机网站。

$('#ImgPreview canvas').each(function(pIndex) {
    vFormData.append(pIndex, canvasToJpegBlob($(this)[0]), vIssueId +'-attachment0'+ pIndex +'.jpg');
});

$.ajax({
    url: '/api/ob/issuefileupload',
    data: vFormData,
    processData: false,
    contentType: false,
    type: 'POST'
}).done(function(pData) {
    window.location = '/issue?id='+ vIssueId;
}).fail(function(pJqXHR) {
    alert(My.Strings.UploadFailed);
});

这适用于 Android 版 Chrome 和 iOS 版 Safari,但在原生 Android 浏览器中,文件的内容长度为 0,名称为 Blob + UID。当文件被添加到 formdata 时,其大小似乎也相当大(900k 与 Chrome 中的 50k 相比)。

canvasToJpegBlob 函数:

function canvasToJpegBlob(pCanvas) {
    var vMimeType = "image/jpeg",
        vDataURI,
        vByteString,
        vBlob,
        vArrayBuffer,
        vUint8Array, i,
        vBlobBuilder;

    vDataURI = pCanvas.toDataURL(vMimeType, 0.8);
    vByteString = atob(vDataURI.split(',')[1]);

    vArrayBuffer = new ArrayBuffer(vByteString.length);
    vUint8Array = new Uint8Array(vArrayBuffer);
    for (i = 0; i < vByteString.length; i++) {
        vUint8Array[i] = vByteString.charCodeAt(i);
    }

    try {
        vBlob = new Blob([vUint8Array.buffer], {type : vMimeType});
    } catch(e) {
        window.BlobBuilder = window.BlobBuilder ||
                             window.WebKitBlobBuilder ||
                             window.MozBlobBuilder ||
                             window.MSBlobBuilder;

        if (e.name === 'TypeError' && window.BlobBuilder) {
            vBlobBuilder = new BlobBuilder();
            vBlobBuilder.append(vUint8Array.buffer);
            vBlob = vBlobBuilder.getBlob(vMimeType);
        } else if (e.name === 'InvalidStateError') {
            vBlob = new Blob([vUint8Array.buffer], {type : vMimeType});
        } else {
            alert(My.Strings.UnsupportedFile);
        }
    }

    return vBlob;
}

有没有办法让它在原生 Android 浏览器中运行?

【问题讨论】:

标签: javascript android


【解决方案1】:

我也遇到了这个问题,需要提出一个更通用的解决方案,因为在某些情况下我无法控制服务器端代码。

最终我找到了一个几乎完全透明的解决方案。该方法是用一个 blob 填充损坏的 FormData,该 blob 以 multipart/form-data 的必要格式附加数据。它覆盖 XHR 的 send(),其版本将 blob 读入请求中发送的缓冲区。

主要代码如下:

var
    // Android native browser uploads blobs as 0 bytes, so we need a test for that
    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;
    })(),

    // Test for constructing of blobs using new Blob()
    blobConstruct = !!(function () {
        try { return new Blob(); } catch (e) {}
    })(),

    // Fallback to BlobBuilder (deprecated)
    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 () {
    var
        // Store a reference to this
        o = this,

        // Data to be sent
        parts = [],

        // Boundary parameter for separating the multipart values
        boundary = Array(21).join('-') + (+new Date() * (1e16*Math.random())).toString(36),

        // Store the current XHR send method so we can safely override it
        oldSend = XMLHttpRequest.prototype.send;

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

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

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

        if (val === o) {
            // Append the final boundary string
            parts.push('--' + boundary + '--');

            // Create the blob
            data = new XBlob(parts);

            // Set up and read the blob into an array to be sent
            fr = new FileReader();
            fr.onload = function () { oldSend.call(oXHR, fr.result); };
            fr.onerror = function (err) { throw err; };
            fr.readAsArrayBuffer(data);

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

然后就这样使用它,之后照常调用fd.append(name, value)

var fd = needsFormDataShim ? new FormDataShim() : new FormData();

【讨论】:

  • 感谢您发布此解决方案。它不适合我。我的服务器无法解析请求数据。在 Chrome Inspector 中,请求负载看起来像这样(粘贴开头):``` ----------------------1e3xa79f9edc04cok0c Content-Disposition: form-数据;名称="照片"; filename="blob" Content-Type: image/jpeg ÿØÿàJFIFÿÛC (1#%(:3=Mqypdx\egcÿÛC//cB8Bcccccccccccc ``` 而通常使用 FormData,文件数据是隐藏的。仅供参考,我强制 Chrome 使用 shim 进行测试。但结果在 Android 4.1 上是相同的。
  • 好的,我通过使用 \r\n 代替 \n 让它工作了。在 HTTP 规范中使用回车符 (w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4),我的服务器 (Django) 需要它来解析。我关于 Chrome 显示二进制文件数据的最后一条说明仍然存在,但这可能是 FormData 的一种特殊情况,并没有影响。
  • @jacob,很高兴你成功了。我现在不在,所以我无法更新答案,但可以随时点击编辑按钮。当我回来时,我会听取您的建议并在我自己的代码中使用回车。干杯;-)
  • 当然,安迪。我就是这么做的。并再次感谢您的解决方案。它就像一个魅力!
  • @AndyE 我有兴趣将此代码纳入“可审查”要点。例如,我想知道 1/ 我们是否不应该将 XHR.prototype.send = oldSend 放入 finally{} 块中,以及 2/ 如果我们连续创建 2 个 FormDataShim,我们不会在 XHR 上制作 2 个装饰吗因此永远不会使连续的 val===o 为真
【解决方案2】:

尝试在画布上绘制它怎么样,使用矩阵将其缩放到您想要的大小,然后使用canvas.toDataURL 将其发送到服务器。看看这个question

【讨论】:

  • 我发现我的问题可能会被误解。现在对其进行编辑以指定这是针对移动网站,而不是应用程序。
  • @bjornarvh 试试我的新解决方案
  • 通过将图像作为 base64 编码字符串发送来使其工作。将此标记为答案,因为正常文件上传似乎没有解决方案。
  • 我无法确认 base64 字符串是否正常工作。浏览器似乎切断了较长字符串的结尾
【解决方案3】:

我用这个来解决问题:

// not use blob, simply use key value
var form = new FormData();
// get you content type and raw data from data url
form.append( 'content_type', type);
form.append( 'content', raw);

【讨论】:

    猜你喜欢
    • 2014-02-11
    • 1970-01-01
    • 2015-08-27
    • 2018-09-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-06
    • 2014-11-10
    相关资源
    最近更新 更多