图像加载过程中的多线程问题

时间:2013-02-23 21:48:11

标签: android multithreading android-asynctask

在我的应用中,有一个ListView使用自定义CursorAdapter将数据加载到其中,基本上是ImageViewTextView。我正在AsyncTask加载图像,现在的问题是,最初所有图像都分配给正确的文本,但是当我快速上下滚动时,它会为每个文本绑定随机图像。由于我使用的是CursorAdapter,我不能在这里使用ViewHolder,那么我该如何解决这个问题?

以下是我的示例代码:

public class MyAdapter extends CursorAdapter {

    private LayoutInflater mLayoutInflater;
    @SuppressWarnings("unused")
    private Context mContext;

    public MyAdapter(Context context, Cursor c, int flags) {
        super(context, c, flags);
        mContext = context;
        mLayoutInflater = LayoutInflater.from(context);
    }
    public void bindView(View view, Context context, Cursor cursor) {
        String title = cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.TITLE));
        String album_id = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM_ID));
        TextView text = (TextView)view.findViewById(R.id.txtTitle);
        text.setText(title);
        Uri sArtworkUri = Uri.parse("content://media/external/audio/albumart");
        Uri uri = ContentUris.withAppendedId(sArtworkUri, Integer.valueOf(album_id));
        new MyImageLoader(context,view).execute(uri);

    }
    @Override
    public View newView(Context context, Cursor cursor, ViewGroup parent) {
        return mLayoutInflater.inflate(R.layout.row, parent, false);
    }
    private class MyImageLoader extends AsyncTask<Uri, Void, Bitmap>{
        Context context;
        View view;


        MyImageLoader(Context context,View view){
            this.context = context;
            this.view = view;
        }
        @Override
        protected Bitmap doInBackground(Uri... uri) {
            ContentResolver res = context.getContentResolver();
            InputStream in = null;
            try {
                in = res.openInputStream(uri[0]);
            } 
            catch (FileNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            Bitmap artwork = BitmapFactory.decodeStream(in);
            return artwork;
        }
        protected void onPostExecute(Bitmap bmp){
            ImageView iv = (ImageView)view.findViewById(R.id.imgIcon);
            if(bmp!=null)
                //iv.setImageBitmap(bmp);
                iv.setImageBitmap(Bitmap.createScaledBitmap(bmp, 100, 100, false));
        }
    }
}

更新:我应用了setTag方法,现在洗牌次数较少,但是当我快速滚动时,旧图像会持续一秒钟,直到加载正确的图像。这是新代码:

public void bindView(View view, Context context, Cursor cursor) {
        String title = cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.TITLE));
        String album_id = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM_ID));
        ImageView iv = (ImageView)view.findViewById(R.id.imgIcon);
        TextView text = (TextView)view.findViewById(R.id.txtTitle);
        text.setText(title);
        Uri sArtworkUri = Uri.parse("content://media/external/audio/albumart");
        Uri uri = ContentUris.withAppendedId(sArtworkUri, Integer.valueOf(album_id));
// *****TAG SET*****
        iv.setTag(uri);
//***PASSING BOTH URI AND IMAGEVIEW TO CONSTRUCTOR***
        new MyImageLoader(context,view,iv,uri).execute(uri);

    }
    @Override
    public View newView(Context context, Cursor cursor, ViewGroup parent) {
        View v = mLayoutInflater.inflate(R.layout.row, parent, false);
        //v.setTag(R.id.imgIcon, v.findViewById(R.id.imgIcon));
        return v;
    }
    private class MyImageLoader extends AsyncTask<Object, Void, Bitmap>{
        Context context;
        View v;
        ImageView iv;
        Uri u;

        MyImageLoader(Context context,View v,ImageView iv,Uri u){
            this.context = context;
            this.v = v;
            this.iv = iv;   
            this.u = u;
        }
        @Override

        protected synchronized Bitmap doInBackground(Object... param) {
            ContentResolver res = context.getContentResolver();
            InputStream in = null;
            try {
                Uri uri= (Uri)param[0];
                in = res.openInputStream(uri);
            } 
            catch (FileNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            Bitmap artwork = BitmapFactory.decodeStream(in);

            return artwork;
        }
        protected void onPostExecute(Bitmap bmp){

            if(bmp!=null)
            {   ImageView iv = (ImageView)v.findViewById(R.id.imgIcon);
                if(iv.getTag().toString().equals(u.toString()))
                    iv.setImageBitmap(Bitmap.createScaledBitmap(bmp, 100, 100, false));
            }
        }
    }

更新:在调用后台任务之前设置占位符图片可以让它更好。

1 个答案:

答案 0 :(得分:2)

我使用的一个解决方案是通过UriImageView附加到setTag()。然后,当您转到更新ImageView时,请检查您要应用的图片的Uri是否与getTag()的值相匹配。如果是,请继续并更新ImageView。如果没有,则ImageView被回收,您可以跳过更新。

另一种方法是ImageView上的use setHasTransientState(true)(或者可能在行上 - 没有尝试过这个),当你决定你有一个缓存未命中并且需要启动{{1 }}。这将导致AsyncTask避免回收该行,直到进行匹配的AdapterView调用。在将动画应用到setHasTransientState(false)行并试图避免回收问题的情况下,Chet Haase有a DevBytes video on this。但是,ListView是API级别16的新功能,因此,如果您尝试支持Android 4.0或更早版本的设备,则这将不是一个可用的选项。