Android 응용프로그램 레이어 View 드로잉 프로세스의 DecorView 및 ViewRootImpl

개술
안드로이드에서 View의 전체 그리기 절차에 대해 잘 알지 못했는데, View는Activity에 어떻게 추가되었습니까?View의 내용이 바뀌었을 때 인터페이스의 리셋을 어떻게 실행합니까?따라서 오늘은 API25를 기반으로 한 뷰의 전체 그리기 프로세스를 소스의 관점에서 분석하려고 합니다.편폭 제한으로 인해 이 글은 맨 윗부분 보기DecorView의 디스플레이 논리만 분석하고 구체적인 보기 트리는 세 곡을 그린다. 그것이 바로 measure,layout,draw이다. 다음 블로그에서 깊이 있게 분석할 것이다.
Activity의 setContentView 방법부터
Activity에 레이아웃을 설정하는 방법은 일반적으로 setContentView 방법을 호출하는 것으로 알고 있습니다. 이 방법은 다음과 같은 몇 가지 재부팅 방법이 있습니다.
public void setContentView(@LayoutRes int layoutResID) {
     getWindow().setContentView(layoutResID);
     initWindowDecorActionBar();
 }

 public void setContentView(View view) {
       getWindow().setContentView(view);
       initWindowDecorActionBar();
   }

public void setContentView(View view, ViewGroup.LayoutParams params) {
       getWindow().setContentView(view, params);
       initWindowDecorActionBar();
}

위의 세 가지 방법에서 볼 수 있듯이 모두 getWindow ()의 상대적인 방법을 사용했다. getWindow () 방법을 살펴보자.
public Window getWindow() {
       return mWindow;
   }

이를 통해 알 수 있듯이 이 방법은 윈도 유형의 변수인 m윈도를 되돌려준다. 그러면 이 m윈도는 반드시Activity 어딘가에서 초기화되었을 것이다. 아래와 같이attach 방법에서 초기화되었다.
final void attach(Context context, ActivityThread aThread,
           Instrumentation instr, IBinder token, int ident,
           Application application, Intent intent, ActivityInfo info,
           CharSequence title, Activity parent, String id,
           NonConfigurationInstances lastNonConfigurationInstances,
           Configuration config, String referrer, IVoiceInteractor voiceInteractor,
           Window window) {
       attachBaseContext(context);

       mFragments.attachHost(null /*parent*/);

       mWindow = new PhoneWindow(this, window);
       mWindow.setWindowControllerCallback(this);
       mWindow.setCallback(this);
       mWindow.setOnWindowDismissedCallback(this);
       mWindow.getLayoutInflater().setPrivateFactory(this);
       ..........
       ..........
   }

mWindow는 PhoneWindow 유형의 변수입니다. 사실 추상 클래스인 Window의 첫 번째 프로필을 보면 PhoneWindow는 Window의 유일한 하위 클래스임을 알 수 있습니다!
/**
 * Abstract base class for a top-level window look and behavior policy.  An
 * instance of this class should be used as the top-level view added to the
 * window manager. It provides standard UI policies such as a background, title
 * area, default key processing, etc.
 *
 * 

The only existing implementation of this abstract class is * android.view.PhoneWindow, which you should instantiate when needing a * Window. */

public abstract class Window {

다음은 Activity에 해당하는 PhoneWindow의 setContentView 메서드와 세 가지 재로드 메서드를 살펴보겠습니다.

 @Override
public void setContentView(int layoutResID) {
    // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
    // decor, when theme attributes and the like are crystalized. Do not check the feature
    // before this happens.
    if (mContentParent == null) {
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }

   if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                getContext());
        transitionTo(newScene);
    } else {
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
    mContentParentExplicitlySet = true;
}

@Override
public void setContentView(View view) {
    setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}

@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
    // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
    // decor, when theme attributes and the like are crystalized. Do not check the feature
    // before this happens.
    if (mContentParent == null) {
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }
    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        view.setLayoutParams(params);
        final Scene newScene = new Scene(mContentParent, view);
        transitionTo(newScene);
    } else {
        mContentParent.addView(view, params);
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
    mContentParentExplicitlySet = true;
}

