91

我目前正在使用Retrofit 2,我想在我的服务器上上传一些照片。我知道,旧版本使用TypedFile类进行上传。如果我们想使用进度条,我们应该重写类中writeTo的方法TypedFile

retrofit 2使用库时是否可以显示进度?

4

17 回答 17

178

首先,你应该使用Retrofit 2 等于或高于2.0 beta2 的版本。其次,创建新类 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 回答
27

修改 Yuriy Kolbasinskiy 以使用 rxjava 并使用 kotlin。添加了同时使用 HttpLoggingInterceptor 的解决方法

class ProgressRequestBody : RequestBody {

    val mFile: File
    val ignoreFirstNumberOfWriteToCalls : Int


    constructor(mFile: File) : super(){
        this.mFile = mFile
        ignoreFirstNumberOfWriteToCalls = 0
    }

    constructor(mFile: File, ignoreFirstNumberOfWriteToCalls : Int) : super(){
        this.mFile = mFile
        this.ignoreFirstNumberOfWriteToCalls = ignoreFirstNumberOfWriteToCalls
    }


    var numWriteToCalls = 0

    protected val getProgressSubject: PublishSubject<Float> = PublishSubject.create<Float>()

    fun getProgressSubject(): Observable<Float> {
        return getProgressSubject
    }


    override fun contentType(): MediaType {
        return MediaType.parse("video/mp4")
    }

    @Throws(IOException::class)
    override fun contentLength(): Long {
        return mFile.length()
    }

    @Throws(IOException::class)
    override fun writeTo(sink: BufferedSink) {
        numWriteToCalls++

        val fileLength = mFile.length()
        val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
        val `in` = FileInputStream(mFile)
        var uploaded: Long = 0

        try {
            var read: Int
            var lastProgressPercentUpdate = 0.0f
            read = `in`.read(buffer)
            while (read != -1) {

                uploaded += read.toLong()
                sink.write(buffer, 0, read)
                read = `in`.read(buffer)

                // when using HttpLoggingInterceptor it calls writeTo and passes data into a local buffer just for logging purposes.
                // the second call to write to is the progress we actually want to track
                if (numWriteToCalls > ignoreFirstNumberOfWriteToCalls ) {
                    val progress = (uploaded.toFloat() / fileLength.toFloat()) * 100f
                    //prevent publishing too many updates, which slows upload, by checking if the upload has progressed by at least 1 percent
                    if (progress - lastProgressPercentUpdate > 1 || progress == 100f) {
                        // publish progress
                        getProgressSubject.onNext(progress)
                        lastProgressPercentUpdate = progress
                    }
                }
            }
        } finally {
            `in`.close()
        }
    }


    companion object {

        private val DEFAULT_BUFFER_SIZE = 2048
    }
}

视频上传接口示例

public interface Api {

    @Multipart
    @POST("/upload")        
    Observable<ResponseBody> uploadVideo(@Body MultipartBody requestBody);
}

发布视频的示例函数:

fun postVideo(){
            val api : Api = Retrofit.Builder()
            .client(OkHttpClient.Builder()
                    //.addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
                    .build())
            .baseUrl("BASE_URL")
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .build()
            .create(Api::class.java)

    val videoPart = ProgressRequestBody(File(VIDEO_URI))
    //val videoPart = ProgressRequestBody(File(VIDEO_URI), 1) //HttpLoggingInterceptor workaround
    val requestBody = MultipartBody.Builder()
            .setType(MultipartBody.FORM)
            .addFormDataPart("example[name]", place.providerId)
            .addFormDataPart("example[video]","video.mp4", videoPart)
            .build()

    videoPart.getProgressSubject()
            .subscribeOn(Schedulers.io())
            .subscribe { percentage ->
                Log.i("PROGRESS", "${percentage}%")
            }

    var postSub : Disposable?= null
    postSub = api.postVideo(requestBody)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe({ r ->
            },{e->
                e.printStackTrace()
                postSub?.dispose();

            }, {
                Toast.makeText(this,"Upload SUCCESS!!",Toast.LENGTH_LONG).show()
                postSub?.dispose();
            })
}
于 2017-05-23T03:28:15.543 回答
12

