View. post 컨트롤 폭 높 은 원리 탐색 가 져 오기

16614 단어 Android
먼저 이 글 을 쓰기 전에 분석 이 좋 은 블 로 그 를 두 편 붙 여 주 셔 서 블 로 거들 의 봉사 정신 에 감 사 드 립 니 다.http://blog.csdn.net/scnuxisan225/article/details/49815269 http://mp.weixin.qq.com/s/laR3_Xvr0Slb4oR8D1eQyQ
안 드 로 이 드 개발 시 Activity 의 onCreate 방법 에서 컨트롤 의 getMeasure Height / getHeight 또는 getMeasure Width / getWidth 방법 으로 얻 은 너비 와 높이 는 모두 0 입 니 다. 저 는 여러분 이 이런 문제 에 직면 했 을 때 먼저 도 모 를 열 고 검색 하면 흔히 볼 수 있 는 두 가지 해결 방안 이 나 올 것 이 라 고 믿 습 니 다.
1. Draw / Layout 이벤트 감청 을 통 해: ViewTreeObserver
 1 view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
 2         @Override
 3         public void onGlobalLayout() {
 4             mScrollView.post(new Runnable() {
 5                 public void run() {
 6                     view.getHeight(); //height is ready
 7                 }
 8             });
 9         }
10 });

우리 가 이 감청 을 등록 할 때 컨트롤 은 onMeasure - > onLayout - > onDraw 의 일련의 방법 으로 렌 더 링 이 끝 난 후에 이 등 록 된 감청 을 되 돌려 줍 니 다. 우 리 는 자 연 스 럽 게 컨트롤 의 너비 와 높이 를 얻 을 수 있 습 니 다.
2. 두 번 째 방법 은 마음 에 들 어 요. View. post () 하나 runnable 로 하면 돼 요.
1 ...
2       view.post(new Runnable() {
3             @Override
4             public void run() {
5                 view.getHeight(); //height is ready
6             }
7         });
8 ...

우 리 는 보통 이렇게 컨트롤 의 너비 와 높이 를 얻 을 수 있 는 방법 을 사용 하 는데, 방법 은 매우 좋 지만, 10 만 개의 왜 태 도 를 가지 고 있 는 지 에 따라 나 는 이 방법의 원 리 를 한 번 정리 하기 로 결정 했다.
안 드 로 이 드 의 모든 화면 은 Activity 이 고 모든 Activity 의 맨 위 는 DecorView 이 며 사용자 정의 레이아웃 을 감 싸 줍 니 다. 자, 다음은 View 의 post 방법 이 무엇 을 했 는 지 살 펴 보 겠 습 니 다.
    public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }

        // Postpone the runnable until we know on which thread it needs to run.
        // Assume that the runnable will be successfully placed after attach.
        getRunQueue().post(action);
        return true;
    }

이 안 에는 mAttachInfo 가 비어 있 는 지 아 닌 지 를 판단 합 니 다. null 이 아 닐 경우 mAttachInfo 에 저 장 된 Handler 대상 을 직접 꺼 내 서 우리 의 Runnable 작업 을 게시 합 니 다. null 이 라면 getRunQueue () 방법 이 무엇 을 하 는 지 살 펴 보 겠 습 니 다.
    private HandlerActionQueue getRunQueue() {
        if (mRunQueue == null) {
            mRunQueue = new HandlerActionQueue();
        }
        return mRunQueue;
    }

Handler Action Queue 대상 을 만 들 고 이 대상 을 되 돌려 줍 니 다. 이 대상 을 눌 러 보 겠 습 니 다.
public class HandlerActionQueue {
    private HandlerAction[] mActions;
    private int mCount;

    public void post(Runnable action) {
        postDelayed(action, 0);
    }

    public void postDelayed(Runnable action, long delayMillis) {
        final HandlerAction handlerAction = new HandlerAction(action, delayMillis);

        synchronized (this) {
            if (mActions == null) {
                mActions = new HandlerAction[4];
            }
            mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
            mCount++;
        }
    }
....

이전 코드 에서 볼 수 있 듯 이 mAttachInfo 가 null 이면 HandlerAction Queue 대상 의 post 방법 을 호출 하여 Runnable 작업 을 전달 합 니 다. 이 어 post Delayed 방법 을 호출 합 니 다. 이 방법 은 우리 의 Runnable 작업 과 지연 되 는 시간 을 HandlerAction 대상 에 밀봉 한 다음 에 아래 의 HandlerAction [] 배열 에 추가 합 니 다.눌 러 도 Handler Action 이 간단 한 패키지 클래스 라 는 것 을 알 수 있다.
 private static class HandlerAction {
        final Runnable action;
        final long delay;

