[java] Android RecyclerView 스크롤 성능

목록 및 카드 만들기 가이드 를 기반으로 RecyclerView 예제를 만들었습니다 . 내 어댑터에는 레이아웃을 확장하기위한 패턴 구현 만 있습니다.

문제는 낮은 스크롤 성능입니다. 이것은 8 개 항목 만있는 RecycleView입니다.

일부 테스트에서 Android L에서이 문제가 발생하지 않음을 확인했습니다. 그러나 KitKat 버전에서는 성능 저하가 분명합니다.



답변

최근에 동일한 문제에 직면했기 때문에 이것이 최신 RecyclerView 지원 라이브러리로 수행 한 작업입니다.

  1. 복잡한 레이아웃 (중첩 된 뷰, RelativeLayout)을 새로 최적화 된 ConstraintLayout으로 바꿉니다 . Android Studio에서 활성화 : SDK Manager-> SDK Tools 탭-> Support Repository-> ConstraintLayout for Android 및 Solver for ConstraintLayout을 확인합니다. 종속성에 추가하십시오.

    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    
  2. 가능하면 RecyclerView의 모든 요소를 동일한 높이로 만드십시오 . 그리고 추가 :

    recyclerView.setHasFixedSize(true);
    
  3. 기본 RecyclerView 드로잉 캐시 방법을 사용하고 경우에 따라 조정하십시오. 이를 위해 타사 라이브러리가 필요하지 않습니다.

    recyclerView.setItemViewCacheSize(20);
    recyclerView.setDrawingCacheEnabled(true);
    recyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
    
  4. 많은 이미지 를 사용하는 경우 크기와 압축이 최적 인지 확인하십시오 . 이미지 크기 조정도 성능에 영향을 미칠 수 있습니다. 문제에는 사용 된 소스 이미지와 디코딩 된 비트 맵이라는 두 가지 측면이 있습니다. 다음 예제는 웹에서 다운로드 한 이미지를 디코딩하는 방법에 대한 힌트를 제공합니다.

    InputStream is = (InputStream) url.getContent();
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inPreferredConfig = Bitmap.Config.RGB_565;
    Bitmap image = BitmapFactory.decodeStream(is, null, options);
    

가장 중요한 부분은 지정 inPreferredConfig하는 것입니다. 이미지의 각 픽셀에 사용될 바이트 수를 정의합니다. 이것이 선호되는 옵션 임을 명심하십시오 . 소스 이미지에 더 많은 색상이있는 경우에도 다른 구성으로 디코딩됩니다.

  1. onBindViewHolder () 가 가능한 한 저렴한 지 확인하십시오 . OnClickListener를 한 번 설정 onCreateViewHolder()하고 인터페이스를 통해 어댑터 외부의 리스너를 호출하여 클릭 된 항목을 전달할 수 있습니다. 이렇게하면 항상 추가 개체를 만들지 않습니다. 또한 여기에서보기를 변경하기 전에 플래그와 상태를 확인하십시오.

    viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View view) {
              Item item = getItem(getAdapterPosition());
              outsideClickListener.onItemClicked(item);
          }
    });
    
  2. 데이터가 변경되면 영향을받는 항목 만 업데이트하십시오 . 예를 들어를 사용하여 전체 데이터 세트를 무효화하는 대신 notifyDataSetChanged()더 많은 항목을 추가 /로드 할 때 다음을 사용하십시오.

    adapter.notifyItemRangeInserted(rangeStart, rangeEnd);
    adapter.notifyItemRemoved(position);
    adapter.notifyItemChanged(position);
    adapter.notifyItemInserted(position);
    
  3. 에서 안드로이드 개발자 웹 사이트 :

마지막 수단으로 notifyDataSetChanged ()를 사용하십시오.

그러나 그것을 사용해야하는 경우 고유 ID로 항목을 유지하십시오 .

    adapter.setHasStableIds(true);

