Android View 소스 코드 해독 DecorView 와 ViewRootImpl 에 대한 간단 한 설명

머리말
Android 개발 자 에 게 View 는 개발 과정 에서 자주 접 하 는 것 임 에 틀림없다.이 는 이벤트 배포 체제,측정,레이아웃,그리 기 절차 등 을 포함한다.만약 에 View 를 사용자 정의 하려 면 상기 절차 에 대해 알 고 연구 해 야 한다.이 시 리 즈 는 여러분 에 게 View 의 업무 절차 에 대한 상세 한 해석 을 가 져 다 줄 것 입 니 다.View 의 측정,구 조 를 깊이 접촉 하고 이 세 가지 절 차 를 그리 기 전에 우 리 는 Activity 에 착안 하여 Activity 가 생 긴 후에 View 의 정식 작업 까지 겪 어야 할 절 차 를 살 펴 보 자.다음 소스 코드 는 모두 Android API 21 에서 추출 합 니 다.
set ContentView 부터.
일반적으로,우 리 는 Activity 에서 onCreate()방법 에 다음 과 같은 문장 을 쓴다.
setContentView(R.layout.main);
분명히 이것 은 activity 에 우리 가 정의 한 main.xml 레이아웃 을 설정 하 는 것 입 니 다.우 리 는 소스 코드 를 추적 해서 이 방법 이 어떻게 하 는 지 보 겠 습 니 다.Activity\#set ContentView:

public void setContentView(@LayoutRes int layoutResID) {
   getWindow().setContentView(layoutResID); //  getWindow  ,  mWindow
   initWindowDecorActionBar();
}
...
public Window getWindow() {  
   return mWindow;
}
위 에서 보 듯 이 안에 mWindow 의 set ContentView 방법 이 호출 되 었 습 니 다.그러면 이'mWindow'는 어떤 신성 한 것 입 니까?원본 코드 를 추적 해 보 니 mWindow 는 Window 형식 이지 만 추상 적 인 유형 입 니 다.set ContentView 도 추상 적 인 방법 이기 때문에 Window 류 의 실현 류 를 찾 아야 합 니 다.Activity 에서 mWindow 가 어디 에 할당 되 었 는 지 찾 아 보면 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) {
    ...
    mWindow = new PhoneWindow(this);
    ...
  }


우 리 는 관건 적 인 부분 만 보고 Phone Window 류 를 예화 했다.이 를 통 해 Phone Window 는 Window 의 실현 류 라 는 것 을 알 수 있다.그러면 우 리 는 Phone Window 류 에서 그의 set ContentView 방법 을 찾 아 그것 이 무엇 을 실현 하 는 지 살 펴 보 자.Phone Window\#set ContentView:

@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) { // 1
    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); // 2
  }
  final Callback cb = getCallback();
  if (cb != null && !isDestroyed()) {
    cb.onContentChanged();
  }
}


먼저 mContentParent 가 null 인지 아 닌 지 를 판 단 했 습 니 다.비어 있 으 면 installDecor()방법 을 실행 합 니 다.그러면 이 mContentParent 는 무엇 입 니까?우 리 는 그것 의 주석 을 좀 봅 시다.

// This is the view in which the window contents are placed. It is either
// mDecor itself, or a child of mDecor where the contents go.
private ViewGroup mContentParent;


이것 은 ViewGroup 형식 입 니 다.② 번 코드 와 결합 하면 이 mContentParent 는 우리 가 설정 한 레이아웃(즉,main.xml)의 부모 레이아웃 임 을 알 수 있 습 니 다.설명 에 따 르 면 이 mContentParent 는 mDecor 자체 또는 mDecor 의 키 요소 입 니 다.이 말 은 무슨 뜻 입 니까?여기에 먼저 의문 을 남 겨 두 고 다음 에 설명 하 겠 습 니 다.
여기 서 이상 의 내용 을 정리 합 니 다.Activity 는 PhoneWindow 의 set ContentView 방법 으로 레이아웃 을 설정 하고 레이아웃 을 설정 하기 전에 mContentParent 가 존재 하 는 지 여 부 를 판단 합 니 다.저희 가 설정 한 레이아웃 파일 은 mContentParent 의 하위 요소 입 니 다.
DecorView 만 들 기
이 어 위 에서 언급 한 installDecor()방법 을 살 펴 보 자.원본 코드,Phone Window\#installDecor:

private void installDecor() {
  if (mDecor == null) {
    mDecor = generateDecor(); // 1
    mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
    mDecor.setIsRootNamespace(true);
    if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
      mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
    }
  }
  if (mContentParent == null) {
    mContentParent = generateLayout(mDecor); // 2
    ...
    } 
  }
}
우선 ① 번 코드 를 실행 하고 Phone Window\#generateDecor 방법 을 호출 합 니 다.

