Android RecyclerView 스크롤 성능


88

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

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

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


1
스크롤 성능을 위해 ViewHolder 디자인 패턴을 사용해보십시오 : developer.android.com/training/improving-layouts/…
Haresh Chhelana 2014

@HareshChhelana 답변 주셔서 감사합니다! 하지만 저는 이미 ViewHolder 패턴을 사용하고 있습니다. 링크 : developer.android.com/training/material/lists-cards.html
falvojr 2014

2
어댑터 설정에 대한 코드와 레이아웃에 대한 XML 파일을 공유 할 수 있습니까? 이것은 정상적으로 보이지 않습니다. 또한 프로필을 작성하고 시간을 보내는 곳을 보셨습니까?
yigit nov.

2
나는 거의 같은 문제에 직면하고있다. 느린 안드로이드 L.의 사전 롤리팝과 (정말) 믿을 수있는 그것의 빠른 제외
Servus7

1
가져 오는 라이브러리의 버전을 공유 할 수도 있습니다.
Droidekas

답변:


231

최근에 동일한 문제에 직면했기 때문에 이것이 최신 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가 원하는만큼 원활하게 작동하지 않을 가능성이 있습니다.


20
adapter.setHasStableIds (true); recyclerview를 빠르게 만드는 데 정말 도움이 된 방법입니다.
Atula 2011

1
파트 7은 완전히 잘못되었습니다! setHasStableIds (true)는 adapter.notifyDataSetChanged ()를 사용하는 것 외에는 아무것도하지 않습니다. 링크 : developer.android.com/reference/android/support/v7/widget/…
localhost

1
recyclerView.setItemViewCacheSize(20);성능을 향상시킬 수있는 이유를 알 수 있습니다. 그러나 recyclerView.setDrawingCacheEnabled(true);recyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);하지만! 이것들이 아무것도 바꿀지 모르겠습니다. 이는 View드로잉 캐시를 비트 맵으로 프로그래밍 방식으로 검색하고 나중에 유용하게 사용할 수 있는 특정 호출입니다. RecyclerView그것에 대해 아무것도하지 않는 것 같습니다.
Abdelhakim AKODADI 2011

2
@AbdelhakimAkodadi, 스크롤은 캐시로 매끄럽게 진행됩니다. 나는 그것을 테스트했다. 그렇지 않다면 어떻게 말할 수 있겠습니까? 물론, 누군가가 미친 듯이 스크롤하면 아무것도 도움이되지 않습니다. 필자의 경우 이미지 품질이 중요하기 때문에 사용하지 않는 setDrawingCacheQuality와 같은 다른 옵션 만 표시합니다. 나는 DRAWING_CACHE_QUALI‌ TY_HIGH를 설교하지 않지만, 더 깊이 탐구하고 옵션을 조정하는 데 관심이있는 사람을 제안합니다.
Galya

2
setDrawingCacheEnabled () 및 setDrawingCacheQuality ()는 더 이상 사용되지 않습니다. 대신 하드웨어 가속을 사용하십시오. developer.android.com/reference/android/view/...
Shayan_Aryan

14

2
이것은 저에게 약간의 성능 향상을 가져옵니다. 그러나 RecyclerView는 여전히 사용자 지정 ListView보다 훨씬 느립니다.
SMBiggs

아,하지만 내 코드가 왜 그렇게 느린 지 발견했습니다. setHasStableIds ()와는 아무 관련이 없습니다. 더 많은 정보와 함께 답변을 게시하겠습니다.
SMBiggs 2016-06-10

13

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

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

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

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


나는 같은 문제가 있습니다. 지금은 이미지로드 및 캐싱에 Fresco를 사용하고 있습니다. RecyclerView 내에서 이미지를로드하고 캐시하는 또 다른 더 나은 솔루션이 있습니까? 감사.
Androidicus

나는 Fresco에 익숙하지 않습니다 (읽기 ... 그들의 약속은 좋습니다). 아마도 그들은 RecyclerViews와 함께 캐시를 가장 잘 사용하는 방법에 대한 통찰력을 가지고있을 것입니다. 그리고 나는 당신 이이 문제를 가진 유일한 사람이 아니라는 것을 알고 있습니다 : github.com/facebook/fresco/issues/414 .
SMBiggs

10

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

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

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


이 댓글은 저에게 정말 도움이되었습니다! recyclerview의 성능을 향상시키기 위해 모든 것을 시도했지만 실제로 도움이되지는 않았지만 릴리스 빌드로 전환 한 후 모든 것이 정상임을 깨달았습니다.
Tal Barda

