使用Asynctask优化ListView,滚动更顺畅
Optimization of ListView with Asynctask for Smoother Scroll
我正在开发音乐应用程序,并且是 android 应用程序的新手。我正在使用 ListView
来显示设备中的音乐文件内容。这是我在 ListView
:
中所做的
- 显示音乐文件标题、艺术家和持续时间
- 音乐文件的专辑封面(如果可用)
所以
我正在做的事情的第一点很容易做到,并且非常有效,滚动行为非常流畅,问题从第二点开始,即加载我正在用 AsyncTask
做的音乐文件的专辑封面。我得到 AsyncTask
工作,但它 不是最聪明的 AsyncTask
。例如,我想要 ListView
和 AsyncTask
:
中的这两种行为
- 仅当用户停止滚动时才开始执行
AsyncTask
- 我想在用户尝试快速浏览视图(快速滚动)时保存重要的 CPU 周期。现在用我的方法,当发生投掷时,所有 AsyncTask
立即开始加载。我已经实现了函数 cancelPotentialTask()
,如果我不需要它,它会立即取消任务,但如果我可以控制启动任务,那会比取消它更有效率。就像当用户停止滚动时 ListView
我得到一个开始的触发器 AsyncTask
.
- 如果
AsyncTask
一次 return 结果是没有可用的专辑封面,那么如果我再次调用它就不需要再次进入任务 - 这可以通过下图轻松理解(抱歉编辑不当)。
在第一部分中,我让视图与 AsyncTask 一起加载。在下一个中,我向上滑动并获得另一个视图,然后让一行转到回收站。第三次向下滑动时,我得到了回收视图,但 Asynctask 加载了,但我不需要 AsyncTask,因为它很明显结果将是空的,因为它是第一次。我知道代码不知道这一点,直到我们不告诉它。但是我怎么知道呢。我在 getView()
中有一条线索,我可以在其中查看是否 convertView == null
。
感谢您到目前为止的阅读,感谢您的耐心等待。这是给你的奶酪 -
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;
}
请大家慢慢来,我不急于很快接受任何答案。希望有人可以玩代码,或者如果没有,只能解释我的方法应该是什么。
如果你们还需要知道什么,请告诉我。谢谢!
编辑
getView() 方法 -
@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;
}
更新
使用 UIL
,结果几乎与我提供的代码相同,即滞后。但是 Picasso
确实值得一试 我会说滞后几乎消失了。这几乎就是我想要实现的。
随着库 Picasso
的使用,延迟滚动消失了。我仍然不确定这可能是什么确切原因,但只是用 Picasso 替换我的 AsyncTask 就像魅力一样。由于缓存是由库本身完成的,因此更加流畅和高效。
我正在开发音乐应用程序,并且是 android 应用程序的新手。我正在使用 ListView
来显示设备中的音乐文件内容。这是我在 ListView
:
- 显示音乐文件标题、艺术家和持续时间
- 音乐文件的专辑封面(如果可用)
所以
我正在做的事情的第一点很容易做到,并且非常有效,滚动行为非常流畅,问题从第二点开始,即加载我正在用 AsyncTask
做的音乐文件的专辑封面。我得到 AsyncTask
工作,但它 不是最聪明的 AsyncTask
。例如,我想要 ListView
和 AsyncTask
:
- 仅当用户停止滚动时才开始执行
AsyncTask
- 我想在用户尝试快速浏览视图(快速滚动)时保存重要的 CPU 周期。现在用我的方法,当发生投掷时,所有AsyncTask
立即开始加载。我已经实现了函数cancelPotentialTask()
,如果我不需要它,它会立即取消任务,但如果我可以控制启动任务,那会比取消它更有效率。就像当用户停止滚动时ListView
我得到一个开始的触发器AsyncTask
. - 如果
AsyncTask
一次 return 结果是没有可用的专辑封面,那么如果我再次调用它就不需要再次进入任务 - 这可以通过下图轻松理解(抱歉编辑不当)。
在第一部分中,我让视图与 AsyncTask 一起加载。在下一个中,我向上滑动并获得另一个视图,然后让一行转到回收站。第三次向下滑动时,我得到了回收视图,但 Asynctask 加载了,但我不需要 AsyncTask,因为它很明显结果将是空的,因为它是第一次。我知道代码不知道这一点,直到我们不告诉它。但是我怎么知道呢。我在 getView()
中有一条线索,我可以在其中查看是否 convertView == null
。
感谢您到目前为止的阅读,感谢您的耐心等待。这是给你的奶酪 -
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;
}
请大家慢慢来,我不急于很快接受任何答案。希望有人可以玩代码,或者如果没有,只能解释我的方法应该是什么。
如果你们还需要知道什么,请告诉我。谢谢!
编辑
getView() 方法 -
@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;
}
更新
使用 UIL
,结果几乎与我提供的代码相同,即滞后。但是 Picasso
确实值得一试 我会说滞后几乎消失了。这几乎就是我想要实现的。
随着库 Picasso
的使用,延迟滚动消失了。我仍然不确定这可能是什么确切原因,但只是用 Picasso 替换我的 AsyncTask 就像魅力一样。由于缓存是由库本身完成的,因此更加流畅和高效。