我已经构建了一个精简的Android应用程序,可以使用签名的URL将文件上传到Amazon S3。这些URL需要进行PUT调用,这与通过POST进行的大多数文件上传稍有不同。
我只是将S3 URL粘贴到textview中。我已经验证了S3 URL可以与此cURL命令一起使用,因此一切正常。
curl -X PUT 'https://mybucket.s3.us-west-2.amazonaws.com/uploads/android/somevideo.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credentia...0&X-Amz-Signature=213123123&X-Amz-SignedHeaders=host' --upload-file ~/Downloads/video.mp4
文件上传在Android应用程序中正确完成,但是我从没进入update
回调,因此无法更新对话框中的进度栏。
因为我正在执行PUT请求,这是否发生了不同的事情,这不是分段提交(我只是将文件作为主体作为八位字节流发送)?
我的活动代码和内部的Progress类都在下面的一个文件中。当我在第126行(update
函数)上设置断点时,我从不输入它。我使用了sample Progress listener linked here,但是由于某种原因,我的代码从未被调用。
package com.example.putuploadwithprogress;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import java.io.File;
import java.io.IOException;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okio.Buffer;
import okio.BufferedSource;
import okio.ForwardingSource;
import okio.Okio;
import okio.Source;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final TextView uploadUrl = findViewById(R.id.uploadUrl);
Button uploadButton = findViewById(R.id.uploadButton);
uploadButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
View layout = getLayoutInflater().inflate(R.layout.upload_progress, null);
final ProgressBar progress = layout.findViewById(R.id.uploadProgressBar);
builder.setView(layout);
builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.dismiss();
}
});
final AlertDialog dialog = builder.show();
final String url = uploadUrl.getText().toString().trim();
try {
new Thread(new Runnable() {
@Override
public void run() {
String mimeType = "video/mp4";
String filename = Environment
.getExternalStoragePublicDirectory(Environment
.DIRECTORY_DOWNLOADS).toString() + "/video.mp4";
File file = new File(filename);
try {
new Progress(url, file, mimeType, MainActivity.this, progress, dialog).run();
MainActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
dialog.hide();
}
});
} catch (Exception e) {
System.out.println( "Got an error: " + e.getMessage());
MainActivity.this.runOnUiThread(new Runnable() { @Override public void run() { dialog.hide(); } });
}
}
}).start();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
class Progress {
private String mime;
ProgressBar progressBar;
AlertDialog dialog;
Activity activity;
private String url;
private File file;
Progress(String _url,
File _file,
String _mime,
Activity _activity,
ProgressBar _progressBar,
AlertDialog _dialog) {
url = _url;
file = _file;
mime = _mime;
dialog = _dialog;
progressBar = _progressBar;
activity = _activity;
}
public void run() {
final Request request = new Request.Builder()
.header("Content-Type", mime)
.url(url)
.put(RequestBody.create(MediaType.parse(mime), file))
.build();
final ProgressListener progressListener = new ProgressListener() {
@Override
public void update(long bytesRead, long contentLength, boolean done) {
if (done) {
if (dialog != null)
activity.runOnUiThread(new Runnable() {
public void run() {
dialog.hide();
}
});
} else {
if (contentLength > 0) {
final int progress = (int) (((double) bytesRead / contentLength) * 100);
if (progressBar != null)
activity.runOnUiThread(new Runnable() {
public void run() {
progressBar.setProgress(progress);
}
});
}
}
}
};
OkHttpClient client = new OkHttpClient.Builder()
.addNetworkInterceptor(new Interceptor() {
@Override public Response intercept(Chain chain) throws IOException {
Response originalResponse = chain.proceed(chain.request());
return originalResponse.newBuilder()
.body(new ProgressResponseBody(originalResponse.body(), progressListener))
.build();
}
})
.build();
try (Response response = client.newCall(request).execute()) {
activity.runOnUiThread(
new Runnable() {
@Override
public void run() {
if (!response.isSuccessful()) {
String message = response.message();
Toast.makeText(activity, "Unable to upload file: " + message, Toast.LENGTH_LONG).show();
} else {
Toast.makeText(activity, "Uploaded file successfully", Toast.LENGTH_LONG).show();
}
dialog.hide();
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
interface ProgressListener {
void update(long bytesRead, long contentLength, boolean done);
}
private static class ProgressResponseBody extends ResponseBody {
private final ResponseBody responseBody;
private final ProgressListener progressListener;
private BufferedSource bufferedSource;
ProgressResponseBody(ResponseBody responseBody, ProgressListener progressListener) {
this.responseBody = responseBody;
this.progressListener = progressListener;
}
@Override
public MediaType contentType() {
return responseBody.contentType();
}
@Override
public long contentLength() {
return responseBody.contentLength();
}
@Override
public BufferedSource source() {
if (bufferedSource == null) {
bufferedSource = Okio.buffer(source(responseBody.source()));
}
return bufferedSource;
}
private Source source(Source source) {
return new ForwardingSource(source) {
long totalBytesRead = 0L;
@Override
public long read(Buffer sink, long byteCount) throws IOException {
long bytesRead = super.read(sink, byteCount);
// read() returns the number of bytes read, or -1 if this source is exhausted.
totalBytesRead += bytesRead != -1 ? bytesRead : 0;
progressListener.update(totalBytesRead, responseBody.contentLength(), bytesRead == -1);
return bytesRead;
}
};
}
}
}
我有两种看法。首先,main_activity.xml
:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/uploadButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Upload File"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/uploadUrl"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:ems="10"
android:hint="S3 URL, paste in here"
android:inputType="textMultiLine|textNoSuggestions"
app:layout_constraintBottom_toTopOf="@+id/uploadButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
upload_progress.xml
:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="20dp">
<ProgressBar
android:id="@+id/uploadProgressBar"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_width="0dp"
android:layout_height="30dp"
android:layout_marginStart="8dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="8dp"
android:max="100"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView"
tools:progress="45" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="10dp"
android:text="Uploading file..."
android:textAppearance="@style/TextAppearance.AppCompat.Headline"
app:layout_constraintBottom_toTopOf="@+id/uploadProgressBar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="spread_inside" />
<TextView
android:id="@+id/uploadStatusMessage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="8dp"
android:text="..."
android:textAppearance="@style/TextAppearance.AppCompat.Light.SearchResult.Title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/uploadProgressBar" />
</android.support.constraint.ConstraintLayout>
Android清单具有正确的Internet权限,并且我已在应用程序权限中启用了读取权限。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.putuploadwithprogress">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
答案 0 :(得分:0)
如nikhil所述,问题是这是在拦截响应而不是请求。
拦截器需要像这样更改:
.addNetworkInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
if (originalRequest.body() == null) {
return chain.proceed(originalRequest);
}
Request progressRequest = originalRequest.newBuilder()
.method(originalRequest.method(),
new CountingRequestBody(originalRequest.body(), progressListener))
.build();
return chain.proceed(progressRequest);
}
下面的完整代码有效。
package com.example.putuploadwithprogress;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import java.io.File;
import java.io.IOException;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okio.Buffer;
import okio.BufferedSink;
import okio.ForwardingSink;
import okio.Okio;
import okio.Sink;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final TextView uploadUrl = findViewById(R.id.uploadUrl);
Button uploadButton = findViewById(R.id.uploadButton);
uploadButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
View layout = getLayoutInflater().inflate(R.layout.upload_progress, null);
final ProgressBar progress = layout.findViewById(R.id.uploadProgressBar);
builder.setView(layout);
builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.dismiss();
}
});
final AlertDialog dialog = builder.show();
final String url = uploadUrl.getText().toString().trim();
try {
new Thread(new Runnable() {
@Override
public void run() {
String mimeType = "video/mp4";
String filename = Environment
.getExternalStoragePublicDirectory(Environment
.DIRECTORY_DOWNLOADS).toString() + "/video.mp4";
File file = new File(filename);
try {
new Progress(url, file, mimeType, MainActivity.this, progress, dialog).run();
MainActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
dialog.hide();
}
});
} catch (Exception e) {
System.out.println( "Got an error: " + e.getMessage());
MainActivity.this.runOnUiThread(new Runnable() { @Override public void run() { dialog.hide(); } });
}
}
}).start();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
class Progress {
private String mime;
ProgressBar progressBar;
AlertDialog dialog;
Activity activity;
private String url;
private File file;
Progress(String _url,
File _file,
String _mime,
Activity _activity,
ProgressBar _progressBar,
AlertDialog _dialog) {
url = _url;
file = _file;
mime = _mime;
dialog = _dialog;
progressBar = _progressBar;
activity = _activity;
}
public void run() {
final Request request = new Request.Builder()
.header("Content-Type", mime)
.url(url)
.put(RequestBody.create(MediaType.parse(mime), file))
.build();
final CountingRequestBody.Listener progressListener = new CountingRequestBody.Listener() {
@Override
public void onRequestProgress(long bytesRead, long contentLength) {
if (bytesRead >= contentLength) {
if (dialog != null)
activity.runOnUiThread(new Runnable() {
public void run() {
dialog.hide();
}
});
} else {
if (contentLength > 0) {
final int progress = (int) (((double) bytesRead / contentLength) * 100);
if (progressBar != null)
activity.runOnUiThread(new Runnable() {
public void run() {
progressBar.setProgress(progress);
}
});
}
}
}
};
OkHttpClient client = new OkHttpClient.Builder()
.addNetworkInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
if (originalRequest.body() == null) {
return chain.proceed(originalRequest);
}
Request progressRequest = originalRequest.newBuilder()
.method(originalRequest.method(),
new CountingRequestBody(originalRequest.body(), progressListener))
.build();
return chain.proceed(progressRequest);
}
})
.build();
try (Response response = client.newCall(request).execute()) {
activity.runOnUiThread(
new Runnable() {
@Override
public void run() {
if (!response.isSuccessful()) {
String message = response.message();
Toast.makeText(activity, "Unable to upload file: " + message, Toast.LENGTH_LONG).show();
} else {
Toast.makeText(activity, "Uploaded file successfully", Toast.LENGTH_LONG).show();
}
dialog.hide();
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
}
class CountingRequestBody extends RequestBody {
protected RequestBody delegate;
protected Listener listener;
protected CountingSink countingSink;
public CountingRequestBody(RequestBody delegate, Listener listener) {
this.delegate = delegate;
this.listener = listener;
}
@Override public MediaType contentType() {
return delegate.contentType();
}
@Override public long contentLength() {
try {
return delegate.contentLength();
} catch (IOException e) {
e.printStackTrace();
}
return -1;
}
@Override public void writeTo(BufferedSink sink) throws IOException {
BufferedSink bufferedSink;
countingSink = new CountingSink(sink);
bufferedSink = Okio.buffer(countingSink);
delegate.writeTo(bufferedSink);
bufferedSink.flush();
}
protected final class CountingSink extends ForwardingSink {
private long bytesWritten = 0;
public CountingSink(Sink delegate) {
super(delegate);
}
@Override public void write(Buffer source, long byteCount) throws IOException {
super.write(source, byteCount);
bytesWritten += byteCount;
listener.onRequestProgress(bytesWritten, contentLength());
}
}
public static interface Listener {
public void onRequestProgress(long bytesWritten, long contentLength);
}
}