【问题标题】:Android stop async loading list element when no more visibleAndroid 在不再可见时停止异步加载列表元素
【发布时间】:2015-03-22 15:14:24
【问题描述】:

正如许多教程和Android developers pages too 中所述,我正在使用异步任务将图像作为缩略图加载到 ListView 中。该任务从 SD 卡加载全尺寸图片,调整大小并将其放入列表项布局的 ImageView 中。

一切都很好,除了在快速上下滚动列表后,单个可见元素的图像在获得正确的图像之前会用不同的图像更新两到三遍。

在我看来,这种行为与recycling views in ListView 相关:当异步任务准备在引用视图中注入列表的元素-X 图像时,视图本身可能已经被回收并分配给列表的元素-是的。

我意识到我的代码中存在一些,例如,我既没有为缩略图实现易失性缓存也没有实现持久性缓存(针对下一个版本),但问题只是部分被它隐藏了。

我找到了possible solution using libraries for loading image,但我正在研究如何修复我的代码,因为这个问题更普遍地与将异步代码与列表结合使用有关,今天我处理图像,但明天我可能会面对加载文本或任何其他类型的数据时出现同样的问题。

我正在研究的可能解决方案是:

  1. 通知异步任务它正在处理的列表中的项目,一旦加载的图像仅在项目可见时更新它
  2. 当列表将视图与元素分离时(我如何检测到这一点?),stop the asynctask
  3. 覆盖列表的 OnScrollListener 以检查是否发生 OnScroll 事件,如果项目退出可见项目的列表并停止其异步任务(如果存在)。

这些解决方案之一是可行的还是您建议的其他解决方案?

这是我的列表适配器(我在片段中使用可扩展列表):

@Override
public View getChildView(int groupPosition, final int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {

    Log.i(TAG, "ExpandableListAdapter.getChildView entered, getting view n. " + groupPosition + "-" + childPosition + ", convertview = " + convertView);

    ViewHolder holder;

    if (convertView == null) {

        convertView = inf.inflate(R.layout.selfie_list_item_layout, parent, false);
        holder = new ViewHolder();

        holder.date = (TextView) convertView.findViewById(R.id.selfieListItemDateView);
        holder.place = (TextView) convertView.findViewById(R.id.selfieListItemPlaceView);
        holder.thumb = (ImageView) convertView.findViewById(R.id.selfieListItemThumbView);
        convertView.setTag(holder);
    } else {
        holder = (ViewHolder) convertView.getTag();
    }

    Integer mChildIndex = (Integer) getChild(groupPosition, childPosition);
    SelfieItem mChildObj = selfies.get(mChildIndex);
    String mText = mChildObj.getDate().toString();
    holder.date.setText(mText);
    holder.thumb.setImageBitmap(BitmapFactory.decodeResource(convertView.getResources(), R.drawable.selfie_place_holder));


    File selfieFile = mChildObj.getFile();

    new LoadSelfieTask(mFragmentContext).executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, selfieFile, holder.thumb);

    return convertView;
}

以下是异步代码:

   @Override
    protected Bitmap doInBackground(Object... params) {

        File selfieFile = (File)params[0];
        Bitmap mySrcBitmap = null;
        Bitmap myDestBitmap = null;

        if (selfieFile.exists()) {

            mySrcBitmap = BitmapFactory.decodeFile(selfieFile.getAbsolutePath());

        }

        if (mySrcBitmap != null) {

            // Get info about view to be updated
            mImageViewToBeUpdated = (ImageView) params[1];
            mImageHeight = mImageViewToBeUpdated.getHeight();
            mImageWidth = mImageViewToBeUpdated.getWidth();

            if (mySrcBitmap.getWidth() >= mySrcBitmap.getHeight()){

                myDestBitmap = Bitmap.createBitmap(
                        mySrcBitmap,
                        mySrcBitmap.getWidth()/2 - mySrcBitmap.getHeight()/2,
                        0,
                        mySrcBitmap.getHeight(),
                        mySrcBitmap.getHeight()
                );

            }else{

                myDestBitmap = Bitmap.createBitmap(
                        mySrcBitmap,
                        0,
                        mySrcBitmap.getHeight()/2 - mySrcBitmap.getWidth()/2,
                        mySrcBitmap.getWidth(),
                        mySrcBitmap.getWidth()
                );
            }

            mySrcBitmap = Bitmap.createScaledBitmap(myDestBitmap, mImageWidth, mImageHeight, true);

        }

        return mySrcBitmap;

    }

提前感谢您的回答。

