Ist es möglich, einen Fortschrittsbalken anzuzeigen, wenn ein Bild über Retrofit 2 hochgeladen wird?


75

Ich verwende gerade Retrofit 2und möchte ein Foto auf meinen Server hochladen. Ich weiß, dass ältere Versionen TypedFileKlasse zum Hochladen verwenden. Und wenn wir den Fortschrittsbalken damit verwenden möchten, sollten wir die writeToMethode in der TypedFileKlasse überschreiben .

Ist es möglich, Fortschritte bei der Verwendung der retrofit 2Bibliothek anzuzeigen?


Für diejenigen, die noch an Retrofit 1.x festhalten
Joshua Pinter

Antworten:


167

Zunächst sollten Sie eine Retrofit 2-Version verwenden, die mindestens 2.0 Beta2 entspricht. Zweitens erweitert das Erstellen einer neuen Klasse Folgendes 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();
    }

Beachten Sie, dass ich den Inhaltstyp hinzugefügt habe, damit er neben dem Bild auch andere Typen aufnehmen kann

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

Drittens erstellen Sie die Schnittstelle

@Multipart
    @POST("/upload")        
    Call<JsonObject> uploadImage(@Part MultipartBody.Part file);

/ * JsonObject oben kann durch Ihr eigenes Modell ersetzt werden, möchte dies nur bemerkenswert machen. * /

Jetzt können Sie den Fortschritt Ihres Uploads abrufen. In Ihrem activity(oder 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
Ich sehe die Daten fast sofort in die Senke geschrieben, aber der eigentliche Anruf dauert bei einer langsamen Verbindung einige Sekunden. Ich habe versucht, dem Aufruf nach dem Schreiben sink.flush () hinzuzufügen, aber es scheint immer noch den Zeitpunkt einer internen Datenübertragung und nicht den Fortschritt der Übertragung über das Netzwerk anzuzeigen.
Jake Hall

6
handler.post (neuer ProgressUpdater (hochgeladen, fileLength)); Diese Zeile sollte sich am Ende der while-Schleife befinden, andernfalls erhalten Sie immer 99%.
Codezjx

9
Ich habe auch das gleiche Problem mit JakeHall und konnte keine Lösung finden ... Die writeTo-Methode wird zweimal aufgerufen und beim ersten Mal sofort geladen, aber beim zweiten Mal wird die tatsächliche Upload-Periode gesucht. Haben Sie einen Kommentar zu diesem @YuriyKolbasinskiy?
Yahoo

23
@yahya Ich habe dasselbe gesehen und festgestellt, dass es durch den von mir eingerichteten HttpLoggingInterceptor verursacht wurde. Das erste schnelle Laden ist HttpLoggingInterceptor, der writeTo aufruft und einen lokalen Puffer nur zu Protokollierungszwecken übergibt.
Devin Pitcher

3
Wie kann ich den Fortschritt für MultipartBody.Part [] anzeigen, während ich mehrere Dateien in einer einzigen Anfrage hochlade?
Usman Rana

24

Yuriy Kolbasinskiy wurde geändert, um rxjava und kotlin zu verwenden. Es wurde eine Problemumgehung für die gleichzeitige Verwendung von HttpLoggingInterceptor hinzugefügt

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

Ein Beispiel für eine Video-Upload-Oberfläche

public interface Api {

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

Eine Beispielfunktion zum Posten eines Videos:

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
Dies zeigt nur die Schreibgeschwindigkeit, nicht die Upload-Geschwindigkeit. Dies scheint, als hätten wir den Fortschritt in der for-Schleife von 0 auf 100 aktualisiert, da mein Upload nicht so schnell ist, wie der Fortschritt zeigt
Akash Dubey

1
@AkashDubey klingt wie Sie den HttpLoggingInterceptor verwenden, aber nicht die auskommentierte Problemumgehung verwendet, die ich bereitgestellt habe
luca992

Entschuldigung, meine schlechte, meine Datei war zu klein, daher wird sie schneller hochgeladen. Es funktioniert gut
Akash Dubey

1
Ich stehe auch vor dem gleichen Problem, auf das @AkashDubey hingewiesen hat. Die Verwendung der Problemumgehung für HttpLoggingInterceptor hat nicht geholfen. Es wird lediglich verhindert, dass die writeTo-Logik zweimal ausgeführt wird und die Daten beim nächsten writeTo-Aufruf immer noch sofort in die Senke geschrieben werden.
Vikalp

Was bedeutet DEFAULT_BUFFER_SIZE? ist es die maximal zulässige Dateigröße?
Alexa289

10

Hier erfahren Sie, wie Sie den Fortschritt der Upload-Datei mit einem einfachen POST anstelle von Multipart behandeln. Weitere Informationen finden Sie in der Lösung von @ Yariy. Darüber hinaus verwendet diese Lösung Inhalts-URIs anstelle von direkten Dateiverweisen.

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

So starten Sie den Upload:

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

Warnung: Wenn Sie vergessen, die Funktion contentLength () zu überschreiben, werden möglicherweise einige undurchsichtige Fehler angezeigt:

retrofit2.adapter.rxjava.HttpException: HTTP 503 client read error

Oder

Write error: ssl=0xb7e83110: I/O error during system call, Broken pipe

Oder

javax.net.ssl.SSLException: Read error: ssl=0x9524b800: I/O error during system call, Connection reset by peer

Dies ist das Ergebnis eines mehrfachen Aufrufs von RequestBody.writeTo (), da die Standard-contentLength () -1 ist.

Wie auch immer, es hat lange gedauert, um herauszufinden, hoffe es hilft.

Nützliche Links: https://github.com/square/retrofit/issues/1217


1
RequestBody.writeTo () wird mehrmals aufgerufen . Hast du dieses Problem gelöst? Weil ich vor diesem stehe. Es ruft zweimal an!
android_griezmann

@android_griezmann Überprüfen Sie, ob Sie die richtige contentLength einstellen. Es gibt ein Problem mit den über Cursor und OpenableColumns.FILE_SIZE zurückgegebenen Dateigrößen. Verwenden Sie stattdessen ParcelFileDescriptor.getStatSize () wie hier beschrieben: stackoverflow.com/questions/21882322/…
Justin Fiedler

Es ist, weil Sie Interceptor
Zihadrizkyef

3

@ luca992 Vielen Dank für Ihre Antwort. Ich habe dies in JAVA implementiert und jetzt funktioniert es gut.

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

    }
}

