用于图像获取的AsyncTask不断被调用

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了用于图像获取的AsyncTask不断被调用相关的知识,希望对你有一定的参考价值。

我有一个小应用程序来保存学生名单。

添加新学生记录时,从我自己的10个地址池中选择一个随机图像URL(字符串),使用AsyncTask在图像视图中提取和设置。

我使用自定义CursorAdapter作为列表,并使用自定义SQLiteOpenHelper DB来处理数据库(包含idnamegradeimage 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调用片段方法

使用 AsyncTask 从 JSON 中获取数据

Asynctask结果显示重新创建片段后

如何在将 Tablayout 与 viewpager 一起使用时从另一个片段调用 AsyncTask?

在 Asynctask 的 onPostExecute() 方法中更改片段