【问题标题】:android progress bar not updating progress correctly (onPostExecute() runs late)android 进度条未正确更新进度(onPostExecute() 运行较晚)
【发布时间】:2017-07-13 05:59:58
【问题描述】:

我正在构建一个用于从 Internet 下载文件的练习和学习应用程序。我确信将来我将不得不对其进行许多更改,但到目前为止,我无法正确更新进度条。当我单击按钮时,应该运行 AsyncTask 子类并获取文件。当从 Internet 读取文件时,进度条应该会更新。问题是,有时进度条似乎立即全部更新,有时它会滞后很长时间保持空白,直到再次,一次全部更新。我发现我使用 buffer.size() 作为 publishProgress() 的参数存在问题,但我不确定如何正确执行此操作。 onPostExecute() 也需要很长时间才能运行。作为一个附带问题,我有一小段代码我注释掉了,它使用 rxjava 来更新进度条。我正在考虑尝试使用类似的东西来替换 onPostExecute()。那会是个坏主意吗?这是“rxjava 的正确用法吗?”这是我的 MainActivity:

public class MainActivity extends AppCompatActivity {

private static final String TAG = "MAIN";
private static final String startURL = "https://www.google.com";
private static final int REQUEST_CODE_EXTERNAL = 0;

private Button runButton;
private EditText urlSpecBox;
private ProgressBar progressBar;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    //request for permission to write to storage here
    if(ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE)
            != PackageManager.PERMISSION_GRANTED){
        ActivityCompat.requestPermissions(this, (new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}), REQUEST_CODE_EXTERNAL);
    }

    progressBar = (ProgressBar) findViewById(R.id.progroessBar);
    progressBar.setMax(100);


    runButton = (Button) findViewById(R.id.dwnldButton);
    runButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            try{
                progressBar.setVisibility(View.VISIBLE);
                progressBar.setProgress(0);
                new AsyncDownload(new URL(startURL), progressBar).execute();

            }catch (MalformedURLException me){
                Log.e(TAG, "error with url", me);
            }
        }
    });

    urlSpecBox = (EditText) findViewById(R.id.urlSpecBox);

}
}

和我的 asynctask 子类:

public class AsyncDownload extends AsyncTask<Void, Integer, Void>{
private static final String TAG = "AsyncDownload";
private static final String STORAGE_LOCATION = "/sdcard/"; //android directory picker is needed

private URL url;
private ProgressBar mProgessBar;
//private ArrayList<Byte> bytes = new ArrayList<>();

public AsyncDownload(URL url, ProgressBar progressBar){
    mProgessBar = progressBar;
    this.url = url;
}

@Override
protected void onProgressUpdate(Integer... progress){
    mProgessBar.setProgress(progress[0]);
}

@Override
protected Void doInBackground(Void... params){

    try{
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));

        ByteArrayOutputStream buffer = new ByteArrayOutputStream();

        int c;
        while ((c = in.read()) != -1){
            buffer.write(c);
            publishProgress(buffer.size());
        }

        Log.i(TAG,  "response received");

        Random rand = new Random(4L);
        String temp = String.valueOf(rand.nextInt());

        String finalLocation = STORAGE_LOCATION + temp;

        File file = new File(finalLocation);
        file.getParentFile().mkdirs();

        Log.i(TAG, file.getName());

        FileOutputStream fOut = new FileOutputStream(file);
        fOut.write(buffer.toByteArray());
        buffer.close();
        fOut.flush();
        fOut.close();
        FileInputStream fIn = new FileInputStream(finalLocation);

        String reRead = new String();
        int a;
        while ((a = fIn.read()) != -1){
            reRead += a;
        }

        Log.i(TAG, "reRead" + reRead);

        //this section is for automatic file naming
        /*Random rand = new Random(5L);
        String fileNumber = String.valueOf(rand.nextInt());
        StringBuilder sb = new StringBuilder();
        sb.append(fileNumber).append("download"); //definitely needs work

        Log.i(TAG, sb.toString());*/

        //FileOutputStream fOut = new FileOutputStream()

    }catch (IOException ioe){
        Log.e(TAG, "network error" + ioe.toString(), ioe);
    }

    /*rx.Observable.just(0) //is it correct to use rxjava this way?
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(
                    new Action1<Integer>() {
                        @Override
                        public void call(Integer integer) {
                            mProgessBar.setProgress(integer);
                            mProgessBar.setVisibility(View.VISIBLE);
                        }
                    }
            );*/

    return null;
}

