Чи можна показати індикатор прогресу при завантаженні зображення через Retrofit 2?


75

Зараз Retrofit 2я використовую, і я хочу завантажити фотографію на свій сервер. Я знаю, що старіша версія використовує TypedFileклас для завантаження. І якщо ми хочемо використовувати індикатор прогресу, ми повинні замінити writeToметод у TypedFileкласі.

Чи можна показати прогрес при використанні retrofit 2бібліотеки?


Для тих, хто все ще залишається на Retrofit 1.x, ось просте, робоче рішення, яке ми в підсумку використали: stackoverflow.com/a/24772058/293280
Джошуа Пінтер

Відповіді:


169

Перш за все, слід використовувати версію Retrofit 2, що дорівнює або перевищує 2,0 beta2. По-друге, створіть новий клас розширень 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);
        }

}

14
Я бачу дані, записані в раковину, майже відразу, однак фактичний дзвінок займає кілька секунд при повільному з'єднанні. Я спробував додати sink.flush () до виклику після запису, але, як і раніше, він все одно відображає час внутрішньої передачі даних, а не хід передачі по мережі.
Джейк Холл,

6
handler.post (новий ProgressUpdater (завантажено, fileLength)); Цей рядок повинен знаходитися внизу циклу while, інакше ви завжди отримаєте 99%.
codezjx

9
Я також стикаюся з тією ж проблемою з JakeHall і не міг знайти рішення ... метод writeTo викликається двічі, і перший раз він завантажується відразу, але другий раз - це реальний період завантаження, який ми шукаємо. Чи є у вас коментар до цього @YuriyKolbasinskiy?
yahya

23
@yahya Я бачив те саме і виявив, що це було викликано HttpLoggingInterceptor, який я створив. Першим, швидким завантаженням є HttpLoggingInterceptor, який викликає writeTo і передає локальний буфер лише для ведення журналу.
Devin Pitcher

4
Як я можу показати прогрес для MultipartBody.Part [], одночасно завантажуючи кілька файлів за один запит?
Усман Рана

24

Модифіковано Юрія Колбасинського для використання 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();
            })
}

1
Це просто показує швидкість запису, а не швидкість завантаження. Це триває, схоже, ми оновили прогрес у циклі for від 0 до 100, оскільки моє завантаження відбувається не так швидко, як показує прогрес
Akash Dubey

1
@AkashDubey звучить так, ніби ви використовуєте HttpLoggingInterceptor, але не використовували прокоментоване обхідне завдання, яке я надав
luca992,

вибачте, погано, мій файл був замалий, отже завантажується швидше, працює нормально
Akash Dubey

1
Я також стикаюся з тим самим питанням, на яке вказав @AkashDubey. Використання обхідного шляху для HttpLoggingInterceptor не допомогло. Це просто перешкоджає виконанню логіки writeTo двічі, а дані все одно записуються в раковину негайно під час наступного виклику writeTo.
Vikalp

що означає DEFAULT_BUFFER_SIZE? це максимально допустимий розмір файлу?
Alexa289

11

Ось як обробляти хід завантаження файлів за допомогою простого POST, а не Multipart. Для отримання деталей ознайомтесь із рішенням @ 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

Вони є результатом багаторазового виклику RequestBody.writeTo (), оскільки за замовчуванням contentLength () дорівнює -1.

У будь-якому випадку це довго затягувалось, сподіваюся, що це допоможе.

Корисні посилання: https://github.com/square/retrofit/issues/1217


1
RequestBody.writeTo () викликається кілька разів . Ви вирішили цю? Тому що я зіткнувся з цим. Це дзвонить двічі!
android_griezmann

@android_griezmann Перевірте, чи правильно ви встановлюєте ContentLength. Виникла проблема з розмірами файлів, що повертаються за допомогою курсору та OpenableColumns.FILE_SIZE, спробуйте скористатися ParcelFileDescriptor.getStatSize (), як описано тут: stackoverflow.com/questions/21882322/…
Джастін Фідлер

саме тому, що ти використовуєш перехоплювач
зихадрізкєф

3

@ luca992 Дякуємо за відповідь. Я застосував це в JAVA, і зараз він працює нормально.

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();
        }

    }
}

Дякую, як відобразити прогрес у головній активності завантаження plsss ??
Ібрамазін

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();
    }
}

Ти врятував мене сьогодні. Велике спасибі
Gulnaz Ghanchi

2

Видаліть перехоплювач реєстрації Http з httpbuilder. В іншому випадку він буде дзвонити writeTo()двічі. Або змініть рівень реєстрації з BODY.


1

Я спробував використати код вище, але я виявив, що інтерфейс застряг, тому я спробував цей код, це працює для мене, або я можу спробувати використати цей код


1
Б'юсь об заклад, у наступні роки ці посилання зникнуть.
TheRealChx101

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);
        }
    }

0

Наскільки я можу бачити в цьому пості, ніяких оновлень щодо зображення відповідь про хід завантаження не було зроблено , і ви все ще повинні overrideв writeToспособі , як показано в цьому SO відповідь, зробивши ProgressListenerінтерфейс і використовувати суб-клас TypedFileдо overrideпо writeToметоду.

Таким чином, є НЕ будь вбудовані шляху , щоб показати прогрес при використанні ДООСНАСТКІ 2 бібліотеки.


Так, але TypedFiledв новому пакеті модернізації немає класу :(
Юрій Колбасинський

Правильно. Я просто перевірив це. Ви можете використовувати RequestBodyклас, як описано в stackoverflow.com/questions/32856850/… . Але це точно не вирішує вашу проблему отримання перебігу завантаження.
Шубхем А.

Я знайшов рішення з RequestBodyкласом :)
Юрій Колбасинський

0

цю відповідь використовувати для 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............!");
            }
        });

сподіваюся, це допоможе.


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)
        }
    }
})

0

Я розумію, що на це запитання було дано відповіді багато років тому, але я думав, що оновлю його для Котліна:

Створіть клас, який розширює 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/*")
    }
}

Завантажте файл за допомогою модернізації:

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>) {

        }
    })

}

0

Я погоджуюся на дану відповідь @Yuriy Kolbasinskiy, але це видає помилку для мене "очікувалося 3037038 байт, але отримано 3039232" після того, як я змінив деякі функції WriteTo (). Відповідь наведено в Котліні, що подано нижче: -

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)
    }
}

-2

Ви можете використовувати 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


1
Ви копіюєте мій код, написали статтю на носії без посилань на оригінальний пост. Це нічого доброго, чувак
Юрій

Це не добре Sheetal, будь ласка, надайте оригінал оригінальному плакату.
humble_wolf

@YuriyKolbasinskiy Я міг використати ваш фрагмент коду. Будь ласка, надішліть посилання на свою публікацію. Я дам кредити. :)
Sheetal Kumar Maurya
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.