Hiệu suất cuộn của Android RecyclerView


86

Tôi đã tạo ví dụ RecyclerView dựa trên hướng dẫn Tạo danh sách và thẻ . Bộ điều hợp của tôi có triển khai mẫu chỉ để tăng bố cục.

Vấn đề là hiệu suất cuộn kém. Điều này trong RecycleView chỉ có 8 mục.

Trong một số thử nghiệm, tôi đã xác minh rằng trong Android L sự cố này không xảy ra. Nhưng trong phiên bản KitKat, việc giảm hiệu suất là điều hiển nhiên.


1
Cố gắng sử dụng mẫu thiết kế ViewHolder cho di chuyển hiệu suất: developer.android.com/training/improving-layouts/...
Haresh Chhelana

@HareshChhelana cảm ơn bạn đã trả lời! Nhưng tôi đã sử dụng ViewHolder mẫu, theo liên kết: developer.android.com/training/material/lists-cards.html
falvojr

2
Bạn có thể chia sẻ một số mã về thiết lập bộ điều hợp của bạn và tệp XML cho bố cục của bạn không. Điều này trông không bình thường. Ngoài ra, bạn đã lập hồ sơ và xem thời gian được sử dụng ở đâu?
yigit

2
Tôi đang phải đối mặt với tất cả những vấn đề tương tự. Trừ nhanh chóng của nó ở trước Lollipop và đáng kinh ngạc (thực sự) chậm trong Android L.
Servus7

1
bạn cũng có thể chia sẻ phiên bản của thư viện mà bạn đang nhập.
Droidekas

Câu trả lời:


227

Gần đây tôi đã gặp phải vấn đề tương tự, vì vậy đây là những gì tôi đã làm với thư viện hỗ trợ RecyclerView mới nhất:

  1. Thay thế một bố cục phức tạp (các dạng xem lồng nhau, RelativeLayout) bằng ConstraintLayout mới được tối ưu hóa. Kích hoạt nó trong Android Studio: Đi tới Trình quản lý SDK -> tab Công cụ SDK -> Kho lưu trữ hỗ trợ -> kiểm tra ConstraintLayout cho Android & Solver cho ConstraintLayout. Thêm vào các phụ thuộc:

    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    
  2. Nếu có thể, hãy tạo tất cả các phần tử của RecyclerView có cùng chiều cao . Và thêm:

    recyclerView.setHasFixedSize(true);
    
  3. Sử dụng các phương pháp cache bản vẽ RecyclerView mặc định và điều chỉnh chúng theo trường hợp của bạn. Bạn không cần thư viện của bên thứ ba để làm như vậy:

    recyclerView.setItemViewCacheSize(20);
    recyclerView.setDrawingCacheEnabled(true);
    recyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
    
  4. Nếu bạn sử dụng nhiều hình ảnh , hãy đảm bảo kích thước và độ nén của chúng là tối ưu . Tỷ lệ hình ảnh cũng có thể ảnh hưởng đến hiệu suất. Có hai mặt của vấn đề - hình ảnh nguồn được sử dụng và Bitmap được giải mã. Ví dụ sau cung cấp cho bạn một gợi ý về cách giải mã một hình ảnh, được tải xuống từ web:

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

Phần quan trọng nhất là chỉ định inPreferredConfig- nó xác định bao nhiêu byte sẽ được sử dụng cho mỗi pixel của hình ảnh. Hãy nhớ rằng đây là một tùy chọn ưu tiên . Nếu hình ảnh nguồn có nhiều màu hơn, nó sẽ vẫn được giải mã bằng một cấu hình khác.

  1. Đảm bảo onBindViewHolder () càng rẻ càng tốt. Bạn có thể đặt OnClickListener một lần vào onCreateViewHolder()và gọi thông qua giao diện một người nghe bên ngoài Bộ điều hợp, chuyển mục được nhấp vào. Bằng cách này, bạn không phải lúc nào cũng tạo thêm các đối tượng. Cũng kiểm tra cờ và trạng thái, trước khi thực hiện bất kỳ thay đổi nào đối với chế độ xem tại đây.

    viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View view) {
              Item item = getItem(getAdapterPosition());
              outsideClickListener.onItemClicked(item);
          }
    });
    
  2. Khi dữ liệu bị thay đổi, hãy cố gắng chỉ cập nhật các mục bị ảnh hưởng . Ví dụ: thay vì làm mất hiệu lực của toàn bộ tập dữ liệu notifyDataSetChanged(), khi thêm / tải nhiều mục hơn, chỉ cần sử dụng:

    adapter.notifyItemRangeInserted(rangeStart, rangeEnd);
    adapter.notifyItemRemoved(position);
    adapter.notifyItemChanged(position);
    adapter.notifyItemInserted(position);
    
  3. Từ trang web nhà phát triển Android :