@Override
protected void onPostExecute(Void result){ // METHOD IS NEVER CALLED
    super.onPostExecute(result);
    Log.i(TAG, "onPostExecute called! - Task Completed!");
    mProgessBar.setProgress(0);
    mProgessBar.setVisibility(View.GONE);
}

}

如果我的问题似乎不清楚,我深表歉意。我要问的基本上是如何更有效地执行与从 Internet 读取相关的进度更新,并减少调用 doInBackground() 和调用 onPostExecute() 之间的延迟。

修改我的代码:

int c;
        int progress = 0;
        int count = buffer.size();
        int fileSize = connection.getContentLength();

        while ((c = in.read()) != -1){
            buffer.write(c);
            try{
                Thread.sleep(TimeUnit.MILLISECONDS.toMillis(100L));
            }catch (InterruptedException ie){
                Log.e(TAG, "thread interrupted", ie);
            }finally {
                if (count > 0){
                    publishProgress((int) ((progress+=count)*100/fileSize));
                }
            }
            //publishProgress(buffer.size());
        }

【问题讨论】:

  • 尝试从 doInBackground 方法返回字符串。所以你可以在 onPostExecute 中查看任务是否完成。
  • 你为什么不在 AsyncTask 类中尝试protected void onProgressUpdate
  • @AshutoshSagar 你的意思是我应该调用 onProgressUpdate 吗?我确实在我的 AsyncTask 中覆盖了它。我需要调用 super.onProgressUpdate 吗?还是我错过了什么?
  • 赞这个protected void onProgressUpdate(Integer... progress) { setProgressPercent(progress[0]); }

标签: java android android-asynctask reactivex


【解决方案1】:

你有延迟,因为你在一个循环中公开进度,它会让主线程多次调用它。我们在这里有一些解决方案:

  1. 请延迟使用 Thread.sleep。至少一亿。

    试试{ 线程.sleep(100); }catch (InterruptedException e){ }最后 { 如果(文件长度> 0){ this.publishProgress((int) ((progress +=count) * 100 / fileLength)); } }

  2. 与之前的百分比相比增加 1% 时的公开进度。

  3. 更新代码:不需要使用缓冲区

    FileOutputStream fOut = new FileOutputStream(file);
    FileInputStream fIn = new FileInputStream(finalLocation);
    byte data[] = new byte[4096];
    long progress = 0;
    int count;
    int fileSize = connection.getContentLength();
    
    while ((c = in.read()) != -1){
        //we should write the data before publish progress
        fOut.write(data, 0, count)
        try{
            Thread.sleep(100);
        }catch (InterruptedException ie){
            Log.e(TAG, "thread interrupted", ie);
        }finally {
            if (fileSize > 0){
                publishProgress((int) ((progress+=count)*100/fileSize));
            }
        }
    }
    

if (fileSize > 0) {
    currentProgress = ((progress += count) * 100 / fileSize);
    // Publish only on increments of 1%
    if (currentProgress >= previousProgress + 1) {
        this.publishProgress(currentProgress);
        previousProgress = currentProgress;
    }

}

【讨论】:

  • 我只是不明白我应该使用什么来进行进度和计数。我正在尝试将 int 初始化为 0,但我不清楚在哪里获得计数。我必须使用 for 循环代替 while 吗?
  • 我发布了对我的代码的编辑,这是我试图遵循您的建议。似乎我做错了,但随着滞后的增加。
  • 谢谢。根据您的建议,我能够让它工作得更好。再次感谢。
  • 但是我确实还有一个问题。 connection.getContentLength 出于某种原因返回-1。为了解决这个问题,我只使用了一个静态整数作为文件大小。它工作得更好,但不是它应该的方式。知道如何获得用于 fileSize 的正确值吗?
  • 如果你在onDoingBackground外部使用静态变量,无法与外部同步,我们必须检查connection.ContetnL > 0 ....
【解决方案2】:

在你的 AsyncDownload 类中使用它

@Override
        protected void onProgressUpdate(Integer... values) {
            progressBar.setProgress(values[0]);
        }

【讨论】:

  • @Override protected void onProgressUpdate(Integer... progress){ mProgessBar.setProgress(progress[0]); } 这是我的用法。对吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-05
  • 2018-10-27
  • 1970-01-01
  • 2015-01-24
  • 2013-03-08
  • 2013-09-24
相关资源
最近更新 更多