blog

ビュー描画処理とイベント配信

onResumeメソッド後のアクティビティは、コンテンツの描画を開始します。対策としては、コントロールのサイズを取得していません。 前回はActivityの起動処理について説明しましたが、今回は引き続...

Jul 26, 2020 · 14 min. read
シェア

アクティビティは、onResumeメソッドの後、setContentViewでコンテンツの描画処理を開始します。対策としては、コントロールのサイズを取得していません。

製図工程

前回の記事では、アクティビティの起動プロセスについて説明しましたが、今回は引き続き、ビューのロードプロセスについて説明します。

final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) {
 unscheduleGcIdler();
 mSomeActivitiesChanged = true;
		// onStartメソッドとonResumeメソッドを呼び出す
 ActivityClientRecord r = performResumeActivity(token, clearHide);
 if (r != null) {
 final Activity a = r.activity;
			// WindowManagerにデコレーションを追加し始める。
			// view描画処理を開始する
 if (r.window == null && !a.mFinished && willBeVisible) {
 r.window = r.activity.getWindow();
 View decor = r.window.getDecorView();
 decor.setVisibility(View.INVISIBLE);
 ViewManager wm = a.getWindowManager();
 WindowManager.LayoutParams l = r.window.getAttributes();
 a.mDecor = decor;
 l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 l.softInputMode |= forwardBit;
 if (a.mVisibleFromClient) {
 a.mWindowAdded = true;
 wm.addView(decor, l); // ViewRootImplを初期化する。
 }
			} 
 WindowManager.LayoutParams l = r.window.getAttributes();
 if ((l.softInputMode & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != forwardBit) {
 l.softInputMode = (l.softInputMode& (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION))| forwardBit;
 if (r.activity.mVisibleFromClient) {
 ViewManager wm = a.getWindowManager();
 View decor = r.window.getDecorView();
 wm.updateViewLayout(decor, l);// 何が起こっているのか?
 }
}
 }
 }
 public void setWindowManager(WindowManager wm, IBinder appToken, String appName, boolean hardwareAccelerated) {
 mAppToken = appToken;
 mAppName = appName;
 mHardwareAccelerated = hardwareAccelerated
 || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
 if (wm == null) {
 wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
 }
 mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
 }
 @Override
 public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
 applyDefaultToken(params);
 mGlobal.addView(view, params, mDisplay, mParentWindow);
 }
 
 public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
 root = new ViewRootImpl(view.getContext(), display);
 view.setLayoutParams(wparams);
 mViews.add(view);
 mRoots.add(root);
 mParams.add(wparams);
 }
 
 } 
 

ViewRootImplはここでdecorViewへのリンクとして機能します!

 @Override
 public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
 applyDefaultToken(params);
 mGlobal.updateViewLayout(view, params);
 }

このメソッドでは、mViewのインデックスに基づいて、対応するViewRootImplを取得します。この初期化のステップは、上記のaddviewの間に追加されます。そして、ViewRootImplのsetLayoutParamsメソッドが呼び出されます。

 public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
 synchronized (mLock) {
 int index = findViewLocked(view, true);
 ViewRootImpl root = mRoots.get(index);
 mParams.remove(index);
 mParams.add(index, wparams);
 root.setLayoutParams(wparams, false);
 }
 }
 void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {
 synchronized (this) {
 if (newView) {
 requestLayout();
 }
 }
 }

スレッドチェックはここで行われます。mThreadはViewRootImplの生成時に指定されるため、これ以降にUIが更新されるとエラーとして報告されます。

 @Override
 public void requestLayout() {
 if (!mHandlingLayoutInLayoutRequest) {
 checkThread();
 mLayoutRequested = true;
 scheduleTraversals();
 }
 }
 
 void scheduleTraversals() {
 if (!mTraversalScheduled) {
 mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL,
		 // mTraversalRunnableのrunメソッドを実行する。
			 mTraversalRunnable, null);
 if (!mUnbufferedInputDispatch) {
 scheduleConsumeBatchedInput();
 }
 notifyRendererOfFramePending();
 pokeDrawLockIfNeeded();
 }
 }
 final class TraversalRunnable implements Runnable {
 @Override
 public void run() {
 doTraversal();
 }
 }
 void doTraversal() {
 if (mTraversalScheduled) {
 performTraversals();
 }
 }
 