RecyclerView는이 메서드를 사용할 때 안정적인 ID를 가지고 있다고보고하는 어댑터에 대해 가시적 인 구조 변경 이벤트를 합성하려고 시도합니다. 이는 애니메이션 및 시각적 개체 지속성을 위해 도움이 될 수 있지만 개별 항목보기는 여전히 리 바인딩하고 다시 배치해야합니다.

모든 것을 올바르게 수행하더라도 RecyclerView가 원하는만큼 원활하게 작동하지 않을 가능성이 있습니다.


답변


답변

당신의 공연을 죽일 수있는 패턴을 하나 이상 발견했습니다. 자주onBindViewHolder() 호출 된다는 것을 기억하십시오 . 따라서 해당 코드에서 수행하는 모든 작업은 성능을 멈출 수 있습니다. RecyclerView가 사용자 지정을 수행하는 경우 실수로이 메서드에 느린 코드를 넣는 것은 매우 쉽습니다.

위치에 따라 각 RecyclerView의 배경 이미지를 변경했습니다. 그러나 이미지를로드하는 데 약간의 작업이 필요하여 RecyclerView가 느리고 불안정합니다.

이미지에 대한 캐시를 만드는 것은 놀라운 일이었습니다. onBindViewHolder()이제는 처음부터로드하는 대신 캐시 된 이미지에 대한 참조를 수정합니다. 이제 RecyclerView가 압축됩니다.

모든 사람이이 정확한 문제를 가지고있는 것은 아니라는 것을 알고 있으므로 코드를로드하는 데 신경 쓰지 않습니다. 그러나 귀하의 작업에서 수행되는 작업은 onBindViewHolder()RecyclerView 성능 저하의 잠재적 인 병목 현상으로 간주하십시오 .


답변

@Galya의 자세한 답변 외에도 최적화 문제 일 수 있지만 디버거를 활성화하면 상황이 많이 느려질 수 있다는 것도 사실입니다.

최적화하기 위해 모든 작업을 수행 RecyclerView했지만 여전히 원활하게 작동하지 않는 경우 빌드 변형을release 하고 비 개발 환경 (디버거가 비활성화 된 상태)에서 어떻게 작동하는지 확인하십시오.

내 앱이 debug빌드 변형 에서 느리게 수행되는 것이 나에게 발생 했지만 변형으로 전환하자마자 release원활하게 작동했습니다. 이것은 release빌드 변형으로 개발해야한다는 것을 의미하지는 않지만 앱을 출시 할 준비가 될 때마다 제대로 작동한다는 것을 아는 것이 좋습니다.


답변

RecyclerView의 공연 에 대해 이야기를 나눴 습니다. 다음은 영어슬라이드러시아어로 녹화 된 비디오입니다. .

그것은 일련의 기술을 포함합니다 (그 중 일부는 이미 @Darya의 답변에 포함되어 있습니다. ).

다음은 간략한 요약입니다.

  • Adapter항목의 크기가 고정 된 경우 다음을 설정하십시오.
    recyclerView.setHasFixedSize(true);

  • 데이터 엔터티를 long ( hashCode()예 :)으로 나타낼 수있는 경우 다음을 설정
    adapter.hasStableIds(true);
    하고 구현합니다.
    // YourAdapter.java
    @Override
    public long getItemId(int position) {
    return items.get(position).hashcode(); //id()
    }
    이 경우에는 의 콘텐츠가 변경 Item.id()되어도 동일하게 유지되므로 작동하지 않습니다 Item.
    추신 DiffUtil을 사용하는 경우에는 필요하지 않습니다!

  • 올바르게 크기 조정 된 비트 맵을 사용하십시오. 바퀴를 재발 명하고 라이브러리를 사용하지 마십시오. 여기
    에서 선택하는 방법에 대한 자세한 정보 .

  • 항상 최신 버전의 RecyclerView. 예를 들어, 25.1.0프리 페치의 성능이 크게 향상되었습니다 . 여기에
    더 많은 정보가 있습니다 .

  • DiffUtill을 사용하십시오.
    DiffUtil은 필수 입니다.
    공식 문서 .

  • 항목의 레이아웃을 단순화하십시오!
    TextView를 풍부하게하는 작은 라이브러리 -TextViewRichDrawable

