Android的runOnUiThread是否调用Adapters中的getView()?
我正在处理延迟加载图像的Gallery
(一个类用于Gallery
和Adapter
,另一个类用于延迟加载部分)。第二个类使用 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;
}
}
这是 HorizontalImageLoader 类(基于此示例< /a>)
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 的原因。
I'm working with lazy loading a Gallery
of images (one class for the Gallery
and Adapter
, and another for the lazy loading part). The second class uses runOnUiThread
to update the first class using its context, but it seems that it's calling the getView()
method of the first class' adapter again. This means that getView()
is being called twice for every image in the Gallery. Check out the code below.
The weird thing is that second getView()
call is only being called on the selected image in the Gallery
widget. If four images are shown at the same time, getView()
will be called on those four images once, and three additional times on the selected image.
Any ideas why this is happening?
Here's the adapter
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;
}
}
Here's the HorizontalImageLoader class (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);
}
}
}
UPDATED AGAIN
If I do the following in my getView()
method, this is what LogCat says:
Code:
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
(note the additional three log entries with "Current position: 1" at the bottom)
If I do this, then here's what LogCat says:
Code:
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
(note that this is returning the correct result - only one call per image)
PS. My Gallery
is set to select index 1 first, that's why position 1 is called first.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
执行:
public View getView(intposition, View ConvertView, ViewGroupparent)
DisplayImage(coverFileNames.get(position), imageView,position)
请记住,如果
getView
包含在DisplayImage
的执行中,您将陷入无限循环。由于不存在无限循环,因此可能存在以下两种情况之一:
DisplayImage
仅执行某事一次,而此某事 em> 正在触发getView
ONCEgetView
。2 不成立,因为
getView
中执行的唯一语句是DisplayImage
1< /strong> 为 true,
DisplayImage
执行一次setImageDrawable
类型之一。根据
setImageDrawable
源码View.requestLayout()
和View.invalidate
被调用。View.requestLayout()
将调用ViewParent
接口的requestLayout()
,根据文档:<块引用>
当某些内容发生更改导致该视图父级的子级布局无效时调用。这将安排视图树的布局过程。
View.invalidate
将调用ViewParent
接口的invalidateChild
,这可能会导致 ViewParent 本身失效。现在,由于此
ImageView
是 Adapter 接口的子级,因此 Adapter 的getView
是最先进的使用包含此ImageView
的确切视图的位置进行调用。最后,不用担心,因为
getView< /code> 在这种情况下相当于
invalidate()
并且您肯定希望调用invalidate()
以便显示您的图像。
这是正常的。
还有一件事
您的应用程序性能不佳并不是由于调用了
getView
!您应该正确实现您的
DisplayImage
。您的课程没有考虑任何优化。这不是BaseAdapter
的责任。建议:
queuePhoto
函数,它总是调用Clean
函数!你一直在打扫卫生!ImageView
!并且您一直在循环中比较ImageView
只是尝试增强您的代码并优化它。您应该考虑 getView 被多次调用。
Execution:
public View getView(int position, View convertView, ViewGroup parent)
DisplayImage(coverFileNames.get(position), imageView, position)
Bear in mind that if
getView
is contained in the execution ofDisplayImage
, You will have an infinite loop.Since there is no infinite loop, then one of two scenarios can be true:
DisplayImage
executes something only ONCE and this something is trigerringgetView
ONCEgetView
.2 is not true, since the only statement executed in
getView
isDisplayImage
1 is true,
DisplayImage
executes one of the flavors ofsetImageDrawable
once.According to the source code of
setImageDrawable
View.requestLayout()
andView.invalidate
are called.View.requestLayout()
will callViewParent
Interface'srequestLayout()
which according to the docs:View.invalidate
will callViewParent
Interface'sinvalidateChild
which might cause an invalidation on the ViewParent itself.Now Since this
ImageView
is a child of an Adapter interface, it is state of art that the Adapter'sgetView
is called with the position of the exact view that contains thisImageView
Finally, Worry Not because
getView
in this case is equivelant toinvalidate()
and you surely wantinvalidate()
to be called so that your images show up.This is normal.
1 MORE THING
The bad performance of your application is not due to calling of the
getView
!You should implement your
DisplayImage
correctly. Your class do not take into account any optimization. It is not the responsibility of theBaseAdapter
.Advice:
queuePhoto
function which is always calling theClean
function! You are cleaning all the time!ImageView
! and you are comparingImageView
s all the time in a loopJust try to enhance your code and optimize it. You should account for getView being called multiple of times.
BitmapDisplayer 是一个在 UI 线程上运行的 Runnable,它调用 ImageView 上的方法。这些方法恰好会生成布局请求,最终会导致 getView() 被调用。
BitmapDisplayer is a Runnable that you run on the UI thread, and it calls methods on ImageView. These methods happen to generate layout requests, which will eventually cause getView() to be invoked.
我看代码没有问题。我认为当选择一个项目时,adapterview调用getview是正常的。这是另一个类似问题的链接,将进一步解释。
自定义列表视图适配器 getView 方法被多次调用,且顺序不连贯
更新
注释掉
setImageResource
DisplayImage
中的行,看看对于所选项目,对 getView 的调用次数是否减少到 2 次。I can see no problem in the code. I think its normal for adapterview to call getview when a item is selcted. Here is a link to another similar question that will explain further.
custom listview adapter getView method being called multiple times, and in no coherent order
Update
Comment out the
setImageResource
line inDisplayImage
and see if the number of calls to getView, for selected item, reduces to 2.这不是奇怪的问题,在显示活动之前多次调用 getView 方法是正常的,这都是关于验证视图的。
要测试它,请尝试创建仅包含文本的普通列表,您将看到每个视图都被调用两次。
This is not Weird issue, its normal to call
getView
method several times before showing the activity, this all about validating the views.to test it try to create normal list with just text, you will see every view is called twice.
我认为这是listview高度的问题。如果您为列表视图高度提供一些固定高度而不是
wrap_content
或fill_parent
那么我认为它会起作用。一旦检查一下。I think it is the problem with listview height. If you provide some fixed height instead of
wrap_content
orfill_parent
for listview height then I think it will work. Once check that.