【问题讨论】:

    标签: android listview asynchronous android-listview android-asynctask


    【解决方案1】:

    如果你需要一些快速的东西,试试 Picasso http://square.github.io/picasso/

    您的代码为每一行创建一个 AsyncTask 以从外部存储中获取图像。当您滚动回一个行项目时,您将创建另一个 AsynTask 以再次获取相同的图像。我建议你创建一个缓存来存储 AsynTask 的结果,并有适当的缓存替换策略。

    【讨论】:

    • 感谢您的建议,这可能会解决我自己的代码的两个问题:缺少任何类型的缓存和所描述的并发问题。关于第二个问题,当对同一视图执行第二个请求时,Picasso 会中止第一次加载。仍在研究它是如何执行此操作的。尚未标记为已接受,因为我的问题更学术,我搜索策略来修改我的代码,而不是添加其他人的代码。
    【解决方案2】:

    我在this Android Developers Training Lesson 的示例代码中找到了我的问题的答案。

    在 ImageWorker.java 中,我们可以找到启动加载图像的背景任务的方法:

    /**
     * Load an image specified by the data parameter into an ImageView (override
     * {@link ImageWorker#processBitmap(Object)} to define the processing logic). A memory and
     * disk cache will be used if an {@link ImageCache} has been added using
     * {@link ImageWorker#addImageCache(android.support.v4.app.FragmentManager, ImageCache.ImageCacheParams)}. If the
     * image is found in the memory cache, it is set immediately, otherwise an {@link AsyncTask}
     * will be created to asynchronously load the bitmap.
     *
     * @param data The URL of the image to download.
     * @param imageView The ImageView to bind the downloaded image to.
     */
    public void loadImage(Object data, ImageView imageView) {
        if (data == null) {
            return;
        }
    
        BitmapDrawable value = null;
    
        if (mImageCache != null) {
            value = mImageCache.getBitmapFromMemCache(String.valueOf(data));
        }
    
        if (value != null) {
            // Bitmap found in memory cache
            imageView.setImageDrawable(value);
        } else if (cancelPotentialWork(data, imageView)) {
            //BEGIN_INCLUDE(execute_background_task)
            final BitmapWorkerTask task = new BitmapWorkerTask(data, imageView);
            final AsyncDrawable asyncDrawable =
                    new AsyncDrawable(mResources, mLoadingBitmap, task);
            imageView.setImageDrawable(asyncDrawable);
    
            // NOTE: This uses a custom version of AsyncTask that has been pulled from the
            // framework and slightly modified. Refer to the docs at the top of the class
            // for more info on what was changed.
            task.executeOnExecutor(AsyncTask.DUAL_THREAD_EXECUTOR);
            //END_INCLUDE(execute_background_task)
        }
    }
    

    对 AsyncTask 实例的引用保存在 AsyncDrawable 类中:

    /**
     * A custom Drawable that will be attached to the imageView while the work is in progress.
     * Contains a reference to the actual worker task, so that it can be stopped if a new binding is
     * required, and makes sure that only the last started worker process can bind its result,
     * independently of the finish order.
     */
    private static class AsyncDrawable extends BitmapDrawable {
        private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
    
        public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
            super(res, bitmap);
            bitmapWorkerTaskReference =
                new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
        }
    
        public BitmapWorkerTask getBitmapWorkerTask() {
            return bitmapWorkerTaskReference.get();
        }
    }
    

    在后台活动结束时,AsyncTask 验证它是否是最后一个被“附加”到它必须更新的视图并仅在没有其他任务被“附加”到视图时才执行更新

        /**
         * Returns the ImageView associated with this task as long as the ImageView's task still
         * points to this task as well. Returns null otherwise.
         */
        private ImageView getAttachedImageView() {
            final ImageView imageView = imageViewReference.get();
            final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
    
            if (this == bitmapWorkerTask) {
                return imageView;
            }
    
            return null;
        }
    
    
       /**
         * Once the image is processed, associates it to the imageView
         */
        @Override
        protected void onPostExecute(BitmapDrawable value) {
            //BEGIN_INCLUDE(complete_background_work)
            // if cancel was called on this task or the "exit early" flag is set then we're done
            if (isCancelled() || mExitTasksEarly) {
                value = null;
            }
    
            final ImageView imageView = getAttachedImageView();
            if (value != null && imageView != null) {
                if (BuildConfig.DEBUG) {
                    Log.d(TAG, "onPostExecute - setting bitmap");
                }
                setImageDrawable(imageView, value);
            }
            //END_INCLUDE(complete_background_work)
        }
    

    【讨论】:

    【解决方案3】:

    取消异步任务?这是一个好主意吗?我发现它多次不起作用,并且总是调用 postExecute(),因此您的图像可能仍会在列表视图中布置,可能是错误的图像会进一步破坏您的场景..

    【讨论】:

    • 你说得对,取消异步任务实际上是向它发送一条消息,告诉它做一些特别的事情......在后台代码中,我必须检查isCancelled() 并做一些聪明的事情。 .. 一个想法可能是让它读取并返回当前视图的图片(比如说,一点也不聪明)
    猜你喜欢
    • 1970-01-01
    • 2021-05-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-10
    相关资源
    最近更新 更多