このメソッドでは、呼び出し側が測定し、レイアウトし、描画します。

 private void performTraversals() {
 // cache mView since it is used so much below...
 // mView   DectorView
 final View host = mView;
 if (!mStopped || mReportNextDraw) {
 boolean focusChangedDueToTouchMode = ensureTouchModeLocally( (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
 if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()|| mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
 int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
 int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
//  
 // Ask host how big it wants to be
 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
 layoutRequested = true;
 }
 }
 } else {
 }
 final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
 boolean triggerGlobalLayoutListener = didLayout
 || mAttachInfo.mRecomputeGlobalAttributes;
 if (didLayout) {
//  		
 performLayout(lp, desiredWindowWidth, desiredWindowHeight);
 }
 
 if (!cancelDraw && !newSurface) {
 if (!skipDraw || mReportNextDraw) {
//  
 performDraw();
 }
 } else {
 
 }
 mIsInTraversal = false;
 }

測定は、呼び出されたビューの測定方法です。

 private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
 Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
 try {
 mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
 } finally {
 Trace.traceEnd(Trace.TRACE_TAG_VIEW);
 }
 }

 public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
 if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT || widthMeasureSpec != mOldWidthMeasureSpec || heightMeasureSpec != mOldHeightMeasureSpec) {
 
 int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 : mMeasureCache.indexOfKey(key);
 if (cacheIndex < 0 || sIgnoreMeasureCache) {
 onMeasure(widthMeasureSpec, heightMeasureSpec);
 } 
 }
 } 

これはFrameLayoutのonMeasureメソッドで、他のレイアウトも状況に応じてオーバーライドします。このメソッドでは、全ての子Viewを取得し、子Viewのchild.measureメソッドを呼び出します。

 /**
 * {@inheritDoc}
 * もし、子Viewのsetプロパティが match_parent 
 * ただし、measureMatchParentChildrenがtrueの場合は、ループを抜けてtrueを返す。
 * その後、この子ビューは2回計測される。
 */
 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 int count = getChildCount();
 //FrameLayoutの幅と高さが同時に match_parent
 //FrameLayoutの幅と高さがsize
 ///measureMatchParentChlidren = falseそうでなければtrueを返す。
 //MeasureSpec.EXACTLY子Viewが存在する場合、現在のViewのサイズはmeasureSpecパラメータで指定された値に等しい。
 //FrameLayoutが精密モードでない場合は、幅と高さをすべて //match_parentビューの子ビューが記録される
 // この時、FrameLayoutの幅と高さも、子Viewの影響を受ける。
 final boolean measureMatchParentChildren = MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY || MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
 mMatchParentChildren.clear();
 int maxHeight = 0;
 int maxWidth = 0;
 int childState = 0;//期待する幅と高さのタイプ
 //子を計測し、同時に最大幅と最大高を計算する。
 for (int i = 0; i < count; i++) {
 final View child = getChildAt(i);
 if (mMeasureAllChildren || child.getVisibility() != GONE) { //GONEでないchildを1つずつ繰り返し処理する。view
 //初めて子を計測する
 measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
 maxWidth = Math.max(maxWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
 maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
 childState = combineMeasuredStates(childState, child.getMeasuredState());
 //リスト内のmMatchParentChlidrenのリストをmatch_parent子Viewが存在する場合、breakループはtrueを返す。
 if (measureMatchParentChildren) {
 if (lp.width == LayoutParams.MATCH_PARENT || lp.height == LayoutParams.MATCH_PARENT) {
 mMatchParentChildren.add(child);
 }
 }
 }
 }
 // Account for padding too
 //父親のpadidng
 maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
 maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
 // Check against our minimum height and width
 // 最小幅と高さ
 maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
 maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
 
 // 前景イメージと比較し、幅と高さが大きい値を取得する。
 final Drawable drawable = getForeground();
 if (drawable != null) {
 maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
 maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
 }
 //計測した幅と高さを設定する
 setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
 resolveSizeAndState(maxHeight, heightMeasureSpec,
 childState << MEASURED_HEIGHT_STATE_SHIFT));
 //上記の計測では、フレームレイアウトの幅と高さを取得したが、以下では、フレームレイアウトの幅と高さに応じて、子の計測をやり直す必要がある。view -   match_parent 子属性
 //子ビューはmatch_parent 
 //再測定
 count = mMatchParentChildren.size();
 if (count > 1) {
 for (int i = 0; i < count; i++) {
 final View child = mMatchParentChildren.get(i);
 final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
 final int childWidthMeasureSpec;
 //属性を設定するmatch_parent再度計測する
 if (lp.width == LayoutParams.MATCH_PARENT) {//子ビューの幅はmatch_parent幅の期待値は、ビューの全幅である。-padding-margin
 final int width = Math.max(0, getMeasuredWidth()
 - getPaddingLeftWithForeground() - getPaddingRightWithForeground()
 - lp.leftMargin - lp.rightMargin);
 childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
 width, MeasureSpec.EXACTLY);
 } else {
 childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
 getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
 lp.leftMargin + lp.rightMargin,
 lp.width);
 }
 //属性を設定するmatch_parent再度計測する
 final int childHeightMeasureSpec;
 if (lp.height == LayoutParams.MATCH_PARENT) {
 final int height = Math.max(0, getMeasuredHeight()
 - getPaddingTopWithForeground() - getPaddingBottomWithForeground()
 - lp.topMargin - lp.bottomMargin);
 childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
 height, MeasureSpec.EXACTLY);
 } else {
 childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
 getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
 lp.topMargin + lp.bottomMargin,
 lp.height);
 }
 //子ビューの幅はmatch_parent幅の期待値は、ビューの全幅である。-padding-margin
 //子ビューのサイズを変更する
 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
 }
 }
 }
 private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) {
 mLayoutRequested = false;
 mScrollMayChange = true;
 mInLayout = true;
 final View host = mView;
 try {
 host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
 } 
 }

