앱 분석 개요

앱 분석은 개발자에게 성능 문제에 대한 알림을 제공하는 도구입니다. 앱 분석은 성능 지침 및 모범 사례 집합에 대해 앱 코드를 실행합니다.

앱 분석은 앱이 발생하는 일반적인 성능 문제의 규칙 집합에서 문제를 식별합니다. 적절한 경우 앱 분석은 Visual Studio의 타임라인 도구, 원본 정보 및 설명서를 가리키며 조사 방법을 제공합니다.

앱 분석의 규칙은 앱이 검사되는 지침 또는 모범 사례를 참조합니다.

렌더링 크기보다 큰 디코딩된 이미지 크기

이미지는 매우 높은 해상도로 캡처되므로 이미지 데이터를 디코딩할 때 더 많은 CPU를 사용하는 앱과 디스크에서 로드된 후 더 많은 메모리를 사용할 수 있습니다. 그러나 고해상도 이미지를 메모리에 디코딩하고 저장하여 네이티브 크기보다 작게 표시하는 것은 의미가 없습니다. 대신 DecodePixelWidth 및 DecodePixelHeight 속성을 사용하여 화면에 그릴 정확한 크기로 이미지 버전을 만듭니다.

영향

네이티브가 아닌 크기로 이미지를 표시하면 CPU 시간(적절한 크기 및 다운로드 시간으로 디코딩으로 인해) 및 메모리 모두에 부정적인 영향을 미칠 수 있습니다.

원인 및 해결 방법

이미지가 비동기적으로 설정되지 않음

앱은 SetSourceAsync() 대신 SetSource()를 사용합니다. 항상 SetSource 사용하지 말고 이미지를 비동기적으로 디코딩하도록 스트림을 설정할 때 SetSourceAsync 사용해야 합니다.

ImageSource가 라이브 트리에 없을 때 이미지가 호출됩니다.

BitmapImage는 SetSourceAsync 또는 UriSource를 사용하여 콘텐츠를 설정한 후 라이브 XAML 트리에 연결됩니다. 원본을 설정하기 전에 항상 BitmapImage를 라이브 트리에 연결시켜야 합니다. 이미지 요소 또는 브러시가 마크업에 지정될 때마다 이 자동으로 적용됩니다. 예제는 다음과 같습니다.

실시간 트리 예시

예제 1 (양호) - 마크업에 지정된 URI(Uniform Resource Identifier)입니다.

<Image x:Name="myImage" UriSource="Assets/cool-image.png"/>

코드 비하인드에 지정된 URI의 예제 2 마크업입니다.

<Image x:Name="myImage"/>

예제 2 코드 비하인드(양호)—UriSource를 설정하기 전에 BitmapImage를 트리에 연결합니다.

var bitmapImage = new BitmapImage();
myImage.Source = bitmapImage;
bitmapImage.UriSource = new URI("ms-appx:///Assets/cool-image.png", UriKind.RelativeOrAbsolute);

예제 2 코드 숨김 (잘못된 사례) - 트리에 연결하기 전에 BitmapImage의 UriSource를 설정하는 경우.

var bitmapImage = new BitmapImage();
bitmapImage.UriSource = new URI("ms-appx:///Assets/cool-image.png", UriKind.RelativeOrAbsolute);
myImage.Source = bitmapImage;

이미지 브러시가 사각형이 아닌 경우

이미지가 비정형 브러시에 사용될 때, 그 이미지는 소프트웨어 래스터화 프로세스를 사용하며, 이미지를 전혀 확대하거나 축소하지 않습니다. 또한 소프트웨어 및 하드웨어 메모리 모두에 이미지의 복사본을 저장해야 합니다. 예를 들어 이미지가 타원에 대한 브러시로 사용되는 경우 잠재적으로 큰 전체 이미지는 내부적으로 두 번 저장됩니다. 직사각형이 아닌 브러시를 사용하는 경우 앱은 이미지의 크기를 렌더링할 크기로 미리 조정해야 합니다.

