Android - 如何使用OkHTTP以块的形式上传视频?

时间:2017-07-15 23:39:55

标签: android video okhttp

请参阅我用于将视频上传到服务器的以下代码。但是,对于足够大的视频,我获得了OutOfMemory异常。

        InputStream stream = getContentResolver().openInputStream(videoUri);
        byte[] byteArray = IOUtils.toByteArray(stream);
        RequestBody requestBody = new MultipartBody.Builder()
                .setType(MultipartBody.FORM)
                .addFormDataPart("file", "fname",
                        RequestBody.create(MediaType.parse("video/mp4"), byteArray))
                .build();
        Request request = new Request.Builder()
                .url(uploadURL)
                .post(requestBody)
                .build();

        OkHttpClient client = new OkHttpClient.Builder().build();
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
            }
            @Override
            public void onResponse(Call call, Response response) throws IOException {
            }
        });

有人能指出我如何避免OutOfMemory异常的正确方向吗?有没有办法从InputStream转到requestBody?

2 个答案:

答案 0 :(得分:4)

您可以创建用于流式传输数据的自定义div。您将不得不小心:它可能会被多次重复使用,因为OkHttp可能会决定重试该请求。确保您每次都可以从头开始重新打开$('body').on({ 'click': function() { alert('clicked'); } }, '.load-from-css');

RequestBody

答案 1 :(得分:3)

  1. 尝试使用 Uri/ InputStream/ AssetFileDescriptor 之一将 ContentResolver 转换为 ParcelFileDescriptor
  2. okio.Source (about source/sink) 创建 InputStream
  3. Source 写入 Sink,然后关闭 InputStream

如果您知道服务器期望什么为 contentType,请对其进行硬编码。因为即使文件是 application/octet-stream,服务器也可能只接受 video/mp4Uri to contentType

检查Uri to contentLength,如果没有找到contentLength,则上传时不会附加Content-Length: X标头。

/** It supports file/content/mediaStore/asset URIs. asset not tested */
fun createAssetFileDescriptor() = try {
    contentResolver.openAssetFileDescriptor(this, "r")
} catch (e: FileNotFoundException) {
    null
}

/** It supports file/content/mediaStore URIs. Will not work with providers that return sub-sections of files */
fun createParcelFileDescriptor() = try {
    contentResolver.openFileDescriptor(this, "r")
} catch (e: FileNotFoundException) {
    null
}

/** - It supports file/content/mediaStore/asset URIs. asset not tested
 * - When file URI is used, may get contentLength error (expected x but got y) error when uploading if contentLength header is filled from assetFileDescriptor.length */
fun createInputStreamFromContentResolver() = try {
    contentResolver.openInputStream(this)
} catch (e: FileNotFoundException) {
    null
}

fun Uri.asRequestBody(contentResolver: ContentResolver,
                      contentType: MediaType? = null,
                      contentLength: Long = -1L)
        : RequestBody {

    return object : RequestBody() {
        /** If null is given, it is binary for Streams */
        override fun contentType() = contentType

        /** 'chunked' transfer encoding will be used for big files when length not specified */
        override fun contentLength() = contentLength

        /** This may get called twice if HttpLoggingInterceptor is used */
        override fun writeTo(sink: BufferedSink) {
            val assetFileDescriptor = createAssetFileDescriptor()
            if (assetFileDescriptor != null) {
                // when InputStream is closed, it auto closes AssetFileDescriptor
                AssetFileDescriptor.AutoCloseInputStream(assetFileDescriptor)
                        .source()
                        .use { source -> sink.writeAll(source) }
            } else {
                val inputStream = createInputStreamFromContentResolver()
                if (inputStream != null) {
                    inputStream
                            .source()
                            .use { source -> sink.writeAll(source) }
                } else {
                    val parcelFileDescriptor = createParcelFileDescriptor()
                    if (parcelFileDescriptor != null) {
                        // when InputStream is closed, it auto closes ParcelFileDescriptor
                        ParcelFileDescriptor.AutoCloseInputStream(parcelFileDescriptor)
                                .source()
                                .use { source -> sink.writeAll(source) }
                    } else {
                        throw IOException()
                    }
                }
            }
        }
    }
}

用法:

val request = uri.asRequestBody(
       contentResolver = context.contentResolver,
       contentType = "application/octet-stream".toMediaTypeOrNull(),
       contentLength = uri.length(context.contentResolver)
)
相关问题