Phương án cuối cùng dựa vào InformDataSetChanged ().

Nhưng nếu bạn cần sử dụng nó, hãy duy trì các mặt hàng của bạn bằng các id duy nhất :

    adapter.setHasStableIds(true);

RecyclerView sẽ cố gắng tổng hợp các sự kiện thay đổi cấu trúc có thể nhìn thấy cho bộ điều hợp báo cáo rằng chúng có ID ổn định khi phương pháp này được sử dụng. Điều này có thể giúp cho các mục đích hoạt ảnh và đối tượng trực quan được duy trì nhưng các chế độ xem từng mục sẽ vẫn cần được phục hồi và hiển thị lại.

Ngay cả khi bạn làm đúng mọi thứ, rất có thể RecyclerView vẫn không hoạt động trơn tru như bạn mong muốn.


18
một phiếu bầu cho adapter.setHasStableIds (true); phương pháp thực sự giúp làm cho quá trình tái chế nhanh chóng.
Atula

1
Phần 7 sai hoàn toàn! setHasStableIds (true) sẽ không làm gì ngoại trừ bạn sử dụng adapter.notifyDataSetChanged (). Liên kết: developer.android.com/reference/android/support/v7/widget/…
localhost

1
Tôi có thể hiểu tại sao điều này recyclerView.setItemViewCacheSize(20);có thể làm ảnh hưởng đến hiệu suất. Nhưng recyclerView.setDrawingCacheEnabled(true);recyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);mặc dù! Tôi không chắc những điều này sẽ thay đổi bất cứ điều gì. Đây là Viewcác lệnh gọi cụ thể cho phép bạn truy xuất bộ nhớ đệm bản vẽ theo chương trình dưới dạng bitmap và sử dụng nó để có lợi cho bạn sau này. RecyclerViewdường như không làm bất cứ điều gì về nó.
Abdelhakim AKODADI

2
@AbdelhakimAkodadi, việc cuộn sẽ mượt mà với bộ nhớ cache. Tôi đã thử nghiệm nó. Làm sao bạn có thể nói khác, đó là điều hiển nhiên. Tất nhiên, nếu ai đó cuộn như điên sẽ không có ích gì. Tôi chỉ hiển thị các tùy chọn khác như setDrawingCacheQuality, mà tôi không sử dụng, vì chất lượng hình ảnh rất quan trọng trong trường hợp của tôi. Tôi không giảng về DRAWING_CACHE_QUALI‌ TY_HIGH, nhưng đề nghị bất kỳ ai quan tâm tìm hiểu sâu hơn và điều chỉnh tùy chọn.
Galya

2
setDrawingCacheEnabled () và setDrawingCacheQuality () không được dùng nữa. Thay vào đó hãy sử dụng tăng tốc phần cứng. developer.android.com/reference/android/view/…
Shayan_Aryan

14

2
Điều này giúp tôi cải thiện hiệu suất một chút. Nhưng RecyclerView vẫn còn chậm - chậm hơn nhiều so với ListView tùy chỉnh tương đương.
SMBiggs

Ahh, nhưng tôi đã phát hiện ra lý do tại sao mã của tôi quá chậm - nó không liên quan gì đến setHasStableIds (). Tôi sẽ đăng một câu trả lời với nhiều thông tin hơn.
SMBiggs

13

Tôi đã phát hiện ra ít nhất một mẫu có thể giết chết hiệu suất của bạn. Hãy nhớ rằng nó onBindViewHolder()được gọi thường xuyên . Vì vậy, bất cứ điều gì bạn làm trong đoạn mã đó đều có khả năng khiến hiệu suất của bạn dừng lại. Nếu RecyclerView của bạn thực hiện bất kỳ tùy chỉnh nào, bạn rất dễ vô tình đặt một số mã chậm vào phương thức này.