또는 명시적 디코딩 크기를 설정하여 DecodePixelWidthDecodePixelHeight 속성을 사용하여 화면에 그릴 정확한 크기로 이미지 버전을 만들 수 있습니다.

<Image>
    <Image.Source>
    <BitmapImage UriSource="ms-appx:///Assets/highresCar.jpg" 
                 DecodePixelWidth="300" DecodePixelHeight="200"/>
    </Image.Source>
</Image>

DecodePixelWidthDecodePixelHeight 단위는 기본적으로 물리적 픽셀입니다. DecodePixelType 속성을 사용하여 이 동작을 변경할 수 있습니다. DecodePixelType을(를) 논리으로 설정하면, 디코딩 크기가 다른 XAML 콘텐츠와 마찬가지로 시스템의 현재 배율 인수를 자동으로 고려하여 조정됩니다. 따라서, 예를 들어 이미지가 표시될 이미지 컨트롤의 Height 및 Width 속성과 DecodePixelWidthDecodePixelHeight를 일치시키려면 DecodePixelType논리적으로 설정하는 것이 일반적으로 적절합니다. 물리적 픽셀을 사용하는 기본 동작에서는 시스템의 현재 배율 인수를 직접 고려해야 하며 사용자가 디스플레이 기본 설정을 변경할 경우 크기 변경 알림을 수신 대기해야 합니다.

적절한 디코딩 크기를 미리 확인할 수 없는 경우 XAML의 자동 적정 크기 디코딩에 따라야 하며, 명시적 DecodePixelWidth/DecodePixelHeight가 지정되지 않은 경우 XAML이 최대한 적절한 크기로 이미지를 디코딩하려고 합니다.

이미지 콘텐츠의 크기를 미리 알고 있는 경우 명시적 디코딩 크기를 설정해야 합니다. 제공된 디코드 크기가 다른 XAML 요소 크기를 기준으로 하는 경우, DecodePixelTypeLogical와 함께 설정해야 합니다. 예를 들어 Image.Width 및 Image.Height를 사용하여 콘텐츠 크기를 명시적으로 설정하는 경우 DecodePixelType을 DecodePixelType.Logical로 설정하여 이미지 컨트롤과 동일한 논리 픽셀 차원을 사용한 다음 BitmapImage.DecodePixelWidth 및/또는 BitmapImage.DecodePixelHeight를 명시적으로 사용하여 이미지 크기를 제어하여 메모리를 크게 절약할 수 있습니다.

디코딩된 콘텐츠의 크기를 결정할 때 Image.Stretch를 고려해야 합니다.

BitmapIcons 내부에서 사용되는 이미지는 자연 크기로 디코딩으로 돌아갑니다.

명시적 디코드 크기를 설정하여 DecodePixelWidthDecodePixelHeight 속성을 사용하여 화면에 그릴 정확한 크기로 이미지 버전을 만듭니다.

화면에 매우 크게 표시되는 이미지는 자연 크기로 디코딩되어 다시 원래 크기로 보입니다.

화면에 매우 크게 표시되는 이미지는 자연 크기로 디코딩되어 복원됩니다. 명시적 디코드 크기를 설정하여 DecodePixelWidthDecodePixelHeight 속성을 사용하여 화면에 그릴 정확한 크기로 이미지 버전을 만듭니다.

이미지가 숨겨져 있습니다.

이미지는 호스트 이미지 요소 또는 브러시 또는 부모 요소에서 Opacity를 0으로 설정하거나 Visibility를 Collapsed로 설정하여 숨겨집니다. 클리핑 또는 투명도로 인해 화면에 표시되지 않는 이미지는 기본 크기로 디코딩될 수 있습니다.

NineGrid 속성을 사용하는 이미지

