I have seen this question asked before, but I can't find an answer that works and I've been looking at this same issue for days glum.
I have a screen with 3 progress bars which I want to update during a lengthy download process. No matter what I try I cannot get the progressbar to update.
The full source is here:
My FileDownloadManager class:
package org.touchandgo.speak;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.net.ConnectivityManager;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.ProgressBar;
import android.widget.TextView;
public class FileDownloadManager extends Activity
{
public static final String FILE_MANIFEST = "http://dl.dropbox.com/u/25690498/TouchAndGoSpeakImages/TouchAndGoSpeak_Download.manifest";
public static final String LOCAL_FILE_MANIFEST = "TouchAndGoSpeak_Download.manifest";
public static final String LOGid = "TouchAndGoSpeak";
private String msBaseFolder = "";
private TextView tvCurrent;
public void onCreate(Bundle icicle)
{
super.onCreate(icicle);
Log.d(LOGid, "Create activity");
setContentView(R.layout.download);
mContext = this;
tvCurrent = (TextView) findViewById(R.id.tvCurrentAction);
tvCurrent.setText("Awaiting user input");
tvCurrent.invalidate();
Button bDownload = (Button) findViewById(R.id.btnStartDownload);
bDownload.setOnClickListener(new View.OnClickListener()
{
public void onClick(View arg0)
{
if (!isNetworkAvailable())
{
AlertDialog adConfirm = new AlertDialog.Builder(mContext).create();
adConfirm.setCancelable(false); // This blocks the 'BACK' button
adConfirm.setMessage("It appears you are offline, phone should be online for this to work.");
adConfirm.setButton("I'm online, go ahead.", new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
dialog.dismiss();
downloadfiles();
}
});
adConfirm.setButton2("I'll try later", new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
dialog.dismiss();
}
});
adConfirm.show();
}
else
{
downloadfiles();
}
}
});
Button bDownloadInfo = (Button) findViewById(R.id.btnDownloadInfo);
bDownloadInfo.setOnClickListener(new View.OnClickListener()
{
public void onClick(View arg0)
{
AlertDialog adConfirm = new AlertDialog.Builder(mContext).create();
adConfirm.setCancelable(false); // This blocks the 'BACK' button
adConfirm.setMessage("This function will go to the internet and download the Touch And Go 'Speak' free image set.\n\nIt is highly recommended that you use a wi-fi connection for this.\n\nThese images are provided free of charge and as is. They are not my work, but are based upon various free resources from the internet that I have pulled together.\nNo copyright infringement is intended. Please contact me if you wish to assert copyright for any of these images.\nIf you own a set of images from another source (for example the {PECS image set) the you can copy these to the applocation folder on the sdcard (" + msBaseFolder + ") - for every folder you want to display in the list you must have a corresponding image file for the app to display (for example if you have a folder called '2. Sentence Starters' then in the base folder you must have am image file called '2. Sentence Starters.jpg'");
adConfirm.setButton("OK", new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
dialog.dismiss();
}
});
adConfirm.show();
}
});
}
private void processManifest()
{
tvCurrent.setText("Processing files to download...");
tvCurrent.invalidate();
//Get the text file
File file = new File(msBaseFolder,LOCAL_FILE_MANIFEST);
Log.d(LOGid, "Created new file " + msBaseFolder+ "/" + LOCAL_FILE_MANIFEST);
//Read text from file
// StringBuilder text = new StringBuilder();
ProgressBar pb = (ProgressBar) findViewById(R.id.barFiles);
int iLineCnt=getLineCount(LOCAL_FILE_MANIFEST);
int iCurrentLine=0;
pb.setMax(iLineCnt);
Log.d(LOGid, "Set progress line count to " + iCurrentLine + " of " + iLineCnt);
try
{
BufferedReader br = new BufferedReader(new FileReader(file));
Log.d(LOGid, "Created buffered reader");
String line;
while ((line = br.readLine()) != null)
{
try
{
iCurrentLine++;
pb.setProgress(iCurrentLine);
pb.invalidate();
Log.d(LOGid, "Set progress line count to " + iCurrentLine + " of " + iLineCnt);
}
catch (Exception ex)
{ // swallow the error
}
String[] sDownloads = line.split(";");
Log.d(LOGid, "Line read was" + line + " of which there are " + sDownloads.length + " parts");
// line should have 3 parts
// 1. Source url
// 2. Target name
// 3. Target folder
if (sDownloads.length == 3)
{
// Does the file already exist
File fil;
File fld;
if (sDownloads[2].equals("."))
{
fil = new File(msBaseFolder, sDownloads[1]);
fld = new File(msBaseFolder);
}
else
{
fil = new File(msBaseFolder + "/" + sDownloads[2], sDownloads[1]);
fld = new File(msBaseFolder + "/" + sDownloads[2]);
}
// Does the folder exist
Log.d(LOGid, "Ensure the dest folder exists: " + fld.getPath());
fld.mkdirs();
if (!fil.exists() )
{
downloadFile(sDownloads[0], sDownloads[1], fld.getPath(), true);
}
else
{
// Skipping a file
}
}
}
}
catch (Exception e)
{
//You'll need to add proper error handling here
Log.e(LOGid, "Error in processmanifest:" + e.getMessage());
}
//Set the text
tvCurrent.setText("Finished Downloading Files...");
tvCurrent.invalidate();
}
private boolean downloadFile(String _source, String _dest, String _Folder, boolean isManifest)
{
try
{
tvCurrent.setText("Downloading file " + _dest + "...");
tvCurrent.invalidate();
//set the download URL, a url that points to a file on the internet
//this is the file to be downloaded
URL url = new URL(_source);
Log.d(LOGid, "Created URL");
//create the new connection
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
Log.d(LOGid, "Created HTTP Connection");
//set up some things on the connection
urlConnection.setRequestMethod("GET");
Log.d(LOGid, "Set the request method");
urlConnection.setDoOutput(true);
Log.d(LOGid, "Set doOutput");
//and connect!
urlConnection.connect();
Log.d(LOGid, "Connected the url connection");
//set the path where we want to save the file
//in this case, going to save it on the root directory of the
//sd card.
File SDCardRoot = Environment.getExternalStorageDirectory();
//create a new file, specifying the path, and the filename
//which we want to save the file as.
File file = new File(_Folder, _dest);
Log.d(LOGid, "Created dest file, path=" + file.getPath() + "...file=" + file.getName());
//this will be used to write the downloaded data into the file we created
FileOutputStream fileOutput = new FileOutputStream(file);
Log.d(LOGid, "Created file output stream");
//this will be used in reading the data from the internet
InputStream inputStream = urlConnection.getInputStream();
Log.d(LOGid, "Created Input stream");
//this is the total size of the file
int totalSize = urlConnection.getContentLength();
Log.d(LOGid, "Got total size " + totalSize);
//variable to store total downloaded bytes
int downloadedSize = 0;
//create a buffer...
byte[] buffer = new byte[1024];
Log.d(LOGid, "Created buffer");
int bufferLength = 0; //used to store a temporary size of the buffer
ProgressBar pb;
if (isManifest)
{
pb = (ProgressBar) findViewById(R.id.barManifest);
}
else
{
pb = (ProgressBar) findViewById(R.id.barOneFile);
}
pb.setMax(totalSize);
Log.d(LOGid, "Set progressbar size");
pb.setProgress(0);
pb.invalidate();
Log.d(LOGid, "Set progress to zero");
//now, read through the input buffer and write the contents to the file
while ( (bufferLength = inputStream.read(buffer)) > 0 )
{
//add the data in the buffer to the file in the file output stream (the file on the sd card
fileOutput.write(buffer, 0, bufferLength);
Log.d(LOGid, "Wrote infor to the buffer");
//add up the size so we know how much is downloaded
downloadedSize += bufferLength;
//this is where you would do something to report the prgress, like this maybe
// updateProgress(downloadedSize, totalSize);
pb.setProgress(downloadedSize);
pb.invalidate();
Log.d(LOGid, "Set progress to " + downloadedSize);
}
pb.setProgress(totalSize);
pb.invalidate();
//close the output stream when done
fileOutput.close();
Log.d(LOGid, "closed the output file");
tvCurrent.setText("Finished Downloading file " + _dest + "...");
tvCurrent.invalidate();
//catch some possible errors...
}
catch (MalformedURLException e)
{
Log.d(LOGid, "Error dowloading file:" + e.getMessage() + "...malformed URL");
return false;
}
catch (IOException e)
{
Log.d(LOGid, "Error dowloading file:" + e.getMessage()+ "...IO Exception");
return false;
}
catch (Exception e)
{
Log.d(LOGid, "Error dowloading file:" + e.getMessage()+ "...generic exception");
return false;
}
return true;
}
private boolean getBaseFolder()
{
try
{
msBaseFolder = Environment.getExternalStorageDirectory() + "/" + SpeakProperties.msAppFolder(getSharedPreferences(SpeakProperties.APP_NAME, 0));
File f = new File(msBaseFolder);
if (!f.exists())
{
msBaseFolder = Environment.getExternalStorageDirectory() + "/" + SpeakProperties.APP_DEF_FOLDER;
f = new File(msBaseFolder);
}
if (!f.exists())
{
return createNewBaseFolder();
}
else
{
Log.d(LOGid, "The base folder for this app" + msBaseFolder + " does exists.");
return true;
}
}
catch (Exception ex)
{
Log.d(LOGid, "Could not get base folder:" + msBaseFolder + ". Error:" + ex.getMessage());
return false;
}
}
private boolean createNewBaseFolder()
{
tvCurrent.setText("Creating new base folder...");
tvCurrent.invalidate();
final String sNewBaseFld = Environment.getExternalStorageDirectory() + "/" + SpeakProperties.APP_DEF_FOLDER;
AlertDialog adDelete = new AlertDialog.Builder(this).create();
adDelete.setCancelable(false); // This blocks the 'BACK' button
adDelete.setMessage("The base folder for the application does not seem to exist. \n\nWould you like to create one now based on the application default folder setting? \n\nThe App Default Folder is:" + sNewBaseFld);
adDelete.setButton("OK", new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
try
{
File fCreate = new File(sNewBaseFld);
if (!fCreate.exists())
{
fCreate.mkdirs();
}
}
catch (Exception ex)
{
Log.e(LOGid, "Could not delete file:" + ex.getMessage());
showToast("Could not create folder:" +sNewBaseFld + ". Error was:" + ex.getMessage());
}
dialog.dismiss();
}
});
adDelete.setButton2("Cancel", new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
dialog.dismiss();
}
});
adDelete.show();
// Now did we create the folder?
File f = new File(sNewBaseFld);
tvCurrent.setText("Finished Creating new base folder...");
tvCurrent.invalidate();
return f.exists();
}
void showToast(String msg) {
AlertDialog ad = new AlertDialog.Builder(this).create();
ad.setCancelable(false); // This blocks the 'BACK' button
ad.setMessage(msg);
ad.setButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
ad.show();
}
private void downloadfiles()
{
CheckBox cb = (CheckBox) findViewById(R.id.cbDownload);
if (!cb.isChecked())
{
return;
}
if (!getBaseFolder())
{
return;
}
if (downloadFile(FILE_MANIFEST, LOCAL_FILE_MANIFEST, msBaseFolder, true))
{
processManifest();
}
else
{
AlertDialog adConfirm = new AlertDialog.Builder(mContext).create();
adConfirm.setCancelable(false); // This blocks the 'BACK' button
adConfirm.setMessage("Could not download the list of image files, perhaps you are offline?");
adConfirm.setButton("OK.", new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
dialog.dismiss();
}
});
adConfirm.show();
}
}
private boolean isThereANewDownload()
{
tvCurrent.setText("Checking to see if there are new images...");
tvCurrent.invalidate();
// check if we have a TouchAndGoSpeak.manifest file on the sd card
File file = new File(msBaseFolder,LOCAL_FILE_MANIFEST);
if (!file.exists())
{
Log.d(LOGid, "No Local Manifest so returning true");
return true;
}
// if we have one then the user has downloaded the manifest before
// download the new file
Log.d(LOGid, "We have a Local Manifest getting again to check");
if (downloadFile(FILE_MANIFEST, LOCAL_FILE_MANIFEST + ".new", msBaseFolder, false))
{
return (compare(LOCAL_FILE_MANIFEST, LOCAL_FILE_MANIFEST + ".new"));
}
else
{
AlertDialog adConfirm = new AlertDialog.Builder(mContext).create();
adConfirm.setCancelable(false); // This blocks the 'BACK' button
adConfirm.setMessage("Could not download the list of image files, perhaps you are offline? Unable to check if there are new files. Will try and download all files next.");
adConfirm.setButton("OK.", new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
dialog.dismiss();
}
});
adConfirm.show();
return true;
}
}
private int getLineCount(String _File)
{
int iLineCount = 0;
//Get the text file
File file = new File(msBaseFolder,_File);
// StringList sl_source = new StringList();
try
{
BufferedReader br = new BufferedReader(new FileReader(file));
String line;
while ((line = br.readLine()) != null)
{
iLineCount++;
}
}
catch (IOException e)
{
//You'll need to add proper error handling here
}
return iLineCount;
}
private boolean compare(String _File1, String _File2)
{
Log.d(LOGid, "Comparing the 2 files");
//Get the text file
File file = new File(msBaseFolder,_File1);
// StringList sl_source = new StringList();
ArrayList<String> _original_data = new ArrayList<String>();
ArrayList<String> _new_data = new ArrayList<String>();
try {
BufferedReader br = new BufferedReader(new FileReader(file));
String line;
while ((line = br.readLine()) != null)
{
_original_data.add(line);
}
}
catch (IOException e)
{
//You'll need to add proper error handling here
}
file = new File(msBaseFolder,_File2);
try
{
BufferedReader br = new BufferedReader(new FileReader(file));
String line;
while ((line = br.readLine()) != null)
{
_new_data.add(line);
}
}
catch (IOException e)
{
//You'll need to add proper error handling here
}
return _original_data.equals(_new_data);
}
/**
* @return the networkAvailable
*/
public boolean isNetworkAvailable()
{
boolean networkAvailable = true;
try
{
Context c = getApplicationContext();
ConnectivityManager connectionManager = (ConnectivityManager) c.getSystemService(Context.CONNECTIVITY_SERVICE);
networkAvailable = connectionManager.getActiveNetworkInfo() != null && connectionManager.getActiveNetworkInfo().isConnected();
}
catch (Exception ex)
{
Log.d(LOGid, "Error determining the network connectivity:" + ex.getClass().toString() + " message:" + ex.getMessage());
}
return networkAvailable;
}
private Context mContext;
}
edit: working downloadFiles method with progressbar updating
My final downloadFile looks like this with the Thread and handler added. The mHandler is a private class variable instantiated inside the onCreate of the UI.
Thanks for all the help everyone!
private boolean downloadFile(String _source, String _dest, String _Folder,
boolean isManifest)
{
m_downloadSuccess = true;
m_source = _source;
m_dest = _dest;
m_Folder = _Folder;
if (isManifest)
{
m_pb = (ProgressBar) findViewById(R.id.barManifest);
}
else
{
m_pb = (ProgressBar) findViewById(R.id.barOneFile);
}
// Start lengthy operation in a background thread
new Thread(new Runnable()
{
public void run()
{
try
{
// Update the progress bar
mHandler.post(new Runnable()
{
public void run()
{
Log.d(LOGid, "Set File max progress to " + mFileTotal);
Log.d(LOGid, "Set File progress to 0");
Log.d(LOGid, "Downloading file " + m_dest + "...");
tvCurrent.setText("Downloading file " + m_dest + "...");
}
});
// set the download URL, a url that points to a file on the
// internet
// this is the file to be downloaded
URL url = new URL(m_source);
Log.d(LOGid, "Created URL");
// create the new connection
HttpURLConnection urlConnection = (HttpURLConnection) url
.openConnection();
Log.d(LOGid, "Created HTTP Connection");
// set up some things on the connection
urlConnection.setRequestMethod("GET");
Log.d(LOGid, "Set the request method");
urlConnection.setDoOutput(true);
Log.d(LOGid, "Set doOutput");
// and connect!
urlConnection.connect();
Log.d(LOGid, "Connected the url connection");
// set the path where we want to save the file
// in this case, going to save it on the root directory of
// the
// sd card.
File SDCardRoot = Environment.getExternalStorageDirectory();
// create a new file, specifying the path, and the filename
// which we want to save the file as.
File file = new File(m_Folder, m_dest);
Log.d(LOGid, "Created dest file, path=" + file.getPath()
+ "...file=" + file.getName());
// this will be used to write the downloaded data into the
// file we created
FileOutputStream fileOutput = new FileOutputStream(file);
Log.d(LOGid, "Created file output stream");
// this will be used in reading the data from the internet
InputStream inputStream = urlConnection.getInputStream();
Log.d(LOGid, "Created Input stream");
// this is the total size of the file
mTotalSize = urlConnection.getContentLength();
Log.d(LOGid, "Got total size " + mTotalSize);
// variable to store total downloaded bytes
int downloadedSize = 0;
// create a buffer...
byte[] buffer = new byte[1024];
Log.d(LOGid, "Created buffer");
int bufferLength = 0; // used to store a temporary size of
// the buffer
mProgressCurrentFileStatus = 0;
// m_pb.setProgress(0);
Log.d(LOGid, "Set progress to zero");
// Update the progress bar
mHandler.post(new Runnable()
{
public void run()
{
Log.d(LOGid, "Set progress totalsize to " + mTotalSize);
m_pb.setMax(mTotalSize);
Log.d(LOGid, "Set File progress to " + mFileCount);
Log.d(LOGid, "Set progress to:" + mProgressCurrentFileStatus);
m_pb.setProgress(mProgressCurrentFileStatus);
}
});
// now, read through the input buffer and write the contents
// to the file
while ((bufferLength = inputStream.read(buffer)) > 0)
{
// add the data in the buffer to the file in the file
// output stream (the file on the sd card
fileOutput.write(buffer, 0, bufferLength);
Log.d(LOGid, "Wrote infor to the buffer");
// add up the size so we know how much is downloaded
downloadedSize += bufferLength;
// this is where you would do something to report the
// prgress, like this maybe
// updateProgress(downloadedSize, totalSize);
mProgressCurrentFileStatus = downloadedSize;
// Update the progress bar
mHandler.post(new Runnable()
{
public void run()
{
Log.d(LOGid, "Set progress size:" + mTotalSize);
m_pb.setMax(mTotalSize);
Log.d(LOGid, "Set progress to:" + mProgressCurrentFileStatus);
m_pb.setProgress(mProgressCurrentFileStatus);
}
});
Log.d(LOGid, "Set progress to " + downloadedSize);
}
// close the output stream when done
fileOutput.close();
Log.d(LOGid, "closed the output file");
mHandler.post(new Runnable()
{
public void run()
{
tvCurrent.setText("Finished Downloading file " + m_dest + "...");
}
});
m_downloadSuccess = true;
// catch some possible errors...
} catch (MalformedURLException e)
{
Log.d(LOGid, "Error dowloading file:" + e.getMessage()
+ "...malformed URL");
m_downloadSuccess = false;
} catch (IOException e)
{
Log.d(LOGid, "Error dowloading file:" + e.getMessage()
+ "...IO Exception");
m_downloadSuccess = false;
} catch (Exception e)
{
Log.d(LOGid, "Error dowloading file:" + e.getMessage()
+ "...generic exception");
m_downloadSuccess = false;
}
}
}).start();
return m_downloadSuccess;
}
It looks like you're doing the work that you're measuring progress on within your UI thread (called directly from a callback, not positioned within a new thread). That doesn't give the system any CPU time to respond to your interim progress updates.
The proper way to handle this is to do your work in a background thread, and periodically update the progress. Those updates must happen from within the UI thread, but your Activity's runOnUiThread(Runnable) method can accomplish that for you. (Alternatively you could post the runnable to your Activity's handler for UI-thread execution.)
Edit: added example for thread creation Your work is done in your downloadFiles() method, so this is what you want in a separate thread. Replace your call of downloadFiles() with, for example,
Now you also need to modify downloadFiles() -- since it's not on the UI thread, it's not permitted to modify the UI. Instead of calling
pb.setMax(totalSize);
, you'll need to get that performed within the UI thread. You can write a runnable (or several of them) to modify the UI from the proper thread in a similar manner to running a new thread:The only thing you need to be careful of here -- your runnable's run() method will probably need access to some of the local variables in the method containing the runOnUiThread() call. To legally provide that access, the variables it uses must be
final
. If you have variables which cannot be final, you need to copy their value to a final variable that the runnable uses. For example: