私は現在使用してRetrofit 2
おり、サーバーに写真をアップロードしたいと考えています。TypedFile
古いバージョンではクラスを使用してアップロードしていることを知っています。プログレスバーを一緒に使用したい場合は、クラスのwriteTo
メソッドをオーバーライドする必要があります。TypedFile
retrofit 2
ライブラリを使用しているときに進行状況を表示することはできますか?
まず、2.0 beta2 以上の Retrofit 2 バージョンを使用する必要があります。次に、新しいクラス extends を作成しますRequestBody
。
public class ProgressRequestBody extends RequestBody {
private File mFile;
private String mPath;
private UploadCallbacks mListener;
private String content_type;
private static final int DEFAULT_BUFFER_SIZE = 2048;
public interface UploadCallbacks {
void onProgressUpdate(int percentage);
void onError();
void onFinish();
}
画像以外の他のタイプに対応できるように、コンテンツ タイプを追加したことに注意してください。
public ProgressRequestBody(final File file, String content_type, final UploadCallbacks listener) {
this.content_type = content_type;
mFile = file;
mListener = listener;
}
@Override
public MediaType contentType() {
return MediaType.parse(content_type+"/*");
}
@Override
public long contentLength() throws IOException {
return mFile.length();
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
long fileLength = mFile.length();
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
FileInputStream in = new FileInputStream(mFile);
long uploaded = 0;
try {
int read;
Handler handler = new Handler(Looper.getMainLooper());
while ((read = in.read(buffer)) != -1) {
// update progress on UI thread
handler.post(new ProgressUpdater(uploaded, fileLength));
uploaded += read;
sink.write(buffer, 0, read);
}
} finally {
in.close();
}
}
private class ProgressUpdater implements Runnable {
private long mUploaded;
private long mTotal;
public ProgressUpdater(long uploaded, long total) {
mUploaded = uploaded;
mTotal = total;
}
@Override
public void run() {
mListener.onProgressUpdate((int)(100 * mUploaded / mTotal));
}
}
}
第三に、インターフェースを作成します
@Multipart
@POST("/upload")
Call<JsonObject> uploadImage(@Part MultipartBody.Part file);
/* 上記の JsonObject は独自のモデルに置き換えることができます。これを注目に値するものにします。*/
これで、アップロードの進行状況を取得できます。あなたの
activity
(またはfragment
)で:
class MyActivity extends AppCompatActivity implements ProgressRequestBody.UploadCallbacks {
ProgressBar progressBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
progressBar = findViewById(R.id.progressBar);
ProgressRequestBody fileBody = new ProgressRequestBody(file, this);
MultipartBody.Part filePart =
MultipartBody.Part.createFormData("image", file.getName(), fileBody);
Call<JsonObject> request = RetrofitClient.uploadImage(filepart);
request.enqueue(new Callback<JsonObject>() {
@Override
public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
if(response.isSuccessful()){
/* Here we can equally assume the file has been downloaded successfully because for some reasons the onFinish method might not be called, I have tested it myself and it really not consistent, but the onProgressUpdate is efficient and we can use that to update our progress on the UIThread, and we can then set our progress to 100% right here because the file already downloaded finish. */
}
}
@Override
public void onFailure(Call<JsonObject> call, Throwable t) {
/* we can also stop our progress update here, although I have not check if the onError is being called when the file could not be downloaded, so I will just use this as a backup plan just in case the onError did not get called. So I can stop the progress right here. */
}
});
}
@Override
public void onProgressUpdate(int percentage) {
// set current progress
progressBar.setProgress(percentage);
}
@Override
public void onError() {
// do something on error
}
@Override
public void onFinish() {
// do something on upload finished,
// for example, start next uploading at a queue
progressBar.setProgress(100);
}
}
マルチパートではなく単純な POST を使用してアップロード ファイルの進行状況を処理する方法を次に示します。マルチパートについては、@ Yariy のソリューションを確認してください。さらに、このソリューションでは、ファイルを直接参照する代わりにコンテンツ URI を使用します。
RestClient
@Headers({
"Accept: application/json",
"Content-Type: application/octet-stream"
})
@POST("api/v1/upload")
Call<FileDTO> uploadFile(@Body RequestBody file);
ProgressRequestBody
public class ProgressRequestBody extends RequestBody {
private static final String LOG_TAG = ProgressRequestBody.class.getSimpleName();
public interface ProgressCallback {
public void onProgress(long progress, long total);
}
public static class UploadInfo {
//Content uri for the file
public Uri contentUri;
// File size in bytes
public long contentLength;
}
private WeakReference<Context> mContextRef;
private UploadInfo mUploadInfo;
private ProgressCallback mListener;
private static final int UPLOAD_PROGRESS_BUFFER_SIZE = 8192;
public ProgressRequestBody(Context context, UploadInfo uploadInfo, ProgressCallback listener) {
mContextRef = new WeakReference<>(context);
mUploadInfo = uploadInfo;
mListener = listener;
}
@Override
public MediaType contentType() {
// NOTE: We are posting the upload as binary data so we don't need the true mimeType
return MediaType.parse("application/octet-stream");
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
long fileLength = mUploadInfo.contentLength;
byte[] buffer = new byte[UPLOAD_PROGRESS_BUFFER_SIZE];
InputStream in = in();
long uploaded = 0;
try {
int read;
while ((read = in.read(buffer)) != -1) {
mListener.onProgress(uploaded, fileLength);
uploaded += read;
sink.write(buffer, 0, read);
}
} finally {
in.close();
}
}
/**
* WARNING: You must override this function and return the file size or you will get errors
*/
@Override
public long contentLength() throws IOException {
return mUploadInfo.contentLength;
}
private InputStream in() throws IOException {
InputStream stream = null;
try {
stream = getContentResolver().openInputStream(mUploadInfo.contentUri);
} catch (Exception ex) {
Log.e(LOG_TAG, "Error getting input stream for upload", ex);
}
return stream;
}
private ContentResolver getContentResolver() {
if (mContextRef.get() != null) {
return mContextRef.get().getContentResolver();
}
return null;
}
}
アップロードを開始するには:
// Create a ProgressRequestBody for the file
ProgressRequestBody requestBody = new ProgressRequestBody(
getContext(),
new UploadInfo(myUri, fileSize),
new ProgressRequestBody.ProgressCallback() {
public void onProgress(long progress, long total) {
//Update your progress UI here
//You'll probably want to use a handler to run on UI thread
}
}
);
// Upload
mRestClient.uploadFile(requestBody);
警告: contentLength() 関数をオーバーライドするのを忘れると、いくつかのあいまいなエラーが発生する可能性があります。
retrofit2.adapter.rxjava.HttpException: HTTP 503 client read error
または
Write error: ssl=0xb7e83110: I/O error during system call, Broken pipe
または
javax.net.ssl.SSLException: Read error: ssl=0x9524b800: I/O error during system call, Connection reset by peer
これらは、デフォルトの contentLength() が -1 であるため、RequestBody.writeTo() が複数回呼び出された結果です。
とにかく、これを理解するのに長い時間がかかりました。
この質問は何年も前に回答されたことに気づきましたが、Kotlin 用に更新すると思いました。
RequestBody を拡張するクラスを作成します。サポートする必要があるコンテンツ タイプを使用するには、必ず ContentType 列挙型クラスを設定してください。
class RequestBodyWithProgress(
private val file: File,
private val contentType: ContentType,
private val progressCallback:((progress: Float)->Unit)?
) : RequestBody() {
override fun contentType(): MediaType? = MediaType.parse(contentType.description)
override fun contentLength(): Long = file.length()
override fun writeTo(sink: BufferedSink) {
val fileLength = contentLength()
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
val inSt = FileInputStream(file)
var uploaded = 0L
inSt.use {
var read: Int = inSt.read(buffer)
val handler = Handler(Looper.getMainLooper())
while (read != -1) {
progressCallback?.let {
uploaded += read
val progress = (uploaded.toDouble() / fileLength.toDouble()).toFloat()
handler.post { it(progress) }
sink.write(buffer, 0, read)
}
read = inSt.read(buffer)
}
}
}
enum class ContentType(val description: String) {
PNG_IMAGE("image/png"),
JPG_IMAGE("image/jpg"),
IMAGE("image/*")
}
}
Retrofit を使用してファイルをアップロードします。
fun uploadFile(fileUri: Uri, progressCallback:((progress: Float)->Unit)?) {
val file = File(fileUri.path)
if (!file.exists()) throw FileNotFoundException(fileUri.path)
// create RequestBody instance from file
val requestFile = RequestBodyWithProgress(file, RequestBodyWithProgress.ContentType.PNG_IMAGE, progressCallback)
// MultipartBody.Part is used to send also the actual file name
val body = MultipartBody.Part.createFormData("image_file", file.name, requestFile)
publicApiService().uploadFile(body).enqueue(object : Callback<MyResponseObj> {
override fun onFailure(call: Call<MyResponseObj>, t: Throwable) {
}
override fun onResponse(call: Call<MyResponseObj>, response: Response<MyResponseObj>) {
}
})
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
long fileLength = mFile.length();
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
FileInputStream in = new FileInputStream(mFile);
long uploaded = 0;
try {
int read;
Handler handler = new Handler(Looper.getMainLooper());
int num = 0;
while ((read = in.read(buffer)) != -1) {
int progress = (int) (100 * uploaded / fileLength);
if (progress > num + 1) {
mListener.onProgressUpdate((int) (100 * uploaded / fileLength));
num = progress;
}
uploaded += read;
if (uploaded == fileLength) {
mListener.onFinish();
}
sink.write(buffer, 0, read);
}
Log.e("Progress", "erwer");
} finally {
in.close();
}
}
このコードは onFinish 呼び出しを保証します。
私は@Yuriy Kolbasinskiyが答えを与えられたことに感謝しますが、WriteTo()関数でいくつかを変更した後、「3037038バイトが必要ですが、3039232を受け取りました」というエラーが表示されます。答えは以下のKotlinにあります:-
override fun writeTo(sink: BufferedSink) {
var uploaded:Long = 0
var source: Source? = null
try {
source = Okio.source(file)
val handler = Handler(Looper.getMainLooper())
do {
val read = source.read(sink.buffer(), 2048)
while (read == -1L) return
uploaded += read
handler.post(ProgressUpdater(uploaded, file.length()))
sink.flush()
} while(true)
} finally {
Util.closeQuietly(source)
}
}