이미지가 "NineGrid"에 사용될 때, 이미지는 소프트웨어 래스터화 경로를 사용하며, 이는 이미지 크기를 전혀 조정하지 않습니다. 또한 소프트웨어 및 하드웨어 메모리 모두에 이미지의 복사본을 저장해야 합니다. NineGrid사용할 때, 앱은 이미지를 렌더링될 크기에 가깝도록 미리 크기를 조정해야 합니다.

NineGrid 속성을 사용하는 이미지는 원래 크기로 디코딩으로 되돌아갑니다. 원래 이미지에 ninegrid 효과를 추가하는 것이 좋습니다.

DecodePixelWidth 또는 DecodePixelHeight는 화면에 표시되는 이미지보다 큰 크기로 설정됩니다.

DecodePixelWidth/Height가 화면에 표시되는 이미지보다 명시적으로 더 크게 설정되면 앱은 픽셀당 최대 4바이트의 추가 메모리를 불필요하게 사용하므로 큰 이미지의 경우 비용이 빠르게 듭니다. 이미지는 쌍선형 크기 조정을 사용하여 축소되므로, 확대 배율이 큰 경우 흐리게 표시될 수 있습니다.

끌어서 놓기 이미지 생성의 일부로 이미지가 디코딩됩니다.

명시적 디코드 크기를 설정하여 DecodePixelWidthDecodePixelHeight 속성을 사용하여 화면에 그릴 정확한 크기로 이미지 버전을 만듭니다.

로드 시 접힌 요소

앱의 일반적인 패턴은 처음에 UI의 요소를 숨기고 나중에 표시하는 것입니다. 대부분의 경우 이러한 요소는 로드 시 요소를 만드는 비용을 지불하지 않도록 x:Load 또는 x:DeferLoadStrategy를 사용하여 지연해야 합니다.

부울 값을 표시로 변환하는 변환기가 나중까지 항목을 숨기는 데 사용되는 경우가 포함됩니다.

영향

축소된 요소는 다른 요소와 함께 로드되며 로드 시간이 증가합니다.

원인

불러오는 시점에 요소가 축소되었기 때문에 이 규칙이 실행되었습니다. 요소를 축소하거나 불투명도를 0으로 설정해도 요소가 만들어지지는 않습니다. 이 규칙은 기본값이 false인 표시 유형 변환기에서 부울을 사용하는 앱에 의해 발생할 수 있습니다.

해결 방법

x:Load 특성 또는 x:DeferLoadStrategy사용하면 UI의 로드를 지연하고 필요할 때 로드할 수 있습니다. 이는 첫 번째 프레임에 표시되지 않는 UI 처리를 지연시키는 좋은 방법입니다. 필요할 때 또는 지연된 논리 집합의 일부로 요소를 로드하도록 선택할 수 있습니다. 로드를 트리거하려면 로드하려는 요소에서 findName을 호출합니다. x:Load는 x:DeferLoadStrategy의 기능을 확장하여 요소를 언로드할 수 있도록 하고, 로드 상태가 x:Bind를 통해 제어되도록 합니다.

경우에 따라 findName을 사용하여 UI 부분을 표시하는 것이 답이 아닐 수 있습니다. 이는 대기 시간이 매우 짧은 단추를 클릭할 때 중요한 UI 부분을 실현하려는 경우에 해당합니다. 이 경우, 추가 메모리를 사용하여 더 빠른 UI 지연 시간을 줄이고자 할 경우, x:DeferLoadStrategy를 사용하고 실현하려는 요소의 Visibility를 Collapsed로 설정해야 합니다. 페이지가 로드되고 UI 스레드가 무료이면 요소를 로드하는 데 필요한 경우 findName을 호출할 수 있습니다. 요소의 Visibility를 Visible로 설정하기 전에는 요소가 사용자에게 표시되지 않습니다.

ListView가 가상화되지 않음

