ここ数日、奇妙な問題と戦っていました。各行に ImageView などが含まれるカスタム ExpandableListAdapter があります。画像が存在する可能性のある多数の場所(ディスクキャッシュ、アプリデータ、リモートサーバーなど)からの画像の非同期読み込みを処理するクラスがあります。私のアダプターの 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 を含むアクティビティをロードすると、リスト内のさまざまな行からサム イメージが欠落していることがよくあります。アクティビティをリロードすると、見つからないサムの一部が表示される場合がありますが、以前に表示されていたサムは表示されない場合があります。私が知る限り、動作はかなりランダムです。画像が欠落している行がリサイクルされるように ListView をスクロールすると、(リサイクルされた行が再び表示されると) 新しいサム イメージが正常に読み込まれます。以前に欠落した画像が含まれていた行にスクロールして戻ると、欠落した画像が表示されます。すべての画像が BitmapFetcher (mFetcher) クラスから正しく読み込まれていることを確認できます。また、他の場所に他の画像をロードすることにも言及する必要があります。時々、彼らも現れません。
ほとんどの髪を抜いた後、次のような変化があることを発見しました。
thumb.post(new Runnable() {
に:
mExpListView.post(new Runnable() {
問題を修正します。ビューへの最終参照を使用していたために問題が発生している可能性があると当初考えていましたが、アプリ内の他の場所ではビューへの非最終参照を使用してメッセージを投稿し、前述のように、それらが機能しない場合がありました. 最終的に、Activity の runOnUiThread() メソッド (および Fragments 内では自分の getUiThreadRunner().execute メソッド) を使用するようにすべてを変更したところ、問題がすべて解決したようです。
View.post() が関連する ViewRoot のメッセージ キューに適切な順序でランナブルを配信できないのはどのような場合ですか? または、getView から View が返される前、つまりルート View からアクセスできる ViewGroup に配置される前に、invalidate() が発生している可能性があります。これらは、画像が表示されないようにする唯一のケースだと思います。少なくとも 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();
ただし、両方のハンドラーが同じスレッドで (または同じルーパーを使用して) 構築されていれば、これは問題になりません。そのため、階層にまだ追加されていないビューを無効にするのが実際に問題なのかどうか疑問に思います。この場合、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 からアクセスできない場合、View は VISIBLE ではないと見なすのは正確ですか? または、VISIBILITY プロパティはビューのコンテナーの影響を受けませんか? 前者の場合 (私はそうだと思います)、懸念が生じます。私の Activity.runOnUiThread の使用は、問題の解決策ではありません。これが機能するのは、invalidate() 呼び出しが別の Handler に送信され、後で実行されるためです(getView が戻った後、行が追加されて画面に表示された後)。他の誰かがこの問題に遭遇しましたか? 良い解決策はありますか?