Ich verwende gerade Retrofit 2
und möchte ein Foto auf meinen Server hochladen. Ich weiß, dass ältere Versionen TypedFile
Klasse zum Hochladen verwenden. Und wenn wir den Fortschrittsbalken damit verwenden möchten, sollten wir die writeTo
Methode in der TypedFile
Klasse überschreiben .
Ist es möglich, Fortschritte bei der Verwendung der retrofit 2
Bibliothek anzuzeigen?
Antworten:
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(); }
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);
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); } }
quelle
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(); }) }
quelle
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
quelle
@ 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(); } } }
quelle
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(); } }
quelle
Entfernen Sie den HTTP-Protokollierungs-Interceptor von
httpbuilder
. Sonst wird eswriteTo()
zweimal anrufen . Oder ändern Sie die Protokollierungsstufe vonBODY
.quelle
Ich habe versucht, den obigen Code zu verwenden, aber ich habe festgestellt, dass die Benutzeroberfläche nicht funktioniert , also habe ich diesen Code ausprobiert. Dies funktioniert für mich oder versucht möglicherweise, diesen Code zu verwenden
quelle
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); } }
quelle
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 ,
override
daswriteTo
Verfahren , wie in gezeigt , dies , indem sie eine SO Antwort -ProgressListener
Schnittstelle und eine Unterklasse der VerwendungTypedFile
zuoverride
demwriteTo
Verfahren.Es gibt also keine integrierte Möglichkeit, den Fortschritt bei der Verwendung der Retrofit 2-Bibliothek anzuzeigen.
quelle
TypedFiled
Klasse im neuen Nachrüstpaket :(RequestBody
Klasse wie unter stackoverflow.com/questions/32856850/… beschrieben verwenden . Dies löst jedoch nicht Ihr Problem, den Upload-Fortschritt sicher zu erhalten.RequestBody
Klasse gefunden :)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.
quelle
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) } } })
quelle
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>) { } }) }
quelle
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) } }
quelle
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
quelle