最近几天我一直在与一个奇怪的问题作斗争。我有一个自定义 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() {
}
});
是的,这很丑陋,但我决定反对一些让 BitmapFetcher.Callback 默认在 UI 线程上执行其方法的合同。
无论如何,当我加载包含 ExpandableListView 的 Activity 时,列表中的不同行通常会丢失拇指图像。重新加载 Activity 可能会导致显示一些丢失的拇指,但以前显示的其他拇指可能不再显示。据我所知,这种行为是非常随机的。滚动 ListView 以使缺少图像的行被回收导致新的拇指图像(当回收的行再次显示时)加载正常。回滚到先前包含缺失图像的行会导致出现缺失图像。我可以确认所有图像都从我的 BitmapFetcher (mFetcher) 类中正确加载。我还应该提到我在其他地方加载了其他图像。每隔一段时间,它们也不会出现。
拔掉大部分头发后,我发现发生了变化:
thumb.post(new Runnable() {
至:
mExpListView.post(new Runnable() {
解决了这个问题。我最初认为这个问题可能会发生,因为我使用的是对视图的最终引用,但是应用程序中的其他位置使用对视图的非最终引用来发布消息,而且,正如我所提到的,有时这些不起作用. 我最终更改了所有内容以使用 Activity 的 runOnUiThread() 方法(以及我自己在 Fragments 中的 getUiThreadRunner().execute 方法),这似乎解决了所有问题。
所以我的问题仍然存在,在什么情况下 View.post() 无法以正确的顺序将可运行对象传递到关联的 ViewRoot 的消息队列?或者,invalidate() 可能在从 getView 返回 View 之前发生,因此在它被放置在可以从根 View 访问的 ViewGroup 之前发生。这些确实是我能想到的唯一会阻止图像出现的情况。我可以保证至少在 onStart 完成执行之前不会发生这些调用。此外,即使尚未将其附加到窗口,也可以将其发布到视图:
// 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();
但是,如果两个处理程序都是在同一个线程中构造的(或使用同一个 Looper),这应该不是问题。这让我想知道是否确实存在使尚未添加到层次结构中的视图无效()的问题。对于这种情况,invalidate 应该 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号更准确!但是,我认为这仅与视图的 VISIBILITY 属性有关。那么,如果无法从 ViewRoot 访问视图,则认为视图被视为不可见是否准确?还是 VISIBILITY 属性不受视图容器的影响?如果是前者(我怀疑是这样),就会引起关注。我使用 Activity.runOnUiThread 并不能解决问题。它只是碰巧起作用,因为 invalidate() 调用被发送到不同的处理程序并稍后执行(在 getView 返回之后以及在添加行并使其在屏幕上可见之后)。有没有其他人遇到过这个问题?有没有好的解决方案?