코딩하기/Android

ViewStub을 통한 뷰 로딩 개선 : 안드로이드 성능 개선 #1

알랭드1종보통 2023. 10. 23. 17:09
반응형

안녕하세요 긍정열매입니다

개발을 하면 사용자 피드백으로

"왜 이리 느려요!!!" 라는 말을 들어보셨을거 같습니다.

그때마다 우리는 말합니다 .... 그...글쎄요....

이런 분들을 위해 자그마한 도움이 되고자 오늘도 글을 작성합니다.

이전 포스팅에서 전반적으로 성능에 대해서 기본적인 개념 및 성능 해결에 대한 방법 접근에 대해서 설명 드렸습니다.

https://ysmlove111.tistory.com/377

 

안드로이드 성능 문제 해결을 위해(오늘밤도 글렀..)

안녕하세요 긍정열매입니다 "왜 이리 느려요?" 어플을 개발하다보면 이런 피드백을 한번쯤 경험하게 됩니다 열심히 개발했지만 일정에 치여서, 원가절감에 의해, 화려한 애니메이션 적용을 위

ysmlove111.tistory.com

 

기억하시죠???

 

흠.....

 

그 중 오늘은 UI를 개발할때 원하는 시점에 컴포넌트를 로딩을 잘해서 성능을 향상 시키는  방법에 대해서 이야기 해보고자 합니다.

보통 안드로이드에서는 원하는 화면을 만들기 위해 다음의 두가지 방식을 많이 사용하게 됩니다.

 1. 동적으로 AddView와 RemoveView를 통해 화면을 구성

 2. xml에 미리 View를 선언해놓고 그 View를 inflate하여 사용하는 방법

위 두가지의 방법은 각각 장단점이 있어 화면 구성 방법에 따라 적절히 배분해서 사용하고 계실겁니다.

오늘 포스팅은 위 방법중 2번째 방법을 사용하는 경우에 사용할수 있는 방법입니다.

 앱을 실행하고 나서 실제 화면에 바로 그리지는 않지만 여러가지 이유로 (ex : view의 배치를 위해서) 레이아웃 xml에 미리 선언해놓고, 보이지는 않게 처리하지 않게 default 설정을 invisible혹은 gone을 설정해 놓고 처리하는 경우가 있습니다.

 그러나 이렇게 화면에 보이지 않게 처리하더라도, xml을 inflating하는 경우에는 해당 view에 대한 정보를 미리 로딩을 해놓기 때문에 성능에 영향을 미칠수가 있습니다. view 설정을 보이지 하지 않게 invisible이나 영역조차도 차지 하지 않게 gone을 설정하더라도 성능에는 영향을 미치게 됩니다.

 위의 설명을 보충하기 위해 아래와 같이 실제 샘플 코드를 작성해보겠습니다.

1. 우선 기본 실행 activity를 만들어 놓습니다

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.i(TAG, "onCreate");
        setContentView(R.layout.activity_main);
        Log.i(TAG, "onCreate : setContentView is called!");
    }
}

2.  그리고 main layout에 포함될 view를 하나 만들어 놓습니다.

public class hiddenLayout extends ConstraintLayout {
    private static final String TAG = "hiddenLayout";

    public hiddenLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        Log.i(TAG, "onCreate");
        inflate(context, R.layout.hidden, this);
    }
}

3. activity_main.xml에는 와 버튼 한개와 버튼아래 중간에 위치하게 hiddenLayout을 선언해놓고 gone으로 설정해놓았습니다.

<?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">

    <Button
        android:id="@+id/main_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <com.example.performance.hiddenLayout
        android:visibility="gone"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/main_text"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"/>

</androidx.constraintlayout.widget.ConstraintLayout>

4. 마지막으로 hidden.xml에는 TextView를 포함한 layout을 만들어 놓습니다.

<?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">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hidden!!!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

이제 실행을 해보면 어떻게 될까요? 우선 아래와 같이 버튼 하나만 보여지는 화면이 실행이 됩니다.

좋습니다 좋아요!!!

 

그런데 말입니다. 실제 내부는 어떻게 동작하게 될까요?

위 첨부된 코드를 보시면 onCreate에 실행 순서에 관한 로그를 발생시키는 코드를 activity와 hiddenLayout에 하나씩 넣어놨습니다.

2023-10-23 15:50:41.537 18167-18167 MainActivity            com.example.performance              I  onCreate
2023-10-23 15:50:41.736 18167-18167 hiddenLayout            com.example.performance              I  onCreate
2023-10-23 15:50:41.808 18167-18167 MainActivity            com.example.performance              I  onCreate : setContentView is called!

음 gone으로 설정해놓았는데도 hiddenLayout의 onCreate가 호출이 된것을 확인할수가 있습니다 이는 실행시기에 불필요한 로드가 발생될수 있음을 나타내고 있는 것입니다 ㅠㅜ

 

물론 xml에서 미리 배치가 표시되기 때문에 실제로도 유용한 방법입니만, 무거운 동작이 필요한 view를 이렇게 할경우에는 화면 로딩 속도에 영향이 많이 있겠죠??

