1

我正在开发一个下载管理器项目,因此,为了显示所有下载/下载操作,我更喜欢使用 ListView 来显示我的下载列表。假设我们有和下载任务一样多,那么所有任务的进度条都必须更新。对于后台下载任务,我创建了一个我命名的新类HttpDownloader。所以,我在这个类的对象上传递这些进度条。当一个新对象添加到我的任务列表时,我调用构造函数HttpDownloader并将新项目进度条传递给它。让我感到困惑的是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.所以,我的问题是,

1.调用notifyDataSetChanged后,对旧listview对象的引用是destructs ?

2.如果是,我如何保留旧视图的参考?

3.如果不是,请解释一下,为什么当后台进程强制通过进度条更改进度值时进度条重置为默认值并且不改变?

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

2 回答 2

1

当您调用 notifyDataSetChanged() 或 Activity 恢复时,列表的每一行都会调用 Adapter 的 getView() 方法。

因此,当您将新项目添加到列表并调用 notifyDataSetChanged() 时,getView() 方法会执行与该时刻列表中的项目一样多的次数,再加上一个原因是您添加的新项目。

如您所知,getView() 方法构建行,因此构建一个新的 ProgressBar,并且显然这个 ProgressBar 从进度的位置 0 开始。

所以答案可以恢复为是,每次调用 notifyDataSetChanged() 时,getView() 方法都会被调用多次,每行一个(如果你将 android:layout_height 参数设置为 0dip 并将 android:layout_weight 设置为 1.0,就像你可能知道 ListViews),这就是为什么当您将新项目添加到 ListView 时会“初始化”ProgressBars 的原因。

于 2012-09-05T07:07:34.187 回答
1

好吧,如果您想保留参考以显示一个好的下载管理器,让我们看看如何做,按部分进行。

这个概念很容易理解,我们有线程下载项目,我们将调用每个工作任务。每个任务都与列表视图中的一行相关联,因为您希望显示每个项目下载的下载进度。

因此,如果每个任务都与每一行相关联,我们将保存实际正在执行的任务,这样我们就可以随时知道哪些任务正在运行以及它们的状态。这很重要,因为就像我之前所说的,getView() 方法被调用了很多次,我们需要知道每次下载正在进行及其状态。

让我们看看我们的适配器的一些代码:

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

关于它的一些解释:

  • 我已将您的适配器类型更改为 Uris 的 ArrayAdapter,以便更好地使用链接。
  • 我添加了一个具有键值对的 Map,键是 Uris,值是 HttpDownloaders。此地图将保存正在使用的每个任务。
  • 我添加了两个重要的方法,一个是 configureRowWithTask(),就像它自己的名字一样,配置一行与一个任务,将它们关联起来,但前提是任务是新的,否则是正在使用的任务。另一种方法是 taskMapHasTaskWithUrl(),简单地检查给定的任务(通过它的 url)当前是否在 taskMap 中。
  • 可能,最重要的方法是configureRowWithTask(),我将深入解释它。

configureRowWithTask() 方法检查一个任务是否正在使用,这个方法是必要的,因为 getView() 被调用了很多次,但是我们不希望同一个下载任务被执行多次,所以这个方法检查,如果任务是新的(不在 taskMap 中)然后创建 HttpDownloader 的新实例并将新任务放入地图中。

如果请求的任务存在于 taskMap 中,则表示该任务之前已被请求,但列表中的行已消失,并且已对 getView() 方法进行了新调用。所以任务是存在的,我们不必再次创建它,可能任务正在下载项目,我们唯一要做的就是查看它的下载进度并将其设置到行的进度条。最后设置对进度条的引用,可能 HttpDownloader 丢失了这个引用。

清楚了这部分,我们继续第二部分,HttpDownloader 类,让我们看一些代码:

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

这个类基本相同,不同的是我在UI线程中使用了一个Handler来改变进度条的进度,并且只有在对进度条的引用不为null的情况下才改变这个进度。

重要的是每次下载字节时,都会刷新下载进度的值。

我已经检查了测试应用程序中的代码,似乎一切运行正常,如果您有问题,请告诉我,我会尽力帮助您;)

希望你能理解。

于 2012-09-05T21:44:48.540 回答