ビューとビューグループのイベント配布

イベント配信を見る

ビューには、配信と消費のためのイベントしかありません。

mOnTouchListenerがfalseを返した場合、ViewのonTouchEventメソッドが実行されます。

mOnTouchListenerがfalseを返した場合、ViewのonTouchEventメソッドがオーバーライドされ、superが呼び出されなければ、クリックイベントは実行されません。

 public boolean dispatchTouchEvent(MotionEvent event) {
 boolean result = false;
 final int actionMasked = event.getActionMasked();
 if (actionMasked == MotionEvent.ACTION_DOWN) {
 stopNestedScroll();
 }
 if (onFilterTouchEventForSecurity(event)) {
 //noinspection SimplifiableIfStatement
 //   -- 中身は、パラメータだらけだ。
 ListenerInfo li = mListenerInfo;
 if (li != null && li.mOnTouchListener != null 
			// enable  true
			&& (mViewFlags & ENABLED_MASK) == ENABLED 
// onTouchListenerが設定されている場合false
			&& li.mOnTouchListener.onTouch(this, event)) {
			
 result = true;
 }
 if (!result && onTouchEvent(event)) {
 result = true;
 }
 
 if (actionMasked == MotionEvent.ACTION_UP ||
 actionMasked == MotionEvent.ACTION_CANCEL ||
 (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
 stopNestedScroll();
 }
 return result;
 }
 public boolean onTouchEvent(MotionEvent event) {
 final float x = event.getX();
 final float y = event.getY();
 final int viewFlags = mViewFlags;
 final int action = event.getAction();
 if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
 switch (action) {
 case MotionEvent.ACTION_UP:
 boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
 if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
 boolean focusTaken = false;
 if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
 if (!focusTaken) {
								if (mPerformClick == null) {
 mPerformClick = new PerformClick();
 }
 if (!post(mPerformClick)) {	// クリックイベント
 performClick();
 }
 }
 }
 
 }
 mIgnoreNextUpEvent = false;
 break;
 }
 return true;
 }
 return false;
 }

ViewGroupイベント配信

