下载文件时无法更新循环进度条

Unable to update Circular ProgressBar while downloading file

我使用Retrofit2 进行文件下载。我无法使用进度值更新 ProgressBar。我得到了进步价值。所以没有问题。当我将进度值设置为进度条时,未反映在 UI.

我说的是 RecyclerView Adapter 中的进度条。

下面是我的改装电话, 点击 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 来更新值,因为改造调用是在其他线程上完成的。所以对于更新 UI 我使用了没有帮助的监听器。

我正在使用 Retrofit2 进行 API 调用。因此,为了在所有活动中更新 UI,我使用了接口侦听器。这适用于所有活动。但是当我在 RecyclerView Adapter class 中尝试同样的事情时,无法更新进度条。在调用 api 之前,我已将进度条设置为 0,最大设置为 100。

以下案例工作正常,

循环 ProgressBar 在 API 调用之前设置为零

循环ProgressBar下载完成后会变成一个刻度线

下面不工作,

带加载指示的圆形进度条

注意:只有在使用 Retrofit2 进行 API 调用时,我才会遇到这个问题。如果我使用普通的 HTTPUrlConnection 在 Asynctask 中进行 API 调用,那么进度加载工作正常。

我已经通过下面的代码检查了进度更新是否发生在主线程上,

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

以上if条件满足。尽管进度条未更新。

下面我也试过了,

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% 下载完成。未反映进度百分比更新前。

请任何人帮我解决这个问题。

提前致谢。

UI 更新需要在 UI 线程中发生。从后台线程(即 AsyncTask)设置颜色实际上不会更新 UI,因为这不会发生在 UI 线程中。在 UI 中有几种更新进度颜色的方法。我建议有一个接口和一个回调函数,这样您就可以调用该回调函数来从实现它的片段的 activity 更新 UI。在此澄清一下。

让我们先声明一个接口。

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

现在在要更新 UI 的 activity 或片段中实现此接口。

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 class 作为参数传递。

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

    UIUpdater listener;

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

以便您可以使用以下内容从 activity/fragment 调用 AsyncTask

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

现在从异步任务中,当您发布进度时,调用侦听器函数以使用 activity/fragment 的 UI 线程更新 UI。

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

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

希望你明白了。

你的问题不是你在使用 RecyclerView,而是你没有正确使用它。

适配器、视图 (RecyclerView) 甚至 ViewHolder 都没有责任确定此处的任何内容。

  • 我假设您有一个带有进度条的 ViewHolder 类型。
  • 我假设您有一个 ListAdapter<T, K> 和相应的 DiffUtilCallback 实现。
  • 我假设进展是 handled/reported 其他地方(在您的存储库中,通过 viewModel 或 Presenter,通过 useCase、Interactor,甚至是普通的 ViewModel)。
  • 您的适配器现在无事可做,只能等待。
  • 更新进度时,您准备要显示的列表,以便更新进度已更改的项目。
  • 之后您将这个新列表(带有新进度)提交给您的适配器。
  • 你的 DiffUtil 会计算这个(它也可以是异步的!)
  • 您的 RecyclerView 已神奇更新,无需修改任何内容。

如果其中任何一个不正确,请执行必要的调整,以便可以正确测试您的代码并更好地分离每段代码的关注点。

这样想,假设您拥有所有这些,并且您更新的“进度值”中有一个小错误。在你的 ViewModel 或 UseCase/interactor 中搜索将 Retrofit 值转换为你的 <Thing> 的小函数,或者在你的意大利面条式回调代码中到处搜索哪个会更容易?

谢谢大家的帮助!!

这个答案帮助我更新了 Circular ProgressBar

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

进行中class,


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