우리는 Activity를 사용할 때, 다음과 같이 레이아웃을 정의해둔 xml과 Activity를 연결하기 위해서 onCreate()에서 setContentView를 호출했었습니다.
class MainActivity2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
}
}
오늘은 이 setContentView가 어떤 역할을 하는지 알아보려고 합니다.
// In AppCompatActivity
@Override
public void setContentView(@LayoutRes int layoutResID){
initViewTreeOwners();
getDelegate().setContentView(layoutResID);
}
AppCompatActivity.java 클래스에 setContentView는 다음과 같이 정의되어있습니다.
첫번째로 initViewTreeOwners();부터 확인해보겠습니다
InitViewTreeOwners()
private void initViewTreeOwners() {
ViewTreeLifecycleOwner.set(getWindow().getDecorView(), this);
ViewTreeViewModelStoreOwner.set(getWindow().getDecorView(), this);
ViewTreeSavedStateRegistryOwner.set(getWindow().getDecorView(),this);
ViewTreeOnBackPressedDispatcherOwner.set(getWindow().getDecorView(),this);
}
ViewTreeOwners를 초기화한다는 함수의 이름에 걸맞게 다양한 Owner들을 설정해주고 있습니다.
ViewTree는 Android 애플리케이션의 UI를 나타내는 View의 계층 구조 입니다. 매개변수로 DecorView를 전달해주고 있는데,
이는 Android의 Root View가 DecorView인것을 짐작할 수 있습니다.
각각의 함수들은 DecorView와 각각의 Owner들을 연결합니다. 이를 통해서 Lifecycle을 처리하거나, ViewModel을 저장하고, 가져오거나, 백버튼 callback 등을 다룰 수 있습니다.
각 함수는 전달된 View에 Key와 함께 Owner들을 등록합니다.
이제 다음함수들을 보겠습니다.
getDelegate().setContnetView(layoutResId)
@NonNull
public AppCompatDelegate getDelegate(){
if(mDelegate == null){
// AppCompatDelegateImpl.java 생성
mDelegate = AppCompatDelegate.create(activity, callback)
}
return mDelegate
}
위의 함수는 AppCompatDelegateImpl.java클래스의 setContentView()를 호출합니다.
코드를 살펴보기 앞서, Activity가 어떻게 이루어져있는지 이미지를 함께 보시겠습니다.
Activity가 생성될때 ActivityThread class는 onCreate()를 호출하고, 이는 PhoneWindow를 생성합니다.
PhoneWindow는 하위로 DecorView를 생성합니다.
// In AppCompatDelegateImpl.java
@Override
public void setContentView(int resId){
ensureSubDecor();
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);
mAppCompatWindowCallBack.bypasssOnContentChanged(mWindow.getCallback());
}
ensureSubDecor()
위 함수는 Activity가 SubDecor(ViewGroup)를 생성 있는지 검증하는 단계를 거치며, 없다면 내부 createSubDecor함수를 호출합니다.
createSubDecor는 먼저 Window가 있는지 확인을 거치고, 그 다음 PhoneWindow.class의 getDecorView를 호출합니다.
private ViewGroup createSubDecor(){
{...}
ensureWindow();
mWindow.getDecorView();
{...}
}
getDecorView() 는 DecorView가 없다면 생성(installDecor)을 하는데 코드를 확인해보도록하겠습니다.
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);
/.../
}
generateDecor는 DecorView 객체를 반환합니다. 그 다음으로 중요한 부분은 generateLayout()함수입니다.
generateLayout은 현재 테마에 맞는 데이터를 설정하고, 기본으로 R.layout.screen_simple이라는 xml 파일을 View객체로 생성합니다.
screen_simple.xml의 내부입니다. 우리는 이 코드를 어디선가 본적이 있습니다.
저희가 LayoutInspector로 View 계층을 볼 때,
위의 사진처럼 볼 수 있습니다.
DrecorView안에 LinearLayout과 FrameLayout등으로 구성됨을 볼 수 있는데, 이는 screen_simple.xml로 이루어진 것을 볼 수 있습니다.
generateLayout은 FrameLayout (ViewGroup)를 반환하는데, 하위 코드에서 icon이나 title들을 지정합니다.
다시 나머지 코드를 보도록하곘습니다.
private ViewGroup createSubDecor(){
{...}
ViewGroup subDecor = null;
subDecor = (ViewGroup) LayoutInflater.from(themedContext)
.inflate(R.layout.abc_screen_toolbar, null);
mDecorContentParent = (DecorContentParent) subDecor
.findViewById(R.id.decor_content_parent);
final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
R.id.action_bar_activity_content);
final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
if (windowContentView != null) {
{...}
windowContentView.setId(View.NO_ID);
contentView.setId(android.R.id.content);
{...}
}
// Now set the Window's content view with the decor
mWindow.setContentView(subDecor);
}
subDecor라는 View객체를 생성하는 것을 볼 수 있는데, 다음이 R.layout.abc_screen_toolbar.xml입니다.
이제 FrameLayout안에 내부 xml이 위와 같이 구성되어잇다는 것을 알 수 있습니다.
windowContentView.setId(View.NO_ID);
contentView.setId(android.R.id.content);
mWindow.setContentView(subDecor);
위의 두개의 코드는 DecorView의 content로 설정된 아이디를 제거하고,
ContentFrameLayout(contentView)을 android.R.id.content ID로 접근가능하도록 해줍니다.
mWindow.setContentView함수는 installDecor로 생성된 FrameLayout에 subDecor를 추가하게 됩니다.
마지막으로 다시 AppCompatDelegateImpl.java를 봅시다.
// In AppCompatDelegateImpl.java
@Override
public void setContentView(int resId){
ensureSubDecor();
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);
mAppCompatWindowCallBack.bypasssOnContentChanged(mWindow.getCallback());
}
setContentView에서 android.R.id.content로 접근하면서 ContentFrameLayout에서 매개변수로 전달받은 xml을 추가할 수 있게 됩니다.
https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=pistolcaffe&logNo=221285539895
https://pluu.github.io/blog/android/2020/12/21/viewtreelifecycle/
'Android' 카테고리의 다른 글
BuildType과 ProductFlavors (2) | 2023.10.12 |
---|---|
Activity (0) | 2023.02.22 |
Android에서 잠금화면 만들어보자 (0) | 2023.01.19 |
Notification을 수신해보자! (0) | 2022.12.08 |
LiveData를 뜯어보자 (0) | 2022.11.23 |