用于图像获取的AsyncTask不断被调用
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了用于图像获取的AsyncTask不断被调用相关的知识,希望对你有一定的参考价值。
我有一个小应用程序来保存学生名单。
添加新学生记录时,从我自己的10个地址池中选择一个随机图像URL(字符串),使用AsyncTask
在图像视图中提取和设置。
我使用自定义CursorAdapter
作为列表,并使用自定义SQLiteOpenHelper
DB来处理数据库(包含id
,name
,grade
,image url str
)。
我正在使用AsyncTask
来从互联网上获取图像
我的问题是我的AsyncTask不断被一遍又一遍地调用,每次点击屏幕时,都会获取之前已经获取的相同图像。
我想我正在使用我的AsyncTask错误(通过bindView
),但不确定。
我的目标是只为每一行获取一次图像
主要内容:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
/* Fields for adding new student to the list */
private EditText mEtName;
private EditText mEtGrade;
private ListView mLvStudents;
/* Our DB model to store student objects */
private SqlDbHelper mDB;
/* Custom SQL-Adapter to connect our SQL DB to the ListView */
private SQLAdapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/* Init fields & needed views */
mEtName = findViewById(R.id.et_name);
mEtGrade = findViewById(R.id.et_grade);
mLvStudents = findViewById(R.id.lv_students);
mDB = new SqlDbHelper(getApplicationContext());
mAdapter = new SQLAdapter(this, mDB.getAllRows(), false);
/* Set click listeners and adapter to our list */
mLvStudents.setAdapter(mAdapter);
findViewById(R.id.button_add).setOnClickListener(this);
}
@Override
public void onClick(View view) {
final String name = mEtName.getText().toString();
final int gradeInt = AidUtils.getGradeInt(mEtGrade.getText().toString());
mDB.addStudent(name, gradeInt, AidUtils.randImageUrl());
mAdapter.changeCursor(mDB.getAllRows());
mEtName.setText("");
mEtGrade.setText("");
}
}
SQLAdapter:
final class SQLAdapter extends CursorAdapter {
private LayoutInflater mInflater;
public SQLAdapter(Activity context, Cursor c, boolean autoRequery) {
super(context, c, autoRequery);
mInflater = LayoutInflater.from(context);
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup viewGroup) {
return mInflater.inflate(R.layout.lv_line, viewGroup, false);
}
@Override
public void bindView(final View view, Context context, Cursor cursor) {
/* Set name */
((TextView)view.findViewById(R.id.tv_name)).setText(
cursor.getString(cursor.getColumnIndex(SqlDbHelper.KEY_NAME)));
/* Set the image URL for it and fetch the image */
final String imageUrlStr = cursor.getString(cursor.getColumnIndex(SqlDbHelper.KEY_IMG));
((TextView)view.findViewById(R.id.tv_image_url)).setText(imageUrlStr);
new AsyncImageSet(imageUrlStr, (ImageView)view.findViewById(R.id.iv_pic)).execute();
/* Set grade and color for it */
final int grade = cursor.getInt(cursor.getColumnIndex(SqlDbHelper.KEY_GRADE));
((TextView)view.findViewById(R.id.tv_grade)).setText(String.valueOf(grade));
}
}
SqlDbHelper:
final class SqlDbHelper extends SQLiteOpenHelper {
private static final String TAG = "SqlDbHelper";
/* Database version */
public static final int VERSION = 1;
/* Relevant string names, keys represent columns */
public static final String DB_NAME = "StudentsDB";
public static final String TABLE_NAME = "students";
public static final String KEY_ID = "_id";
public static final String KEY_NAME = "Name";
public static final String KEY_GRADE = "Grade";
public static final String KEY_IMG = "Image";
public SqlDbHelper(Context context) {
super(context, DB_NAME, null, VERSION);
}
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
StringBuilder createQuery = new StringBuilder();
createQuery.append("CREATE TABLE " + TABLE_NAME + " (")
.append(KEY_ID + " INTEGER PRIMARY KEY AUTOINCREMENT,")
.append(KEY_NAME + " TEXT,")
.append(KEY_GRADE + " INT,")
.append(KEY_IMG + " TEXT")
.append(")");
Log.d(TAG, "Create table query: " + createQuery.toString());
sqLiteDatabase.execSQL(createQuery.toString());
}
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {}
public void addStudent(final String name, final int grade, final String imageUrl) {
ContentValues cv = new ContentValues();
cv.put(KEY_NAME, name);
cv.put(KEY_GRADE, grade);
cv.put(KEY_IMG, imageUrl);
getWritableDatabase().insert(TABLE_NAME, null, cv);
}
public Cursor getAllRows() {
return (getReadableDatabase().
rawQuery("SELECT * FROM " + TABLE_NAME, null));
}
}
AsyncImageSet:
public class AsyncImageSet extends AsyncTask<Void, Void, Bitmap> {
private String mImageUrl;
private ImageView mImageView;
public AsyncImageSet(String imageUrl, ImageView imageView) {
mImageUrl = imageUrl;
mImageView = imageView;
}
@Override
protected Bitmap doInBackground(Void... voids) {
Log.v("AsyncImageSet", "New Async Task launched!");
Bitmap image = null;
try {
image = AidUtils.getBitmapFromUrl(AidUtils.buildUrl(mImageUrl));
} catch (IOException e) {
e.printStackTrace();
} finally {
return image;
}
}
@Override
protected void onPostExecute(Bitmap image) {
if(image != null) {
mImageView.setImageBitmap(image);
}
}
}
我在这做错了什么?
谢谢
我在这做错了什么?
您不能简单地创建一个新的AsyncTask并在bindView()中执行它。每次ListView的新行进入屏幕时都会调用该方法(并且在其他情况下也可以调用),因此当用户上下滚动列表时,您将创建大量的AsyncTask实例。
处理此问题的正确方法是执行AsyncTask以仅在没有为您想要获取的该图像运行的AsyncTask运行时获取图像。处理此问题的最简单方法是在适配器中使用Map将String(imageUrl)映射到AsyncTask实例(它将获取该imageUrl指向的图像):
final class SQLAdapter extends CursorAdapter {
private LayoutInflater mInflater;
private Map<String, AsyncImageSet> mappings = new HashMap<>();
//...
然后,在你的bindView()方法中我们上面的地图:
//...
final String imageUrlStr = cursor.getString(cursor.getColumnIndex(SqlDbHelper.KEY_IMG));
// at this point look in our map to see if we didn't already create an AsyncTask for this imageUrl
if (mappings.get(imageUrl) != null) {
// there's a task for this imageUrl already created so we use that
AsyncImageSet task = mappings.get(imageUrl);
task.updateView((ImageView)view.findViewById(R.id.iv_pic));
} else {
// there isn't a task for this imageUrl so create one and execute it(and save it in our mappings)
AsyncImageSet task = AsyncImageSet(imageUrlStr, (ImageView)view.findViewById(R.id.iv_pic));
mappings.put(imageUrl, task);
task.execute();
}
((TextView)view.findViewById(R.id.tv_image_url)).setText(imageUrlStr);
//...
您还需要更改AsyncTask以添加额外的方法:
public class AsyncImageSet extends AsyncTask<Void, Void, Bitmap> {
//...
private Bitmap bitmap;
public void updateView(ImageView imageView) {
mImageView = imageView;
// if the task is already finished it means the bitmap is
// already available
if (getStatus() == AsyncTask.Status.FINISHED) {
mImageView.setImageBitmap(bitmpa);
}
}
@Override
protected void onPostExecute(Bitmap image) {
if(image != null) {
bitmap = image;
mImageView.setImageBitmap(image);
}
}
这是一个非常简单的实现,它将位图保留在内存中,如果图像很大,这可能不起作用。
理想情况下,正如其他答案所提到的那样,您应该使用像Picasso这样的图像加载库,它可以帮助您避免实现自己的缓存系统的许多陷阱。
而不是从URL获取位图使用Picasso将相同的URL加载到图像视图中。
代替
new AsyncImageSet(imageUrlStr, (ImageView)view.findViewById(R.id.iv_pic)).execute();
使用Picasso库加载图像,如:
Picasso.with(context).load(imageUrlStr).into(imageView);
对于毕加索设置,请参阅here
以上是关于用于图像获取的AsyncTask不断被调用的主要内容,如果未能解决你的问题,请参考以下文章
如何从选项卡片段中的 AsyncTask Resftful WS 加载批量数据
从Asynctask ONPostExecute调用片段方法