자세한 설명 은 슬라이드 를 참조하십시오 .


답변

setHasStableId플래그 사용으로 문제가 해결 될지 잘 모르겠습니다 . 제공 한 정보에 따르면 성능 문제는 메모리 문제와 관련이있을 수 있습니다. 사용자 인터페이스 및 메모리 측면에서 애플리케이션 성능은 상당히 관련이 있습니다.

지난주에 내 앱이 메모리 누수를 발견했습니다. 내 앱을 사용한 지 20 분 후에 UI 성능이 정말 느리다는 것을 알았 기 때문에 이것을 발견했습니다. 활동 닫기 / 열기 또는 여러 요소가있는 RecyclerView 스크롤은 정말 느 렸습니다. http://flowup.io/를 사용하여 프로덕션의 일부 사용자를 모니터링 한 후 다음을 발견했습니다.

여기에 이미지 설명 입력

프레임 시간은 정말 높았고 초당 프레임 수는 정말 낮았습니다. 일부 프레임은 렌더링하는 데 약 2 초가 필요하다는 것을 알 수 있습니다. : S.

이 잘못된 프레임 시간 / fps의 원인을 알아 내려고 여기에서 볼 수 있듯이 메모리 문제가 있음을 발견했습니다.

여기에 이미지 설명 입력

평균 메모리 소비가 동시에 15MB에 가까웠을 때도 앱은 프레임을 떨어 뜨 렸습니다.

이것이 제가 UI 문제를 발견 한 방법입니다. 앱에서 메모리 누수가 발생하여 많은 가비지 수집기 이벤트가 발생했으며 Android VM이 내 앱을 중지하여 매 프레임마다 메모리를 수집해야했기 때문에 UI 성능이 저하되었습니다.

코드를 살펴보면 Android Choreographer 인스턴스에서 리스너를 등록 취소하지 않았기 때문에 사용자 정의 뷰 내부에 누수가있었습니다. 수정 사항을 발표 한 후 모든 것이 정상화되었습니다. 🙂

메모리 문제로 인해 앱에서 프레임이 삭제되는 경우 다음 두 가지 일반적인 오류를 검토해야합니다.

앱이 초당 여러 번 호출되는 메서드 내에서 개체를 할당하는지 검토합니다. 이 할당은 응용 프로그램이 느려지는 다른 장소에서 수행 될 수 있습니다. 예를 들어 리사이클 러 뷰 뷰 홀더의 onBindViewHolder에있는 onDraw 사용자 정의 뷰 메서드 내에 객체의 새 인스턴스를 생성 할 수 있습니다. 앱이 Android SDK에 인스턴스를 등록하지만 릴리스하지 않는지 검토합니다. 버스 이벤트에 리스너를 등록하면 누수가 발생할 수도 있습니다.

면책 조항 : 내 앱을 모니터링하는 데 사용했던 도구는 개발 중입니다. 저는 개발자 중 하나이기 때문에이 도구에 액세스 할 수 있습니다. :)이 도구에 액세스하려면 곧 베타 버전을 출시 할 것입니다! 웹 사이트 http://flowup.io/에 가입 할 수 있습니다 .

다른 도구를 사용하려면 traveview, dmtracedump, systrace 또는 Android Studio에 통합 된 Andorid 성능 모니터를 사용할 수 있습니다. 그러나이 도구는 연결된 장치를 모니터링하고 나머지 사용자 장치 나 Android OS 설치는 모니터링하지 않습니다.


답변

Recyclerview에 넣은 상위 레이아웃을 확인하는 것도 중요합니다. nestedscrollview에서 recyclerView를 테스트 할 때 비슷한 스크롤 문제가 발생했습니다. 스크롤하는 동안 성능이 저하 될 수있는 다른보기에서 스크롤하는보기