UI 가상화는 컬렉션 성능을 개선하기 위해 수행할 수 있는 가장 중요한 개선 사항입니다. 즉, 항목을 나타내는 UI 요소가 요청 시 만들어집니다. 1000개 항목 컬렉션에 바인딩된 항목 컨트롤의 경우 모든 항목을 동시에 표시할 수 없으므로 동시에 모든 항목에 대한 UI를 만드는 것은 리소스 낭비입니다. ListView 및 GridView(및 기타 표준 ItemsControl 파생 컨트롤)는 UI 가상화를 수행합니다. 항목이 곧 보이게 스크롤될 때(몇 페이지 이내에), 프레임워크가 항목의 UI를 생성하고 이를 캐시합니다. 항목이 다시 표시될 가능성이 낮으면 프레임워크는 메모리를 다시 클레임합니다.

UI 가상화는 컬렉션 성능 향상을 위한 몇 가지 주요 요소 중 하나일 뿐입니다. 컬렉션 항목 및 데이터 가상화의 복잡성을 줄이는 것은 컬렉션 성능을 향상시키는 데 중요한 두 가지 측면입니다. ListView 및 GridViews 내에서 컬렉션 성능을 향상시키는 방법에 대한 자세한 내용은 ListView 및 GridView UI 최적화ListView 및 Gridview 데이터 가상화문서를 참조하세요.

영향

가상화되지 않은 ItemsControl은 필요한 것보다 더 많은 자식 항목을 로드하여 로드 시간과 리소스 사용량을 증가합니다.

원인

뷰포트의 개념은 프레임워크가 표시될 가능성이 있는 요소를 만들어야 하기 때문에 UI 가상화에 매우 중요합니다. 일반적으로 ItemsControl의 뷰포트는 논리 컨트롤의 범위입니다. 예를 들어 ListView의 뷰포트는 ListView 요소의 너비와 높이입니다. 일부 패널은 ScrollViewer와 자동으로 크기가 조정되는 행이나 열이 있는 Grid와 같은 방식으로 자식 요소에게 무제한의 공간을 허용합니다. 가상화된 ItemsControl이 이러한 패널에 배치되면 모든 항목을 표시할 수 있는 충분한 공간이 필요하므로 가상화가 무효화됩니다.

해결 방법

사용 중인 ItemsControl에서 너비와 높이를 설정하여 가상화를 복원합니다.

로드하는 동안 UI 스레드 차단 또는 유휴 상태

UI 스레드 차단은 UI 스레드를 차단하는 오프 스레드를 실행하는 함수에 대한 동기 호출을 나타냅니다.

앱 시작 성능을 개선하기 위한 모범 사례의 전체 목록은 귀하의 앱 시작 성능을 위한 모범 사례UI 스레드 응답성을 유지하는 방법을 참조하세요.

영향

로드 시간 동안 차단되거나 유휴 상태인 UI 스레드는 레이아웃 및 기타 UI 작업을 방지하여 시작 시간이 늘어나게 합니다.

원인

UI용 플랫폼 코드와 앱의 UI 코드는 모두 동일한 UI 스레드에서 실행됩니다. 한 번에 하나의 명령만 해당 스레드에서 실행할 수 있으므로 앱 코드가 이벤트를 처리하는 데 너무 오래 걸리는 경우 프레임워크는 레이아웃을 실행하거나 사용자 상호 작용을 나타내는 새 이벤트를 발생할 수 없습니다. 앱의 응답성은 작업을 처리하는 UI 스레드의 가용성과 관련이 있습니다.

해결 방법

