android 进度条未正确更新进度(onPostExecute() 运行迟了)
android progress bar not updating progress correctly (onPostExecute() runs late)
我正在构建一个用于练习和学习的应用程序,旨在从 Internet 下载文件。我确信将来我将不得不对其进行许多更改,但截至目前,我无法正确更新进度条。当我单击按钮时,AsyncTask 子类应该 运行 并获取文件。当从互联网上读取文件时,进度条应该会更新。问题在于,有时进度条似乎会立即立即更新,有时会滞后很长时间保持空白,直到再次更新,一次更新。我看到我使用 buffer.size() 作为 publishProgress() 的参数有问题,但我不确定如何正确地做到这一点。 onPostExecute() 也需要很长时间才能 运行。作为附带问题,我有一小段代码被注释掉,它使用 rxjava 来更新进度条。我正在考虑尝试使用类似这样的东西来替换 onPostExecute()。那会是个坏主意吗?是 "correct usage of 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);
}
}
和我的异步任务子类:
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());
}
你有滞后是因为你 public 在一个循环中进行,这会让主线程调用它很多次。我们这里有一些解决方案:
请延迟使用Thread.sleep。至少 1 亿
尝试{
Thread.sleep(100);
}赶上(InterruptedException e){
}最后 {
如果(文件长度 > 0){
this.publishProgress((int) ((progress +=count) * 100 / fileLength));
}
}
当它比之前的百分比增加 1% 时只是 public 进步。
更新代码:不需要使用缓冲区
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;
}
}
在你的 AsyncDownload 中使用这个 class
@Override
protected void onProgressUpdate(Integer... values) {
progressBar.setProgress(values[0]);
}
我正在构建一个用于练习和学习的应用程序,旨在从 Internet 下载文件。我确信将来我将不得不对其进行许多更改,但截至目前,我无法正确更新进度条。当我单击按钮时,AsyncTask 子类应该 运行 并获取文件。当从互联网上读取文件时,进度条应该会更新。问题在于,有时进度条似乎会立即立即更新,有时会滞后很长时间保持空白,直到再次更新,一次更新。我看到我使用 buffer.size() 作为 publishProgress() 的参数有问题,但我不确定如何正确地做到这一点。 onPostExecute() 也需要很长时间才能 运行。作为附带问题,我有一小段代码被注释掉,它使用 rxjava 来更新进度条。我正在考虑尝试使用类似这样的东西来替换 onPostExecute()。那会是个坏主意吗?是 "correct usage of 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);
}
}
和我的异步任务子类:
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());
}
你有滞后是因为你 public 在一个循环中进行,这会让主线程调用它很多次。我们这里有一些解决方案:
请延迟使用Thread.sleep。至少 1 亿
尝试{ Thread.sleep(100); }赶上(InterruptedException e){ }最后 { 如果(文件长度 > 0){ this.publishProgress((int) ((progress +=count) * 100 / fileLength)); } }
当它比之前的百分比增加 1% 时只是 public 进步。
更新代码:不需要使用缓冲区
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;
}
}
在你的 AsyncDownload 中使用这个 class
@Override
protected void onProgressUpdate(Integer... values) {
progressBar.setProgress(values[0]);
}