1
난 부착 디버거없이 같은 문제에 직면하지만 곧 릴리스 빌드로 이동으로, 문제는 더 이상 볼 수 없었다
Farmaan Elahi

8

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

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


7

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 설치는 모니터링하지 않습니다.


3

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


2

제 경우에는 지연의 주목할만한 원인이 #onBindViewHolder()메서드 내부에서 빈번한 드로어 블 로딩이라는 것을 알았습니다 . ViewHolder 내부에서 이미지를 Bitmap으로 한 번 로드 하고 언급 된 방법에서 액세스 하여 문제를 해결했습니다 . 그게 내가 한 전부입니다.


2

내 RecyclerView에서 내 item_layout의 배경에 비트 맵 이미지를 사용합니다.
@Galya가 말한 모든 것은 사실입니다 (그리고 그의 훌륭한 답변에 감사드립니다). 그러나 그들은 나를 위해 일하지 않았습니다.

이것이 내 문제를 해결 한 것입니다.

BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;
Bitmap bitmap = BitmapFactory.decodeStream(stream, null, options);

자세한 내용은 이 답변을 읽으십시오 .


2

내 경우에는 복잡한 recyclerview 차일이 있습니다. 따라서 활동 로딩 시간에 영향을 미쳤습니다 (활동 렌더링의 경우 ~ 5 초)

postDelayed ()로 어댑터를로드합니다-> 이것은 활동 렌더링에 좋은 결과를 줄 것입니다. 내 recyclerview로드를 부드럽게 렌더링 한 후.

이 대답을 시도해보십시오.

    recyclerView.postDelayed(new Runnable() {
        @Override
        public void run() {
            recyclerView.setAdapter(mAdapter);
        }
    },100); 

1

이미 ViewHolder패턴을 구현하고 있음을 주석에서 볼 수 있지만 패턴을 사용하는 예제 어댑터를 여기에 게시하여 RecyclerView.ViewHolder비슷한 방식으로 통합하고 있는지 확인할 수 있습니다. 생성자는 필요에 따라 달라질 수 있습니다. 예 :

public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.ViewHolder> {

    Context mContext;
    List<String> mNames;

    public RecyclerAdapter(Context context, List<String> names) {
        mContext = context;
        mNames = names;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
        View view = LayoutInflater.from(viewGroup.getContext())
                .inflate(android.R.layout.simple_list_item_1, viewGroup, false);

        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(ViewHolder viewHolder, int position) {
        //Populate.
        if (mNames != null) {
            String name = mNames.get(position);

            viewHolder.name.setText(name);
        }
    }

    @Override
    public int getItemCount() {

        if (mNames != null)
            return mNames.size();
        else
            return 0;
    }

    /**
     * Static Class that holds the RecyclerView views. 
     */
    static class ViewHolder extends RecyclerView.ViewHolder {
        TextView name;

        public ViewHolder(View itemView) {
            super(itemView);
            name = (TextView) itemView.findViewById(android.R.id.text1);
        }
    }
}

작업하는 데 문제가 있으면 GradleRecyclerView.ViewHolder 에서 항상 확인할 수있는 적절한 종속성이 있는지 확인 하십시오.

문제가 해결되기를 바랍니다.


1

이것은 더 부드러운 스크롤링을 얻는 데 도움이되었습니다.

어댑터에서 onFailedToRecycleView (ViewHolder holder) 재정의

진행중인 애니메이션 (있는 경우)을 중지합니다. holder. "animateview".clearAnimation ();

true를 반환하는 것을 잊지 마십시오.


1

이 코드 줄로 해결했습니다.

recyclerView.setNestedScrollingEnabled(false);

1

@Galya의 대답에 추가하면 bind viewHolder에서 Html.fromHtml () 메서드를 사용했습니다. 분명히 이것은 성능에 영향을 미칩니다.


0

i Picasso 라이브러리에서 한 줄만 사용하여이 문제를 해결합니다.

.적당한()

Picasso.get().load(currentItem.getArtist_image())

                    .fit()//this wil auto get the size of image and reduce it 

                    .placeholder(R.drawable.ic_doctor)
                    .into(holder.img_uploaderProfile, new Callback() {
                        @Override
                        public void onSuccess() {


                        }

                        @Override
                        public void onError(Exception e) {
                            Toast.makeText(context, "Something Happend Wrong Uploader Image", Toast.LENGTH_LONG).show();
                        }
                    });
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.