使用Asynctask优化ListView,滚动更顺畅

Optimization of ListView with Asynctask for Smoother Scroll

我正在开发音乐应用程序,并且是 android 应用程序的新手。我正在使用 ListView 来显示设备中的音乐文件内容。这是我在 ListView:

中所做的
  1. 显示音乐文件标题、艺术家和持续时间
  2. 音乐文件的专辑封面(如果可用)

所以

我正在做的事情的第一点很容易做到,并且非常有效,滚动行为非常流畅,问题从第二点开始,即加载我正在用 AsyncTask 做的音乐文件的专辑封面。我得到 AsyncTask 工作,但它 不是最聪明的 AsyncTask。例如,我想要 ListViewAsyncTask:

中的这两种行为
  1. 仅当用户停止滚动时才开始执行 AsyncTask - 我想在用户尝试快速浏览视图(快速滚动)时保存重要的 CPU 周期。现在用我的方法,当发生投掷时,所有 AsyncTask 立即开始加载。我已经实现了函数 cancelPotentialTask(),如果我不需要它,它会立即取消任务,但如果我可以控制启动任务,那会比取消它更有效率。就像当用户停止滚动时 ListView 我得到一个开始的触发器 AsyncTask.
  2. 如果 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 就像魅力一样。由于缓存是由库本身完成的,因此更加流畅和高效。

这里是图书馆https://github.com/square/picasso