91

私は現在使用してRetrofit 2おり、サーバーに写真をアップロードしたいと考えています。TypedFile古いバージョンではクラスを使用してアップロードしていることを知っています。プログレスバーを一緒に使用したい場合は、クラスのwriteToメソッドをオーバーライドする必要があります。TypedFile

retrofit 2ライブラリを使用しているときに進行状況を表示することはできますか?

4

17 に答える 17

178

まず、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);
        }

}
于 2015-10-28T06:44:03.147 に答える
12

マルチパートではなく単純な 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() が複数回呼び出された結果です。

とにかく、これを理解するのに長い時間がかかりました。

便利なリンク: https://github.com/square/retrofit/issues/1217

于 2016-06-28T18:40:52.157 に答える
0

この質問は何年も前に回答されたことに気づきましたが、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>) {

        }
    })

}
于 2019-12-09T15:53:48.193 に答える
0
@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 呼び出しを保証します。

于 2021-12-21T21:41:52.810 に答える
0

私は@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)
    }
}
于 2020-01-07T13:27:02.547 に答える
-1

この投稿で確認できる限り、画像のアップロード進行状況の応答に関する更新は行われておらず、インターフェイスを作成し、メソッドのサブクラスを使用して、このSO の回答に示されているメソッドoverrideを 使用する必要があります。writeToProgressListenerTypedFileoverridewriteTo

そのため、retrofit 2 ライブラリの使用時に進行状況を表示する組み込みの方法はありません。

于 2015-10-26T04:26:46.807 に答える