【问题标题】:AsyncTask with ViewHolder pattern doesn't work具有 ViewHolder 模式的 AsyncTask 不起作用
【发布时间】:2015-03-14 15:15:02
【问题描述】:

我正在尝试使用 AsyncTask 将我的代码优化到我的适配器中,以执行 Internet 请求以读取 JSON 并显示其数据。 我在使用 AsyncTask 更改之前的代码运行良好,所以我现在做错了,因为它在请求过期后没有设置文本。我希望在滚动列表视图时,AsyncTask 运行互联网连接的代码,完成后,它将文本从默认更改为文本视图。

适配器类

public class ListBookmarksAdapter extends ArrayAdapter<BookmarksHandler>{
    private final Context context;
    private List<BookmarksHandler> list;
    DatabaseHandler dh;
    SQLiteDatabase db;
    ViewHolder viewHolder;
    Gson gson;

    public ListBookmarksAdapter(Context context, List<BookmarksHandler> list) {
        super(context, R.layout.listbookmarks, list);

        this.context = context;
        this.list = list;
    }

    static class ViewHolder{
        TextView tvTitle;
        TextView tvChapter;
        ImageView immagine;
    }

    @Override
    public View getView(final int position, View rowView, ViewGroup parent) {
        gson = new Gson();
        dh = new DatabaseHandler(context);
        db = dh.getWritableDatabase();

        if(rowView==null){
            LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            rowView = inflater.inflate(R.layout.listbookmarks, parent, false);

            viewHolder = new ViewHolder();

            viewHolder.tvTitle = (TextView) rowView.findViewById(R.id.tvTitle);
            viewHolder.tvChapter = (TextView) rowView.findViewById(R.id.tvChapter);
            viewHolder.immagine = (ImageView) rowView.findViewById(R.id.imageView);

            rowView.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder) rowView.getTag();
        }

        new AsyncList(position).execute("http://www.myurl.com/" +list.get(position).getId_manga() + "/");

        return rowView;
    }


    private class AsyncList extends AsyncTask<String, Void, String> {
        int position;
        public AsyncList(int position){
            this.position = position;
        }

        @Override
        protected String doInBackground(String... params) {
            String urlManga = null;
            try {
                urlManga = MainActivity.connessione(params[0]);
            } catch (Exception e) {
                e.printStackTrace();
            }
            assert urlManga != null;
            return urlManga.trim();
        }

        @Override
        protected void onPostExecute(String result) {
            super.onPostExecute(result);

            MangaSpec manga = gson.fromJson(result, MangaSpec.class);

            viewHolder.tvTitle.setText(manga.getTitle());
            viewHolder.tvTitle.setSelected(true);

            if(Integer.parseInt(String.valueOf(manga.getStatus()))==2)
                viewHolder.immagine.setImageResource(R.drawable.book_close);

            List generic = manga.getChapters();

            for(int i=0; i<generic.size(); i++){
                List chapters = (List) generic.get(i);
                if(((String) chapters.get(3)).equals(list.get(position).getId_chapter())){
                    double numero = (Double) chapters.get(0);
                    String titoloC = (String) chapters.get(2);
                    if((numero-(int)numero)!=0)
                        viewHolder.tvChapter.setText(numero+" - "+titoloC);
                    else
                        viewHolder.tvChapter.setText((int)numero+" - "+titoloC);

                    viewHolder.tvChapter.setSelected(true);
                }
            }
        }
    }
}

这里是 MainActivity.connessione 方法:

public static String connessione(String url) throws Exception {
    URL website = new URL(url);
    URLConnection connection = website.openConnection();
    BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));

    StringBuilder response = new StringBuilder();
    String inputLine;

    while ((inputLine = in.readLine()) != null)
        response.append(inputLine);

    in.close();

    return response.toString();
}

这是我的意思的屏幕:

“Shingeki no Kyoijin”和“63 - Catene”是第一排的项目(按照箭头)。列表中的其他两项具有不同的值。如您所见,前两项的文本设置为默认值(文本),最后一项设置错误。

【问题讨论】:

  • 是的,但是具体出了什么问题
  • 当请求过期并创建适配器时,必须在 AsyncTask postExecute 中设置的文本未设置!
  • 为了让 323go 看到你的评论,你必须在评论中用@323go 标记他,就像我在这里标记你一样
  • 这里有几个问题:不要在getview中调用new Gsonnew DatabaseHandler。这称为每次您创建一个新的列表视图项(向上和向下滚动)。
  • @JaredBurrows 谢谢,现在优化

标签: android asynchronous android-asynctask adapter


【解决方案1】:

ViewHolder 特定于项目而不是整个适配器,因此将 ViewHolder 变量声明移动到 getItem 方法中。现在您看到了问题:AsyncTask 没有看到任何 ViewHolders。

