Android/Java

[Java][Android] Android ViewTreeObserVer onGlobalLayoutListener 활용하기

뎁요 2022. 8. 24. 01:34

오늘도 회사에서 메모리를 정리 중 앱이 destory 되었음에도 porifler에서 인스턴스들을 참조하고 있는 것이 확인이 되었습니다.

GC를 아무리 돌려도 참조가 해제 되지 않아 해당 부분을 확인해본 결과 MainView에서 ViewTressObeserver에 onGlobalLayoutListener를 등록한 것이 해제가 되지 않는 것으로 확인되었습니다. 

 

그런 의미로 오늘은 ViewTreeObserver가 뭔지 또 어떻게 사용하는지에 왜 ViewTreeObserver에서 참조가 해제가 되지 않았는지에 대해 포스팅해보려 합니다 😎

 

ViewTreeObserver란 무엇인가?

A view tree observer is used to register listeners that can be notified of global changes in the view tree. Such global events include, but are not limited to, layout of the whole tree, beginning of the drawing pass, touch mode change.... A ViewTreeObserver should never be instantiated by applications as it is provided by the views hierarchy

 

Android Developer에서는 위와 같이 설명하고 있습니다.

번역하자면 ViewTreeObserver는 ViewTree의 변경 사항을 알릴 수 있는 리스너를 등록하는 데 사용됩니다. 전역 이벤트에는 전체 트리의 레이아웃, 드로잉 패스의 시작, 터치 모드 변경 등이 있으며, ViewTreeObserver는 뷰 계층 구조에서 제공하므로 인스턴스화 해서는 안됩니다.라고 변역 되면 간단하게 말해 ViewTree의 상태를 관찰하는 관찰자라고 생각하면 될 것 같습니다.

 

ViewTreeObserver에서 제공하는 이벤트는 아래와 같으며 필요한 상황에 따라 사용하면 됩니다.

ViewTreeObserver.OnDrawListener
//뷰를 그릴 때


ViewTreeObserver.OnGlobalFocusChangeListener
//전체 뷰의 포커스가 바뀔 때


ViewTreeObserver.OnGlobalLayoutListener
//전체 뷰가 그려질 때


ViewTreeObserver.OnPreDrawListener
//뷰가 그려지기 전


ViewTreeObserver.OnScrollChangedListener
//스크롤 상태의 변경 될 때


ViewTreeObserver.OnTouchModeChangeListener
// 터치 모드 변경 될 때


ViewTreeObserver.OnWindowAttachListener
// 뷰의 계층 구조에 붙는 상황들 (붙거나 떨어지거나 둘다)


ViewTreeObserver.OnWindowFocusChangeListener
// 윈도우 포커스가 변경 될 때

 

그 중 오늘 제가 포스팅할 내용은 onGlobalLayoutListener에 대한 내용이며 이미지가 그려질 때 이미지의 높이를 받아 TextView에 표시하는 동작을 구현해보았습니다.

 

코드는 아래와 같습니다.

 

- 레이아웃 xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <ImageView
            android:id="@+id/image_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/android_bg"
            />

        <TextView
            android:id="@+id/height_text_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:layout_gravity="center"
            android:gravity="center"
            android:text="높이"
            android:textSize="22sp" />
    </LinearLayout>



</androidx.constraintlayout.widget.ConstraintLayout>

- JAVA 코드

package com.example.viewtreeobserverparctice;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private final static String TAG = "MainActivity";

    TextView mTextView;
    ImageView mImageView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "onCreate()");
        setContentView(R.layout.activity_main);

        initView();

        if(mImageView != null){
            mImageView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    Log.d(TAG, "onGlobalLayout: ImageView set!");
                    if(mTextView != null){
                        mTextView.setText("높이는 " + mImageView.getHeight() + "입니다.");
                        mImageView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                    }
                }
            });
        }
    }

    private void initView() { //initialize views
        Log.d(TAG, "initView()");
        mTextView = findViewById(R.id.height_text_view);
        mImageView = findViewById(R.id.image_view);
    }

    private void removeInstance() {
        Log.d(TAG, "removeInstance()");
        mTextView = null;
        mImageView = null;
    }

    @Override
    protected void onDestroy() {
        removeInstance();
        super.onDestroy();
        Log.d(TAG, "onDestroy()");
    }
}

 

※ ViewTreeObserver는 변경 감지가 더 이상 필요하지 않을 경우에는 추가해준 리스너를 반드시 제거해야 하며 해당 동작을 수행하지 않을 경우 메모리 누수 및 Listener를 계속 호출 하는 상태가 발생 할 수 있습니다. 그렇기에 해당 리스너를 더이상 사용이 필요하지 않을 경우에는 판단에 따라 적절하게 리스너를 해제해주어야 합니다. 방법은 위 와 같이 removeOnGlobalLayoutListener()를 통해 해제해주면 됩니다.