J'utilise actuellement Retrofit 2
et je veux télécharger des photos sur mon serveur. Je sais que cette version plus ancienne utilise la classe TypedFile
pour le téléchargement. Et si nous voulons utiliser la barre de progression avec elle, nous devons surcharger la méthode writeTo
dans la classe TypedFile
.
Est-il possible de montrer les progrès en utilisant retrofit 2
bibliothèque?
Tout d’abord, vous devriez utiliser Retrofit 2 version égale ou supérieure à 2.0 beta2. Deuxièmement, créer une nouvelle classe étend 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();
}
Prenez note, j'ai ajouté le type de contenu afin qu'il puisse accueillir d'autres types à part image
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));
}
}
}
Troisièmement, créer une interface
@Multipart
@POST("/upload")
Call<JsonObject> uploadImage(@Part MultipartBody.Part file);
/ * JsonObject ci-dessus peut être remplacé par votre propre modèle, je veux juste que ce soit remarquable. * /
Maintenant, vous pouvez obtenir des progrès de votre téléchargement. Dans votre
activity
(oufragment
):
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);
}
}
Yuriy Kolbasinskiy a été modifié pour utiliser rxjava et kotlin. Ajout d'une solution de contournement pour utiliser HttpLoggingInterceptor en même temps
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 exemple d'interface de téléchargement vidéo
public interface Api {
@Multipart
@POST("/upload")
Observable<ResponseBody> uploadVideo(@Body MultipartBody requestBody);
}
Un exemple de fonction pour poster une vidéo:
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();
})
}
Voici comment gérer la progression du fichier téléchargé avec un simple POST plutôt que plusieurs parties. Pour une solution multipartie, consultez la solution de Yariy. De plus, cette solution utilise l'URI de contenu plutôt que des références directes à des fichiers.
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;
}
}
Pour lancer le téléchargement:
// 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);
Attention, si vous oubliez de remplacer la fonction contentLength (), vous risquez de recevoir quelques erreurs obscures:
retrofit2.adapter.rxjava.HttpException: HTTP 503 client read error
Ou
Write error: ssl=0xb7e83110: I/O error during system call, Broken pipe
Ou
javax.net.ssl.SSLException: Read error: ssl=0x9524b800: I/O error during system call, Connection reset by peer
Cela résulte du fait que RequestBody.writeTo () a été appelé plusieurs fois, car la valeur par défaut contentLength () est égale à -1.
Quoi qu'il en soit, cela a pris du temps à comprendre, espérons que cela aidera.
Liens utiles: https://github.com/square/retrofit/issues/1217
@ luca992 Merci pour votre réponse. J'ai implémenté ceci dans Java et maintenant cela fonctionne 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();
}
}
}
Pour éviter deux problèmes en cours d'exécution. Nous pouvons définir l'indicateur à zéro initialement et l'indicateur à un après le premier appel du dialogue de progression.
@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);
}
}
Je met à jour progressbar onProgressUpdate. Ce code peut obtenir de meilleures performances.
@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();
}
}
Supprimez l'intercepteur de journalisation HTTP de httpbuilder
. Sinon, il appellera writeTo()
deux fois. Ou changez le niveau de journalisation de BODY
.
Vous pouvez utiliser FileUploader qui utilise Retrofit Library pour se connecter à l'API. Pour télécharger le fichier, le squelette de code est le suivant:
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);
}
});
Les étapes complètes sont disponibles chez Medium:
Mise à niveau de plusieurs fichiers avec la progression dans Android
D'après ce que je peux voir dans this post, aucune mise à jour concernant la réponse à la progression du téléchargement des images n'a été effectuée et vous devez toujours utiliser la méthode override
de writeTo
, comme indiqué dans this SO répondez en créant une interface ProgressListener
et en utilisant une sous-classe de TypedFile
à override
le writeTo
méthode.
Ainsi, il n'y a pas de moyen intégré pour afficher les progrès réalisés lors de l'utilisation de la bibliothèque de mise à niveau 2.