developer tip

Retrofit 2를 통해 이미지를 업로드 할 때 진행률 표시 줄을 표시 할 수 있습니까?

copycodes 2020. 11. 3. 08:18
반응형

Retrofit 2를 통해 이미지를 업로드 할 때 진행률 표시 줄을 표시 할 수 있습니까?


현재 사용 중이며 Retrofit 2서버에 사진을 업로드하고 싶습니다. 나는 이전 버전 TypedFile이 업로드를 위해 클래스를 사용한다는 것을 알고 있습니다. 진행률 표시 줄을 함께 사용하려면 클래스 writeTo에서 메서드를 재정의해야합니다 TypedFile.

retrofit 2라이브러리 사용시 진행 상황을 표시 할 수 있습니까?


우선, 2.0 beta2 이상인 Retrofit 2 버전을 사용해야합니다. 둘째, 새 클래스 확장을 만듭니다 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 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);
        }


    }

rxjava를 사용하고 kotlin을 사용하도록 Yuriy Kolbasinskiy를 수정했습니다. 동시에 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();
            })
}

Multipart가 아닌 간단한 POST로 업로드 파일 진행 상황을 처리하는 방법은 다음과 같습니다. 다중 부분의 경우 @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

이는 기본 contentLength ()가 -1이므로 RequestBody.writeTo ()가 여러 번 호출 된 결과입니다.

어쨌든 이것은 알아내는 데 오랜 시간이 걸렸으며 도움이되기를 바랍니다.

유용한 링크 : https://github.com/square/retrofit/issues/1217


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

    }
}

진행률 표시 줄 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();
    }
}

위의 코드를 사용하려고했지만 UI가 멈춰서이 코드를 시도해 보았습니다. 이것은 저에게 효과가 있거나이 코드를 사용해 볼 수 있습니다.


에서 Http 로깅 인터셉터를 제거합니다 httpbuilder. 그렇지 않으면 writeTo()두 번 호출 됩니다. 또는에서 로깅 수준을 변경 BODY합니다.


두 번 실행 문제를 피하려면. 처음에는 플래그를 0으로 설정하고 진행 대화 상자를 처음 호출 한 후 플래그를 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);
        }
    }

As far as I can see in this post, no updates regarding the image upload progress response has been made and you still have to override the writeTo method as shown in this SO answer by making a ProgressListener interface and using a sub-class of TypedFile to override the writeTo method.

So, there isn't any built-in way to show progress when using retrofit 2 library.


You can use FileUploader that usage Retrofit Library for connecting to API. To upload the file the code skeleton is as follow:

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

Full steps are available at Medium:

Retrofit multiple file upload with progress in Android

참고URL : https://stackoverflow.com/questions/33338181/is-it-possible-to-show-progress-bar-when-upload-image-via-retrofit-2

반응형