blog

[Android] Handler.postとView.postの比較

Handlerを使用して、postはmsgを構築し、対応するルーパーに挿入し、msgオブジェクトが処理されるまで待機し、キューから取り出して実行します。 view.post() も内部的にハンドラを呼...

May 14, 2020 · 5 min. read
シェア

ハンドラ.post vs ビュー.post

コードセグメント 1.

Handler().post({
// do something
})

コードセグメント 2.

someview.post({
// do something
})

この2つの違いは何ですか?

Handler.post

Handlerでは、post message/runnableはすべてmsgを構築し、対応するlooperとmessageQueueに挿入し、msgオブジェクトが処理されるまで待ち、キューから取り出して実行します。

View.post

よくあるシナリオ

  • UIの更新
  • View の実際の幅と高さを取得します。

view.post()はHandler内部でも呼び出され、Handlerのpostはmsgの呼び出しタイミングとは異なるため、以下にView.postのソースコード解析を詳しく展開します。

比較

  • これらはすべてUIスレッドに移動してアクションを実行することができます。
  • これらは両方とも、Handler+msgを介して内部的に実装されています。

View.post

つまり、onResume後に描画する場合、onCreateでHandler().postを使用しても、ViewTreeObserverやdelayに関係なく、同じスレッドキューにあるため、幅と高さを取得できません。

  • View.post()はタスクのタイミングを調整します。

View.post()

Viewのソースコードを開き、Viewのpostメソッドを以下のように探します:

public boolean post(Runnable action) {
// まず、AttachInfoがnullかどうかを判断する。
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
// もしnullでなければ、内部のHandlerのpost()を直接呼び出す。
return attachInfo.mHandler.post(action);
}
// そうでなければ、現在のViewの待ち行列に追加される。
getRunQueue().post(action);
return true;
}

まずgetRunQueue().post()を見る必要があります:

private HandlerActionQueue getRunQueue() {
if (mRunQueue == null) {
mRunQueue = new HandlerActionQueue();
}
return mRunQueue;
}
// getRunQueue() リターンはHandlerActionQueueで、これはHandlerActionQueueのpostメソッドへの呼び出しである:
public void post(Runnable action) {
// postDelayedメソッドの呼び出しは、メッセージを送信するHandlerのようなものだ。
postDelayed(action, 0);
}
// postDelayedの実際の呼び出し
public void postDelayed(Runnable action, long delayMillis) {
// HandlerAction実行するタスクを示す
final HandlerAction handlerAction = new HandlerAction(action, delayMillis);
synchronized (this) {
if (mActions == null) {
// HandlerActionsの配列を作成する。
mActions = new HandlerAction[4];
}
// 実行されるタスクを表すHandlerActionはmActions配列に格納される。
mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
// mActions配列の添え字の位置は1ずつ増加する。
mCount++;
}
}
// HandlerAction 実行されるタスクを表し、実行されるRunnableと遅延時間を保持する:
private static class HandlerAction {
// post 
final Runnable action;
// 遅延
final long delay;
public HandlerAction(Runnable action, long delay) {
this.action = action;
this.delay = delay;
}
// 同じタスクかどうかを比較する
// RunnableとHandlerActionをマッチさせる
public boolean matches(Runnable otherAction) {
return otherAction == null && action == null
|| action != null && action.equals(otherAction);
}
}

postDelayed()は、デフォルトの長さが4のHandlerAction配列を作成し、post()によって追加されたタスクを保持するために使用されることに注意してください:

View.post()によって追加されたタスクが実行されていませんか?

実際、この時点で、AttachInfoのコンストラクタ・メソッドから始まるAttachInfoの作成プロセスをもう一度見直すことが重要です:

AttachInfo(IWindowSession session, IWindow window, Display display,
ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer,
Context context) {
mSession = session;
mWindow = window;
mWindowToken = window.asBinder();
mDisplay = display;
// 現在のViewRootImplを保持する
mViewRootImpl = viewRootImpl;
// 現在のレンダースレッドハンドラ
mHandler = handler;
mRootCallbacks = effectPlayer;
// そのためにViewTreeObserverを作成する。
mTreeObserver = new ViewTreeObserver(context);
}

