Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lags when RecyclerView scrolling

I can not understand what is the reason the brakes when you scroll through my RecyclerView. Scrolling is called by mRecyclerView.smoothScrollToPosition(position); When you scroll through the list of twitches (rendering time frame> 16ms)

AlphaAdapter.java

public class AlphaAdapter extends RecyclerView.Adapter<AlphaAdapter.ViewHolder> {
    private static final String TAG = "AlphaAdapter";
    private Context mContext;
    private List<PrimaryWeapon> mGunList;
    private boolean isAnimate;

    private final int VIEW_TYPE_NULL        = 0;
    private final int VIEW_TYPE_ITEM        = 1;


    public AlphaAdapter(Context mContext, List<PrimaryWeapon> mGunList) {
        this.mContext = mContext;
        this.mGunList = mGunList;
    }

    public AlphaAdapter(Context mContext, List<PrimaryWeapon> mGunList, boolean a) {
        this.mContext = mContext;
        this.mGunList = mGunList;
        this.isAnimate = a;
    }

    @Override
    public int getItemViewType(int position) {
        try {
            if (mGunList.get(position).getNameWeapon().equals(""))
                return VIEW_TYPE_NULL;
            else return VIEW_TYPE_ITEM;
        } catch (Exception e) {
            e.printStackTrace();
            Log.e("AlphaAdapter", String.valueOf(position));
            return VIEW_TYPE_NULL;
        }
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = null;
        if (viewType == VIEW_TYPE_ITEM)
            view = LayoutInflater.from(mContext).inflate(R.layout.item_game, parent, false);
        else
            view = LayoutInflater.from(mContext).inflate(R.layout.item_null, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void onViewDetachedFromWindow(ViewHolder holder) {
        super.onViewDetachedFromWindow(holder);
        holder.itemView.clearAnimation();
    }

    @Override
    public boolean onFailedToRecycleView(ViewHolder holder) {
        return true;
    }

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    @Override
    public void onBindViewHolder(final ViewHolder holder, int position) {
        try {
            long startTime = System.currentTimeMillis();
            if (!getWeapon(position).getNameWeapon().equals("")) {
                if (isAnimate) {
                    if (mLastAnimPosition < position) {
                        mLastAnimPosition = position;
                        Animation a = AnimationUtils.loadAnimation(mContext, R.anim.item_game_anim);
                        holder.itemView.startAnimation(a);
                        a.setAnimationListener(new Animation.AnimationListener() {
                            @Override
                            public void onAnimationStart(Animation animation) {

                            }

                            @Override
                            public void onAnimationEnd(Animation animation) {
                            }

                            @Override
                            public void onAnimationRepeat(Animation animation) {

                            }
                        });
                    }
                }

               Picasso.with(mContext)
                        .load("file:///android_asset/" + getWeapon(position).getImagePath())
                        .placeholder(R.drawable.loading)
                        .error(R.drawable.load_fail)
                        .into(holder.image);
                holder.main.setText(getWeapon(position).getNameWeapon());
                holder.desc.setText(getWeapon(position).getNameSkin());

            }
            Log.i(TAG, String.valueOf(System.currentTimeMillis() - startTime) + "ms onBind (" + String.valueOf(position) + ");");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    private PrimaryWeapon getWeapon(int position) {
        return mGunList.get(position);
    }

    @Override
    public int getItemCount() {
        return mGunList.size();
    }

    class ViewHolder extends RecyclerView.ViewHolder {

        TextView main, desc;
        ImageView image;

        ViewHolder(View itemView) {
            super(itemView);
            main = (TextView) itemView.findViewById(R.id.item_textMain);
            desc = (TextView) itemView.findViewById(R.id.item_textDescription);
            image = (ImageView) itemView.findViewById(R.id.item_image);
        }
    }

}

Layout element that participates in the adapter consists of LinearLayout, ImageView, 2 TextView.

At first I thought that the problem in downloading images, try to load them into AsyncTask, upload bitmap through Picasso and without, but after all removed from their mark, and the list will still lag.

To dump the RAM after the scroll is, it shows that there are 71 memory instances originally ViewHolder, after adding

@Override
    public boolean onFailedToRecycleView(ViewHolder holder) {
        return true;
    }

this value is reduced to 15-16, although the rate for RecyclerView - 5 copies, and it should override them, but does not create new ones when you scroll. Screenshot 1

item_null.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="74dp"
    android:layout_height="80dp">

</LinearLayout>

item_game.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_gravity="center"
    android:padding="2dp"
    android:id="@+id/layout"
    android:layout_height="80dp"
    android:fitsSystemWindows="true"
    android:layout_width="74dp">

    <ImageView
        android:id="@+id/item_image"
        android:layout_width="match_parent"
        android:layout_height="56dp"
        />

    <TextView
        android:layout_gravity="top"
        android:layout_width="match_parent"
        android:singleLine="true"
        android:id="@+id/item_textMain"
        android:paddingLeft="4dp"
        android:paddingRight="4dp"
        android:paddingTop="1dp"
        android:gravity="left"
        android:textColor="#FFF"
        android:maxLines="1"
        android:textSize="8sp"
        android:layout_height="10dp" />

        <TextView
            android:layout_gravity="bottom"
            android:layout_width="match_parent"
            android:id="@+id/item_textDescription"
            android:paddingLeft="4dp"
            android:paddingRight="4dp"
            android:gravity="left"
            android:textColor="#FFF"
            android:singleLine="true"
            android:maxLines="1"
            android:maxLength="40"
            android:textSize="6sp"
            android:layout_height="12dp" />

</LinearLayout>
like image 716
Nikita G. Avatar asked Jan 29 '17 17:01

Nikita G.


2 Answers

You are passing a context to adapter. First of all this could lead to memory leaks and also could be affecting your performance. Instead of passing the context into adapter, just simply get it from ViewHolder. You can always get a context reference inside RecyclerView.Adapter without a need to pass it around.

To dump the RAM after the scroll is, it shows that there are 71 memory instances originally ViewHolder.

Judging from the dump this is most probably the case.

@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    Context context = parent.getContext();
    ...
}

@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
    Context context = holder.itemView.getContext();
    ...
}
like image 168
Andrej Jurkin Avatar answered Nov 08 '22 06:11

Andrej Jurkin


In response to the answer that was accepted here:

How could a Context be a memory leak on an adapter? Or even make it slower? It's attached only to the Activity's RecyclerView anyway... Once the Activity is gone, the context is GC-ed together with the adapter. Not only that, but getting the Context from the view will always return you the same one, so it's useless and calls to functions without any need. You can even make the adapter a static inner class, so that you could access it directly, instead of having a declared field. In short, the context isn't the reason you have a memory leak.

What could be the issue, then? I think you need to cancel the previous image request on Picasso, each time you bind to a ViewHolder, like this:

 Picasso.with(context).cancelRequest(holder.image);

You might also want to do it for all, in case the Activity was destroyed (yet not because of orientation change), but this depends on your needs.

I suggest to also try Glide library instead. It seems more modern and still being maintained.

like image 1
android developer Avatar answered Nov 08 '22 07:11

android developer