I am working on a music application and a novice in android applications. I am using ListView
to show content which is music files in the device. Here is what I am doing in the ListView
:
So
The first point of what I am doing is easy to do and quite efficient with really smooth scroll behaviour, problem starts from second point i.e. loading album art of music file which I am doing with AsyncTask
. I get AsyncTask
working but it's not the smartest AsyncTask
. For instance I want these two behaviours from my ListView
and AsyncTask
:
AsyncTask
only when user stops scrolling - I want to save important CPU cycles when user tries to fling through views (scrolls fastly all across). Right now with my approach, when fling occurs all AsyncTask
starts loading right away. I have implemented function cancelPotentialTask()
and it cancels the task right away if I dont need it but what if I can control starting the task then that would be more efficient than cancelling it. Like when user stop scrolling ListView
I get a trigger to start AsyncTask
.AsyncTask
once return result that no album art is available then it do not need to get into the task again if I call it again - This can be easily understood by picture below (sorry for poor editing).
In first part, I let views to load up along with AsyncTask. In next one, I swipe up and get another view and let one row go to recycler. Third time I swipe down I get the recycled view back but Asynctask loads up but I do not need AsyncTask as it is really obvious result will be null as it was first time. I know code do not know this till we do not tell it. But how can I tell. I have a clue in getView()
where I see if convertView == null
.
Thanks for reading so far, I appreciate your patience. Here is the cheese for you -
public Bitmap getAlbumart(Long album_id) {
Bitmap bm = null;
try {
final Uri sArtworkUri = Uri
.parse("content://media/external/audio/albumart");
Uri uri = ContentUris.withAppendedId(sArtworkUri, album_id);
ParcelFileDescriptor pfd = activity.getContentResolver()
.openFileDescriptor(uri, "r");
if (pfd != null) {
FileDescriptor fd = pfd.getFileDescriptor();
bm = BitmapFactory.decodeFileDescriptor(fd);
}
} catch (Exception ignored) {
}
return bm;
}
// This is what I call to get image in getView()
public void loadBitmap(String resId, ImageView imageView) {
if (cancelPotentialWork(resId, imageView)) {
String imageKey = String.valueOf(resId);
LoadRows task = new LoadRows(imageView);
SoftReference<Bitmap> bitmap1;
try {
bitmap1 = new SoftReference<>(Bitmap.createScaledBitmap(getBitmapFromMemCache(imageKey), 80, 80, true));
imageView.setImageBitmap(bitmap1.get());
} catch (Exception e) {
SoftReference<AsyncDrawable> asyncDrawable = new SoftReference<>(new AsyncDrawable(resId, null,
task));
imageView.setImageDrawable(asyncDrawable.get());
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, resId);
}
}
}
public class LoadRows extends AsyncTask<String, Void, Bitmap> {
String data = null;
private final WeakReference<ImageView> imageViewReference;
public LoadRows(ImageView imageView) {
// Use a WeakReference to ensure the ImageView can be garbage
// collected
imageViewReference = new WeakReference<ImageView>(imageView);
}
@Override
protected Bitmap doInBackground(String... params) {
data = params[0];
Bitmap imageResult;
imageResult = getAlbumart(Long.parseLong(data));
if (imageResult != null) {
imageResult = Bitmap.createScaledBitmap(imageResult, 300, 300, true);
addBitmapToMemoryCache(data, imageResult);
imageResult = Bitmap.createScaledBitmap(imageResult, 80, 80, true);
}
return imageResult;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
if (isCancelled()) {
bitmap = null;
}
if (bitmap != null) {
ImageView imageView = imageViewReference.get();
LoadRows bitmapWorkerTask = getLoadRows(imageView);
if (this == bitmapWorkerTask) {
imageView.setImageBitmap(bitmap);
}
}
}
}
static class AsyncDrawable extends BitmapDrawable {
private final WeakReference<LoadRows> LoadRowsReference;
@SuppressWarnings("deprecation")
public AsyncDrawable(String res, Bitmap bitmap, LoadRows LoadRows) {
super(bitmap);
LoadRowsReference = new WeakReference<LoadRows>(LoadRows);
}
public LoadRows getLoadRows() {
return LoadRowsReference.get();
}
}
public static boolean cancelPotentialWork(String data, ImageView imageView) {
final LoadRows LoadRows = getLoadRows(imageView);
if (LoadRows != null) {
final String bitmapData = LoadRows.data;
// If bitmapData is not yet set or it differs from the new data
if (bitmapData == "0" || bitmapData != data) {
// Cancel previous task
LoadRows.cancel(true);
} else {
// The same work is already in progress
return false;
}
}
// No task associated with the ImageView, or an existing task was
// cancelled
return true;
}
private static LoadRows getLoadRows(ImageView imageView) {
if (imageView != null) {
final Drawable drawable = imageView.getDrawable();
if (drawable instanceof AsyncDrawable) {
final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
return asyncDrawable.getLoadRows();
}
}
return null;
}
Please guys take your time, I have no hurry to accept any answer really soon. Hope someone can play with code or if nothing but explain me what should be my approach.
If you guys need anything else to know, just let me know. Thank you!
Edit
getView() method -
@SuppressWarnings("deprecation")
@SuppressLint("SimpleDateFormat")
public View getView(final int position, View convertView, ViewGroup parent) {
View vi = convertView;
final ViewHolder holder;
if (convertView == null) {
/****** Inflate tabitem.xml file for each row ( Defined below ) *******/
vi = inflater.inflate(R.layout.row, null);
/****** View Holder Object to contain tabitem.xml file elements ******/
holder = new ViewHolder();
holder.text = (TextView) vi.findViewById(R.id.text);
holder.text1 = (TextView) vi.findViewById(R.id.text1);
holder.duration = (TextView) vi.findViewById(R.id.textTime);
holder.image = (ImageView) vi.findViewById(R.id.image);
holder.btn = (Button) vi.findViewById(R.id.button1);
/************ Set holder with LayoutInflater ************/
vi.setTag(holder);
} else {
holder = (ViewHolder) vi.getTag();
}
if (data.size() > 0) {
/***** Get each Model object from Arraylist ********/
tempValues = null;
tempValues = (ListModel) data.get(position);
final String duration = tempValues.getDuration();
final String artist = tempValues.getArtist();
final String songName = tempValues.getName();
final String title = tempValues.getTitle();
final String albumid = tempValues.getAlbumID();
String finalTitle;
if (title != null) {
finalTitle = title;
} else {
finalTitle = songName;
}
loadBitmap(albumid, holder.image); // HERE I GET IMAGE ***********
holder.text.setText(finalTitle);
holder.text1.setText(artist);
holder.duration.setText(duration);
vi.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
MusicUtils.play(position, listModelToArrayList(), activity, songsManager);
}
});
final PopupMenu pop = new PopupMenu(activity, holder.btn);
int[] j = new int[6];
j[0] = MusicUtils.PLAY;
j[1] = MusicUtils.PLAY_NEXT;
j[2] = MusicUtils.ADD_TO_QUEUE;
j[3] = MusicUtils.ADD_TO_PLAYLIST;
j[4] = MusicUtils.SHUFFLE_PLAY;
j[5] = MusicUtils.DELETE;
MusicUtils.generateMenu(pop, j);
pop.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case MusicUtils.PLAY:
MusicUtils.play(position, listModelToArrayList(), activity, songsManager);
return true;
case MusicUtils.DELETE:
return true;
case MusicUtils.PLAY_NEXT:
MusicUtils.playNext(listModelToArrayList().get(position));
return true;
case MusicUtils.ADD_TO_QUEUE:
MusicUtils.addToQueue(listModelToArrayList().get(position), songsManager);
return true;
case MusicUtils.ADD_TO_PLAYLIST:
MusicUtils.addToPlaylist(position, listModelToArrayList().get(position), activity, songsManager);
return true;
case MusicUtils.SHUFFLE_PLAY:
MusicUtils.shufflePlay(position, listModelToArrayList(), activity, songsManager);
return true;
default:
return false;
}
}
});
holder.btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
pop.show();
}
});
tempValues = null;
vi.setVisibility(View.VISIBLE);
} else {
vi.setVisibility(View.INVISIBLE);
}
return vi;
}
Update
With using UIL
, the result is almost same as the code I provided up i.e. laggy. But Picasso
is really worth trying I would say the lag is almost gone. This is almost what I was looking to implement.
The laggy scroll is gone with usage of library Picasso
. I am still not sure what can be exact reason for this but just replacing my AsyncTask with Picasso works like charm. It is much more smoother and efficient as caching is done by library itself.
Here is the library https://github.com/square/picasso.