以下是如何使用简单的 POST 而不是 Multipart 来处理上传文件的进度。对于多部分检查@Yariy 的解决方案。此外,此解决方案使用内容 URI 而不是直接文件引用。

休息客户端

@Headers({
    "Accept: application/json",
    "Content-Type: application/octet-stream"
})
@POST("api/v1/upload")
Call<FileDTO> uploadFile(@Body RequestBody file);

进度请求体

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

这些是 RequestBody.writeTo() 被多次调用的结果,因为默认 contentLength() 为 -1。

无论如何,这需要很长时间才能弄清楚,希望对您有所帮助。

有用的链接: https ://github.com/square/retrofit/issues/1217

于 2016-06-28T18:40:52.157 回答
5

@luca992 谢谢你的回答。我已经在 J​​AVA 中实现了这个,现在它工作正常。

public class ProgressRequestBodyObservable extends RequestBody {

    File file;
    int ignoreFirstNumberOfWriteToCalls;
    int numWriteToCalls;`enter code here`

    public ProgressRequestBodyObservable(File file) {
        this.file = file;

        ignoreFirstNumberOfWriteToCalls =0;
    }

    public ProgressRequestBodyObservable(File file, int ignoreFirstNumberOfWriteToCalls) {
        this.file = file;
        this.ignoreFirstNumberOfWriteToCalls = ignoreFirstNumberOfWriteToCalls;
    }


    PublishSubject<Float> floatPublishSubject = PublishSubject.create();

   public Observable<Float> getProgressSubject(){
        return floatPublishSubject;
    }

    @Override
    public MediaType contentType() {
        return MediaType.parse("image/*");
    }

    @Override
    public long contentLength() throws IOException {
        return file.length();
    }



    @Override
    public void writeTo(BufferedSink sink) throws IOException {
        numWriteToCalls++;


        float fileLength = file.length();
        byte[] buffer = new byte[2048];
        FileInputStream in = new  FileInputStream(file);
        float uploaded = 0;

        try {
            int read;
            read = in.read(buffer);
            float lastProgressPercentUpdate = 0;
            while (read != -1) {

                uploaded += read;
                sink.write(buffer, 0, read);
                read = in.read(buffer);

                // when using HttpLoggingInterceptor it calls writeTo and passes data into a local buffer just for logging purposes.
                // the second call to write to is the progress we actually want to track
                if (numWriteToCalls > ignoreFirstNumberOfWriteToCalls ) {
                    float progress = (uploaded / fileLength) * 100;
                    //prevent publishing too many updates, which slows upload, by checking if the upload has progressed by at least 1 percent
                    if (progress - lastProgressPercentUpdate > 1 || progress == 100f) {
                        // publish progress
                        floatPublishSubject.onNext(progress);
                        lastProgressPercentUpdate = progress;
                    }
                }
            }
        } finally {
        in.close();
        }

    }
}
于 2017-06-23T09:19:54.430 回答
2

我更新进度条 onProgressUpdate。这段代码可以获得更好的性能。

@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 ){
                // update progress on UI thread
                handler.post(new ProgressUpdater(uploaded, fileLength));
                num = progress;
            }

            uploaded += read;
            sink.write(buffer, 0, read);
        }
    } finally {
        in.close();
    }
}
于 2017-01-18T03:11:38.520 回答
2

httpbuilder. 否则它将调用writeTo()两次。或从 更改日志记录级别BODY

于 2018-02-08T12:42:26.570 回答
1

此答案用于 MultipartBody 和上传多个文件。我的服务器端代码是 mvc 开发。首先,您需要这样的 ApiService 类:

