Невозможно обновить Circular ProgressBar при загрузке файла

Я использовал Retrofit2 для загрузки файлов. Я не могу обновить ProgressBar со значением прогресса. Я получил значение прогресса. Так что нет проблемы. Когда я устанавливаю значение прогресса, чтобы индикатор выполнения не отображался в пользовательском интерфейсе.

Я говорю о индикаторе выполнения, который присутствует внутри адаптера RecyclerView.

Ниже приведен мой вызов модификации, и этот метод будет вызываться при нажатии элемента внутри RecyclerView.

 private void downloadFileFromServer(String otpapi, String userName, String password, String code, String vmFileName, String filePath, String vmFileSize, int position, CircularProgressBar circularProgress) {
      
        GetDataForApiCall getDataForApiCall= RetrofitInstance.getRetrofit(url,otpapi,context).create(GetDataForApiCall.class);
      
        Call<ResponseBody> downloadVoicemail=  getDataForApiCall.downloadVoiceMail(userName,password,code,vmFileName);
        this.circularProgressBar=circularProgress;
        this.circularProgressBar.setIndeterminate(false);
        this.circularProgressBar.setProgress(0);
        this.circularProgressBar.setMax(100);
        this.circularProgressBar.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                AndroidLogger.log(5,"onClick","circularProgressBar onClick executed!!");
                Toast.makeText(context,"cancel clicked",Toast.LENGTH_LONG).show();
                cancelDownload = true;
            }
        });
        downloadVoicemail.enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {

boolean downloadResult = writeResponseBodyToDisk(response.body(),vmFileSize,filePath);
                if(downloadResult) {
                    Toast.makeText(context, "File downloaded", Toast.LENGTH_SHORT).show();
                    updateVoiceMailFilePath(position, filePath);
                    updateViews(position);
                }else {
                    deleteVoiceMailFileFromLocalSystem(filePath);
                    updateViews(position);
                }

            }

            @Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {

            }
        });
    }

private boolean writeResponseBodyToDisk( ResponseBody body, String fileSize, String filePath) {
        try {
            InputStream inputStream = null;
            OutputStream outputStream = null;

            try {
                byte[] fileReader = new byte[8192];
                //long fileSize = body.contentLength();
                long fileSizeDownloaded = 0;
                long lengthOfFile = Long.parseLong( String.format( "%.0f",Double.parseDouble(fileSize )) )  * 1024;
                AndroidLogger.log(5,TAG,"filesize"+fileSize + "length of file"+lengthOfFile);
                inputStream = body.byteStream();
                outputStream = new FileOutputStream(filePath);

                while (true) {
                    int read = inputStream.read(fileReader);

                    if(cancelDownload){
                        inputStream.close();
                        return false;
                    }
                    if (read == -1) {
                        AndroidLogger.log(5,TAG,"-1 value so break");
                        break;
                    }
                    outputStream.write(fileReader, 0, read);

                    fileSizeDownloaded += read;
                    if(lengthOfFile >0) {
                    AndroidLogger.log(5,TAG,"FileSize downloaded"+ fileSizeDownloaded);
                        int progress = (int) (fileSizeDownloaded * 100 / lengthOfFile);
                    AndroidLogger.log(5,TAG,"Length of  file"+ lengthOfFile);
                    AndroidLogger.log(5,TAG,"Progress"+ progress);
                    this.circularProgressBar.setProgress(progress);
                        update(progress);
                    }
                    AndroidLogger.log(5,TAG, "file download: " + fileSizeDownloaded + " of " + fileSize);
                }

                outputStream.flush();

                return true;
            } catch (IOException e) {
                return false;
            } finally {
                if (inputStream != null) {
                    inputStream.close();
                }

                if (outputStream != null) {
                    outputStream.close();
                }
            }
        } catch (IOException e) {
            return false;
        }
    }

Также я попытался Listener обновить значение, потому что вызов модификации выполняется в каком-то другом потоке. Поэтому для обновления пользовательского интерфейса я использовал прослушиватель, который не помог.