子プロセスがViewの場合、上記のView配布処理が実行されます。child が viewGroup の場合、処理を再開します。子Viewが消費された場合、ループを抜け、trueを返します。

 public boolean dispatchTouchEvent(MotionEvent ev) {
 
 boolean handled = false;
 if (onFilterTouchEventForSecurity(ev)) {
 final int action = ev.getAction();
 final int actionMasked = action & MotionEvent.ACTION_MASK;
			// これは非常に重要なポイントである。
 if (actionMasked == MotionEvent.ACTION_DOWN) {
				// TouchState mFirstTouchTargetが空になった。
				cancelAndClearTouchTargets(ev);
 resetTouchState();
 }
 // Check for interception.
 final boolean intercepted;
 if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
				if (!disallowIntercept) {
					// down イベントの場合、その行が呼び出され、インターセプトされたメソッドが呼び出される。
 intercepted = onInterceptTouchEvent(ev); // デフォルトはfalseで、サブクラスに依存する。
 ev.setAction(action); // restore action in case it was changed
 } else {
 intercepted = false;
 }
 } else { 
 intercepted = true;
 }
 
 if (intercepted || mFirstTouchTarget != null) {
 ev.setTargetAccessibilityFocus(false);
 }
 // Check for cancelation.
 final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL;
 
 final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
 TouchTarget newTouchTarget = null;
 boolean alreadyDispatchedToNewTouchTarget = false;
			
			//インターセプトもキャンセルもなければ、デフォルトはtrueとなり、if文を実行できる。
 if (!canceled && !intercepted) {
 
 View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus() ? findChildWithAccessibilityFocus() : null;
 if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
 final int actionIndex = ev.getActionIndex(); // always 0 for down
 final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex) : TouchTarget.ALL_POINTER_IDS;
 
 removePointersFromTouchTargets(idBitsToAssign);
					// ここで実行が終了すると
 final int childrenCount = mChildrenCount;
 if (newTouchTarget == null && childrenCount != 0) {
 final float x = ev.getX(actionIndex);
 final float y = ev.getY(actionIndex);
 // Find a child that can receive the event.
 // Scan children from front to back.
 final ArrayList<View> preorderedList = buildOrderedChildList();
 final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled();
 final View[] children = mChildren;
						// ループの順序を逆にし、fragmentlayouの場合はトップレベルから開始する。i
 for (int i = childrenCount - 1; i >= 0; i--) {
 final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
 final View child = (preorderedList == null) ? children[childIndex] : preorderedList.get(childIndex);
 
 if (childWithAccessibilityFocus != null) {
 if (childWithAccessibilityFocus != child) {
 continue;
 }
 childWithAccessibilityFocus = null;
 i = childrenCount - 1;
 }
 if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) {
 ev.setTargetAccessibilityFocus(false);
 continue;
 }
							// 11 
 newTouchTarget = getTouchTarget(child); // ビューのtarget
 if (newTouchTarget != null) { 
 newTouchTarget.pointerIdBits |= idBitsToAssign;
 break;
 } 
 resetCancelNextUpFlag(child);
							// 子ビューのdispatchTouchEvent 
 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
 // Child wants to receive touch within its bounds.
 mLastTouchDownTime = ev.getDownTime();
 if (preorderedList != null) {
 // childIndex points into presorted list, find original index
 for (int j = 0; j < childrenCount; j++) {
 if (children[childIndex] == mChildren[j]) {
 mLastTouchDownIndex = j;
 break;
 }
 }
 } else {
 mLastTouchDownIndex = childIndex;
 }
 mLastTouchDownX = ev.getX();
 mLastTouchDownY = ev.getY();
 newTouchTarget = addTouchTarget(child, idBitsToAssign);
 alreadyDispatchedToNewTouchTarget = true;
 break;
 }
 ev.setTargetAccessibilityFocus(false);
 }
 if (preorderedList != null) preorderedList.clear();
 }
 if (newTouchTarget == null && mFirstTouchTarget != null) {
 // Did not find a child to receive the event.
 // Assign the pointer to the least recently added target.
 newTouchTarget = mFirstTouchTarget;
 while (newTouchTarget.next != null) {
 newTouchTarget = newTouchTarget.next;
 }
 newTouchTarget.pointerIdBits |= idBitsToAssign;
 }
 }
 }
 // Dispatch to touch targets.
 if (mFirstTouchTarget == null) { 
 handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
 } else { 
 TouchTarget predecessor = null;
 TouchTarget target = mFirstTouchTarget;
 while (target != null) {
 final TouchTarget next = target.next;
 if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
 handled = true;
 } else {
 final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;
 if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) {
 handled = true;
 }
 if (cancelChild) {
 if (predecessor == null) {
 mFirstTouchTarget = next;
 } else {
 predecessor.next = next;
 }
 target.recycle();
 target = next;
 continue;
 }
 }
 predecessor = target;
 target = next;
 }
 }
 }
 return handled;
 }
Read next

資本のブロックチェーンから離れ、背後にある自己への回帰は無力さを露呈する

資本の後押しがなかった後、ブロックチェーン産業の発展はかつてない静けさを取り戻し始めました。資本志向の投機家の逃亡により、ブロックチェーンの開発は概念に別れを告げ、自らに戻ることができるようになりました。しかし、ブロックチェーンはまだ黎明期にあり、ブロックチェーンの既存の力だけでは永続的な発展の勢いを得るには不十分であるという現実は否定できません。資本撤退後の市場はもはや気まぐれではありませんが、ブロックチェーン市場...

Jul 26, 2020 · 2 min read