3

I have a Music class that needs to return its cover art as a bitmap in order to use it on a RecylerView. I am using an AsyncTask inner class in the class to perform the retrieval, however, my app freezes once the list is being created from cover arts. Please see the code below for Music.java:

public class Music {
    private static final String LOG_TAG = Music.class.getSimpleName();
    private String mId;
    private String mTitle;
    private String mUrl;
    private Bitmap mCoverArt;

    public Music(String id, String title, String url) {
        mId = id;
        mTitle = title;
        mUrl = url;
        mCoverArt = null; //Initialize with null
    }

    String getId() {
        return mId;
    }

    String getTitle() {
        return mTitle;
    }

    String getUrl() {
        return mUrl;
    }

    Bitmap getCoverArt() {
        if(mCoverArt != null) {
            return mCoverArt;
        }
        else {
            Bitmap bmp = null;
            try {
                bmp = new GetCoverArt().execute(mUrl).get();
            } catch (InterruptedException e) {
                Log.e(LOG_TAG, "InterruptedException: " + e.getMessage());
            } catch (ExecutionException e) {
                Log.e(LOG_TAG, "ExecutionException: " + e.getMessage());
            }
            return bmp;

        }
    }
    public void setCoverArt(Bitmap bmp) { mCoverArt = bmp; }

    private static class GetCoverArt extends AsyncTask<String, Void, Bitmap> {
        @Override
        protected Bitmap doInBackground(String... paths) {
            MediaMetadataRetriever mmr = new MediaMetadataRetriever();
            mmr.setDataSource(paths[0], new HashMap<String,String>());
            byte[] picData = mmr.getEmbeddedPicture();
            return BitmapFactory.decodeByteArray(picData, 0, picData.length);
        }
    }
}

I am calling getCoverArt() in onBindViewHolder for my RecyclerView this way:

@Override
public void onBindViewHolder(ViewHolder holder, int position) {
    Music song = mDataset.get(position);
    Bitmap songCoverArt = song.getCoverArt();
    String songTitle = song.getTitle();
    String songId = song.getId();
    String songUrl = song.getUrl();

    if(songCoverArt != null) {
        Glide.with(mContext).load(songCoverArt).into(holder.coverArt);
    }
    else {
        holder.coverArt.setImageResource(R.drawable.coverart_fallback);
    }
    Bitmap bmp = song.getCoverArt();
    if(bmp != null) {
        Glide.with(mContext).load(bmp).into(holder.coverArt);
    }
    else {
        Glide.with(mContext).load(R.drawable.coverart_fallback).into(holder.coverArt);
    }

I do not understand why doInBackground in AsyncTask might cause the UI thread to freeze. I thought it all runs in the background, but it seems my RecyclerView is waiting for it to finish the job before it can use the value returned. Currently, as an a bad workaround, I am doing such processing in another AsyncTask in the main activity along with other network operations when I construct Music objects and add them to an ArrayList:

 for( int j = 0 ; j < songs.length() ; j++) {
                            JSONObject song = songs.getJSONObject(j); //get song at index j
                            String songId = song.getString( getString(R.string.json_song_id) );
                            String title = song.getString( getString(R.string.json_song_title));
                            String path = song.getString( getString(R.string.json_filepath) );
                            //Create a temp Music object to extract Music info
                            Music songObj = new Music(songId, title, path);
                            Bitmap bmp = createCoverArtBmp(path);
                            songObj.setCoverArt(bmp);
                            mMusicTrackArray.add(songObj); //Add the music object into array
                        }
Mehdi Haghgoo
  • 3,144
  • 7
  • 46
  • 91

2 Answers2

2

You don't need to convert it to bitmap from the url to show the images. Simply pass the image url to Glide and it will load it for you.

By calling bitmap making in the getCoverArt() you are not doing it async but waiting for the task to finish as a result freezing your view. See here how to use Glide with url : https://github.com/bumptech/glide#how-do-i-use-glide

Umar Hussain
  • 3,461
  • 1
  • 16
  • 38
  • I don't have image url. I have the url to mp3 file only :( – Mehdi Haghgoo Dec 05 '17 at 10:28
  • 1
    Oh okay. In that case what you can do is move the async inside your recycler bindviewholder. Pass the imageview reference to the async task. Then execute the async task and in your postExecute method set the bitmap to imageview with the help of Glide. – Umar Hussain Dec 05 '17 at 12:09
  • Doing this will allow you to have async task in background for every image in recycler and as soon as cover of album is downloaded it wil be set in the recycler – Umar Hussain Dec 05 '17 at 12:10
  • This seems a very good idea. You mean I create a field in Music.java that holds a reference to an ImageView and then set it in onPostExecute()? – Mehdi Haghgoo Dec 05 '17 at 12:23
  • no not in Music.java but in GetCoverArt, in its constructor pass the reference to imageview and store that reference locally inside the GetCoverArt. Then in onPostExecute use this reference. – Umar Hussain Dec 05 '17 at 13:09
1

@Umar Hussain is correct with passing urls, but Glide can also use local files or URI's doing so is covered in:

Glide load local image by Uri.

The benefit here of using local files (I suggest saving to your cache) is that you don't have to pass around bulky bitmaps. Out of memory exceptions make developers sad.

I have noticed you are also using fallbacks from if statements, but Glide has a placeholder method

Glide.with(getContext())
                    .load(some_bitmap_file_url_or_drawable)
                    .placeholder(some_placeholder_drawable)
                    .into(view_you_want_it_to_appear);

This should transition to the image you want when it loads, plus provide a fallback if it doesn't

  • It seems there should be a Glide module in the app and then we can use generated API using GlideApp.load().placeholder(). Glide.load.placeholder does not work. – Mehdi Haghgoo Dec 07 '17 at 22:01
  • You have to add the Glide dependency to your build.gradle file [Glide github](https://github.com/bumptech/glide) –  Dec 08 '17 at 20:21