Reputation: 4472
I am working on a Download Manager project, so, to show all downloaded / downloading actions, i prefer to use ListView to show my download list. Suppose that, we have as many as downloading task, so, the progress bars of all tasks must be updated. For background download task, i created a new class that i named it HttpDownloader
. So, i pass these progress bars on objects of this class. When a new object is added to my tasklist, so, i call the constructor of HttpDownloader
and pass the new item progress bar to it. The thing confused me is When i add a new object to tasklist and call notifyDataSetChanged of adapter, my list is refreshed, so all progress bar reset to default layout values but HTTPDownloader Thread is running in background successfully.
So, it is my question that,
1. After calling notifyDataSetChanged, references to old listview's objects are destructs ?
2. If yes, How can i keep old view's reference ?
3. If no, please explain me, why progress bars reset to default and do not change when background process force to passed progressbar to change the value of progress ?
HTTPDownloader class
class HttpDownloader implements Runnable {
public class HttpDownloader (String url, ProgressBar progressBar)
{
this.M_url = url;
this.M_progressBar = progressBar;
}
@Override
public void run()
{
DefaultHttpClient client = new DefaultHttpClient();
HttpGet get = new HttpGet(this.M_url);
HttpResponse response;
try{
response = client.execute(get);
InputStream is = response.getEntity().getContent();
long contentLength = response().getEntity().getContentLength();
long downloadedLen = 0;
int readBytes = 0;
byte [] buffer = new byte [1024];
while ((readBytes = in.read(buffer, 0, buffer.length)) != -1) {
downloadedLen += readBytes;
//Some storing to file codes
runOnUiThread(new Runnable() {
@Override
public void run() {
M_progressBar.setProgress((100f * downloadedLen) / contentLength);
}
});
}
is.close();
} catch (ClientProtocolException e) {
Log.e("HttpDownloader", "Error while getting response");
} catch (IOException e) {
Log.e("HttpDownloader", "Error while reading stream");
}
}
}
AdapterClass
class MyAdapter extends ArrayAdapter<String> {
ArrayList<String> M_list;
public MyAdapter(ArrayList<String> list) {
super(MainActivity.this, R.layout.download_item, list);
this.M_list = list;
}
@Override
public int getCount() {
return this.M_list.size();
}
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
View rowView = inflater.inflate(R.layout.download_item, parent, false);
ProgressBar bar = (ProgressBar) rowView.findViewById(R.id.progrees);
new Thread (new HttpDownloader(this.M_list.get(position), bar)).start();
return rowView;
}
}
Upvotes: 1
Views: 827
Reputation: 626
Well if you want to keep the references to show a good download manager, lets see how do it, go by parts.
The concept is easy to understand, we have threads downloading items, we are going to call each of this piece of work tasks. Each task is associated with a row in the list view, because you want to show a download progress of each item download.
So if each task is associated with each row, we are going to save tasks actually are in execution, in this way, we know in all moment what tasks are running and their state. This is importante because, like I have said before, getView() method is called many times, and we need to know each download in progress and its state.
Lets see some code of our Adapter:
class MyAdapter extends ArrayAdapter<Uri> {
ArrayList<Uri> M_list;
Map<Uri, HttpDownloader> taskMap;
public MyAdapter(ArrayList<Uri> list) {
super(MainActivity.this, R.layout.download_item, list);
this.M_list = list;
taskMap = new HashMap<Uri, HttpDownloader>();
}
@Override
public int getCount() {
return this.M_list.size();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View rowView = inflater.inflate(R.layout.download_item, parent, false);
ProgressBar progressBar = (ProgressBar) rowView.findViewById(R.id.progressBar);
configureRowWithTask(position, progressBar);
return rowView;
}
private void configureRowWithTask(int position,final ProgressBar bar) {
Uri url = M_list.get(position);
HttpDownloader task;
if (!(taskMapHasTaskWithUrl(url))) {
task = new HttpDownloader(url, bar);
Thread thread = new Thread(task);
thread.start();
taskMap.put(url, task);
} else {
task = taskMap.get(url);
bar.setProgress(task.getProgress());
task.setProgressBar(bar);
}
}
private boolean taskMapHasTaskWithUrl(Uri url) {
if (url != null) {
Iterator taskMapIterator = taskMap.entrySet().iterator();
while(taskMapIterator.hasNext()) {
Map.Entry<Uri, HttpDownloader> entry = (Map.Entry<Uri, HttpDownloader>) taskMapIterator.next();
if (entry.getKey().toString().equalsIgnoreCase(url.toString())) {
return true;
}
}
}
return false;
}
}
Some explanations about it:
configureRowWithTask() method checks if one task is in use, this method is needed because getView() is called many times, but we dont want the same downloading task be executed more than once, so this method checks that, if the task is new (not present in the taskMap) then creates a new instance of HttpDownloader and put the new task in the map.
If the task requested is present in the taskMap, it means that the task has been requested previously but the row in the list has gone out and there have been a new call to getView() method. So the task is present, we dont have to create it again, probably the task is downloading the item and the only thing we have to do is see its download progress and set it to the row's progress bar. And finally sets the reference to the progress bar, probably the HttpDownloader has lost this reference.
With this part clear, we go with the second part, the HttpDownloader class, lets see some code:
public class HttpDownloader implements Runnable {
private Uri url;
private ProgressBar progressBar;
private int progress = 0;
private Handler handler;
public HttpDownloader (Uri url, ProgressBar progressBar) {
this.url = url;
this.progressBar = progressBar;
progressBar.setProgress(progress);
handler = new Handler();
}
@Override
public void run() {
DefaultHttpClient client = new DefaultHttpClient();
HttpGet get = new HttpGet(url.toString());
HttpResponse response;
try {
response = client.execute(get);
InputStream is = response.getEntity().getContent();
long contentLength = response.getEntity().getContentLength();
int downloadedLen = 0;
int readBytes = 0;
byte [] buffer = new byte [1024];
while ((readBytes = is.read(buffer, 0, buffer.length)) != -1) {
downloadedLen += readBytes;
//Some storing to file codes
progress = (int) ((100f * downloadedLen) / contentLength);
handler.post(new Runnable() {
@Override
public void run() {
if (progressBar != null) {
progressBar.setProgress(progress);
}
}
});
}
} catch (ClientProtocolException e) {
Log.e("HttpDownloader", "Error while getting response");
} catch (IOException e) {
e.printStackTrace();
Log.e("HttpDownloader", "Error while reading stream");
}
}
public int getProgress() {
return progress;
}
public void setProgressBar(ProgressBar progressBar) {
this.progressBar = progressBar;
}
}
This class is basically the same, the diference is that I use a Handler to change the progress of the progress bar in the UI thread, and change this progress only if the reference to the progress bar is not null.
Important thing is every time bytes are downloaded, value of download progress is refreshed.
I have checked the code in a test app and seems that all runs ok, if you have some problems, let me know and I will try to help you ; )
Hope you understand it.
Upvotes: 1
Reputation: 626
when you call notifyDataSetChanged() or Activity resumes, getView() method of the Adapter is called by each row of the list.
So, when you add new item to the list and call notifyDataSetChanged(), getView() method is executed as many times as items are in the list in that moment, plus one more cause the new item you have added.
Like you know, getView() method builds the row, so builds a new ProgressBar, and obvioulsy this ProgressBar begins at position 0 of progress.
So the answer could be resumed as yes, each time you call notifyDataSetChanged(), getView() method is called many times, one for each row (if you set android:layout_height param to 0dip and android:layout_weight to 1.0, like you probably know for ListViews), thats the reason why you get ProgressBars "initialized" when you add new item to the ListView.
Upvotes: 1