具有 ViewHolder 模式的 AsyncTask 不起作用
AsyncTask with ViewHolder pattern doesn't work
我正在尝试使用 AsyncTask 将我的代码优化到我的适配器中,以执行互联网请求以读取 JSON 并显示其数据。
我在使用 AsyncTask 进行更改之前的代码运行良好,所以我现在做错了,因为它没有在请求过期后设置文本。我希望当我滚动列表视图时,AsyncTask 运行 互联网连接的代码,完成后,它会将文本从默认更改为文本视图。
适配器class
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”是第一行的项目(跟随箭头)。列表中的其他两项具有不同的值。如您所见,前两项的文本设置为默认文本 (Text),而最后一项的文本设置错误。
ViewHolder 特定于一个项目而不是整个适配器,因此将 ViewHolder 变量声明移到 getItem
方法中。现在您看到了问题:AsyncTask 没有看到任何 ViewHolder。
而不是 int position
(您不使用)让异步任务构造函数采用 ViewHolder
参数。此外,将您的自定义异步任务声明为 static
,如下所示:
private static class AsyncList extends AsyncTask<String, Void, String> {
通过这样做,您可以确保您无法从异步任务内部访问您不需要/不应访问的任何内容。
建议:将 JSON 解析移至后台(并将异步任务结果类型更改为 MangaSpec
)。
您最大的问题是您声明了一个 ViewHolder 来处理适配器中的所有视图。
问题是这样发生的:
ListView 在您的适配器中为第一项调用 getView()
。由于没有要回收的视图,您可以扩充视图、创建 ViewHolder 并开始下载。 ListView 使用膨胀布局中的值绘制第一个项目。
ListView 在下载仍在进行时调用 getView()
两次以绘制其他项目。由于整个适配器只有一个 viewHolder
,所以每次你说 viewHolder = new ViewHolder()
时,你都在 替换由前一项创建的 ViewHolder。
第一项下载完成。但是由于你覆盖了ViewHolder,first位置的下载数据被写入到third位置的ViewHolder的视图中。一旦其他下载完成,它们也会被写入同一个 ViewHolder。
我想如果你上下滚动这个列表,你会看到项目在你滚动列表时随机变化。
现在,即使您将代码修改为每个视图都有一个单独的 ViewHolder,您仍然会遇到问题,因为在下载之间回收视图时,视图回收会导致下载的数据绘制在错误的位置开始下载结束。
此外,您还设置了一种情况,当用户上下滚动时,将一遍又一遍地下载相同的服务器数据,这样效率不高。
这是我认为你应该做的:
如果您的列表项有标题、章节和图像,那么您应该创建一个模型 class,其中有一个字符串 属性 作为标题,一个字符串 属性 用于章节,字符串 属性 用于图像名称或图像 URL。我们称之为 class Bookmark
.
因为 Android UI 使用 Model/View/Controller 模式,这意味着适配器列表中的任何内容都应该是显示的实际数据。您传递的书签 ID 数组似乎不是列表项中显示的内容。
创建 ArrayList<Bookmark>
。然后为列表中的每个 BookmarksHandler 创建一个空白书签 object,将其添加到列表中,然后将此列表传递给 ArrayAdapter。这意味着您有一个空白项目列表可以开始。
您的 getView()
方法将简单地提取书签列表位置 n 中的任何数据,以创建项目位置 n[=67= 的视图].如果您在 getView()
中使用 ViewHolder,请确保创建一个不与其他项目共享的新 ViewHolder。
开始下载数据。看起来您的 REST 界面一次只能获取一个书签。如果有一种方法可以更改它,那么您可以 post 列表中的所有书签 ID 并获得一个大 JSON 数组返回所有书签数据,这样会更有效率。
数据下载完成后,您将遍历 JSON 数据并更新适配器 ArrayList 中的每个空白书签项,然后在适配器上调用 notifyDataSetChanged()
。这告诉 ListView 它的支持数据已经改变,是时候更新屏幕上的 ListView 了。
这里最重要的是下载的数据应该进入适配器数据,这样您的 getView()
就可以从中创建一个列表项。从下载直接进入视图仅适用于实际图像。
图片下载不是一件小事。为此,我建议您参考 this excellent article on the Android Developer's blog,它比我解释得更好。
希望对您有所帮助;我知道有很多东西需要消化。有关更多信息,请在线查找更多有关如何使用下载的数据更新 ListView 的教程。
我正在尝试使用 AsyncTask 将我的代码优化到我的适配器中,以执行互联网请求以读取 JSON 并显示其数据。 我在使用 AsyncTask 进行更改之前的代码运行良好,所以我现在做错了,因为它没有在请求过期后设置文本。我希望当我滚动列表视图时,AsyncTask 运行 互联网连接的代码,完成后,它会将文本从默认更改为文本视图。
适配器class
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”是第一行的项目(跟随箭头)。列表中的其他两项具有不同的值。如您所见,前两项的文本设置为默认文本 (Text),而最后一项的文本设置错误。
ViewHolder 特定于一个项目而不是整个适配器,因此将 ViewHolder 变量声明移到 getItem
方法中。现在您看到了问题:AsyncTask 没有看到任何 ViewHolder。
而不是 int position
(您不使用)让异步任务构造函数采用 ViewHolder
参数。此外,将您的自定义异步任务声明为 static
,如下所示:
private static class AsyncList extends AsyncTask<String, Void, String> {
通过这样做,您可以确保您无法从异步任务内部访问您不需要/不应访问的任何内容。
建议:将 JSON 解析移至后台(并将异步任务结果类型更改为 MangaSpec
)。
您最大的问题是您声明了一个 ViewHolder 来处理适配器中的所有视图。
问题是这样发生的:
ListView 在您的适配器中为第一项调用
getView()
。由于没有要回收的视图,您可以扩充视图、创建 ViewHolder 并开始下载。 ListView 使用膨胀布局中的值绘制第一个项目。ListView 在下载仍在进行时调用
getView()
两次以绘制其他项目。由于整个适配器只有一个viewHolder
,所以每次你说viewHolder = new ViewHolder()
时,你都在 替换由前一项创建的 ViewHolder。第一项下载完成。但是由于你覆盖了ViewHolder,first位置的下载数据被写入到third位置的ViewHolder的视图中。一旦其他下载完成,它们也会被写入同一个 ViewHolder。
我想如果你上下滚动这个列表,你会看到项目在你滚动列表时随机变化。
现在,即使您将代码修改为每个视图都有一个单独的 ViewHolder,您仍然会遇到问题,因为在下载之间回收视图时,视图回收会导致下载的数据绘制在错误的位置开始下载结束。
此外,您还设置了一种情况,当用户上下滚动时,将一遍又一遍地下载相同的服务器数据,这样效率不高。
这是我认为你应该做的:
如果您的列表项有标题、章节和图像,那么您应该创建一个模型 class,其中有一个字符串 属性 作为标题,一个字符串 属性 用于章节,字符串 属性 用于图像名称或图像 URL。我们称之为 class Bookmark
.
因为 Android UI 使用 Model/View/Controller 模式,这意味着适配器列表中的任何内容都应该是显示的实际数据。您传递的书签 ID 数组似乎不是列表项中显示的内容。
创建 ArrayList<Bookmark>
。然后为列表中的每个 BookmarksHandler 创建一个空白书签 object,将其添加到列表中,然后将此列表传递给 ArrayAdapter。这意味着您有一个空白项目列表可以开始。
您的 getView()
方法将简单地提取书签列表位置 n 中的任何数据,以创建项目位置 n[=67= 的视图].如果您在 getView()
中使用 ViewHolder,请确保创建一个不与其他项目共享的新 ViewHolder。
开始下载数据。看起来您的 REST 界面一次只能获取一个书签。如果有一种方法可以更改它,那么您可以 post 列表中的所有书签 ID 并获得一个大 JSON 数组返回所有书签数据,这样会更有效率。
数据下载完成后,您将遍历 JSON 数据并更新适配器 ArrayList 中的每个空白书签项,然后在适配器上调用 notifyDataSetChanged()
。这告诉 ListView 它的支持数据已经改变,是时候更新屏幕上的 ListView 了。
这里最重要的是下载的数据应该进入适配器数据,这样您的 getView()
就可以从中创建一个列表项。从下载直接进入视图仅适用于实际图像。
图片下载不是一件小事。为此,我建议您参考 this excellent article on the Android Developer's blog,它比我解释得更好。
希望对您有所帮助;我知道有很多东西需要消化。有关更多信息,请在线查找更多有关如何使用下载的数据更新 ListView 的教程。