让异步任务构造函数采用ViewHolder 参数,而不是int position(您不使用)。此外,将您的自定义异步任务声明为static,如下所示:

private static class AsyncList extends AsyncTask<String, Void, String> {

通过这样做,您可以确保您无法从异步任务内部访问您不需要/不应访问的任何内容。

建议:将JSON解析移至后台(并将异步任务结果类型更改为MangaSpec)。

【讨论】:

  • 当在 AsyncTask 中使用 ViewHolder 时,ViewHolder 将需要对该 AsyncTask 的引用,因此当视图被回收时,可以取消正在进行的下载。即使你这样做了,使用 VIewHolder 意味着当用户滚动时,相同的数据将被一遍又一遍地下载。当然,您可以有一个标志来不开始下载,但是您需要将该标志存储在适配器列表中的某个位置......将数据下载到适配器并从中更新 ListView 更加简单。
  • 我在 if(((String) chapters.get(3)).equals(list.get(position).getId_chapter())) 行使用“位置”变量,所以我可以不要删除它。我也将json解析移到了后台,正如你现在所说的,如果它正确加载章节,但对于第一个项目,它会遍历所有章节,直到他找到正确设置的章节。不容易解释..
  • 我终于修好了,现在我觉得比以前好多了,谢谢!
  • @Fondesa 很高兴我能帮上忙。如果你没有,请阅读 krislarson 的评论。您需要实现这一点,以避免在例如在列表中快速滚动。
【解决方案2】:

您最大的问题是您声明了一个 ViewHolder 来处理适配器中的所有视图。

所以问题是这样发生的:

  1. ListView 在您的适配器中调用 getView() 以获得第一项。由于没有要回收的视图,因此您膨胀视图、创建 ViewHolder 并开始下载。 ListView 使用膨胀布局中的值绘制第一项。

  2. ListView 再调用两次getView() 以在下载仍在进行时绘制其他项目。由于整个适配器只有一个viewHolder,因此每次您说viewHolder = new ViewHolder(),您都替换了前一项创建的ViewHolder

  3. 第一个项目的下载完成。但是由于您覆盖了 ViewHolder,因此 first 位置的下载数据被写入到 第三 位置的 ViewHolder 的视图中。其他下载完成后,它们也会被写入同一个 ViewHolder。

我想如果你上下滚动这个列表,当你滚动你的列表时,你会看到项目非常随机地变化。

现在,即使您确实将代码修复为每个视图都有一个单独的 ViewHolder,您仍然会遇到问题,因为当视图在下载开始和下载结束。

此外,您设置了一种情况,即当用户上下滚动时,将一遍又一遍地下载相同的服务器数据,这样效率不高。

我认为你应该这样做:

如果您的列表项具有标题、章节和图像,那么您应该创建一个模型类,该模型类具有标题的 String 属性、章节的 String 属性和图像名称的 String 属性,或者图片网址。我们称这个类为Bookmark

因为 Android UI 使用模型/视图/控制器模式,这意味着适配器列表中的任何内容都应该是显示的实际数据。看起来您传递的书签 ID 数组不是列表项中显示的内容。

创建一个ArrayList&lt;Bookmark&gt;。然后为列表中的每个 BookmarksHandler 创建一个空白 Bookmark 对象,将其添加到列表中,然后将此列表交给 ArrayAdapter。这意味着您有一个空白项目列表可供开始。

您的getView() 方法将简单地提取书签列表位置n 中的任何数据来创建项目位置n 的视图。如果您在 getView() 中使用 ViewHolder,请确保创建一个不与其他项目共享的新 ViewHolder。

开始您的数据下载。看起来您的 REST 界面一次只能获取一个书签。如果有一种方法可以更改它,以便您可以在列表中发布所有书签 ID,并获取一个包含所有书签数据的大型 JSON 数组,那将更有效率。

当您的数据下载完成时,您将遍历您的 JSON 数据并更新适配器的 ArrayList 中的每个空白书签项,然后在适配器上调用 notifyDataSetChanged()。这告诉 ListView 其支持数据已更改,是时候更新屏幕上的 ListView 了。

这里要知道的最重要的事情是下载的数据应该进入适配器数据,以便您的getView() 可以从中创建一个列表项。从下载中直接进入视图仅适用于实际图像。

图像下载不是一项简单的任务。为此,我将您推荐给this excellent article on the Android Developer's blog,它比我能解释得更好。

我希望这会有所帮助;我知道要消化的东西很多。有关详细信息,请在线查找有关如何使用下载的数据更新 ListViews 的更多教程。

【讨论】:

  • 如果用户打开应用程序并单击商店,我有最初存储喜欢计数的项目数组列表;就像在获取 web 服务数据后,更新后的喜欢计数设置为该文本视图如何解决这个问题帮帮我
猜你喜欢
  • 2015-12-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多