setContentView(View view, ViewGroup.LayoutParams) 메서드를 분석합니다.
  • 먼저 mContentParent가null인지 아닌지를 판단하고null이면 실행 방법인installDecor를 실행한다. 이 mContentParent는 ViewGroup 유형이다. 이 방법은 다음과 같다.
  •  private void installDecor() {
            mForceDecorInstall = false;
            if (mDecor == null) {
                mDecor = generateDecor(-1);
                mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
                mDecor.setIsRootNamespace(true);
                if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                    mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
                }
            } else {
                mDecor.setWindow(this);
            }
            if (mContentParent == null) {
                mContentParent = generateLayout(mDecor);
            ........
            .......
    }

    generate Decor는 mDecor를 생성하는 데 사용되는데 mDecor는 DecorView 유형이고 전체Activity의 맨 윗부분 보기입니다. DecorView는 Frame Layout의 하위 클래스입니다. 관심 있는 사람은 DecorView 원본을 볼 수 있습니다. 여기는 단지 결론을 내릴 뿐입니다.
  • 그 다음에generateLayout 방법이다. 이 방법은 매우 길다. 핵심 코드를 보면
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
      if (contentParent == null) {
          throw new RuntimeException("Window couldn't find content container view");
      }
    ....
    return contentParent;
  • 위에서 설명한 mContent Parent는 최상위 뷰 mDecor의 하위 뷰이고 이 하위 뷰의 id는 다음과 같습니다.
      /**
        * The ID that the main layout in the XML layout file should have.
        */
       public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
  • 따라서 설치 데코 방법을 실행한 후에 mDecor와 mContent Parent를 얻었고 그 다음에 중요한 코드가 다음과 같다.
  •      mContentParent.addView(view, params);

    이 코드를 통해Activity에서 설정한 레이아웃 보기를 mContent Parent에 추가합니다.
    계층 관계는 DecorView > contentParent > Activity의 레이아웃입니다.
    mContentParent는 ViewGroup 유형이므로 다음과 같이 ViewGroup#addView 메서드를 봅니다.
    /**
        * Adds a child view with the specified layout parameters.
        *
        * 

    Note: do not invoke this method from * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)}, * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.

    * * @param child the child view to add * @param index the position at which to add the child or -1 to add last * @param params the layout parameters to set on the child */
    public void addView(View child, int index, LayoutParams params) { if (DBG) { System.out.println(this + " addView"); } if (child == null) { throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup"); } // addViewInner() will call child.requestLayout() when setting the new LayoutParams // therefore, we call requestLayout() on ourselves before, so that the child's request // will be blocked at our level requestLayout(); invalidate(true); addViewInner(child, index, params, false); }

    이addView 방법은 마지막으로 RequestLayout () 방법과 invalidate () 방법을 사용합니다. 이 두 가지 방법은 전체 View 트리를 다시 그립니다.Activity에서 사용자 정의한 레이아웃 파일을 보여 줍니다.
    전체 과정은 다음과 같이 요약된다.
    Activity.setContentView -> PhoneWindow.set Content View ->Phone Window의 mDecor와 mContent Parent 초기화 ->Activity의 레이아웃 보기를 mContent Parent ->에 추가하여 전체 View 트리를 다시 그려서 레이아웃 파일을 표시합니다!
    DecorView가 어떻게 나오는지
    앞에서 말했듯이 DecorView는 전체Activity의 맨 윗부분 보기입니다. 그러면 이 DecorView는 어떻게 나타납니까?주요 실현 과정은ActivityThread의handleResumeActivity 방법에서 다음과 같다.
    final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        ActivityClientRecord r = mActivities.get(token);
        if (!checkAndUpdateLifecycleSeq(seq, r, "resumeActivity")) {
            return;
        }
    
        // If we are getting ready to gc after going to the background, well
        // we are back active so skip it.
        unscheduleGcIdler();
        mSomeActivitiesChanged = true;
    
        // TODO Push resumeArgs into the activity for consideration
        r = performResumeActivity(token, clearHide, reason);
    
        if (r != null) {
            final Activity a = r.activity;
    
            if (localLOGV) Slog.v(
                TAG, "Resume " + r + " started activity: " +
                a.mStartedActivity + ", hideForNow: " + r.hideForNow
                + ", finished: " + a.mFinished);
    
            final int forwardBit = isForward ?
                    WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;
    
            // If the window hasn't yet been added to the window manager,
            // and this guy didn't finish itself or start another activity,
            // then go ahead and add the window.
            boolean willBeVisible = !a.mStartedActivity;
            if (!willBeVisible) {
                try {
                    willBeVisible = ActivityManagerNative.getDefault().willActivityBeVisible(
                            a.getActivityToken());
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            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 (r.mPreserveWindow) {
                    a.mWindowAdded = true;
                    r.mPreserveWindow = false;
                    // Normally the ViewRoot sets up callbacks with the Activity
                    // in addView->ViewRootImpl#setView. If we are instead reusing
                    // the decor view we have to notify the view root that the
                    // callbacks may have changed.
                    ViewRootImpl impl = decor.getViewRootImpl();
                    if (impl != null) {
                        impl.notifyChildRebuilt();
                    }
                }
                if (a.mVisibleFromClient && !a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                }
    
            ................................
        }
    }

    상기 방법에서 보듯이 우선ActivityThread가 저장한 mActivities 목록에서 ActivityClientRecord 대상을 얻을 수 있으며 이 대상은Activity의 일부 정보를 저장한다.
  • Activity를 받은 후 View decor = r.window를 호출합니다.getDecorView();방법은 맨 윗부분 보기DecorView를 얻을 수 있다. 이 보기는 앞에서 말했듯이 PhoneWindow에 저장된다. 즉,Activity가 PhoneWindow에 대응하여DecorView에 대응한다.
  • 그 다음에 ViewManagerwm = a.getWindow Manager()를 호출합니다.방법은Activity의 윈도우 관리자 대상을 얻는데 왜 ViewManager 대상을 되돌려줍니까?Window Manager 인터페이스를 보면 다음과 같이 Window Manager가 ViewManager 인터페이스를 상속하는 것을 알 수 있습니다.
  •       public interface WindowManager extends ViewManager {

    ViewManager 인터페이스는 다음과 같습니다. 주석에서 알 수 있듯이 이 인터페이스는 주로 왕래하는Activity에 View를 추가하거나 제거합니다.
    /** Interface to let you add and remove child views to an Activity. To get an instance
      * of this class, call {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.
      */
    public interface ViewManager
    {
        /**
         * Assign the passed LayoutParams to the passed View and add the view to the window.
         * 

    Throws {@link android.view.WindowManager.BadTokenException} for certain programming * errors, such as adding a second view to a window without removing the first view. *

    Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a * secondary {@link Display} and the specified display can't be found * (see {@link android.app.Presentation}). * @param view The view to be added to this window. * @param params The LayoutParams to assign to view. */

    public void addView(View view, ViewGroup.LayoutParams params); public void updateViewLayout(View view, ViewGroup.LayoutParams params); public void removeView(View view); }
  • Activity의 윈도우 관리자를 받은 후wm를 호출합니다.addView(decor, l);Window Manager에 DecorView를 추가하는 방법입니다.우리는 윈도우 관리자가 하나의 인터페이스일 뿐이라는 것을 알고 있다. 구체적인 실현 유형은 윈도우 관리자 Impl이다. 그 addView 방법을 살펴보자.
  • @Override
       public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
           applyDefaultToken(params);
           mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
       }

    이 addView 방법은 사실 구성원 변수 mGlobal의addVeiw 방법을 호출한 것을 발견했다. mGlobal은 Window Manager Global 유형이다. 다음의addView 방법을 살펴보자. 아래와 같다.
    public void addView(View view, ViewGroup.LayoutParams params,
               Display display, Window parentWindow) {
           ........................
           ViewRootImpl root;
           View panelParentView = null;
    
          .........................
               root = new ViewRootImpl(view.getContext(), display);
    
               view.setLayoutParams(wparams);
    
               mViews.add(view);
               mRoots.add(root);
               mParams.add(wparams);
          ...........................
    
           // do this last because it fires off messages to start doing things
           try {
               root.setView(view, wparams, panelParentView);
           } catch (RuntimeException e) {
               // BadTokenException or InvalidDisplayException, clean up.
               synchronized (mLock) {
                   final int index = findViewLocked(view, false);
                   if (index >= 0) {
                       removeViewLocked(index, true);
                   }
               }
               throw e;
           }
       }

    상기 코드를 통해 맨 윗부분 보기DecorView가 최종적으로ViewRootImpl에 추가되었고 setView 방법에서 일부 조작을 실행하여DecorView가 표시되었음을 알 수 있다.이 방법은 비교적 길어서 모두가 스스로 볼 수 있는데 그 안에 관건적인 코드가 하나 있다.
    // Schedule the first layout -before- adding to the window
    // manager, to make sure we do the relayout before receiving
    // any other events from the system.
    requestLayout();

    RequestLayout 메서드는 다음과 같습니다.
        @Override
        public void requestLayout() {
            if (!mHandlingLayoutInLayoutRequest) {
                checkThread();
                mLayoutRequested = true;
                scheduleTraversals();
            }
        }

    이 방법에서는 다음과 같이 scheduleTraversals 방법이 호출됩니다.
    void scheduleTraversals() {
           if (!mTraversalScheduled) {
               mTraversalScheduled = true;
               mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
               mChoreographer.postCallback(
                       Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
               if (!mUnbufferedInputDispatch) {
                   scheduleConsumeBatchedInput();
               }
               notifyRendererOfFramePending();
               pokeDrawLockIfNeeded();
           }
       }

    이 방법에서 mTraversal Runnable을post에 메시지 대기열에 넣고 이 Runnable에서 어떤 조작을 실행했는지 보십시오:
    final class TraversalRunnable implements Runnable {
           @Override
           public void run() {
               doTraversal();
           }
       }
       final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

    위에서 보듯이 TraversalRunnable에서doTraversal 방법을 실행했습니다.
    void doTraversal() {
           if (mTraversalScheduled) {
               mTraversalScheduled = false;
               mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
    
               if (mProfile) {
                   Debug.startMethodTracing("ViewAncestor");
               }
    
               performTraversals();
    
               if (mProfile) {
                   Debug.stopMethodTracing();
                   mProfile = false;
               }
           }
       }

    이 방법에서performTraversals 방법을 집행했는데 이 방법은 최종적으로 전체View트리의 그리기 절차를 책임지기 때문에 이 방법이 비교적 관건적이다.이 방법은 비교적 길고 그림을 그리는 View 트리의 핵심 문장은 다음과 같다. 그 중에서 mView가 바로 맨 윗부분 보기DecorView이다.
    private void performTraversals() {
            ......
            int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
            int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
            ......
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            ......
            mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
            ......
            mView.draw(canvas);
            ......
        }

    위에서 상단 보기DecorView의 디스플레이 메커니즘을 깊이 있게 분석한 결과 층층이 분석을 통해 최종적으로ViewRootImpl이 전체 View 트리의 그리기를 책임진다는 것을 알 수 있다.measure,layout,draw는View 트리에서 세 가지 주요 프로세스를 그리는데 이 세 가지 기본 프로세스의 원리를 이해해야만 View를 사용자 정의할 때 여유를 가질 수 있다(물론 View 이벤트 분배 메커니즘도 관건적이다)!이 세 가지 절차에 대한 구체적인 세부 분석은 다음 블로그에서 설명할 것이다.읽어주셔서 감사합니다!

    좋은 웹페이지 즐겨찾기