앱이 완전히 작동하지 않는 부분이 있더라도 앱은 대화형일 수 있습니다. 예를 들어 앱에서 검색하는 데 시간이 걸리는 데이터를 표시하는 경우 데이터를 비동기적으로 검색하여 해당 코드를 앱의 시작 코드와 독립적으로 실행하도록 할 수 있습니다. 데이터를 사용할 수 있는 경우 앱의 사용자 인터페이스를 데이터로 채웁니다. 앱의 응답성을 유지하기 위해 플랫폼은 많은 API의 비동기 버전을 제공합니다. 비동기 API를 사용하면 활성 실행 스레드가 상당한 시간 동안 차단되지 않습니다. UI 스레드에서 API를 호출하는 경우 사용 가능한 경우 비동기 버전을 사용합니다.

{x:Bind} 대신 {Binding}을(를) 사용하고 있습니다.

이 규칙은 앱에서 {Binding} 문을 사용할 때 발동됩니다. 앱 성능을 향상시키려면 앱에서 {x:Bind}를 사용하는 것이 좋습니다.

영향

{Binding}은 {x:Bind}보다 더 많은 시간과 더 많은 메모리로 실행됩니다.

원인

앱이 {x:Bind} 대신 {Binding}을(를) 사용하고 있습니다. {Binding}은(는) 복잡한 작업 집합 및 CPU 오버헤드를 수반합니다. {Binding}을 만들면 일련의 할당이 발생하고, 바인딩 대상을 업데이트할 때 리플렉션 및 박싱이 발생할 수 있습니다.

해결 방법

빌드 시 바인딩을 컴파일하는 {x:Bind} 태그 확장을 사용합니다. {x:Bind} 바인딩(컴파일된 바인딩이라고도 함)은 성능이 뛰어나고, 바인딩 식의 컴파일 시간 유효성 검사를 제공하며, 페이지의 부분 클래스로 생성된 코드 파일에서 중단점을 설정할 수 있도록 하여 디버깅을 지원합니다.

x:Bind는 지연 바인딩된 시나리오와 같은 모든 경우에 적합하지 않습니다. {x:Bind}에서 다루지 않는 전체 사례 목록은 {x:Bind} 설명서를 참조하세요.

x:Key 대신 x:Name을 사용하고 있습니다.

ResourceDictionaries는 일반적으로 앱이 여러 위치에서 참조하려는 스타일, 브러시, 템플릿 등의 리소스를 전역 수준에서 저장하는 데 사용됩니다. 일반적으로 리소스를 요청하지 않는 한 리소스를 인스턴스화하지 않도록 ResourceDictionaries를 최적화했습니다. 그러나 조금 조심해야 할 곳은 거의 없습니다.

영향

x:Name이 있는 모든 리소스는 ResourceDictionary가 만들어지는 즉시 인스턴스화됩니다. 이 현상은 x:Name이 플랫폼에 앱이 이 리소스에 대한 필드 액세스가 필요하다는 것을 알리기 때문입니다. 따라서 플랫폼은 참조를 생성하기 위해 어떤 대상을 만들어야 합니다.

원인

앱이 리소스에서 x:Name을 설정하고 있습니다.

해결 방법

코드 숨김에서 리소스를 참조하지 않는 경우 x:Name 대신 x:Key를 사용합니다.

컬렉션 컨트롤이 가상화되지 않은 패널을 사용하고 있습니다.

사용자 지정 항목 패널 템플릿(ItemsPanel 참조)을 제공하는 경우 ItemsWrapGrid 또는 ItemsStackPanel과 같은 가상화 패널을 사용해야 합니다. VariableSizedWrapGrid, WrapGrid 또는 StackPanel을 사용하는 경우 가상화가 표시되지 않습니다. 또한 ItemsWrapGrid 또는 ItemsStackPanel을 사용하는 경우에만 다음 ListView 이벤트가 발생합니다: ChoosingGroupHeaderContainer, ChoosingItemContainer, 및 ContainerContentChanging.