자 그럼 이런경우에는 어떻게 처리하는게 좋을건가 한번 생각해보겠습니다

우선 Runtime에 AddView를 하고 RemoveView를 하는 방법으로 대체하는 방법도 있습니다.

그리고 오늘 소개할 ViewStub를 이용해서 원하는 시점에 inflate하게 하는 것도 한 방법입니다.

음...그리고.....

또.......어떤 방법이......흠냐..생각이......

어쨌든 ViewStub를 설명해보겠습니다 구글에서는 아래와 같이 설명하고 있네요, 

==============================================================

종류(예: 항목 세부정보, 진행률 표시기 또는 실행취소 메시지)에 상관없이 필요할 때만 뷰를 로드하여 메모리 사용을 줄이고 렌더링 속도를 높일 수 있습니다.

리소스 로드를 지연하는 것은 나중에 앱에 필요할 수도 있는 복잡한 뷰가 있을 때 사용할 중요한 기법입니다. 복잡하고 거의 사용되지 않는 뷰의 ViewStub을 정의하여 이 기법을 구현할 수 있습니다.

ViewStub은 차원이 없는 가벼운 뷰로, 무엇을 그리거나 레이아웃에 참여하지 않습니다. 따라서 확장하든 뷰 계층 구조에 그대로 남겨두든 리소스 사용이 적습니다.

link : 클릭시 이동

==============================================================

넵 알겠습니다. 

다들 아시겠죠?? .....  결국 영향은 적으니 xml에 정의해놓고 사용할때만 호출하라는 거네요

 

그럼 코드를 바꿔 보겠습니다

1. 우선 main.xml의 코드를 다음과 같이 ViewStub로 바꿉니다.

<?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">

    <Button
        android:id="@+id/main_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <ViewStub
        android:id="@+id/stub_hidden"
        android:layout="@layout/hidden"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/main_text"
        tools:visibility="visible"/>

</androidx.constraintlayout.widget.ConstraintLayout>

2. 기존에 클래스를 import했던 부분을 hidden.xml의 layout로 변경해야 됩니다.

<?xml version="1.0" encoding="utf-8"?>
<com.example.performance.hiddenLayout 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">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hidden!!!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</com.example.performance.hiddenLayout>

3. hiddenLayout의 inflate했던 코드는 삭제가 필요합니다.

package com.example.performance;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout;

public class hiddenLayout extends ConstraintLayout {
    private static final String TAG = "hiddenLayout";

    public hiddenLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        Log.i(TAG, "onCreate");
    }
}

4. 이제 사전 작업이 다 끝났으니 activity코드에서 호출하는 방법을 보겠습니다

package com.example.performance;

import androidx.appcompat.app.AppCompatActivity;
import androidx.constraintlayout.widget.ConstraintLayout;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.ViewStub;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    ConstraintLayout testView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.i(TAG, "onCreate");
        setContentView(R.layout.activity_main);
        Log.i(TAG, "onCreate : setContentView is called!");
        Button button = findViewById(R.id.main_text);
        button.setOnClickListener(view -> {
            Log.i(TAG, "button is clicked!");
            if (testView == null) {
                ViewStub stub = findViewById(R.id.stub_hidden);
                testView = (ConstraintLayout) stub.inflate();
                Log.i(TAG, "inflate is completed");
            }
        });
    }
}

위 코드에 대해 짧게 설명 드리면, view stub를 활성화 하는 시기는 버튼이 클릭되는 시기로 정했습니다. 그리고 한번 inflate되면 더이상 해당 id에는 접근이 되지 않기에, testView로 접근해서 사용하시면 됩니다.

이제 실제 로그로 확인을 해볼까요?? 아래와 같이 첫 화면을 그릴때는 hiddenLayout의 onCreate가 호출되지 않고 있습니다.

2023-10-23 16:54:26.447 26241-26241 MainActivity            com.example.performance              I  onCreate
2023-10-23 16:54:27.212 26241-26241 MainActivity            com.example.performance              I  onCreate : setContentView is called!

이후 버튼을 누르면, 아래와 같이 hiddenLayout에 onCreate가 호출이 됩니다

2023-10-23 17:02:07.117 30372-30372 MainActivity            com.example.performance              I  button is clicked!
2023-10-23 17:02:07.150 30372-30372 hiddenLayout            com.example.performance              I  onCreate
2023-10-23 17:02:07.227 30372-30372 MainActivity            com.example.performance              I  inflate is completed

 

이렇게 원하는 시점에 컴포넌트를 로드할수 있기에 화면에 당장 그릴 필요가 없는 View의 경우 ViewStub를 이용해 성능을 향상 시킬수 있습니다

 

그럼 오늘도 개발하시느라 고생하시는데 방문해주시고 읽어주셔서 감사합니다

지난 포스팅에 이어 오랜만에 글을 올렸는데요.. 다음에는 좀더 빠르게 찾아뵙겠습니다!!

 

반응형