목록 및 카드 만들기 가이드 를 기반으로 RecyclerView 예제를 만들었습니다 . 내 어댑터에는 레이아웃을 확장하기위한 패턴 구현 만 있습니다.
문제는 낮은 스크롤 성능입니다. 이것은 8 개 항목 만있는 RecycleView입니다.
일부 테스트에서 Android L에서이 문제가 발생하지 않음을 확인했습니다. 그러나 KitKat 버전에서는 성능 저하가 분명합니다.
목록 및 카드 만들기 가이드 를 기반으로 RecyclerView 예제를 만들었습니다 . 내 어댑터에는 레이아웃을 확장하기위한 패턴 구현 만 있습니다.
문제는 낮은 스크롤 성능입니다. 이것은 8 개 항목 만있는 RecycleView입니다.
일부 테스트에서 Android L에서이 문제가 발생하지 않음을 확인했습니다. 그러나 KitKat 버전에서는 성능 저하가 분명합니다.
답변:
최근에 동일한 문제에 직면했기 때문에 이것이 최신 RecyclerView 지원 라이브러리로 수행 한 작업입니다.
복잡한 레이아웃 (중첩 된 뷰, 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'
가능하면 RecyclerView의 모든 요소를 동일한 높이로 만드십시오 . 그리고 추가 :
recyclerView.setHasFixedSize(true);
기본 RecyclerView 드로잉 캐시 방법을 사용하고 경우에 따라 조정하십시오. 이를 위해 타사 라이브러리가 필요하지 않습니다.
recyclerView.setItemViewCacheSize(20);
recyclerView.setDrawingCacheEnabled(true);
recyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
많은 이미지 를 사용하는 경우 크기와 압축이 최적 인지 확인하십시오 . 이미지 크기 조정도 성능에 영향을 미칠 수 있습니다. 문제에는 사용 된 소스 이미지와 디코딩 된 비트 맵이라는 두 가지 측면이 있습니다. 다음 예제는 웹에서 다운로드 한 이미지를 디코딩하는 방법에 대한 힌트를 제공합니다.
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
하는 것입니다. 이미지의 각 픽셀에 사용될 바이트 수를 정의합니다. 이것이 선호되는 옵션 임을 명심하십시오 . 소스 이미지에 더 많은 색상이있는 경우에도 다른 구성으로 디코딩됩니다.
onBindViewHolder () 가 가능한 한 저렴한 지 확인하십시오 . OnClickListener를 한 번 설정 onCreateViewHolder()
하고 인터페이스를 통해 어댑터 외부의 리스너를 호출하여 클릭 된 항목을 전달할 수 있습니다. 이렇게하면 항상 추가 개체를 만들지 않습니다. 또한 여기에서보기를 변경하기 전에 플래그와 상태를 확인하십시오.
viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Item item = getItem(getAdapterPosition());
outsideClickListener.onItemClicked(item);
}
});
데이터가 변경되면 영향을받는 항목 만 업데이트하십시오 . 예를 들어를 사용하여 전체 데이터 세트를 무효화하는 대신 notifyDataSetChanged()
더 많은 항목을 추가 /로드 할 때 다음을 사용하십시오.
adapter.notifyItemRangeInserted(rangeStart, rangeEnd);
adapter.notifyItemRemoved(position);
adapter.notifyItemChanged(position);
adapter.notifyItemInserted(position);
에서 안드로이드 개발자 웹 사이트 :
마지막 수단으로 notifyDataSetChanged ()를 사용하십시오.
그러나 그것을 사용해야하는 경우 고유 ID로 항목을 유지하십시오 .
adapter.setHasStableIds(true);
RecyclerView는이 메서드를 사용할 때 안정적인 ID를 가지고 있다고보고하는 어댑터에 대해 가시적 인 구조 변경 이벤트를 합성하려고 시도합니다. 이는 애니메이션 및 시각적 개체 지속성을 위해 도움이 될 수 있지만 개별 항목보기는 여전히 리 바인딩하고 다시 배치해야합니다.
모든 것을 올바르게 수행하더라도 RecyclerView가 원하는만큼 원활하게 작동하지 않을 가능성이 있습니다.
recyclerView.setItemViewCacheSize(20);
성능을 향상시킬 수있는 이유를 알 수 있습니다. 그러나 recyclerView.setDrawingCacheEnabled(true);
및 recyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
하지만! 이것들이 아무것도 바꿀지 모르겠습니다. 이는 View
드로잉 캐시를 비트 맵으로 프로그래밍 방식으로 검색하고 나중에 유용하게 사용할 수 있는 특정 호출입니다. 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에서 내 item_layout의 배경에 비트 맵 이미지를 사용합니다.
@Galya가 말한 모든 것은 사실입니다 (그리고 그의 훌륭한 답변에 감사드립니다). 그러나 그들은 나를 위해 일하지 않았습니다.
이것이 내 문제를 해결 한 것입니다.
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;
Bitmap bitmap = BitmapFactory.decodeStream(stream, null, options);
자세한 내용은 이 답변을 읽으십시오 .
내 경우에는 복잡한 recyclerview 차일이 있습니다. 따라서 활동 로딩 시간에 영향을 미쳤습니다 (활동 렌더링의 경우 ~ 5 초)
postDelayed ()로 어댑터를로드합니다-> 이것은 활동 렌더링에 좋은 결과를 줄 것입니다. 내 recyclerview로드를 부드럽게 렌더링 한 후.
이 대답을 시도해보십시오.
recyclerView.postDelayed(new Runnable() {
@Override
public void run() {
recyclerView.setAdapter(mAdapter);
}
},100);
이미 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
에서 항상 확인할 수있는 적절한 종속성이 있는지 확인 하십시오.
문제가 해결되기를 바랍니다.
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();
}
});