Tôi đã thay đổi hình nền của mỗi RecyclerView tùy thuộc vào vị trí. Nhưng việc tải hình ảnh hơi mất công, khiến RecyclerView của tôi bị chậm và giật.

Tạo bộ nhớ cache cho hình ảnh đã làm việc kỳ diệu; onBindViewHolder()bây giờ chỉ cần sửa đổi một tham chiếu đến một hình ảnh được lưu trong bộ nhớ cache thay vì tải nó từ đầu. Bây giờ RecyclerView kéo dài.

Tôi biết rằng không phải ai cũng sẽ gặp phải vấn đề chính xác này, vì vậy tôi không bận tâm đến việc tải mã. Nhưng vui lòng coi bất kỳ công việc nào được thực hiện trong bạn onBindViewHolder()như một lỗ hổng tiềm ẩn cho hiệu suất RecyclerView kém.


Im có cùng một vấn đề. Hiện tại, tôi đang sử dụng Fresco để tải và lưu hình ảnh vào bộ nhớ đệm. Bạn có giải pháp khác tốt hơn để tải và lưu hình ảnh vào bộ nhớ cache trong RecyclerView. Cảm ơn.
Androidicus

Tôi không quen với Fresco (đọc ... những lời hứa của họ rất hay). Có thể họ có một số thông tin chi tiết về cách sử dụng tốt nhất bộ nhớ đệm của họ với RecyclerViews. Và tôi thấy bạn không phải là người duy nhất gặp vấn đề này: github.com/facebook/fresco/issues/414 .
SMBiggs

10

Ngoài câu trả lời chi tiết của @ Galya, tôi muốn nói rằng mặc dù nó có thể là một vấn đề tối ưu hóa, nhưng cũng đúng là việc kích hoạt trình gỡ lỗi có thể làm mọi thứ chậm lại rất nhiều.

Nếu bạn làm mọi cách để tối ưu hóa mà RecyclerViewnó vẫn không hoạt động trơn tru, hãy thử chuyển biến thể xây dựng của bạn sang releasevà kiểm tra xem nó hoạt động như thế nào trong môi trường không phát triển (đã tắt trình gỡ lỗi).

Điều xảy ra với tôi là ứng dụng của tôi hoạt động chậm trong debugbiến thể xây dựng, nhưng ngay sau khi tôi chuyển sang releasebiến thể đó, nó hoạt động trơn tru. Điều này không có nghĩa là bạn nên phát triển với releasebiến thể xây dựng, nhưng bạn nên biết rằng bất cứ khi nào bạn sẵn sàng giao hàng ứng dụng của mình, nó sẽ hoạt động tốt.


Nhận xét này thực sự hữu ích cho tôi! Tôi đã thử mọi thứ để cải thiện hiệu suất của Recyclerview nhưng không thực sự có ích gì, nhưng khi tôi đã chuyển sang phiên bản phát hành, tôi nhận ra rằng mọi thứ vẫn ổn.
Tal Barda

1
Tôi gặp phải vấn đề tương tự ngay cả khi không có trình gỡ lỗi đính kèm nhưng ngay sau khi chuyển sang phiên bản phát hành, vấn đề không còn được nhìn thấy nữa
Farmaan Elahi

8

Tôi đã có một cuộc nói chuyện về RecyclerViewhiệu suất của. Đây là các slide bằng tiếng Anhvideo được quay bằng tiếng Nga .

Nó chứa một tập hợp các kỹ thuật (một số kỹ thuật đã được đề cập trong câu trả lời của @ Darya ).