public interface ApiService {

@POST("Home/UploadVideos")
Call<ResponseBody> postMeme(@Body RequestBody files);
}

你需要这样的 Apiclient:

public class ApiClient {
public static final String API_BASE_URL = "http://192.168.43.243/Web/";

private static OkHttpClient.Builder httpClient = new OkHttpClient.Builder();

private static Retrofit.Builder builder = new Retrofit.Builder().baseUrl(API_BASE_URL).addConverterFactory(GsonConverterFactory.create());

public static ApiService createService(Class<ApiService> serviceClass)
{
    Retrofit retrofit = builder.client(httpClient.build()).build();
    return retrofit.create(serviceClass);
}
}

之后你需要这样的 ReqestBody 类:

public class CountingFileRequestBody extends RequestBody {
private static final String TAG = "CountingFileRequestBody";

private final ProgressListener listener;
private final String key;
private final MultipartBody multipartBody;
protected CountingSink mCountingSink;

public CountingFileRequestBody(MultipartBody multipartBody,
                               String key,
                               ProgressListener listener) {
    this.multipartBody = multipartBody;
    this.listener = listener;
    this.key = key;
}

@Override
public long contentLength() throws IOException {
    return multipartBody.contentLength();
}

@Override
public MediaType contentType() {
    return multipartBody.contentType();
}

@Override
public void writeTo(BufferedSink sink) throws IOException {
    mCountingSink = new CountingSink(sink);
    BufferedSink bufferedSink = Okio.buffer(mCountingSink);
    multipartBody.writeTo(bufferedSink);
    bufferedSink.flush();
}

public interface ProgressListener {
    void transferred(String key, int num);
}

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 {
        bytesWritten += byteCount;
        listener.transferred(key, (int) (100F * bytesWritten / contentLength()));
        super.write(source, byteCount);
        delegate().flush(); // I have added this line to manually flush the sink
    }
}

}

最后,您需要以下代码:

ApiService service = ApiClient.createService(ApiService.class);

        MultipartBody.Builder builder = new MultipartBody.Builder();
        builder.setType(MultipartBody.FORM);
        builder.addFormDataPart("files",file1.getName(), RequestBody.create(MediaType.parse("video/*"), file1));
        builder.addFormDataPart("files",file3.getName(), RequestBody.create(MediaType.parse("video/*"), file3));

        MultipartBody requestBody = builder.build();

        CountingFileRequestBody requestBody1 = new CountingFileRequestBody(requestBody, "files", new CountingFileRequestBody.ProgressListener() {
            @Override
            public void transferred(String key, int num) {
                Log.d("FinishAdapter","Perecentae is :"+num);
                //update progressbar here
                dialog.updateProgress(num);
                if (num == 100){
                    dialog.dismiss();
                }

            }
        });

        Call<ResponseBody> call = service.postMeme(requestBody1);
        call.enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
               // Toast.makeText(getBaseContext(),"All fine",Toast.LENGTH_SHORT).show();
                Log.d("FinishAdapter","every thing is ok............!");
                Log.d("FinishAdapter",response.toString());
            }

            @Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                //Toast.makeText(getBaseContext(),t.getMessage(),Toast.LENGTH_SHORT).show();
                Log.d("FinishAdapter","every thing is failed............!");
            }
        });

希望能帮助到你。

于 2019-10-22T17:27:25.270 回答
1

这是 Kotlin 的扩展功能。

/** Returns a new request body that transmits the content of this. */
fun File.asRequestBodyWithProgress(
    contentType: MediaType? = null,
    progressCallback: ((progress: Float) -> Unit)?
): RequestBody {
    return object : RequestBody() {
        override fun contentType() = contentType

        override fun contentLength() = length()

        override fun writeTo(sink: BufferedSink) {
            val fileLength = contentLength()
            val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
            val inSt = FileInputStream(this@asRequestBodyWithProgress)
            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)
                }
            }
        }
    }
}

这是上述函数与Flow的用法