AttachInfoは現在のスレッドのHandlerを保持することに注意してください。

Viewのソースコードを見ると、mAttachInforへの代入は2つしかなく、1つは値を代入すること、もう1つはnullに設定することです。

mAttachInfo 割り当てプロセス:

void dispatchAttachedToWindow(AttachInfo info, int visibility) {
// すべてのViewが同じAttachInfoを共有している場合、現在のViewにAttachInfoを割り当てる。
mAttachInfo = info;
// ViewAndroid 4で導入されたフローター.3 
if (mOverlay != null) {
// どのViewもViewOverlayを持っている
// ViewGroupViewGroupOverlay
// これは、このようにRelativeLaout/FrameLayoutに直接Viewを追加するのとは異なり、ViewOverlayを介して追加された要素はイベントを持たない。
// この時点では、主に以下のView floatに分散されている。
mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
}
mWindowAttachCount++;
// ... 省略
if ((mPrivateFlags & PFLAG_SCROLL_CONTAINER) != 0) {
mAttachInfo.mScrollContainers.add(this);
mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED;
}
// mRunQueue,これは前のgetRunQueue()にある。.post()
// 実際の型はHandlerActionQueueで、現在のViewを保持する。.post 
if (mRunQueue != null) {
// 実行はView.Post()を使用する。.post 
// レンダリングスレッドのHandlerにポストしていることに注意。
mRunQueue.executeActions(info.mHandler);
// この時点では、すべてのViewがAttachInfoを共有しているため、遅延タスクを保存するためのキューはnullに設定されている。
mRunQueue = null;
}
performCollectViewAttributes(mAttachInfo, visibility);
// ViewのonAttachedToWindowメソッドにコールバックする
// ActivityのonResumeメソッドでは呼ばれるが、Viewの描画処理では呼ばれない。
onAttachedToWindow();
ListenerInfo li = mListenerInfo;
final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
li != null ? li.mOnAttachStateChangeListeners : null;
if (listeners != null && listeners.size() > 0) {
for (OnAttachStateChangeListener listener : listeners) {
// ViewのonAttachToWindowをリッスンしているすべてのクライアントに通知する。.addOnAttachStateChangeListener();
// しかし、この時点ではまだViewは描画を開始していないので、計測したサイズやViewの実際のサイズを正しく取得することができない。
listener.onViewAttachedToWindow(this);
}
}
// ... 省略
// ViewのonVisibilityChangedへのコールバック
// なお、Viewの描画処理はまだ始まっていない!
onVisibilityChanged(this, visibility);
// ... 省略
}
public void executeActions(Handler handler) {
synchronized (this) {
// タスクキュー
final HandlerAction[] actions = mActions;
// すべてのタスクを繰り返し処理する
for (int i = 0, count = mCount; i < count; i++) {
final HandlerAction handlerAction = actions[i];
//これをHandlerに送り、実行されるのを待つ。
handler.postDelayed(handlerAction.action, handlerAction.delay);
}
//この時点ではこれは必要なく、後続のpostはAttachInfoに追加される。
mActions = null;
mCount = 0;
}
}

保存されたすべてのタスクを繰り返し実行し、実行のためにキューに入れるためにそれらをHandlerに送ります。後続のView.post()がそれらをAttachInfo内のHandlerに直接追加するため、保存されたタスクのmActionsをnullに設定します。そのため、dispatchAttachedToWindow()がいつ呼び出されたかを追跡する必要があります。

Read next

leetcode219. 重複要素が存在する II.

整数の配列と整数 k が与えられたとき、nums [i] = nums [j] であり、かつ i と j の差の絶対値が最大でも k であるような、2つの異なるインデックス i と j が配列中に存在するかどうかを調べます。配列を繰り返し処理し、各要素について、その前後k個のインデックス内に同じ値を持つ要素があるかどうかを判断し、もしあればreturn...

May 14, 2020 · 2 min read