protected DecorView generateDecor() {
  return new DecorView(getContext(), -1);
}
이 를 통 해 알 수 있 듯 이 DecorView 는 Phone Window 류 의 내부 류 로 FrameLayout 에 계승 되 었 다.이 를 통 해 알 수 있 듯 이 이것 도 View Group 이다.
그렇다면 디 크 로 뷰 는 어떤 역할 을 맡 았 을 까?
사실은 DecorView 는 전체 ViewTree 의 최상 위 View 로 FrameLayout 레이아웃 으로 전체 응용 화면 을 대표 합 니 다.이 레이아웃 아래 에는 제목 view 와 내용 view 라 는 두 가지 요소 가 있 고 내용 view 는 위 에서 언급 한 mContentParent 입 니 다.② 번 코드,PhoneWindow\#generateLayout 방법 을 살 펴 보 겠 습 니 다.

protected ViewGroup generateLayout(DecorView decor) {
    // Apply data from current theme.
    //             
    TypedArray a = getWindowStyle();

    ...

    if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
      requestFeature(FEATURE_NO_TITLE);
    } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
      // Don't allow an action bar if there is no title.
      requestFeature(FEATURE_ACTION_BAR);
    }

    if(...){
      ...
    }

    // Inflate the window decor.
    //       
    int layoutResource;
    int features = getLocalFeatures();
    // System.out.println("Features: 0x" + Integer.toHexString(features));
    if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
      layoutResource = R.layout.screen_swipe_dismiss;
    } else if(...){
      ...
    }

    View in = mLayoutInflater.inflate(layoutResource, null);  //  layoutResource
    decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); // DecorView    View, mContentParent
    mContentRoot = (ViewGroup) in;

    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); //        mContentParent
    if (contentParent == null) {
      throw new RuntimeException("Window couldn't find content container view");
    }

    if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
      ProgressBar progress = getCircularProgressBar(false);
      if (progress != null) {
        progress.setIndeterminate(true);
      }
    }

    if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
      registerSwipeCallbacks();
    }

    // Remaining setup -- of background and title -- that only applies
    // to top-level windows.
    ...

    return contentParent;
  }


상기 코드 를 통 해 알 수 있 듯 이 이 방법 은 상당히 많은 작업 을 했 습 니 다.먼저 설 정 된 테마 스타일 에 따라 DecorView 의 스타일 을 설정 합 니 다.예 를 들 어 titlebar 같은 것 이 있 는 지 없 는 지,그 다음 에 DecorView 에 서브 View 를 추 가 했 습 니 다.여기 있 는 서브 View 는 위 에서 언급 한 mContentParent 입 니 다.만약 에 위 에 FEATURE 가 설치 되 어 있다 면.NO_ACTION BAR,그러면 DecorView 는 mContentParent 의 키 View 만 있 습 니 다.이것 은 위의 의문 을 설명 합 니 다.mContentParent 는 DecorView 자체 또는 DecorView 의 키 요소 입 니 다.
DecorView 의 구 조 는 다음 과 같다.