private val key = "file"
private val multiPart = "multipart/form-data".toMediaType()

@WorkerThread
fun uploadFile(
    path: String,
    onStart: () -> Unit,
    onComplete: () -> Unit,
    onProgress: (progress: Float) -> Unit,
    onError: (String?) -> Unit
) = flow {
    val file = File(path)
    val requestFile = file.asRequestBodyWithProgress(multiPart, onProgress)
    val requestBody = MultipartBody.Part.createFormData(key, file.name, requestFile)
    //val requestBody = MultipartBody.Builder().addFormDataPart(key, file.name, requestFile).build()

    val response = uploadClient.uploadFile(requestBody)
    response.suspendOnSuccess {
        data.whatIfNotNull {
            emit(it)
        }
    }.onError {
        /** maps the [ApiResponse.Failure.Error] to the [ErrorResponse] using the mapper. */
        map(ErrorResponseMapper) { onError("[Code: $code]: $message") }
    }.onException { onError(message) }
}.onStart { onStart() }.onCompletion { onComplete() }.flowOn(Dispatchers.IO)

更新 -> 自 API 30 以来,很难获得文件的真实路径。所以我们可以使用 InputStream,如下所述。

@WorkerThread
fun uploadFile(
    path: String,
    onStart: () -> Unit,
    onComplete: (String?) -> Unit,
    onProgress: (progress: Float) -> Unit,
    onError: (String?) -> Unit
) = flow {
    openStream(path).whatIfNotNull { inputStream ->
        val requestFile = inputStream.asRequestBodyWithProgress(MultipartBody.FORM, onProgress)
        val requestBody = MultipartBody.Part.createFormData(key, "file", requestFile)
        //val requestBody = MultipartBody.Builder().addFormDataPart(key, file.name, requestFile).build()

        uploadClient.uploadFile(requestBody).suspendOnSuccess {
            data.whatIfNotNull {
                link = it.link
                emit(it)
            }
        }.onError {
            /** maps the [ApiResponse.Failure.Error] to the [ErrorResponse] using the mapper. */
            map(ErrorResponseMapper) { onError("[Code: $code]: $message") }
        }.onException { onError(message) }
    }
}.onStart { onStart() }.onCompletion { onComplete(link) }.flowOn(Dispatchers.IO)

private fun openStream(path: String): InputStream? {
    return context.contentResolver.openInputStream(Uri.parse(path))
}


/** Returns a new request body that transmits the content of this. */
fun InputStream.asRequestBodyWithProgress(
    contentType: MediaType? = null,
    progressCallback: ((progress: Float) -> Unit)?
): RequestBody {
    return object : RequestBody() {
        override fun contentType() = contentType

        override fun contentLength() = try {
            available().toLong()
        } catch (e: IOException){
            Timber.e(e)
            0
        }

        override fun writeTo(sink: BufferedSink) {
            val fileLength = contentLength()
            val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
            val inputStream = this@asRequestBodyWithProgress
            var uploaded = 0L
            inputStream.use {
                var read: Int = inputStream.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 = inputStream.read(buffer)
                }
            }
        }
    }
}
于 2021-05-14T11:03:15.607 回答
0

我尝试使用上面的代码,但我发现 UI 卡住了,所以我尝试了这个代码,这对我有用,或者可以尝试使用这个代码

于 2017-06-03T14:22:06.337 回答
0

避免两次运行问题。我们可以在第一次调用进度对话框后将标志设置为 0,然后将标志设置为 1。

 @Override
    public void writeTo(BufferedSink sink) throws IOException {

        Source source = null;
        try {
            source = Okio.source(mFile);
            total = 0;
            long read;

            Handler handler = new Handler(Looper.getMainLooper());

            while ((read = source.read(sink.buffer(), DEFAULT_BUFFER_SIZE)) != -1) {

                total += read;
                sink.flush();

                // flag for avoiding first progress bar .
                if (flag != 0) {
                    handler.post(() -> mListener.onProgressUpdate((int) (100 * total / mFile.length())));

                }
            }

            flag = 1;

        } finally {
            Util.closeQuietly(source);
        }
    }