Я использую Retrofit2 для вызовов API. Поэтому для обновления пользовательского интерфейса во всех действиях я использовал прослушиватели интерфейса. Это работает идеально для всех видов деятельности. Но когда я попробовал то же самое в классе адаптера RecyclerView, я не смог обновить индикатор выполнения. Перед вызовом API я установил индикатор выполнения на 0 и максимум на 100.

Нижний корпус работает нормально,

Круговой индикатор выполнения перед вызовом API установлен на ноль

Круглая полоса прогресса после завершения загрузки изменится на галочку

Ниже не работает,

Круглая полоса прогресса с индикацией загрузки

ПРИМЕЧАНИЕ. Я сталкиваюсь с этой проблемой только при использовании Retrofit2 для вызова API. Если бы я использовал обычный HTTPUrlConnection для вызова API внутри Asynctask, то загрузка прогресса работала бы нормально.

Я проверил, происходит ли обновление прогресса в основном потоке или нет по приведенному ниже коду,

if(Looper.myLooper() == Looper.getMainLooper()) {
circularProgressBar.setProgress(progress);
}

Вышеупомянутое, если условие выполнено. Несмотря на то, что индикатор выполнения не обновляется.

Также я пробовал ниже,

Handler mainHandler = new Handler(Looper.getMainLooper());

                        Runnable myRunnable = new Runnable() {
                            @Override
                            public void run() {
                                AndroidLogger.log(5,TAG,"Running on UI thread");
                                circularProgressBar.setProgress(progress);
                            } 
                        };
                        mainHandler.post(myRunnable);


                    }

Я поместил это внутри метода writeResponseBodyToDisk внутри цикла while, но он вызывался только два раза, а индикатор выполнения не обновлялся.

Я прокомментировал часть, где загрузка индикатора выполнения изменится на галочку. После этого, когда я попытался загрузить, после завершения загрузки я смог увидеть 100-процентную загрузку на индикаторе выполнения. До прогресса процентное обновление не отражалось.

Пожалуйста, помогите мне решить эту проблему.

Заранее спасибо.


person Kousalya    schedule 30.08.2020    source источник


Ответы (3)


Обновления пользовательского интерфейса должны происходить в потоке пользовательского интерфейса. Установка цвета из фонового потока (например, AsyncTask) фактически не обновит пользовательский интерфейс, поскольку этого не происходит в потоке пользовательского интерфейса. Есть несколько способов обновить цвет прогресса в пользовательском интерфейсе. Я бы рекомендовал иметь интерфейс вместе с функцией обратного вызова, чтобы вы могли вызывать эту функцию обратного вызова для обновления пользовательского интерфейса из активности фрагмента, который ее реализовал. Вот уточнение.

Давайте сначала объявим интерфейс.

public interface UIUpdater {
    void updateUI(int progressValue);
}

Теперь реализуйте этот интерфейс в действии или фрагменте, где вы хотите обновить пользовательский интерфейс.

public class MainActivity extends Activity implements UIUpdater {

    @Override
    public void updateUI(int progressValue) {
        // Do the UI update here. 
        circularProgress.setProgress(progressValue); // Or anything else that you want to do. 
    }
}

Вы хотите изменить конструктор инициализации вашего AsyncTask, чтобы класс UIUpdater передавался в качестве параметра.

public class YourAsyncTask extends AsyncTask<String, Void, String> {

    UIUpdater listener;

    public YourAsyncTask(UIUpdater listener) {
        this.listener = listener;
    }
}

Так что вы можете вызвать AsyncTask из действия/фрагмента, используя что-то вроде следующего.

YourAsyncTask myTask = new YourAsyncTask(this); // When you are passing this from activity, you are implicitly passing the interface that it implemented. 
myTask.execute(); 

Теперь из асинхронной задачи, пока вы публикуете прогресс, вызовите функцию прослушивателя, чтобы обновить пользовательский интерфейс, используя поток пользовательского интерфейса вашей активности/фрагмента.

protected void onProgressUpdate(Integer... values) {
    super.onProgressUpdate(values);

    try {
       listener.updateUI(values[0]);
    }
} 

Надеюсь, вы уловили идею.

