Search code examples
javaandroidandroid-asynctaskreactivex

android progress bar not updating progress correctly (onPostExecute() runs late)


i am building an application for practice and learning that is meant to download files from the internet. i am sure that i am going to have to make many changes to it in the future but as of now i am having trouble getting the progress bar to update correctly. when i click the button an AsyncTask subclass is supposed to run and get the file. the progress bar is supposed to be updated as the file is read from the internet. the problem is that sometimes the progress bar updates seemingly all at once, immediately, and sometimes it lags for a long time staying blank until again, updating all at once. i see that there is a problem with my use of buffer.size() as the argument for publishProgress(), but i am not sure how i can do this correctly. onPostExecute() also takes a very long time to run. as a side quetion i have a small section of code that i commented out that uses rxjava to update the progress bar. i was considering trying to use something like this to replace onPostExecute(). would that be a bad idea? is that a "correct usage of rxjava?" here is my 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);

}
}

and my asynctask subclass:

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

}

i apologize if my question seems unclear. what i am asking is basically how i can more efficiently perform the progress update related to reading from the internet, and reduce the delay between doInBackground() being called and onPostExecute() being called.

an edit to my code:

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

Solution

  • You got lag because you public progress inside a loop, it will make main thread call it many time. We have some solution here:

    1. Please delay use Thread.sleep. at least 100 mil.

      try{ Thread.sleep(100); }catch (InterruptedException e){ }finally { if (fileLength > 0) { this.publishProgress((int) ((progress +=count) * 100 / fileLength)); } }

    2. Just public progress when it increase 1% compare to previous percent.

    3. Update code: Dont need to use buffer

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

    or

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