这几天我一直在打一个奇怪的问题。我有一个自定义的ExpandableListAdapter,其中每行包含一个ImageView等。我有一个类来处理来自它们可能驻留的众多地方(磁盘缓存,应用程序数据,远程服务器等)的图像的异步加载。在我的适配器的getView方法中,我委托将View返回到列表Item本身的责任(我的组列表有多个行类型)。我按如下方式请求图像加载:
final ImageView thumb = holder.thumb;
holder.token = mFetcher.fetchThumb(mImage.id, new BitmapFetcher.Callback() {
@Override
public void onBitmap(final Bitmap b) {
thumb.post(new Runnable() {
@Override
public void run() {
thumb.setImageBitmap(b);
}
});
}
@Override
public void onFailure() {
}
});
是的,这很难看,但我决定反对一些合约,你默认在BitformFetcher.Callback上执行其方法。
无论如何,当我加载包含ExpandableListView的Activity时,列表中的不同行经常会丢失拇指图像。重新加载活动可能会导致一些丢失的拇指显示,但之前显示的其他人可能不再显示。据我所知,这种行为是随机的。滚动ListView以使具有缺失图像的行被回收导致新的拇指图像(当再循环行再次显示时)加载正常。向后滚动回以前包含缺失图像的行会导致丢失的图像出现。我可以确认所有图像都是从我的BitmapFetcher(mFetcher)类正确加载的。我还要提一下,我在其他地方加载其他图像。每隔一段时间他们也不会出现。
将大部分头发拉出后,我发现改变了:
thumb.post(new Runnable() {
为:
mExpListView.post(new Runnable() {
解决了这个问题。我最初认为问题可能正在发生,因为我使用的是对View的最终引用,但应用程序中的其他位置使用对视图的非最终引用来发布消息,并且,正如我所提到的,有时这些不起作用。我最终改变了所有内容以使用Activity的runOnUiThread()方法(以及我自己的getUiThreadRunner()。在Fragments内部执行方法),这似乎解决了这个问题。
所以我的问题仍然存在,在什么情况下View.post()无法以正确的顺序将runnable传递给关联的ViewRoot的消息队列?或者,也许invalidate()在从getView返回View之前发生,因此在将它放入可以从根View访问的ViewGroup之前。这些是我能想到的唯一可以防止图像出现的情况。我可以保证在至少onStart完成执行之前不会发生这些调用。此外,即使尚未附加到Window,看起来发布到View也没关系:
// Execute enqueued actions on every traversal in case a detached view enqueued an action
getRunQueue().executeActions(attachInfo.mHandler);
(在performTraversal中)。 runOnUiThread和post之间的唯一区别似乎是Activity具有与ViewRootImpl不同的Handler。
的活动:
final Handler mHandler = new Handler();
而在ViewRootImpl中:
final ViewRootHandler handler = new ViewRootHandler();
但是,如果两个Handler都在同一个Thread中构建(或使用相同的Looper),这应该不是问题。这让我想知道,确实是一个问题,无效()一个尚未添加到层次结构的视图。对于这种情况,无效应该是1.如果不可见则不做任何事情,或者2.仅对发生的下一个performTraversal()有效。
View.invalidate()检查一个没有记录的名为skipInvalidate()的好私有方法:
/**
* Do not invalidate views which are not visible and which are not running an animation. They
* will not get drawn and they should not set dirty flags as if they will be drawn
*/
private boolean skipInvalidate() {
return (mViewFlags & VISIBILITY_MASK) != VISIBLE && mCurrentAnimation == null &&
(!(mParent instanceof ViewGroup) ||
!((ViewGroup) mParent).isViewTransitioning(this));
}
看起来数字1更准确!但是,我认为这仅适用于View的VISIBILITY属性。那么,如果从ViewRoot无法访问View,那么是否可以认为不 VISIBLE?或者VISIBILITY属性不受View容器的影响?如果前者是这种情况(我怀疑它是这样的话),那引起了关注。我对Activity.runOnUiThread的使用并不是解决问题的方法。它只是起作用,因为invalidate()调用被发送到另一个处理程序并且稍后执行 (在getView返回之后以及在添加行并在屏幕上显示之后)。还有其他人遇到过这个问题吗?有一个很好的解决方案吗?
答案 0 :(得分:1)
view.post(Runnable r)
的基本要求是view
应该附加到Runnable执行窗口。但是,由于您在第一种情况下异步加载图像,因此在发出请求时有可能imageView
未附加到窗口,因此某些图像无法加载。
引用早期版本的文档:
View.post():使Runnable添加到消息队列中。 runnable将在用户界面线程上运行。这种方法可以 仅当此视图为时,才从UI线程外部调用 附在窗户上。
切换到下一个问题,处理这种情况的最佳解决方案是什么?
无法评论最佳解决方案。但是,我认为handler.post()
和activity.runOnUIThread()
都很好。因为,它们基本上可以在主线程队列中发布runnable而不管什么,并且通常,显示列表行的请求将在我们thumb.post()
之前排队。因此,对于大多数情况,它们可能完美无缺。 (至少我从来没有遇到过他们的问题!)。然而。如果您找到更好的解决方案,请与我分享。
答案 1 :(得分:0)
试试这个:setBitmap(),如下所示:
runOnUiThread(new Runnable() {
@Override
public void run() {
thumb.setImageBitmap(b);
}
});