Android的runOnUiThread是否在Adapters中调用getView()?

时间:2011-09-03 09:29:25

标签: android android-widget gallery imageview android-3.0-honeycomb

我正在使用延迟加载Gallery个图片(一个类用于GalleryAdapter,另一个用于延迟加载部分。第二个类使用runOnUiThread使用其上下文更新第一个类,但似乎它再次调用第一个类'适配器的getView()方法。这意味着,对于图库中的每个图像,都会调用getView()两次。看看下面的代码。

奇怪的是,仅在getView()小部件中的所选图像上调用第二个Gallery调用。如果同时显示四个图像,则会在这四个图像上调用getView()一次,并在所选图像上再调用三次。

为什么会发生这种情况?

这是适配器

public class ImageAdapter extends BaseAdapter {

        public HorizontalImageLoader horImageLoader;

        public ImageAdapter() {
            horImageLoader = new HorizontalImageLoader(Main.this);
        }

        public int getCount() {
            return coverFileNames.size();
        }

        public Object getItem(int position) {
            return position;
        }

        public long getItemId(int position) {
            return position;
        }

        public View getView(int position, View convertView, ViewGroup parent) {

            ImageView imageView;

            if (convertView == null) {
                imageView = new ImageView(Main.this);
            } else {
                imageView = (ImageView) convertView;
            }

            // If I just use this, getView() is only called once per image (the correct way):
            // imageView.setImageResource(R.drawable.noposterxl);

            // If I just use this, getView() is only called once per
            // image, and additional times on the selected image (not correct):
            horImageLoader.DisplayImage(coverFileNames.get(position), imageView, position);

            return imageView;
        }

    }

这是Horizo​​ntalImageLoader类based on this example

public class HorizontalImageLoader {

    private Activity activity;
    private Map<ImageView, String> imageViews=Collections.synchronizedMap(new WeakHashMap<ImageView, String>());

    public HorizontalImageLoader(Activity activity) {
        this.activity = activity;
        photoLoaderThread.setPriority(Thread.NORM_PRIORITY-1);
    }

    public void DisplayImage(String fileUrl, ImageView imageView, final int pos) {
        imageViews.put(imageView, fileUrl);
        queuePhoto(fileUrl, activity, imageView, pos);
        imageView.setImageResource(R.drawable.noposterxl);
    }

    private void queuePhoto(String url, Activity activity, ImageView imageView, int position) {
        //This ImageView may be used for other images before. So there may be some old tasks in the queue. We need to discard them.
        photosQueue.Clean(imageView);
        PhotoToLoad p=new PhotoToLoad(url, imageView, position);
        synchronized(photosQueue.photosToLoad){
            photosQueue.photosToLoad.push(p);
            photosQueue.photosToLoad.notifyAll();
        }

        //start thread if it's not started yet
        if(photoLoaderThread.getState()==Thread.State.NEW)
            photoLoaderThread.start();
    }

    private Bitmap getBitmap(String fileUrl, int position) {

        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inPurgeable = true;

        Bitmap bm = BitmapFactory.decodeFile(fileUrl, options);

        return bm;
    }

    //Task for the queue
    private class PhotoToLoad
    {
        public String url;
        public ImageView imageView;
        public int pos;
        public PhotoToLoad(String u, ImageView i, int p){
            url=u;
            imageView=i;
            pos = p;
        }
    }

    PhotosQueue photosQueue=new PhotosQueue();

    public void stopThread()
    {
        photoLoaderThread.interrupt();
    }

    //stores list of photos to download
    class PhotosQueue
    {
        private Stack<PhotoToLoad> photosToLoad=new Stack<PhotoToLoad>();

        //removes all instances of this ImageView
        public void Clean(ImageView image)
        {
            try {
                for(int j=0 ;j<photosToLoad.size();){
                    if(photosToLoad.get(j).imageView==image)
                        photosToLoad.remove(j);
                    else
                        ++j;
                }
            } catch (Exception e) {
                // Do nothing
            }
        }
    }

    class PhotosLoader extends Thread {
        public void run() {
            try {
                while(true)
                {
                    //thread waits until there are any images to load in the queue
                    if(photosQueue.photosToLoad.size()==0)
                        synchronized(photosQueue.photosToLoad){
                            photosQueue.photosToLoad.wait();
                        }
                    if(photosQueue.photosToLoad.size()!=0)
                    {
                        PhotoToLoad photoToLoad;
                        synchronized(photosQueue.photosToLoad){
                            photoToLoad=photosQueue.photosToLoad.pop();
                        }
                        Bitmap bmp = getBitmap(photoToLoad.url, photoToLoad.pos);
                        String tag=imageViews.get(photoToLoad.imageView);

                        if(tag!=null && tag.equals(photoToLoad.url)){
                            BitmapDisplayer bd=new BitmapDisplayer(bmp, photoToLoad.imageView);
                            Activity a=(Activity)photoToLoad.imageView.getContext();
                            a.runOnUiThread(bd); // This seems to be causing the additional calls to getView()
                        }
                    }
                    if(Thread.interrupted())
                        break;
                }
            } catch (InterruptedException e) {
                //allow thread to exit
            }
        }
    }

    PhotosLoader photoLoaderThread=new PhotosLoader();

    //Used to display bitmap in the UI thread
    class BitmapDisplayer implements Runnable
    {
        Bitmap bitmap;
        ImageView imageView;
        public BitmapDisplayer(Bitmap b, ImageView i){
            bitmap=b;
            imageView=i;
        }
        public void run()
        {
            if(bitmap!=null)
                imageView.setImageBitmap(bitmap);
            else
                imageView.setImageResource(R.drawable.noposterxl);
        }
    }

}

再次更新

如果我在getView()方法中执行以下操作,这就是LogCat所说的:

代码:

Log.d("POSITION", "Current position: " + position);
horImageLoader.DisplayImage(coverFileNames.get(position), imageView, position);

logcat的:

09-03 13:59:11.920: DEBUG/POSITION(15278): Current position: 1
09-03 13:59:11.960: DEBUG/POSITION(15278): Current position: 2
09-03 13:59:11.960: DEBUG/POSITION(15278): Current position: 3
09-03 13:59:11.960: DEBUG/POSITION(15278): Current position: 0
09-03 13:59:12.110: DEBUG/POSITION(15278): Current position: 1
09-03 13:59:12.240: DEBUG/POSITION(15278): Current position: 1
09-03 13:59:12.300: DEBUG/POSITION(15278): Current position: 1

(注意底部有“当前位置:1”的其他三个日志条目)

如果我这样做,那么这就是LogCat所说的:

代码:

Log.d("POSITION", "Current position: " + position);
imageView.setImageResource(R.drawable.noposterxl);

logcat的:

09-03 14:02:47.340: DEBUG/POSITION(15412): Current position: 1
09-03 14:02:47.360: DEBUG/POSITION(15412): Current position: 2
09-03 14:02:47.370: DEBUG/POSITION(15412): Current position: 3
09-03 14:02:47.370: DEBUG/POSITION(15412): Current position: 0

(请注意,这会返回正确的结果 - 每张图片只能调用一次)

PS。我的Gallery设置为首先选择索引1,这就是首先调用位置1的原因。

5 个答案:

答案 0 :(得分:2)

执行:

  1. public View getView(int position, View convertView, ViewGroup parent)
  2. DisplayImage(coverFileNames.get(position), imageView, position)
  3. 请记住,如果getView的执行中包含DisplayImage,您将拥有无限循环。

    由于没有无限循环,因此两种情况中的一种可能是真的:

    1. DisplayImage ONCE 执行 >
    2. 其他东西正在煽动getView
    3. 2 不成立,因为getView中执行的唯一语句是getView

      1 为真,DisplayImage执行DisplayImage一种风格。

      根据the source code of setImageDrawable setImageDrawableView.requestLayout()被调用。

      • View.invalidate会根据文档调用View.requestLayout()界面ViewParent

          

        当某些内容发生更改时调用,这会使此视图父级的子级布局无效。这将安排视图树的布局传递。

      • requestLayout()会调用View.invalidate接口的ViewParent,这可能会导致ViewParent本身失效。


        

      因此,可以肯定地说,设置invalidateChild的图像会导致自身失效。 (事实确实如此,无论如何都不需要证明)

      现在,由于此ImageView是适配器接口的子级,因此调用适配器的ImageView并使用包含此getView <的精确视图的位置是最先进的。 / p>


      最后, 担心不 ,因为在这种情况下 ImageViewgetView同等,您肯定希望{{ 1}}被调用以便您的图像显示

      这是正常的

      1更多内容

      您的应用程序性能不佳不是因为调用了invalidate()

      您应该正确实施invalidate()。您的班级没有考虑任何优化。这不是getView的责任。

      建议

      1. 我相信一个很大的瓶颈就是你的DisplayImage函数总是调用BaseAdapter函数!你一直在打扫卫生!
      2. 您的密钥是queuePhoto!并且你在循环中一直比较Clean
      3. 尝试增强代码并优化代码。您应该考虑多次调用getView。

答案 1 :(得分:2)

BitmapDisplayer是一个在UI线程上运行的Runnable,它调用ImageView上的方法。这些方法碰巧生成布局请求,最终会导致调用getView()。

答案 2 :(得分:1)

我可以看到代码中没有问题。我认为适配器视图在选择项目时调用getview是正常的。以下是另一个类似问题的链接,可以进一步解释。

custom listview adapter getView method being called multiple times, and in no coherent order

更新

setImageResource中注释掉DisplayImage行,看看针对所选项目的getView调用次数是否减少到2次。

public void DisplayImage(String fileUrl, ImageView imageView, final int pos) {
    imageViews.put(imageView, fileUrl);
    queuePhoto(fileUrl, activity, imageView, pos);
 //   imageView.setImageResource(R.drawable.noposterxl); // coment this line
}

答案 3 :(得分:1)

这不是奇怪的问题,在显示活动之前多次调用getView方法是正常的,这都是关于验证视图的。

测试它尝试用文本创建普通列表,你会看到每个视图被调用两次。

答案 4 :(得分:0)

我认为这是listview高度的问题。如果您为listview高度提供一些固定高度而不是wrap_contentfill_parent,那么我认为它会起作用。检查一下。