person Reaz Murshed    schedule 30.08.2020
comment
У меня есть индикатор выполнения в моем элементе recylerview. Я назначаю прогресс внутри адаптера recycler. Поэтому я не знаю, как использовать прослушиватель в этом случае. - person Kousalya; 30.08.2020
comment
Вы можете иметь аналогичную реализацию и для адаптеров. Адаптер может реализовать UIUpdater и может переопределить в нем тот же метод. Возможно, вам придется изменить интерфейс, чтобы принять дополнительный параметр для позиции для работы с RecyclerView, если это поможет. - person Reaz Murshed; 30.08.2020
comment
спасибо за ваш ответ :-). но не могу решить мой pbm. Я делаю Asynctas внутри ответа Retrofit2. Может в этом проблема думаю так. - person Kousalya; 31.08.2020
comment
переопределенный метод вызывается, но не отображается в пользовательском интерфейсе. Поэтому пробовал разные вещи, такие как использование обработчика, runOnUIthread. Но ничего не помогло - person Kousalya; 31.08.2020

Ваша проблема не в том, что вы используете RecyclerView, ваша проблема в том, что вы используете его неправильно.

Ни адаптер, ни представление (RecyclerView), ни даже ViewHolder не несут ответственности за определение чего-либо здесь.

  • Я предполагаю, что у вас есть тип ViewHolder с индикатором выполнения.
  • Я предполагаю, что у вас есть ListAdapter<T, K> с соответствующей реализацией DiffUtilCallback.
  • Я предполагаю, что прогресс обрабатывается/сообщается в другом месте (в вашем репозитории, через модель представления или презентатор, через вариант использования, интерактор или даже обычную модель представления).
  • Теперь вашему адаптеру нечего делать, кроме как ждать.
  • Когда прогресс обновляется, вы подготавливаете отображаемый список, чтобы элемент, прогресс которого изменился, обновлялся.
  • После этого вы отправляете этот новый список (с новым прогрессом) вашему адаптеру.
  • Ваш DiffUtil вычисляет это (он тоже может быть асинхронным!)
  • Ваш RecyclerView волшебным образом обновляется, не нужно ничего взламывать.

Если что-то из этого не соответствует действительности, внесите необходимые корректировки, чтобы ваш код можно было должным образом протестировать, а проблемы каждого фрагмента кода были лучше разделены.

Подумайте об этом таким образом, представьте, что у вас есть все это, и есть небольшая ошибка в значении прогресса, которое вы обновляете. Что было бы проще, искать в маленькой функции в вашей ViewModel или UseCase/interactor, которая преобразует значение Retrofit в ваш <Thing>, или повсюду в вашем спагетти-коде обратных вызовов?

person Martin Marconcini    schedule 09.09.2020
comment
Спасибо за ваш ответ. В моем случае я выполняю вызов модификации внутри прослушивателя элемента onclick. Я обновляю индикатор выполнения внутри цикла while в методе writeResponseBodyToDisk. Но не отражается в пользовательском интерфейсе. - person Kousalya; 09.09.2020
comment
Как вы сообщаете своему адаптеру, что данные изменились? - person Martin Marconcini; 10.09.2020
comment
спасибо за помощь :-) Я нашел решение. Опубликую это здесь позже. - person Kousalya; 10.09.2020
comment
Не беспокойтесь, рад узнать, что вы нашли решение. Если вам интересно, я изменил несвязанный пример, чтобы показать индикатор выполнения во время фальшивой медленной операции. Вы можете посмотреть на разницу между мастером (или веткой темы, в которой были viewMOdels) и этой новой веткой здесь github.com/Gryzor/CheckBoxCounter/compare/ Если вы запустите его, вы заметите, что можете нажимать флажки, и появится прогресс, как только они будут сделаны, они снова обновятся, чтобы показать правильный пользовательский интерфейс. . Счетчик не имеет значения. :) Удачи. - person Martin Marconcini; 10.09.2020

Всем спасибо за попытку помочь!!

Этот ответ помог мне обновить Circular ProgressBar https://stackoverflow.com/a/42119419/11630822