        public HandlerAction(Runnable action, long delay) {
            this.action = action;
            this.delay = delay;
        }

        public boolean matches(Runnable otherAction) {
            return otherAction == null && action == null
                    || action != null && action.equals(otherAction);
        }
    }

이제 고 개 를 돌려 생각해 보면 의 심 스 러 울 것 입 니 다. 상황 은 post 방법 으로 돌아 갑 니 다. 우 리 는 mAttach Info 라 는 값 에 따라 직접 post 로 작업 을 보 내 는 지, 아니면 작업 을 대기 열 에 넣 는 지 판단 합 니 다. 그러면 이 값 은 언제 할당 되 었 습 니까?
정 답 은 ViewRootImpl 류 에서 ViewRootImpl 구조 에서 이러한 엔 드 코드 가 AttachInfo 대상 을 만 들 었 습 니 다.
 mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);

그리고 permTraversals () 방법 에서 이 코드 를 호출 합 니 다. 이 코드 는 여러분 이 잘 아 는 permMeasure, permLayout, permDraw 방법 전에 실 행 됩 니 다.
host.dispatchAttachedToWindow(mAttachInfo, 0);

host 는 DecorView 입 니 다. ViewGroup 이기 때문에 ViewGroup 의 dispatchAttached ToWindow 방법 을 살 펴 보 겠 습 니 다.
    @Override
    void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        mGroupFlags |= FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
        super.dispatchAttachedToWindow(info, visibility);
        mGroupFlags &= ~FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;

        final int count = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < count; i++) {
            final View child = children[i];
            child.dispatchAttachedToWindow(info,
                    combineVisibility(visibility, child.getVisibility()));
        }
        final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
        for (int i = 0; i < transientCount; ++i) {
            View view = mTransientViews.get(i);
            view.dispatchAttachedToWindow(info,
                    combineVisibility(visibility, view.getVisibility()));
        }
    }

주요 이 방법 에서 자신 과 모든 child 부모 클래스, 즉 View 의 dispatchAttached ToWindow 방법 을 호출 하 는 것 을 볼 수 있 습 니 다. View 의 dispatchAttached ToWindow 방법 이 도대체 무슨 일 을 했 는 지 살 펴 보 겠 습 니 다.
  /**
     * @param info the {@link android.view.View.AttachInfo} to associated with
     *        this view
     */
    void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        mAttachInfo = info;
        if (mOverlay != null) {
            mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
        }
        mWindowAttachCount++;
        // We will need to evaluate the drawable state at least once.
        mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
        if (mFloatingTreeObserver != null) {
            info.mTreeObserver.merge(mFloatingTreeObserver);
            mFloatingTreeObserver = null;
        }

        registerPendingFrameMetricsObservers();

        if ((mPrivateFlags&PFLAG_SCROLL_CONTAINER) != 0) {
            mAttachInfo.mScrollContainers.add(this);
            mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED;
        }
        // Transfer all pending runnables.
        if (mRunQueue != null) {
            mRunQueue.executeActions(info.mHandler);
            mRunQueue = null;
        }
        performCollectViewAttributes(mAttachInfo, visibility);
        onAttachedToWindow();

        ListenerInfo li = mListenerInfo;
        final CopyOnWriteArrayList listeners =
                li != null ? li.mOnAttachStateChangeListeners : null;
        if (listeners != null && listeners.size() > 0) {
            // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
            // perform the dispatching. The iterator is a safe guard against listeners that
            // could mutate the list by calling the various add/remove methods. This prevents
            // the array from being modified while we iterate it.
            for (OnAttachStateChangeListener listener : listeners) {
                listener.onViewAttachedToWindow(this);
            }
        }

        int vis = info.mWindowVisibility;
        if (vis != GONE) {
            onWindowVisibilityChanged(vis);
            if (isShown()) {
                // Calling onVisibilityChanged directly here since the subtree will also
                // receive dispatchAttachedToWindow and this same call
                onVisibilityAggregated(vis == VISIBLE);
            }
        }

        // Send onVisibilityChanged directly instead of dispatchVisibilityChanged.
        // As all views in the subtree will already receive dispatchAttachedToWindow
        // traversing the subtree again here is not desired.
        onVisibilityChanged(this, visibility);

        if ((mPrivateFlags&PFLAG_DRAWABLE_STATE_DIRTY) != 0) {
            // If nobody has evaluated the drawable state yet, then do it now.
            refreshDrawableState();
        }
        needGlobalAttributesUpdate(false);
    }