于 2018-05-18T10:43:42.763 回答
0

用于创建零件的扩展。调用服务期间将调用回调

fun File.toPart(type: String = "image/*", callback: (progress: Int)->Unit) = MultipartBody.Part.createFormData(name, name, object : RequestBody() {
    val contentType = MediaType.parse(type)
    val length = this@toPart.length()
    var uploaded = 0L
    override fun contentType(): MediaType? {
        return contentType
    }

    override fun contentLength(): Long = length

    @Throws(IOException::class)
    override fun writeTo(sink: BufferedSink) {
        var source: Source? = null
        try {
            source = Okio.source(this@toPart)

            do {
                val read = source.read(sink.buffer(), 2048)
                if(read == -1L) return // exit at EOF
                sink.flush()
                uploaded += read
                callback((uploaded.toDouble()/length.toDouble()*100).toInt())
            } while(true)
            //sink.writeAll(source!!)
        } finally {
            Util.closeQuietly(source)
        }
    }
})
于 2019-10-24T22:57:08.717 回答
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

我喜欢@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 回答
0

这是一个受此线程启发的扩展函数,它运行得非常好。

fun File.toRequestBody(progressCallback: ((progress: Int) -> Unit)?): RequestBody {
    return object : RequestBody() {

        private var currentProgress = 0
        private var uploaded = 0L

        override fun contentType(): MediaType? {
            val fileType = name.substringAfterLast('.', "")
            return fileType.toMediaTypeOrNull()
        }

        @Throws(IOException::class)
        override fun writeTo(sink: BufferedSink) {
            source().use { source ->
                do {
                    val read = source.read(sink.buffer, 2048)
                    if (read == -1L) return // exit at EOF
                    sink.flush()
                    uploaded += read

                    /**
                     * The value of newProgress is going to be in between 0.0 - 2.0
                     */
                    var newProgress = ((uploaded.toDouble() / length().toDouble()))

                    /**
                     * To map it between 0.0 - 100.0
                     * Need to multiply it with 50
                     * (OutputMaxRange/InputMaxRange)
                     * 100 / 2 = 50
                     */
                    newProgress = (50 * newProgress)

                    if (newProgress.toInt() != currentProgress) {
                        progressCallback?.invoke(newProgress.toInt())
                    }
                    currentProgress = newProgress.toInt()
                } while (true)
            }
        }
    }
}
于 2021-07-12T19:40:57.960 回答
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 回答
-1

据我在这篇文章中看到的,没有关于图像上传进度响应的更新,您仍然必须通过创建一个接口并使用override该 方法的子类来使用SO 答案writeTo中所示的方法。ProgressListenerTypedFileoverridewriteTo

因此,在使用 Retrofit 2 库时,没有任何内置方法可以显示进度。

于 2015-10-26T04:26:46.807 回答
-4

您可以使用使用 Retrofit Library 的 FileUploader 连接到 API。上传文件的代码骨架如下:

FileUploader fileUploader = new FileUploader();
fileUploader.uploadFiles("/", "file", filesToUpload, new FileUploader.FileUploaderCallback() {
    @Override
    public void onError() {
        // Hide progressbar
    }

    @Override
    public void onFinish(String[] responses) {
        // Hide progressbar

        for(int i=0; i< responses.length; i++){
            String str = responses[i];
            Log.e("RESPONSE "+i, responses[i]);
        }
    }

    @Override
    public void onProgressUpdate(int currentpercent, int totalpercent, int filenumber) {
        // Update Progressbar
        Log.e("Progress Status", currentpercent+" "+totalpercent+" "+filenumber);
    }
});

完整步骤可在 Medium 获得:

在 Android 中使用进度改进多个文件上传

于 2019-05-21T06:03:51.217 回答