Đây là một bản tóm tắt ngắn gọn:

  • Nếu Adaptercác mục có kích thước cố định thì hãy đặt:
    recyclerView.setHasFixedSize(true);

  • Nếu các thực thể dữ liệu có thể được biểu diễn bằng long ( hashCode()ví dụ) thì hãy đặt:
    adapter.hasStableIds(true);
    và hiện thực:
    // YourAdapter.java
    @Override
    public long getItemId(int position) {
    return items.get(position).hashcode(); //id()
    }
    Trong trường hợp này Item.id()sẽ không hoạt động, vì nó sẽ giữ nguyên ngay cả khi Itemnội dung của thay đổi.
    PS Điều này là không cần thiết nếu bạn đang sử dụng DiffUtil!

  • Sử dụng bitmap được chia tỷ lệ chính xác. Đừng phát minh lại bánh xe và sử dụng các thư viện.
    Thông tin thêm về cách chọn ở đây .

  • Luôn sử dụng phiên bản mới nhất của RecyclerView. Ví dụ, đã có những cải tiến hiệu suất rất lớn trong 25.1.0- tìm nạp trước.
    Thông tin thêm ở đây .

  • Sử dụng DiffUtill.
    DiffUtil là phải .
    Tài liệu chính thức .

  • Đơn giản hóa bố cục mặt hàng của bạn!
    Thư viện nhỏ để làm phong phú TextViews - TextViewRichDrawable

Xem trang trình bày để giải thích chi tiết hơn.


7

Tôi thực sự không chắc liệu việc sử dụng setHasStableIdcờ có khắc phục được sự cố của bạn hay không. Dựa trên thông tin bạn cung cấp, vấn đề hiệu suất của bạn có thể liên quan đến sự cố bộ nhớ. Hiệu suất ứng dụng của bạn về giao diện người dùng và bộ nhớ là khá liên quan.

Tuần trước, tôi phát hiện ứng dụng của mình bị rò rỉ bộ nhớ. Tôi phát hiện ra điều này vì sau 20 phút sử dụng ứng dụng của mình, tôi nhận thấy giao diện người dùng hoạt động rất chậm. Việc đóng / mở một hoạt động hoặc cuộn RecyclerView với một loạt các phần tử thực sự rất chậm. Sau khi theo dõi một số người dùng của tôi trong quá trình sản xuất bằng http://flowup.io/, tôi thấy điều này:

nhập mô tả hình ảnh ở đây

Thời gian khung hình thực sự rất cao và khung hình trên giây thực sự rất thấp. Bạn có thể thấy rằng một số khung hình cần khoảng 2 giây để kết xuất: S.

Đang cố gắng tìm ra nguyên nhân gây ra khung hình / khung hình / giây kém này, tôi đã phát hiện ra mình có vấn đề về bộ nhớ như bạn có thể thấy ở đây:

nhập mô tả hình ảnh ở đây

Ngay cả khi mức tiêu thụ bộ nhớ trung bình là gần 15MB cùng lúc ứng dụng đang giảm khung hình.

Đó là cách tôi phát hiện ra vấn đề về giao diện người dùng. Tôi đã bị rò rỉ bộ nhớ trong ứng dụng của mình gây ra rất nhiều sự kiện bộ thu gom rác và điều đó gây ra hiệu suất giao diện người dùng kém vì máy ảo Android phải dừng ứng dụng của tôi để thu thập bộ nhớ mỗi khung hình.

Nhìn vào đoạn mã, tôi đã bị rò rỉ bên trong chế độ xem tùy chỉnh vì tôi không hủy đăng ký người nghe từ phiên bản Android Choreographer. Sau khi phát hành bản sửa lỗi, mọi thứ trở nên bình thường :)

Nếu ứng dụng của bạn bị giảm khung hình do sự cố bộ nhớ, bạn nên xem lại hai lỗi phổ biến:

Xem lại liệu ứng dụng của bạn có đang phân bổ các đối tượng bên trong một phương thức được gọi nhiều lần mỗi giây hay không. Ngay cả khi việc phân bổ này có thể được thực hiện ở một nơi khác, nơi ứng dụng của bạn đang trở nên chậm chạp. Một ví dụ có thể là tạo các phiên bản mới của một đối tượng bên trong phương thức xem tùy chỉnh onDraw trên onBindViewHolder trong trình giữ chế độ xem chế độ xem tái chế của bạn. Xem lại nếu ứng dụng của bạn đang đăng ký một phiên bản vào Android SDK nhưng không phát hành phiên bản đó. Đăng ký một người nghe vào một sự kiện xe buýt cũng có thể bị rò rỉ.

