23.06.15
DEVOTEE를 활성화 시키면
지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.
버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.
| 컨텐츠 유형 | 제목 | 저장일 | 삭제 |
|---|
본인인증 로그인에 실패하였습니다.
회원이 아니시거나 본인인증 등록이
완료되지 않은 사용자입니다.
앱의 품질을 말할 때 기능성 외에 사용자들이 체감할 수 있는 가장 중요한 품질은 바로 반응 속도입니다.
이는 앱의 비기능적 품질 속성 중에서도 사용자의 만족도에 직접적인 영향을 주는 요소입니다.
‘반응 속도’라는 표현에는 앱 실행 속도, 페이지 로딩 속도, 콘텐츠 다운로드 시간, 화면 렌더링 속도 등 다양한 요소가 포함됩니다.
이 글에서는 이러한 성능 품질에 대해 살펴보려 합니다.
그럼 앱 성능 측정을 할 때 제일 먼저 고려해야 할 사항은 무엇일까요?
테스트 대상, 환경, 범위, 테스트 시나리오, 품질 기준, 테스트 수행 방법 등은 모든 테스트의 기본 항목이며, 성능 테스트에서도 예외가 아닙니다.
테스트 대상이 되는 앱이 네이티브 앱인지 하이브리드 앱인지에 따라 성능 측정 지표를 선택해야 합니다.
웹뷰 기반 앱의 성능을 측정할 때는 앱 성능 지표(예: 앱 시작 속도, 메모리 사용량, 프레임 드랍율 등)과 함께 웹 성능 지표(예: FCP, LCP, CLS 등)도 함께 고려해야 정확한 품질 진단이 가능합니다.
앱은 빠르게 실행되더라도, 웹뷰 내부의 웹 콘텐츠 로딩이 느릴 경우 사용자는 전체적으로 느린 앱으로 인식하게 됩니다.
웹뷰 포함 여부와 관계없이 모든 플랫폼에서 공통으로 고려할 수 있는 성능 지표입니다.
서버나 백엔드와의 상호작용을 포함하는 모든 앱에서 기본적으로 측정됩니다.
응답 시간 (Response Time): 사용자의 요청에 대해 시스템이 응답하는 데 걸리는 시간
API 지연 시간 (API Latency): 백엔드 서버와의 통신 시간
처리량 (Throughput): 단위 시간당 처리 가능한 요청 수 (RPS, TPS 등)
CPU / 메모리 사용량: 실행 중인 프로세스가 소비하는 시스템 자원
오류율 (Error Rate): 전체 요청 대비 실패 요청 비율
Page Load Time (PL): 화면 또는 페이지가 요청된 후 첫 번째 UI가 완전히 표시될 때까지의 시간 (3초를 넘으면 사용자의 43%가 이탈하며, 5초가 넘으면 이탈률이 90%까지 증가한다고 합니다.)
사용자 인터랙션 반응 시간: 사용자가 탭, 스크롤, 입력 등의 행동을 했을 때, 앱이 이에 반응하는 속도. UI 반응이 느리면 기능이 잘 작동해도 “느린 앱”으로 인식.
Tap Latency: 탭 입력 후 UI 반영까지 걸리는 시간. 0.1초 (100ms) 가 '순간적(instantaneous)'으로 인식되며 가장 이상적. 0.5~1초는 '즉각적(immediate)'으로, 1초를 넘기면 체감 반응 속도가 급격히 떨어짐.
Scroll Responsiveness: 스크롤 시작 후 화면이 부드럽고 빠르게 따라오는 정도. 스크롤 중 프레임 드롭이 발생하거나 16ms(60fps) 기준을 반복적으로 벗어나면 버벅임으로 인식됨.
INP (Interaction to Next Paint): 페이지 상호작용 전반에 대한 반응성 지표. (0.2초
네이티브 앱 또는 웹뷰 앱 전체를 기준으로 측정할 때는 아래와 같은 앱 전용 성능 지표를 고려해야 합니다.
사용자가 앱 내에서 탭을 이동하거나 버튼을 눌렀을 때 새 화면이 얼마나 빠르게 나타나는지를 측정합니다.
특히, 탭 UI, 스크롤 가능한 콘텐츠, 네트워크 기반 페이지 등은 체감 성능에 큰 영향을 줍니다.
앱 시작 시간 (Cold / Warm / Hot Start): 앱 실행 시 초기화에 걸리는 시간
Navigation Time: 현재 화면에서 다른 화면(예: 탭 이동, 버튼 클릭 등)으로 전환되는 데 걸리는 시간. 네트워크/애니메이션 영향 포함.
Fragment Render Time: 안드로이드에서 프래그먼트(Fragment)처럼 구분된 UI 컴포넌트가 생성되어 화면에 완전히 렌더링될 때까지의 시간. 복합적인 UI일 경우 이 시간이 길어짐.
Screen Rendering Time: 한 화면이 렌더링 완료되는 데 걸리는 시간. 16ms (60fps) 초과 시 프레임 드롭 체감.
프레임 드랍 (FPS, Jank Rate): 화면 렌더링 중 애니메이션이 끊기는 현상. 초당 프레임 수(FPS)와 프레임 드랍률. FPS가 낮거나 Jank가 많으면 사용자 체감 품질 저하.
배터리 소모량 (Battery Impact): 특정 기능 사용 시 소비되는 배터리 사용량 측정.
메모리 사용량 (Memory Usage): 앱이 사용하는 RAM의 크기.
CPU / GPU 부하 (Load): 앱 동작 중 CPU/GPU 사용률.
크래시율: 앱이 비정상 종료되는 비율 (Crash per session / user 기준).
웹뷰(WebView)나 하이브리드 앱에서 웹 콘텐츠가 로드되는 성능은 아래와 같은 웹 전용 지표를 통해 측정할 수 있습니다.
이 지표들은 주로 Google Lighthouse나 Web Vitals 기반 도구로 측정할 수 있습니다.
FCP (First Contentful Paint): 화면에 첫 콘텐츠가 표시되기까지 걸리는 시간
LCP (Largest Contentful Paint): 주요 콘텐츠(예: 이미지, 텍스트 블록)가 로드되는 시간
TTI (Time to Interactive): 페이지가 실제로 상호작용 가능한 상태가 되기까지 걸리는 시간
CLS (Cumulative Layout Shift): 페이지 로딩 중 요소가 예상치 못하게 이동하는 정도
페이지 로딩 시간: 전체 HTML 문서가 로딩되기까지 걸리는 시간
자바스크립트 처리 시간: JS 번들 실행 및 렌더링에 걸리는 시간
LCP(Largest Contentful Paint): 가장 큰 콘텐츠가 화면에 뜨는 데 걸린 시간
CLS (Cumulative Layout Shift): 페이지가 로딩되면서 화면이 흔들리는 정도
INP (Interaction to Next Paint): 사용자 클릭 등 상호작용에 반응하기까지의 시간
Android Vitals의 주요 항목 중 APP Launch Time 성능을 알아 보겠습니다.
APP Launch Time은 크게 Cold, Warm, Hot Start 세 가지 상태로 분류됩니다.
Cold Start (콜드 스타트): 처음 앱을 실행했을 때 초기 로딩 속도를 말합니다. 앱을 완전히 처음부터 실행하기 때문에 가장 무거운 시작 방식입니다.
시스템 프로세스 생성 → Application.onCreate() 실행 → 액티비티 생성 및 초기 화면 렌더링 등 모든 과정이 포함합니다.
여기서 최적화하면 나머지 상태도 전반적으로 빠르게 개선될 수 있습니다. 그만큼 개선하기 어렵다는 뜻이기도 합니다.
Warm Start (웜 스타트): 백그라운드에 있던 앱을 다시 열었을 때의 로딩 속도를 말합니다.
이미 앱 프로세스는 살아있으나, 새로운 액티비티 생성이 필요한 경우로 Cold Start보다 가볍지만 여전히 onCreate() 호출 등이 포함될 가능성이 있습니다.
Hot Start (핫 스타트): 이미 열린 앱의 화면 간의 전환을 수행했을 때 로딩 속도를 말합니다.
앱이 백그라운드에 있을 뿐 화면만 전환되는 경우로 이미 생성된 UI를 빠르게 다시 보여주므로 가장 빠른 반응 속도를 보입니다.
하지만 메모리 부족 상황이 생기면 일부 작업이 추가될 수 있습니다.
구글 Android Vitals의 APP Launch Time 품질 기준은 아래 표와 같습니다. 실무에서는 App Launch Time을 Cold Start 기준으로 측정하는 것이 일반적입니다.
시작 유형 | 지연 임계값 | adb 명령어 | 수행방법 |
|---|---|---|---|
Cold Start | ≥ 5초 | am force-stop → am start -W | 앱 프로세스가 완전히 새로 시작되는 경우. 앱 완전 종료 후 실행 |
Warm Start | ≥ 2초 | input keyevent 3 → am start -W | 프로세스는 살아 있고, 액티비티만 새로 시작되는 경우. 홈 버튼 → 다시 실행 |
Hot Start | ≥ 1.5초 | input keyevent 187 → input keyevent ENTER | 기존 액티비티가 바로 복원되는 경우. 앱 전환 키 → 복귀 |
앱 시작 시간 측정 방법은 테스트 수행 방법에 따라 다릅니다.
코드상으로 측정하는지 E2E로 측정할지에 따라 수행하는 방법이 달라집니다.
adb 명령어를 이용하여 측정하는 경우 패키지명과 액티비티명을 알아야 합니다.
패키지명: com.example.app
액티비티명: com.example.app.MainActivity
adb shell am start -W com.example.app/.MainActivity #예시
TotalTime: 2350
WaitTime: 2350
Complete
#확인방법
adb shell dumpsys window | grep -E 'mCurrentFocus|mFocusedApp'
mCurrentFocus=null웹사이트나 웹뷰 기반 앱에서 사용자가 체감하는 속도와 반응성은 단순한 로딩 시간만으로 설명되지 않습니다.
사용자는 콘텐츠가 얼마나 빨리 보이는지, 클릭했을 때 얼마나 빨리 반응하는지, 화면이 얼마나 안정적으로 유지되는지를 종합적으로 느낍니다.
Google은 Web Vitals에서 소개하는 3가지 웹 성능 지표를 알아보겠습니다.
LCP(Largest Contentful Paint)
가장 큰 콘텐츠가 뜨는 데 걸린 시간
사용자가 페이지를 열었을 때, 큰 이미지나 텍스트 같은 주요 콘텐츠가 화면에 나타날 때까지의 시간입니다.
페이지가 ‘이제 거의 다 떴다’는 느낌을 주는 시점입니다.
https://web.dev/articles/lcp 에서 보여주는 예시 중 아래 이미지는 LCP 타임라인 중 새 콘텐츠가 DOM에 추가되어 가장 큰 요소가 변경되는 예시입니다.
INP (Interaction to Next Paint)
사용자 클릭에 반응하는 데 걸린 시간
페이지를 사용하는 동안 사용자가 클릭, 탭, 키보드 입력을 했을 때, 그 동작에 화면이 반응하는 데 걸린 시간을 측정합니다.
전체 사용 중에 반응이 느렸던 상호작용들을 기준으로 평가합니다.
https://web.dev/articles/inp 예시에서 오른쪽의 예시는 아코디언이 즉각적으로 반응하여 열리고, 왼쪽의 예는 응답이 느려서 사용자 환경이 저하되는 방식을 보여줍니다.
CLS (Cumulative Layout Shift)
화면이 갑자기 움직이는 정도
페이지가 로딩되는 중에 버튼이나 이미지가 갑자기 밀리거나 움직이는 현상을 점수로 나타낸 것입니다.
숫자가 0에 가까울수록 좋고, 클수록 화면이 흔들리는 경우가 많았다는 뜻입니다.
https://web.dev/articles/cls/ 에서 제시하는 예시를 살펴보겠습니다.
layout shift score = impact fraction * distance fraction 입니다.
위의 예시 이미지를 계산하면 영향도 분수 (impact fraction)*는 0.75이고 거리 분수(distance fraction)는 0.25이므로 레이아웃 이동 점수 (layout shift score)는 0.75 * 0.25 = 0.1875
LCP, INP, CLS는 각 측정 방법은 링크에 더 자세히 나와있으니 도움이 되시길 바랍니다.
다만, 이 항목들은 네이티브 앱(Android/iOS)에는 적용되지 않으며, 앱에서는 FPS, Launch Time, Jank Rate, Memory 등 앱 전용 지표를 사용합니다.
FPS는 초당 몇 개의 프레임(화면 이미지)을 그릴 수 있는지를 나타내는 지표입니다.
즉, 앱이나 게임, 웹에서 화면이 얼마나 부드럽게 움직이는지를 수치로 표현한 것입니다.
FPS가 높을수록 화면 전환이 부드럽고 자연스럽다고 느끼고 FPS가 낮거나 불안정하면 끊김(Lag)이나 버벅임(Jank)이 발생해 사용자 경험이 급격히 나빠집니다.
안드로이드는 1초에 60장을 그려야 하므로, 한 장을 16.67ms 안에 그려야 합니다.
gfxinfo 은 프레임 하나가 그려지는 동안 어떤 작업에 얼마나 시간이 들었는지 기록합니다.
시간이 너무 오래 걸리면 ‘끊김(jank)’으로 판단합니다. adb 명령어를 이용하여 측정할 수 있습니다.
측정할 때는 reset 명령을 수행 후 스크롤 등의 렌더링 수행을 하고 나서 다시 측정합니다.
결과가 길어서 txt 파일로 받아서 분석하는 것을 추천합니다.
adb shell dumpsys gfxinfo com.example.app reset
동작수행
adb shell dumpsys gfxinfo com.example.app framestats > fps_h_4.txt홈/검색/마이/설정 탭 측정한다면 아래 처럼 매번 측정해야 합니다.
홈 탭 → reset → 스크롤/탭 클릭 → gfxinfo 측정
검색 탭 → reset → 검색어 입력/리스트 스크롤 → gfxinfo 측정
마이 탭 → reset → 설정 버튼 클릭 등 → gfxinfo 측정
설정 탭 → reset → 토글 변경 등 → gfxinfo 측정
gpt와 함께 파이썬을 이용해 자동화 코드를 작성해 보겠습니다.
리셋 후 화면 스크롤 수행 등의 내용을 포함했습니다.
결과는 txt 파일로 출력하도록 했습니다.
여기에 화면 탭내용을 추가하면 4개탭을 한번에 측정할 수 있습니다.
import subprocess
import re
import time
import os
def get_fps_info(package_name):
try:
# gfxinfo 명령 실행
output = subprocess.check_output(
f"adb shell dumpsys gfxinfo {package_name} framestats", shell=True, stderr=subprocess.STDOUT
).decode("utf-8")
except subprocess.CalledProcessError as e:
print("ADB 명령 실패:", e.output.decode())
return 0, 0, ""
print("=== Raw Output ===")
print(output[:500]) # 처음 500자만 출력해서 확인
# 정규표현식으로 FPS 관련 정보 추출
match = re.search(r"Total frames rendered:\s+(\d+)", output)
janky = re.search(r"Janky frames:\s+(\d+)\s+\(([\d\.]+)%\)", output)
if match and janky:
total = int(match.group(1))
janky_percent = float(janky.group(2))
return total, janky_percent, output
else:
print("정규표현식에 맞는 결과 없음")
return 0, 0, output
# 앱 패키지 및 액티비티 설정
package_name = "com.example.app"
activity_name = "com.example.app.MainActivity"
# 앱 실행
subprocess.run(f"adb shell am start -n {package_name}/{activity_name}", shell=True)
# 초기화 및 UI 조작
try:
subprocess.run(f"adb shell dumpsys gfxinfo {package_name} reset", shell=True, check=True)
print("초기화 완료. 자동 UI 조작 시작...")
# UI 조작 예시: 스크롤 및 탭
subprocess.run("adb shell input swipe 500 1500 500 500 300", shell=True) # 스크롤 다운
subprocess.run("adb shell input tap 500 600", shell=True) # 탭
subprocess.run("adb shell input swipe 500 500 500 1500 300", shell=True) # 스크롤 업
time.sleep(2) # 렌더링 반영 시간 대기
# FPS 측정
total, janky, raw_output = get_fps_info(package_name)
# 결과 출력 (화면용)
print(f"Total Frames: {total}\nJanky Frames (%): {janky}\n\nRaw Output (First 500 chars):\n{raw_output[:500]}\n")
# 결과 파일 저장 (전체 출력 포함)
result_text = f"Total Frames: {total}\nJanky Frames (%): {janky}\n\nFull Raw Output:\n{raw_output}\n"
output_path = os.path.expanduser("~/Documents/fps_result.txt")
with open(output_path, "w") as f:
f.write(result_text)
print(f"결과가 저장되었습니다: {output_path}")
except Exception as e:
print("실행 중 오류 발생:", e)성능 측정시 앱은 포그라운드 상태여야 합니다. 결과 분석은 아래 내용을 참고합니다.
지표 | 설명 |
|---|---|
Total frames rendered | 앱이 그린 프레임 수 |
Janky frames | 16.67ms 넘게 걸린 프레임 수 (끊긴 프레임) |
50th/90th Percentile | 절반/90%의 프레임이 이 시간 안에 그림 |
Missed Vsync | 화면 주기를 놓친 경우 (버벅임 원인) |
Slow UI thread | UI 처리에 오래 걸린 경우 |
Histogram | 각 프레임이 몇 ms 걸렸는지 구간별로 모은 표 |
Frame deadline missed | 16.67ms 안에 마감 못한 프레임 |
프레임 드랍 비율(Janky frames) 결과 기준은 아래와 같습니다.
구간 | 평가 |
|---|---|
1~2% | 매우 양호 |
3~5% | 보통, 일부 개선 필요 |
6~10% | ⚠️ 끊김 체감 가능, 주의 필요 |
10% 이상 | ❗ 사용자 불만 가능성 높음 |
만약 현재 Janky Frame 비율: 6.94%, Legacy 기준으로는 10.98% 이라면 중간 이상 수준의 렉이 발생하고 있으며, 사용자가 특정 UI에서 버벅임을 느낄 수 있다 판단합니다.
새로운 기능이 추가되거나 새로운 버전이 업데이트될 때 QA에 우선순위는 기능의 정상동작이 될 수밖에 없습니다.
그러나 성능 검증은 피할 수 없는 숙제입니다.
가장 효율적으로 측정하는 방법을 찾아내고 품질 기준을 마련하여 사용자에게 가장 최적의 성능 품질을 제공할 수 있도록 하는 것도 QA의 역할이기 때문입니다.
https://developer.android.com/topic/performance/vitals/launch-time?hl=ko
https://developer.android.com/topic/performance/app-score?hl=ko&_gl=1ldi0tz_upMQ.._gaNjUwNzY5OTk4LjE3NTMxNDUyNDQ._ga_6HH9YJMN9M*czE3NTMxNDUyNDMkbzEkZzAkdDE3NTMxNDUyNDMkajYwJGwwJGg5OTM1MDI5Njk.
https://support.google.com/webmasters/answer/9205520?hl=ko&sjid=16130578911030262122-NC
DEVOTEE를 활성화 시키면
지금 작성한 댓글에 AI가 댓글을 달아줍니다.