android - example - ¿Es posible mostrar la barra de progreso al cargar la imagen a través de Retrofit 2?
retrofit post (10)
@ luca992 Gracias por tu respuesta. He implementado esto en JAVA y ahora funciona bien.
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();
}
}
}
Actualmente
Retrofit 2
usando
Retrofit 2
y quiero subir alguna foto en mi servidor.
Lo sé, esa versión anterior usa la clase
TypedFile
para cargar.
Y si queremos usar la barra de progreso con ella, debemos anular el método
TypedFile
en la clase
TypedFile
.
¿Es posible mostrar progreso al usar la biblioteca
retrofit 2
?
Actualizo la barra de progreso en ProgressUpdate. Este código puede obtener un mejor rendimiento.
@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();
}
}
Aquí se explica cómo manejar el progreso de la carga de archivos con un POST simple en lugar de Multipart. Para multiparte, consulte la solución de @ Yariy. Además, esta solución utiliza URI de contenido en lugar de referencias directas a archivos.
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;
}
}
Para iniciar la carga:
// 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);
Advertencia, si olvida anular la función contentLength (), puede recibir algunos errores oscuros:
retrofit2.adapter.rxjava.HttpException: HTTP 503 client read error
O
Write error: ssl=0xb7e83110: I/O error during system call, Broken pipe
O
javax.net.ssl.SSLException: Read error: ssl=0x9524b800: I/O error during system call, Connection reset by peer
Estos son el resultado de que RequestBody.writeTo () se llama varias veces ya que el contentLength () predeterminado es -1.
De todos modos, esto tomó mucho tiempo para darse cuenta, espero que ayude.
Enlaces útiles: https://github.com/square/retrofit/issues/1217
Elimine el interceptor Http Logging de
httpbuilder
.
De lo contrario, llamará a
writeTo()
dos veces.
O cambie el nivel de registro de
BODY
.
En primer lugar, debe usar la versión Retrofit 2 igual o superior a 2.0 beta2.
En segundo lugar, crear una nueva clase extiende
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();
}
Tome nota, agregué el tipo de contenido para que pueda acomodar otros tipos aparte de la imagen
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));
}
}
}
Tercero, crear interfaz
@Multipart
@POST("/upload")
Call<JsonObject> uploadImage(@Part MultipartBody.Part file);
/ * JsonObject anterior se puede reemplazar con su propio modelo, solo quiero que esto sea notable. * /
Ahora puede obtener el progreso de su carga. En su
activity
(ofragment
):
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 out 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 incase 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 queue
progressBar.setProgress(100);
}
}
Intenté usar el código anterior pero encontré que la interfaz de usuario se estaba atascando, así que probé este código, esto funciona para mí o puedo intentar usar este código
Para evitar dos veces el problema de ejecución. Podemos establecer la bandera como cero inicialmente y establecer la bandera como una después de la primera llamada al diálogo de progreso.
@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);
}
}
Por lo que puedo ver en
this
publicación, no se han realizado actualizaciones con respecto a la respuesta del progreso de carga de la imagen y aún debe
override
el método
writeTo
como se muestra en
this
respuesta SO haciendo una interfaz
ProgressListener
y usando una subclase de
TypedFile
para
override
el método
writeTo
.
Por lo tanto, no hay ninguna forma integrada de mostrar el progreso al usar la biblioteca retrofit 2.
Puede usar FileUploader que utiliza la Biblioteca de actualización para conectarse a la API. Para cargar el archivo, el esqueleto del código es el siguiente:
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);
}
});
Los pasos completos están disponibles en Medium:
Actualizar la carga de múltiples archivos con progreso en Android
Se modificó Yuriy Kolbasinskiy para usar rxjava y usar kotlin. Se agregó una solución alternativa para usar HttpLoggingInterceptor al mismo tiempo
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
}
}
Un ejemplo de interfaz de carga de video
public interface Api {
@Multipart
@POST("/upload")
Observable<ResponseBody> uploadVideo(@Body MultipartBody requestBody);
}
Una función de ejemplo para publicar un video:
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();
})
}