Tuyên bố từ chối trách nhiệm: Công cụ tôi đang sử dụng để giám sát ứng dụng của mình đang được phát triển. Tôi có quyền truy cập vào công cụ này vì tôi là một trong những nhà phát triển :) Nếu bạn muốn truy cập vào công cụ này, chúng tôi sẽ sớm phát hành phiên bản beta! Bạn có thể tham gia vào trang web của chúng tôi: http://flowup.io/ .

Nếu bạn muốn sử dụng các công cụ khác nhau, bạn có thể sử dụng: traveview, dmtracedump, systrace hoặc màn hình hiệu suất Andorid được tích hợp vào Android Studio. Nhưng hãy nhớ rằng công cụ này sẽ giám sát thiết bị được kết nối của bạn chứ không phải phần còn lại của thiết bị người dùng hoặc cài đặt hệ điều hành Android của bạn.


3

Điều quan trọng nữa là kiểm tra bố cục gốc mà bạn đưa vào Recyclerview của mình. Tôi đã gặp sự cố cuộn tương tự khi tôi thử nghiệm RecyclerView trong chế độ xem lồng nhau. Chế độ xem cuộn trong một chế độ xem khác mà cuộn có thể bị ảnh hưởng đến hiệu suất trong khi cuộn


Có điểm hợp lệ. Tôi cũng phải đối mặt với vấn đề tương tự!
Ranjith Kumar

2

Trong trường hợp của tôi, tôi phát hiện ra rằng nguyên nhân đáng chú ý của độ trễ là do #onBindViewHolder()phương thức bên trong tải có thể kéo thường xuyên . Tôi đã giải quyết nó chỉ bằng cách tải các hình ảnh dưới dạng Bitmap một lần bên trong ViewHolder và truy cập nó từ phương pháp đã đề cập. Đó là tất cả những gì tôi đã làm.


2

Trong RecyclerView của mình, tôi sử dụng Hình ảnh Bitmap Cho nền item_layout của mình.
Mọi điều @Galya nói đều đúng (và tôi cảm ơn anh ấy vì câu trả lời tuyệt vời của anh ấy). Nhưng chúng không hiệu quả với tôi.

Đây là những gì đã giải quyết vấn đề của tôi:

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

Để biết thêm thông tin, vui lòng đọc câu trả lời này .


2

Trong tủ của tôi, tôi có những đứa trẻ tái chế phức tạp. Vì vậy, nó ảnh hưởng đến thời gian tải hoạt động (~ 5 giây để hiển thị hoạt động)

Tôi tải bộ điều hợp bằng postDelayed () -> điều này sẽ cho kết quả tốt cho việc hiển thị hoạt động. sau khi hoạt động kết xuất tải xử lý tái chế của tôi mượt mà.

Hãy thử câu trả lời này,

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

1

Tôi thấy trong các nhận xét rằng bạn đang triển khai ViewHoldermẫu, nhưng tôi sẽ đăng một bộ điều hợp mẫu ở đây sử dụng RecyclerView.ViewHoldermẫu để bạn có thể xác minh rằng bạn đang tích hợp nó theo cách tương tự, một lần nữa hàm tạo của bạn có thể thay đổi tùy theo nhu cầu của bạn, tại đây là một ví dụ:

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);
        }
    }
}

Nếu bạn gặp bất kỳ sự cố nào khi làm việc, RecyclerView.ViewHolderhãy đảm bảo rằng bạn có các phụ thuộc thích hợp mà bạn có thể xác minh luôn tại Gradle Please

Hy vọng nó giải quyết được vấn đề của bạn.


1

Điều này đã giúp tôi cuộn trơn tru hơn:

ghi đè onFailedToRecycleView (người giữ ViewHolder) trong bộ điều hợp

và dừng mọi hoạt ảnh đang diễn ra (nếu có) người giữ. "animateview" .clearAnimation ();

nhớ trả về true;


1

Tôi đã giải quyết nó bằng dòng mã này

recyclerView.setNestedScrollingEnabled(false);

1

Thêm vào câu trả lời của @ Galya, trong bind viewHolder, tôi đã sử dụng phương thức Html.fromHtml (). rõ ràng điều này có tác động đến hiệu suất.


0

Tôi giải quyết vấn đề này bằng cách sử dụng một dòng duy nhất trong thư viện Picasso

.Phù hợp()

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();
                        }
                    });
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.