我已经尝试了一周的大部分时间,终于可以运行可恢复的上传。它不像我预期的那样工作,但它确实有效。
不要对所有东西都使用 Drive REST API
我了解到,据我所知,Google Drive REST API 并不能真正进行分块上传。这可能是一个错误,也可能是设计使然。我也可能太傻了。
但让我想到的是,我在任何地方都看不到代码示例。每个人都一直在谈论Http 标头。所以这就是我们下面要做的。我们将只使用标题。
以下是使用 Google Drive REST API 和 Android 进行可恢复、分块上传的方法:
0) 初始化
String accountName = "account_name";
GoogleAccountCredential credential = GoogleAccountCredential.usingOAuth2(context, Arrays.asList(SCOPES)).setBackOff(new ExponentialBackOff()).setSelectedAccountName(accountName);
1) 启动可恢复会话
遵循 Google 在this document 中列出的规则:
POST /upload/drive/v3/files?uploadType=resumable HTTP/1.1
Host: www.googleapis.com
Authorization: Bearer your_auth_token
Content-Length: 38
Content-Type: application/json; charset=UTF-8
X-Upload-Content-Type: image/jpeg
X-Upload-Content-Length: 2000000
{
"name": "My File"
}
设置所有标题字段,就像在 Google 的示例中一样。将其作为POST 请求发送。使用您的 credential 变量获取授权令牌。 X-Upload-Content-Type 的 mime 类型并不那么重要,没有它也可以工作(this SO answer 提供了一个很好的函数来从路径中检索它)。将X-Upload-Content-Length 设置为文件的总长度。将Content-Type 设置为 JSON 格式,因为我们的正文将以 JSON 格式为 Google 提供元数据。
现在创建您的元数据正文。我输入了文件名和父级。将Content-Length 设置为body 的长度(以字节为单位)。然后将你的正文写入request.getOutputStream() 输出流。
URL url = new URL("https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable");
HttpURLConnection request = (HttpURLConnection) url.openConnection();
request.setRequestMethod("POST");
request.setDoInput(true);
request.setDoOutput(true);
request.setRequestProperty("Authorization", "Bearer " + credential.getToken());
request.setRequestProperty("X-Upload-Content-Type", getMimeType(file.getPath()));
request.setRequestProperty("X-Upload-Content-Length", String.format(Locale.ENGLISH, "%d", file.length()));
request.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
String body = "{\"name\": \"" + file.getName() + "\", \"parents\": [\"" + parentId + "\"]}";
request.setRequestProperty("Content-Length", String.format(Locale.ENGLISH, "%d", body.getBytes().length));
OutputStream outputStream = request.getOutputStream();
outputStream.write(body.getBytes());
outputStream.close();
request.connect();
2) 保存可恢复会话 URI
最后,connect() 并等待回复。如果响应代码为200,则您已成功启动分块可恢复上传。现在将location 标头URI 保存在某处(数据库、文本文件等)。你以后会需要它的。
if (request.getResponseCode() == HttpURLConnection.HTTP_OK) {
String sessionUri = request.getHeaderField("location");
}
3) 上传文件
PUT {session_uri} HTTP/1.1
Host: www.googleapis.com
Content-Length: 524288
Content-Type: image/jpeg
Content-Range: bytes 0-524287/2000000
bytes 0-524288
将以下代码放入循环中,直到上传整个文件。在每个块之后,您将收到带有代码 308 和 range 标头的响应。从这个range 标头中,您可以读取下一个块开始(参见(4))。
Content-Type 将再次成为 mime 类型。 Content-Length 是您在此块中上传的字节数。 Content-Range 需要采用 bytes startByte-EndByte/BytesTotal 的形式。你把它放在PUT 请求中。
然后您创建一个FileInputStream 并将位置设置为您的起始字节(您从上一个响应range 标头中获得)并将另一个块读入您的缓冲区。然后将此缓冲区写入连接输出流。最后,connect()。
URL url = new URL(sessionUri);
HttpURLConnection request = (HttpURLConnection) url.openConnection();
request.setRequestMethod("PUT");
request.setDoOutput(true);
request.setConnectTimeout(10000);
request.setRequestProperty("Content-Type", getMimeType(file.getPath()));
long uploadedBytes = chunkSizeInMb * 1024 * 1024;
if (chunkStart + uploadedBytes > file.length()) {
uploadedBytes = (int) file.length() - chunkStart;
}
request.setRequestProperty("Content-Length", String.format(Locale.ENGLISH, "%d", uploadedBytes));
request.setRequestProperty("Content-Range", "bytes " + chunkStart + "-" + (chunkStart + uploadedBytes - 1) + "/" + file.length());
byte[] buffer = new byte[(int) uploadedBytes];
FileInputStream fileInputStream = new FileInputStream(file);
fileInputStream.getChannel().position(chunkStart);
if (fileInputStream.read(buffer, 0, (int) uploadedBytes) == -1) { /* break, return, exit*/ }
fileInputStream.close();
OutputStream outputStream = request.getOutputStream();
outputStream.write(buffer);
outputStream.close();
request.connect();
4) 处理响应
在此之后,您将收到带有代码308 的响应(如果成功)。此响应包含 range 标头(已提及)。
HTTP/1.1 308 Resume Incomplete
Content-Length: 0
Range: bytes=0-524287
您将其拆分并获取新的块起始字节。
String range = chunkUploadConnection.getHeaderField("range");
int chunkPosition = Long.parseLong(range.substring(range.lastIndexOf("-") + 1, range.length())) + 1;
5) 响应码不是308?!
您可能会收到5xx 响应。您的互联网连接可能会失败,文件可能会在上传过程中被删除/重命名,等等。
别担心。只要您保存会话 URI 和块起始字节,您就可以随时恢复上传。
为此,请发送以下格式的标头:
PUT {session_uri} HTTP/1.1
Content-Length: 0
Content-Range: bytes */TotalFileLength
URL url = new URL(sessionUri);
HttpURLConnection request = (HttpURLConnection) url.openConnection();
request.setRequestMethod("PUT");
request.setDoOutput(true);
request.setConnectTimeout(10000);
request.setRequestProperty("Content-Length", "0");
request.setRequestProperty("Content-Range", "bytes */" + file.length());
request.connect();
然后您将收到带有range 标头的308,您可以从中读取最后上传的字节(就像我们上面所做的那样)。取这个数字并重新开始循环。
我希望我能帮助你们中的一些人。如果您还有其他问题,请在 cmets 中提问,我会编辑答案。