英文:
Unable to update Circular ProgressBar while downloading file
问题
我已经使用Retrofit2进行了文件下载。我无法使用进度值更新ProgressBar。我获得了进度值,所以没有问题。但是当我将进度值设置为进度条时,UI中没有反映出来。
我正在谈论的是位于RecyclerView适配器内部的进度条。
以下是我的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调用在其他线程上完成。所以为了更新UI,我使用了Listener,但没有帮助。
我正在使用Retrofit2进行API调用。因此,为了在所有Activities中更新UI,我已经使用了接口监听器。这对于所有Activities都有效。但是当我在RecyclerView适配器类中尝试相同的操作时,无法更新进度条。在调用api之前,我已经将进度条设置为0并将最大值设置为100。
以下情况有效,
在API调用之前将圆形ProgressBar设置为零
下载完成后,圆形ProgressBar将变为一个勾号
以下情况无效,
带有加载指示的圆形ProgressBar
注意:只有当我使用Retrofit2进行API调用时,我才会遇到此问题。如果我在AsyncTask内部使用普通的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%的下载完成进度条。在更新进度百分比之前,进度百分比未反映出来。
请有人帮助我解决这个问题。
提前谢谢。
英文:
I have used Retrofit2 for file download. I am not able to update ProgressBar with progress value. I got progress value. So there is not issue. When I set the progress value to progress bar not reflected in UI.
I am talking about Progress Bar which is present inside RecyclerView Adapter.
Below is my retrofit call,
And this method will be called when clicking a item inside 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;
}
}
Also I have tried Listener to update value because retrofit call done on some other thread. So for update UI I have used listener which is not helped.
I am using Retrofit2 for making API calls. So for updating UI in all Activities I had used interface listeners. This works perfect for all Activities. But when I tried the same thing in RecyclerView Adapter class, not able to update progress bar. Before calling api I had set Progress bar to 0 and max to 100.
Below case Works fine,
Circular ProgressBar before API call set to Zero
Circular ProgressBar after download Completed will change to a tick mark
Below is not Working,
Circular ProgressBar with indication of loading
NOTE: I am facing this issue only when used Retrofit2 to make API call. If I used normal HTTPUrlConnection for making API call inside a Asynctask, then progress loading working fine.
I have checked whether the progress updation is occurs on main thread or not by below code,
if(Looper.myLooper() == Looper.getMainLooper()) {
circularProgressBar.setProgress(progress);
}
The above if condition is satisfied. Eventhough Progress bar not updated.
Also I have tried below,
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);
}
I have placed this inside writeResponseBodyToDisk method, inside while loop,
But it was called only two times and progress bar not updated.
I commented the part, where the progress bar loading will change to a tick mark. After that when I tried download,once download completed able to see 100 percent download completed in progress bar. Before progress percent updation not reflected.
Please Anybody help me out to solve this issue.
Thanks in advance.
答案1
得分: 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) {
// 在这里进行UI更新。
circularProgress.setProgress(progressValue); // 或者您想要进行的其他操作。
}
}
您希望修改初始化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); // 当您从活动传递this时,实际上隐式地传递了实现的接口。
myTask.execute();
现在,在异步任务中,在发布进度时调用监听器函数,以便使用活动/片段的UI线程来更新UI。
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
try {
listener.updateUI(values[0]);
}
}
希望您理解了这个思路。
英文:
The UI updates are needed to be happened in the UI thread. Setting the color from a background thread (i.e. AsyncTask
) will not actually update the UI as this is not happening in the UI thread. There are several ways to update the progress color in the UI. I would recommend having an interface along with a callback function so that you can invoke that callback function to update the UI from the activity of fragment that implemented it. Here's a clarification.
Let us declare an interface first.
public interface UIUpdater {
void updateUI(int progressValue);
}
Now implement this interface in the activity or fragment where you want to update the 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.
}
}
You want to modify the constructor of initializing your AsyncTask
to have the UIUpdater
class to be passed as parameter.
public class YourAsyncTask extends AsyncTask<String, Void, String> {
UIUpdater listener;
public YourAsyncTask(UIUpdater listener) {
this.listener = listener;
}
}
So that you can call the AsyncTask
from the activity/fragment using something as following.
YourAsyncTask myTask = new YourAsyncTask(this); // When you are passing this from activity, you are implicitly passing the interface that it implemented.
myTask.execute();
Now from the async task, while you are publishing the progress, invoke the listener function in order to update the UI using the UI thread of your activity/fragment.
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
try {
listener.updateUI(values[0]);
}
}
Hope you get the idea.
答案2
得分: 0
你的问题并不在于你使用了 RecyclerView,而是你没有正确地使用它。
既不是适配器,也不是视图(RecyclerView),甚至不是 ViewHolder 在这里确定任何内容。
- 我假设你有一个 ViewHolder 类型,其中包含一个进度条。
- 我假设你有一个带有相应的 DiffUtilCallback 实现的
ListAdapter<T, K>
。 - 我假设进度在其他地方进行处理/报告(可能在存储库中,通过 viewModel 或 Presenter,在使用案例、交互器中,甚至是纯粹的 ViewModel 中)。
- 现在你的适配器除了等待什么都不需要做。
- 当进度更新时,你准备要显示的 Thing 列表,以便更新已更改进度的项目。
- 然后将这个新列表(带有新进度)提交给你的适配器。
- 你的 DiffUtil 会计算这个(它也可以是异步的!)
- 你的 RecyclerView 就会神奇地更新,无需进行任何操作。
如果其中任何一点不成立,请进行必要的调整,以便可以对你的代码进行适当的测试,并更好地分离每段代码的关注点。
这样考虑一下,想象一下如果你拥有了所有这些,然后在你更新 "进度值" 时出现了一个小 bug。哪个更容易,在你的 ViewModel 或 UseCase/交互器中搜索将 Retrofit 值转换为你的 <Thing>
的小函数,还是在你的回调混乱的代码中四处查找?
英文:
Your problem is not that you are using a RecyclerView, your problem is that you're not using it correctly.
It's is neither the adapter, nor the view (RecyclerView) nor even the ViewHolder's responsibility to determine anything here.
- I assume you have a ViewHolder type with a progress bar in it.
- I assume you have a
ListAdapter<T, K>
with the correspondingDiffUtilCallback
implementation. - I assume progress is handled/reported elsewhere (in your repository, through a viewModel or Presenter, via a useCase, Interactor, or even the plain ViewModel).
- Your adapter now has nothing to do but wait.
- When progress is updated, you prepare the List<Things> you are displaying so the item whose progress has changed is updated.
- Afterwards you submit this new list (with the new progress) to your adapter.
- Your DiffUtil calculates this (it can be async too!)
- Your RecyclerView is magically updated, no need to hack anything.
If any of these is not true, perform the necessary adjustments so your code can be properly tested and the concerns of each piece of code is better separated.
Think about it this way, imagine you had all that, and there was a small bug in the "progress value" you update. Which would be easier, search in the little function in your ViewModel or UseCase/interactor that transforms the Retrofit value into your <Thing>
, or all over the place in your spaghetti code of callbacks?
答案3
得分: 0
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;
}
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;
}
}
这是你提供的代码的翻译。如果你有任何其他问题或需要进一步的帮助,请随时提问。
英文:
Thank you all for trying to help me!!
This answer helped me to update 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;
}
In Progressbody 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;
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论