UI 가상화는 컬렉션 성능을 개선하기 위해 수행할 수 있는 가장 중요한 개선 사항입니다. 즉, 항목을 나타내는 UI 요소가 요청 시 만들어집니다. 1000개 항목 컬렉션에 바인딩된 항목 컨트롤의 경우 모든 항목을 동시에 표시할 수 없으므로 동시에 모든 항목에 대한 UI를 만드는 것은 리소스 낭비입니다. ListView 및 GridView(및 기타 표준 ItemsControl 파생 컨트롤)는 UI 가상화를 수행합니다. 항목이 곧 보이게 스크롤될 때(몇 페이지 이내에), 프레임워크가 항목의 UI를 생성하고 이를 캐시합니다. 항목이 다시 표시될 가능성이 낮으면 프레임워크는 메모리를 다시 클레임합니다.

UI 가상화는 컬렉션 성능 향상을 위한 몇 가지 주요 요소 중 하나일 뿐입니다. 컬렉션 항목 및 데이터 가상화의 복잡성을 줄이는 것은 컬렉션 성능을 향상시키는 데 중요한 두 가지 측면입니다. ListView 및 GridViews 내에서 컬렉션 성능을 향상시키는 방법에 대한 자세한 내용은 ListView 및 GridView UI 최적화ListView 및 Gridview 데이터 가상화문서를 참조하세요.

영향

가상화되지 않은 ItemsControl은 필요한 것보다 더 많은 자식 항목을 로드하여 로드 시간과 리소스 사용량을 증가합니다.

원인

가상화를 지원하지 않는 패널을 사용하고 있습니다.

해결 방법

ItemsWrapGrid 또는 ItemsStackPanel과 같은 가상화 패널을 사용합니다.

접근성: 이름이 없는 UIA 요소

XAML에서는 AutomationProperties.Name 설정하여 이름을 제공할 수 있습니다. 많은 자동화 피어는 AutomationProperties.Name이 설정이 되어 있지 않은 경우 UIA에 기본 이름을 제공합니다.

영향

사용자가 이름 없이 요소에 도달하면 요소와 관련된 내용을 알 수 없는 경우가 많습니다.

원인

요소의 UIA 이름이 null이거나 비어 있습니다. 이 규칙은 AutomationProperties.Name 값이 아니라 UIA에서 보는 항목을 확인합니다.

해결 방법

컨트롤의 XAML에 있는 AutomationProperties.Name 속성을 적절한 지역화된 문자열로 설정합니다.

경우에 따라 올바른 애플리케이션 수정은 이름을 제공하는 것이 아니라 원시 트리를 제외한 모든 항목에서 UIA 요소를 제거하는 것입니다. AutomationProperties.AccessibilityView = "Raw"설정하여 XAML에서 이 작업을 수행할 수 있습니다.

접근성: Controltype이 같은 UIA 요소의 이름은 같으면 안 됩니다.

동일한 UIA 부모를 가진 두 UIA 요소는 Name과 ControlType이 동일해서는 안 됩니다. ControlType이 다른 경우 이름이 같은 두 개의 컨트롤이 있으면 괜찮습니다.

이 규칙은 다른 부모와 중복된 이름을 확인하지 않습니다. 그러나 대부분의 경우 다른 부모가 있는 경우에도 전체 창 내에서 이름 및 ControlType을 복제해서는 안 됩니다. 창 내에서 중복된 이름을 사용할 수 있는 경우는 동일한 항목이 있는 두 개의 목록입니다. 이 경우 목록 항목에는 동일한 이름 및 ControlType이 있어야 합니다.

영향

사용자가 동일한 UIA 부모를 가진 다른 요소와 동일한 Name 및 ControlType을 가진 요소에 도달하면 사용자가 요소 간의 차이를 구분하지 못할 수 있습니다.

원인

UIA 부모가 동일한 UIA 요소는 Name 및 ControlType이 동일합니다.

해결 방법

AutomationProperties.Name 사용하여 XAML에서 이름을 설정합니다. 일반적으로 발생하는 목록에서 바인딩을 사용하여 AutomationProperties.Name 값을 데이터 원본에 바인딩합니다.