Vielen Dank, Wie wird der Fortschritt in den wichtigsten Upload-Aktivitäten angezeigt?
Ibramazin

2

Ich aktualisiere den Fortschrittsbalken auf ProgressUpdate. Dieser Code kann eine bessere Leistung erzielen.

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

Du hast mich heute gerettet. Vielen Dank
Gulnaz Ghanchi

2

Entfernen Sie den HTTP-Protokollierungs-Interceptor von httpbuilder. Sonst wird es writeTo()zweimal anrufen . Oder ändern Sie die Protokollierungsstufe von BODY.



1

Um zu vermeiden, dass das Problem zweimal ausgeführt wird. Wir können das Flag zunächst auf Null und nach dem ersten Aufruf des Fortschrittsdialogs auf Eins setzen.

 @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

Soweit ich sehen kann in diesem Beitrag haben keine Aktualisierungen in Bezug auf der Bild - Upload Fortschritt Antwort gemacht worden , und Sie immer noch haben , overridedas writeToVerfahren , wie in gezeigt , dies , indem sie eine SO Antwort - ProgressListenerSchnittstelle und eine Unterklasse der Verwendung TypedFilezu overridedem writeToVerfahren.

Es gibt also keine integrierte Möglichkeit, den Fortschritt bei der Verwendung der Retrofit 2-Bibliothek anzuzeigen.


Ja, aber es gibt keine TypedFiledKlasse im neuen Nachrüstpaket :(
Yuriy Kolbasinskiy

Richtig. Ich habe es gerade überprüft. Sie können die RequestBodyKlasse wie unter stackoverflow.com/questions/32856850/… beschrieben verwenden . Dies löst jedoch nicht Ihr Problem, den Upload-Fortschritt sicher zu erhalten.
Shubham A.

Ich habe eine Lösung mit RequestBodyKlasse gefunden :)
Yuriy Kolbasinskiy

0

Diese Antwort wird für MultipartBody und zum Hochladen mehrerer Dateien verwendet. Meine serverseitigen Codes sind MVC-Entwicklung. Zunächst benötigen Sie eine ApiService-Klasse wie folgt:

public interface ApiService {

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

und du brauchst Apiclient wie folgt:

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

Danach benötigen Sie die ReqestBody-Klasse wie folgt:

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

}

und schließlich benötigen Sie diesen Code:

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............!");
            }
        });

ich hoffe es hilft.


0

Erweiterung zum Erstellen eines Teils. Der Rückruf wird beim Aufrufen des Dienstes aufgerufen

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

Mir ist klar, dass diese Frage vor Jahren beantwortet wurde, aber ich dachte, ich würde sie für Kotlin aktualisieren:

Erstellen Sie eine Klasse, die RequestBody erweitert. Stellen Sie sicher, dass Sie die ContentType-Enum-Klasse ausfüllen, um die zu unterstützenden Inhaltstypen zu verwenden.

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/*")
    }
}

Laden Sie die Datei mit Retrofit hoch:

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

Ich appericiate @Yuriy Kolbasinskiy gegebene Antwort, aber es gibt Fehler für mich "erwartete 3037038 Bytes, aber 3039232 erhalten", nachdem ich einige auf WriteTo () Funktion geändert habe. Die Antwort ist in Kotlin, die unten angegeben ist: -

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

Sie können FileUploader verwenden, der die Retrofit Library für die Verbindung zur API verwendet. Um die Datei hochzuladen, lautet das Code-Skelett wie folgt:

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

Vollständige Schritte finden Sie unter Mittel:

Laden Sie das Hochladen mehrerer Dateien mit dem Fortschritt in Android nach


1
Sie kopieren meinen Code, haben einen Artikel auf Medium ohne Links zum Originalbeitrag geschrieben. Das ist nicht gut, Alter
Yuriy Kolbasinskiy

Dies ist kein gutes Blatt, bitte geben Sie das Originalplakat an.
bescheidene_Wolf

@YuriyKolbasinskiy Ich habe möglicherweise einen Code-Ausschnitt von Ihnen verwendet. Bitte senden Sie den Link Ihres Beitrags. Ich werde die Credits geben. :)
Sheetal Kumar Maurya
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.