简体   繁体   English

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

[英]Unable to update Circular ProgressBar while downloading file

I have used Retrofit2 for file download.我使用 Retrofit2 进行文件下载。 I am not able to update ProgressBar with progress value.我无法使用进度值更新 ProgressBar。 I got progress value.我得到了进步值。 So there is not issue.所以没有问题。 When I set the progress value to progress bar not reflected in UI.当我将进度值设置为 UI 中未反映的进度条时。

I am talking about Progress Bar which is present inside RecyclerView Adapter.我说的是 RecyclerView Adapter 中存在的进度条。

Below is my retrofit call, And this method will be called when clicking a item inside 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;
        }
    }

Also I have tried Listener to update value because retrofit call done on some other thread.我也尝试过 Listener 来更新值,因为在其他线程上完成了改造调用。 So for update UI I have used listener which is not helped.因此,对于更新 UI,我使用了没有帮助的侦听器。

I am using Retrofit2 for making API calls.我正在使用 Retrofit2 进行 API 调用。 So for updating UI in all Activities I had used interface listeners.因此,为了更新所有活动中的 UI,我使用了界面侦听器。 This works perfect for all Activities.这适用于所有活动。 But when I tried the same thing in RecyclerView Adapter class, not able to update progress bar.但是当我在 RecyclerView Adapter 类中尝试同样的事情时,无法更新进度条。 Before calling api I had set Progress bar to 0 and max to 100.在调用 api 之前,我已将进度条设置为 0,最大设置为 100。

Below case Works fine,下面的案例工作正常,

Circular ProgressBar before API call set to Zero API 调用前的圆形 ProgressBar 设置为零

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.注意:我只有在使用 Retrofit2 进行 API 调用时才会遇到这个问题。 If I used normal HTTPUrlConnection for making API call inside a Asynctask, then progress loading working fine.如果我使用普通的 HTTPUrlConnection 在 Asynctask 内进行 API 调用,那么进度加载工作正常。

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.我已经把它放在 writeResponseBodyToDisk 方法中,在 while 循环中,但它只被调用了两次并且进度条没有更新。

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.之后,当我尝试下载时,下载完成后可以在进度条中看到 100% 下载完成。 Before progress percent updation not reflected.未反映进度百分比更新之前。

Please Anybody help me out to solve this issue.请任何人帮我解决这个问题。

Thanks in advance.提前致谢。

The UI updates are needed to be happened in the UI thread. UI 更新需要在 UI 线程中进行。 Setting the color from a background thread (ie AsyncTask ) will not actually update the UI as this is not happening in the UI thread.从后台线程(即AsyncTask )设置颜色实际上不会更新 UI,因为这不会发生在 UI 线程中。 There are several ways to update the progress color in the UI.有多种方法可以更新 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.我建议有一个接口和一个回调函数,这样你就可以调用该回调函数来从实现它的片段的活动更新 UI。 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.现在在要更新 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.您想修改初始化AsyncTask的构造函数,以将UIUpdater类作为参数传递。

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.这样您就可以使用以下内容从活动/片段中调用AsyncTask

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.现在从异步任务中,当您发布进度时,调用侦听器函数以使用您的活动/片段的 UI 线程更新 UI。

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

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

Hope you get the idea.希望你明白。

Your problem is not that you are using a RecyclerView, your problem is that you're not using it correctly.您的问题不是您使用的是 RecyclerView,而是您没有正确使用它。

It's is neither the adapter, nor the view (RecyclerView) nor even the ViewHolder's responsibility to determine anything here.在这里确定任何事情都不是适配器,也不是视图(RecyclerView),甚至不是 ViewHolder 的责任。

  • I assume you have a ViewHolder type with a progress bar in it.我假设您有一个带有进度条的 ViewHolder 类型。
  • I assume you have a ListAdapter<T, K> with the corresponding DiffUtilCallback implementation.我假设你有一个ListAdapter<T, K>和相应的DiffUtilCallback实现。
  • I assume progress is handled/reported elsewhere (in your repository, through a viewModel or Presenter, via a useCase, Interactor, or even the plain ViewModel).我假设进度是在其他地方处理/报告的(在您的存储库中,通过 viewModel 或 Presenter,通过用例、交互器,甚至是普通的 ViewModel)。
  • Your adapter now has nothing to do but wait.您的适配器现在无事可做,只能等待。
  • When progress is updated, you prepare the List 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!)你的 DiffUtil 计算这个(它也可以是异步的!)
  • Your RecyclerView is magically updated, no need to hack anything.你的 RecyclerView 神奇地更新了,不需要破解任何东西。

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?在您的 ViewModel 或 UseCase/interactor 中搜索将 Retrofit 值转换为您的<Thing>的小函数,或者在您的意大利面条式回调代码中的所有地方,哪个会更容易?

Thank you all for trying to help me!!感谢大家试图帮助我!!

This answer helped me to update Circular ProgressBar https://stackoverflow.com/a/42119419/11630822这个答案帮助我更新了 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,在 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;
        }
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM