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

4

我使用了 Retrofit2 进行文件下载。但我无法使用进度值更新 ProgressBar。我已经获取到进度值,因此没有问题。但是,当我将进度值设置为 ProgressBar 时,UI 上并未反映出来。

我在谈论 RecyclerView Adapter 内部存在的 ProgressBar。

以下是我的 Retrofit 调用, 当在 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更新值,因为Retrofit调用是在其他线程上完成的。因此,我使用了Listener来更新UI,但没有帮助。
我正在使用Retrofit2进行API调用。因此,为了在所有活动中更新UI,我使用了接口监听器。这对所有活动都有效。但是当我在RecyclerView Adapter类中尝试相同的操作时,无法更新进度条。在调用api之前,我将进度条设置为0且最大为100。
以下情况可以正常工作,
循环进度条在API调用之前设置为零
下载完成后,循环进度条将更改为一个勾号
以下情况不起作用,
具有加载指示的循环进度条
注意:只有在使用Retrofit2进行API调用时才遇到此问题。如果我在异步任务内部使用普通的HTTPUrlConnection进行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%下载完成。在更新进度百分比之前,进度没有反映。

请有人帮助我解决这个问题。

提前致谢。

3个回答

4
UI更新需要在UI线程中进行。从后台线程(如AsyncTask)设置颜色实际上不会更新UI,因为这不是在UI线程中发生的。有几种方法可以在UI中更新进度颜色。我建议使用接口和回调函数,以便您可以从实现它的活动或片段调用该回调函数来更新UI。以下是详细说明。
首先让我们声明一个接口。
public interface UIUpdater {
    void updateUI(int progressValue);
}

现在在你想要更新UI的活动或片段中实现此接口。

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

这样你就可以通过以下方式从 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(); 

现在,在异步任务中发布进度时,调用监听器函数以便使用您的活动/片段的UI线程更新UI。
protected void onProgressUpdate(Integer... values) {
    super.onProgressUpdate(values);

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

希望你能理解这个概念。

我在我的RecyclerView项中有一个进度条。我正在适配器内部分配进度。因此,我不知道如何在这种情况下使用监听器。 - Kousalya
你也可以为适配器实现类似的方法。适配器可以实现 UIUpdater 接口并覆盖其中相同的方法。如果有帮助的话,你可能需要修改接口以添加一个额外的位置参数来与 RecyclerView 一起使用。 - Reaz Murshed
谢谢你的回答 :-). 但是不能解决我的问题。我正在Retrofit2响应内部执行Asynctas。 或许这就是问题所在。 - Kousalya
重写的方法被调用了,但在用户界面中没有反映。因此尝试了不同的方法,如使用处理程序(handler)、runOnUIthread等,但都没有帮助。 - Kousalya

0

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

既不是适配器,也不是视图(RecyclerView),甚至不是ViewHolder的责任来决定这里的任何事情。

  • 我假设你有一个ViewHolder类型,其中包含一个进度条。
  • 我假设你有一个ListAdapter<T, K>,并且有相应的DiffUtilCallback实现。
  • 我假设进度在其他地方处理/报告(在存储库中,在viewModel或Presenter中,通过useCase、Interactor或甚至是普通的ViewModel)。
  • 现在你的适配器除了等待什么都不用做。
  • 当进度更新时,你准备正在显示的列表,以便更新进度已更改的项目。
  • 之后,你将这个新列表(带有新进度)提交给你的适配器。
  • 你的DiffUtil计算这个(它也可以是异步的!)
  • 你的RecyclerView神奇地更新了,不需要任何黑客手段。

如果这些中的任何一个不是真的,请进行必要的调整,以便能够正确测试你的代码,并更好地分离每个代码片段的关注点。

这样想,假设你拥有所有这些,并且在你更新“进度值”的小函数中出现了一个小错误。哪个更容易,是在你的ViewModel或UseCase/interactor中搜索将Retrofit值转换为<Thing>的小函数,还是在你的回调代码混乱的地方到处查找?


谢谢您的回答。在我的情况下,我正在一个项目的onclick监听器中执行retrofit调用。我正在writeResponseBodyToDisk方法的while循环中更新进度条。但是没有反映在UI上。 - Kousalya
你是如何告诉你的适配器数据已经改变的? - Martin Marconcini
谢谢你的帮助 :-) 我找到了解决方案。稍后会在这里发布。 - Kousalya
没问题,很高兴知道你找到了解决方案。如果你感兴趣,我修改了一个不相关的示例,展示了在“虚假”缓慢操作期间显示进度条的方法。你可以查看主分支(或具有viewModels的主题分支)和这个新分支之间的“diff”,链接在这里https://github.com/Gryzor/CheckBoxCounter/compare/simulatedProgressBars?expand=1 如果你运行它,你会注意到你可以点击复选框,进度条会出现,一旦它们“完成”,它们会再次更新以显示正确的UI。计数器是无关紧要的。祝你好运。 - Martin Marconcini

0

非常感谢大家的帮助!!

这个答案帮助我更新了圆形进度条 https://dev59.com/wlgQ5IYBdhLWcg3w3Hl-#42119419

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

进行中的body类


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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接