이 코드 는 비교적 길 지만, 우 리 는 한눈 에 mAttachInfo 가 바로 여기에 할당 되 어 있 는 것 을 볼 수 있 습 니 다. 할당 되 어 호출 된 곳 은 바로 위 ViewRootImpl 의 permTraversals () 방법 입 니 다!그리고 이 방법 을 이어서 보도 록 하 겠 습 니 다.
 mRunQueue.executeActions(info.mHandler);

이 코드 를 호출 하여 앞에서 언급 한 Handler Action 류 의 execute Actions 방법 을 실 행 했 습 니 다. 이 방법 이 무엇 을 했 는 지 살 펴 보 겠 습 니 다.
    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.postDelayed(handlerAction.action, handlerAction.delay);
            }

            mActions = null;
            mCount = 0;
        }
    }

ok, 모든 생각 이 뚜렷 해 졌 습 니 다. 이 방법 은 우리 가 전달 한 mAttach Info 의 Handler 로 우리 View. post 에 저 장 된 모든 Runnable 작업 을 옮 겨 다 닐 것 입 니 다.
이로써 모든 절 차 를 다 분석 한 것 같 지만 세심 한 동창 회 가 앞의 분석 에 구멍 이 있다 는 것 을 발견 하면 어디 일 까?ViewRootImpl 의 performTraversals () 방법 을 실행 할 때,
host.dispatchAttachedToWindow(mAttachInfo, 0);

이 방법 을 사용 하면 분명히 측정, 레이아웃, 세 가지 방법 을 그리 기 전에 실 행 됩 니 다. 즉, 이 방법 을 사용 하면 우리 가 mAttach Info 에서 Handler 를 전달 하여 View. post 중의 Runnable 을 실행 한 다음 에 측정, 레이아웃, 세 가지 방법 을 사용 할 수 있 습 니 다. 그러면 이론 적 으로 너비 와 높 은 값 을 얻 지 못 합 니 다. 이때 측정 을 실행 하지 않 았 습 니 다.그런데 왜 결 과 를 얻 을 수 있 을까요??
괜 찮 습 니 다. 저도 처음에 그렇게 생각 했 고 오 랜 시간 동안 곤 혹 스 러 웠 습 니 다. 이것 은 안 드 로 이 드 메시지 체 제 를 모 르 기 때문에 안 드 로 이 드 의 운행 은 사실은 메시지 구동 모델 입 니 다. 즉, 안 드 로 이 드 메 인 스 레 드 에서 기본적으로 Handler 를 만 들 었 습 니 다.또한 이 메 인 스 레 드 에 실행 대기 열 에 있 는 Message 를 반복 적 으로 옮 겨 다 니 는 Looper 를 만 들 었 습 니 다. 이 실행 은 동기 화 되 었 습 니 다. 즉, Message 를 실행 한 후에 야 다음 을 계속 실행 할 수 있 습 니 다. permTraversals () 를 호출 하 는 방법 은 메 인 스 레 드 의 Looper 를 통 해 실 행 된 것 입 니 다. 이 방법 은 아직 실행 이 끝나 지 않 았 습 니 다.그리고 우 리 는 이 방법 에서 mAttach Info 에서 Handler 를 통 해 View. post 의 Runnable 을 실행 합 니 다. mAttach Info 에서 Handler 도 메 인 스 레 드 에 만 들 어 졌 기 때문에 이전 메시지 가 실 행 된 후에 실 행 됩 니 다. 즉, 측정, 레이아웃, 그리 기 가 실 행 된 후에 실 행 됩 니 다. 그러면 자 연 스 럽 게 컨트롤 의 너비 와 높이 를 얻 을 수 있 습 니 다.
나 는 안 드 로 이 드 팀 의 이 메커니즘 이 너무 교묘 하 게 설계 되 었 다 는 것 에 탄복 하지 않 을 수 없다.

좋은 웹페이지 즐겨찾기