RecyclerView and async Loading

Jemil Riahi picture Jemil Riahi · Jul 26, 2016 · Viewed 8.5k times · Source

I have a slight problem. Need a nudge in the right direction. I am doing a video editor like Vine and/or instagram. Where they show a timeline with screencaps from the video enter image description here

It just adds more pictures depending on the videos duration. What i did for my app is that i added a recyclerView. This recyclerview has an adapter that calls the following function every time onBindViewHolder

  public Bitmap getFrameFromCurrentVideo(int seconds) {
    Bitmap bitmap = null;
    if(mMediaMetadataRetriever != null) {
      bitmap = mMediaMetadataRetriever.getFrameAtTime(seconds * 1000000, MediaMetadataRetriever.OPTION_CLOSEST);
    }
    return bitmap;
  }

This works and it adds the proper amount of images that i want. But the problem is that it is too heavy on the UI thread. Since the recyclerView is recycling everything. It then lags up every time it has to get a frame.

So i thought that i have to do some async task and then cache the images. But what i read is that AsyncTask is not recommended for recycler views since it recycles.

So what should i do to enchance the performance? Any good idea?

Answer

Jemil Riahi picture Jemil Riahi · Jul 26, 2016

This is what i did to solve my problem. I created async task and memory cache my result.

My adapter checks if the image already exist. If it does. Then we skip doing the background work. Otherwise i do the async task and try to load the image. We also tag the view just in case the user scrolls while the task is not finished.

This helps us check if the Tag is different from what the task have. If it is the same. Then we can safely put the right image in the imageview.

Snippet from my adapter

  @Override
  public void onBindViewHolder(PostVideoRecyclerViewHolder holder, int position) {
    holder.mImageView.getLayoutParams().width = mScreenWidth / mMaxItemsOnScreen;
    holder.mImageView.setImageDrawable(null);
    int second = position * 3 - 3;
    String TAG = String.valueOf(second);
    holder.mImageView.setTag(TAG);

    Bitmap bitmap = mFragment.getBitmapFromMemCache(TAG);
    if(bitmap == null) {
      PostVideoBitmapWorkerTask task = new PostVideoBitmapWorkerTask(holder.mImageView, TAG, mFragment);
      task.execute(second);
    }
    else {
      holder.mImageView.setImageBitmap(bitmap);
    }
  }

My AsyncTask class

public class PostVideoBitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
  private ImageView mImageView;
  private String TAG;
  private PostVideoFeedFragment mFragment;

  public PostVideoBitmapWorkerTask(ImageView imageView, String TAG, PostVideoFeedFragment fragment) {
    mImageView = imageView;
    this.TAG = TAG;
    mFragment = fragment;
  }

  @Override
  protected Bitmap doInBackground(Integer... params) {
    Bitmap bitmap = mFragment.getFrameFromCurrentVideo(params[0]);
    mFragment.addBitmapToCache(TAG,bitmap);
    return bitmap;
  }

  @Override
  protected void onPostExecute(Bitmap bitmap) {
    if(mImageView.getTag().toString().equals(TAG)) {
      mImageView.setImageBitmap(bitmap);
    }
  }
}

Snippet from my fragment class

  public void addBitmapToCache(String key, Bitmap bitmap) {
    if (getBitmapFromMemCache(key) == null) {
      mMemoryCache.put(key, bitmap);
    }
  }

  public Bitmap getBitmapFromMemCache(String key) {
    return mMemoryCache.get(key);
  }

I can recommend https://developer.android.com/training/displaying-bitmaps/cache-bitmap.html if you want to read up on caching

And also https://developer.android.com/reference/android/os/AsyncTask.html for reading up on asyncTask's