public static Retrofit getDownloadRetrofit(String baseUrl, DownloadVoicemailListener listener) {

        return new Retrofit.Builder()
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .client(getOkHttpDownloadClientBuilder(listener).build())
                .build();

    }

    private static OkHttpClient.Builder getOkHttpDownloadClientBuilder(DownloadVoicemailListener listener) {

        OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder().connectionSpecs(Collections.singletonList(getConnectionSpec()));

        HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
        if (!releaseMode) {

            logging.level(HttpLoggingInterceptor.Level.BODY);
            httpClientBuilder.addInterceptor(logging);
        }

        httpClientBuilder.connectTimeout(20, TimeUnit.SECONDS);
        httpClientBuilder.writeTimeout(0, TimeUnit.SECONDS);
        httpClientBuilder.readTimeout(5, TimeUnit.MINUTES);

        httpClientBuilder.addInterceptor(new Interceptor() {
            @NotNull
            @Override
            public Response intercept(@NotNull Interceptor.Chain chain) throws IOException {
                if (listener == null) return chain.proceed(chain.request());

                Response originalResponse = chain.proceed(chain.request());
                return originalResponse.newBuilder()
                        .body(new ProgressResponseBody(originalResponse.body(), listener))
                        .build();
            }
        });

        return httpClientBuilder;
    }

В классе Progressbody,


public class ProgressResponseBody extends ResponseBody {
    private final String TAG=ProgressResponseBody.class.getSimpleName();
    private  ResponseBody responseBody;
    private BufferedSource bufferedSource;

    public ProgressResponseBody(ResponseBody responseBody, DownloadVoicemailListener progressListener) {
        this.responseBody = responseBody;
        progressListener.readFile(responseBody);
    }

    @Override public MediaType contentType() {
        return responseBody.contentType();
    }

    @Override public long contentLength() {
        return responseBody.contentLength();
    }

    @NotNull
    @Override public BufferedSource source() {

        if (bufferedSource == null) {
            bufferedSource = Okio.buffer(source(responseBody.source()));
        }
        return bufferedSource;

    }

    private Source source(Source source) {
        return new ForwardingSource(source) {
            long totalBytesRead = 0L;

            @Override public long read(Buffer sink, long byteCount) throws IOException {

                return byteCount;
            }
        };
    }


}


@Override
    public void readFile(ResponseBody responseBody) {
        boolean result = writeResponseBodyToDisk(responseBody);
       
    }
private boolean writeResponseBodyToDisk(ResponseBody body) {
        try {

            InputStream inputStream = null;
            OutputStream outputStream = null;

            try {
                long fileSizeDownloaded = 0;
                AndroidLogger.log(5, TAG, "File path" + filePath);
                AndroidLogger.log(5, TAG, "File size" + fileSize);
                long lengthOfFile = Long.parseLong(String.format("%.0f", Double.parseDouble(this.fileSize))) * 1024;
                AndroidLogger.log(5, TAG, "filesize" + fileSize + "length of file" + lengthOfFile);
                inputStream = body.byteStream();
                outputStream = new FileOutputStream(this.filePath);

                byte[] data = new byte[4096];
                long total = 0;
                int count;
                while ((count = inputStream.read(data)) != -1) {
                    if (cancelDownload) {
                        AndroidLogger.log(5,TAG,"Cancel download clicked");
                        inputStream.close();
                        return false;
                    }
                    total += count;
                    outputStream.write(data, 0, count);
                    fileSizeDownloaded += count;
                    if (lengthOfFile > 0) {
                        AndroidLogger.log(5, TAG, "FileSize downloaded" + fileSizeDownloaded);
                        int progress = (int) (fileSizeDownloaded * 100 / lengthOfFile);
                        AndroidLogger.log(5, TAG, "Length of  file" + lengthOfFile);
                        AndroidLogger.log(5, TAG, "Progress" + progress);
                        ((Activity) context).runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                circularProgressBar.setProgress(progress);
                            }
                        });

                    }
                }
                AndroidLogger.log(5, TAG, "file download: " + fileSizeDownloaded + " of " + fileSize);
                outputStream.flush();
                return true;
            } catch (IOException e) {
                e.printStackTrace();
                return false;
            } finally {
                if (inputStream != null) {
                    inputStream.close();
                }

                if (outputStream != null) {
                    outputStream.close();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
    }
person Kousalya    schedule 14.09.2020