소결:DecorView 는 최고급 View 로 내부 에 titlebar 와 contentParent 두 개의 키 요소 가 있 습 니 다.contentParent 의 id 는 content 이 고 우리 가 설정 한 main.xml 레이아웃 은 contentParent 의 키 요소 입 니 다.
DecorView 생 성 이 끝 난 후에 Phone Window\#setContentView 방법 으로 돌아 가 ② 번 코드:mLayout Inflater.inflate(layoutResID,mContentParent)를 직접 봅 니 다.여기에 우리 가 설정 한 main.xml 레이아웃 파일 을 불 러 오고 mContentParent 를 main.xml 의 부모 레이아웃 으로 설정 합 니 다.어떻게 불 러 오 는 지 에 대해 서 는 설명 하지 않 겠 습 니 다.
지금까지 set ContentView 방법 을 통 해 DecorView 를 만 들 고 우리 가 제공 하 는 레이아웃 을 불 러 왔 습 니 다.그러나 이때 우리 의 View 는 보이 지 않 습 니 다.우 리 는 레이아웃 만 불 러 왔 을 뿐 View 에 대해 어떠한 측정,레이아웃,그리 기 작업 도 하지 않 았 기 때 문 입 니 다.View 에서 측정 절 차 를 진행 하기 전에 한 단계 더 진행 해 야 합 니 다.그것 은 DecorView 를 window 에 추가 한 다음 에 일련의 과정 을 거 쳐 ViewRootImpl\#permTraversals 방법 을 촉발 하 는 것 입 니 다.이 방법 내부 에서 본 격 적 으로 측정,구조,이 세 가지 절 차 를 그립 니 다.이 일련의 과정 이 어떤 것 인지 에 대해 많은 메커니즘 과 관련 되 었 기 때문에 여기 서 간단하게 설명 한다.
DecorView 를 Window 에 추가 하기
모든 Activity 구성 요 소 는 프로그램 창 을 설명 하기 위해 연 결 된 Window 대상 이 있 습 니 다.모든 프로그램 창 내부 에는 프로그램 창의 보 기 를 설명 하 는 View 대상 이 포함 되 어 있 습 니 다.이 글 은 DecorView 를 만 드 는 과정 을 분 석 했 으 며,이 제 는 DecorView 를 Window 대상 에 추가 해 야 합 니 다.이 과정 을 이해 하려 면 먼저 Activity 의 생 성 과정 을 간단하게 알 아야 합 니 다.
우선,Activity Thread\#handle LaunchActivity 에서 Activity 를 시작 합 니 다.이 안에서 Activity\#onCreate 방법 으로 호출 되 어 위 에서 말 한 DecorView 생 성 동작 을 완성 합 니 다.onCreate()방법 이 실 행 될 때 handle LaunchActivity 방법 은 Activity Thread\#handle Resume Activity 방법 으로 계속 호출 됩 니 다.이 방법의 소스 코드 를 살 펴 보 겠 습 니 다.

final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) { 
  //...
  ActivityClientRecord r = performResumeActivity(token, clearHide); //       onResume()  

  if (r != null) {
    final Activity a = r.activity;

    //...
    if (r.window == null && !a.mFinished && willBeVisible) {
      r.window = r.activity.getWindow(); //   window  
      View decor = r.window.getDecorView(); //   DecorView  
      decor.setVisibility(View.INVISIBLE);
      ViewManager wm = a.getWindowManager(); //   windowManager  
      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); //   addView  
      }
      //...
    }
  }
}
이 방법 내부 에서 이 activity 와 관련 된 window 대상,DecorView 대상,window Manager 대상 을 가 져 옵 니 다.Window Manager 는 추상 적 인 클래스 입 니 다.이 실현 류 는 Window Manager Impl 이기 때문에 다음 에 Window Manager Impl\#addView 방법 을 호출 합 니 다.소스 코드 를 보 겠 습 니 다.

public final class WindowManagerImpl implements WindowManager {  
  private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
  ...
  @Override
  public void addView(View view, ViewGroup.LayoutParams params) {
    mGlobal.addView(view, params, mDisplay, mParentWindow);
  }
}
이 어 mGlobal 의 구성원 함 수 를 호출 했 고 mGlobal 은 Window Manager Global 의 인 스 턴 스 입 니 다.그러면 Window Manager Global\#addView 방법 을 보 겠 습 니 다.

public void addView(View view, ViewGroup.LayoutParams params,
      Display display, Window parentWindow) {
    ...

    ViewRootImpl root;
    View panelParentView = null;

    synchronized (mLock) {
      ...

      root = new ViewRootImpl(view.getContext(), display); // 1

      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); // 2
    } catch (RuntimeException e) {
      // BadTokenException or InvalidDisplayException, clean up.
      synchronized (mLock) {
        final int index = findViewLocked(view, false);
        if (index >= 0) {
          removeViewLocked(index, true);
        }
      }
      throw e;
    }
  }
① 번 코드 를 먼저 보고 ViewRootImpl 류 를 예화 한 다음 ② 번 코드 에서 ViewRootImpl\#setView 방법 을 호출 하고 DecorView 를 매개 변수 로 전달 합 니 다.이 방법 내부 에 서 는 프로 세 스 를 뛰 어 넘 는 방식 으로 WMS(Window Manager Service)에 호출 하여 DecorView 를 최종 적 으로 Window 에 추가 합 니 다.이 과정 에서 ViewRootImpl,DecorView 와 WMS 는 서로 관련 이 있 을 것 이 며 상세 한 과정 은 여기 서 전개 되 지 않 는 다.
마지막 으로 WMS 를 통 해 ViewRootImpl\#performTraverals 방법 으로 View 의 측정,레이아웃,그리 기 절 차 를 시작 합 니 다.이 세 가지 절 차 는 다음 글 에서 상세 하 게 설명 하 겠 습 니 다.읽 어 주 셔 서 감사합니다.

좋은 웹페이지 즐겨찾기