【Android Developers Training】 57. 在UI线程

系统 1858 0

注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好。

原文链接: http://developer.android.com/training/displaying-bitmaps/process-bitmap.html


在上节课(博客链接: http://www.cnblogs.com/jdneo/p/3514060.html )中所讨论的 BitmapFactory.decode* 方法,在数据源是从闪存盘或者网络(任何非手机存储的数据来源)读取的,那么就不能再主UI线程中执行。因为加载这个数据所花费的时间是不可估计的,并且它依赖于虚度因素(从网络读取的速度,图片尺寸,CPU的处理能力,等等)。如果其中一个任务阻塞的UI线程,那么系统将会把你的应用标记为未响应,之后用户就可以选择强制关闭它(可以阅读: Designing for Responsiveness )。

这节课会教你如何在一个后台线程,使用 AsyncTask 处理图像,并告诉你如何处理并发问题。


一). 使用一个AsyncTask

AsyncTask 类提供一个简单地方法在后台线程执行一些任务,并将结果反馈给UI线程。要使用它,可以创建一个子类,并覆写一些函数。下面是一个使用 AsyncTask decodeSampledBitmapFromResource() 方法把一个大图加载到 ImageView 中的例子:

      
        class
      
       BitmapWorkerTask 
      
        extends
      
       AsyncTask<Integer, Void, Bitmap>
      
         {

    
      
      
        private
      
      
        final
      
       WeakReference<ImageView>
      
         imageViewReference;

    
      
      
        private
      
      
        int
      
       data = 0
      
        ;



    
      
      
        public
      
      
         BitmapWorkerTask(ImageView imageView) {

        
      
      
        //
      
      
         Use a WeakReference to ensure the ImageView can be garbage collected
      
      

        imageViewReference = 
      
        new
      
       WeakReference<ImageView>
      
        (imageView);

    }



    
      
      
        //
      
      
         Decode image in background.
      
      
            @Override

    
      
      
        protected
      
      
         Bitmap doInBackground(Integer... params) {

        data 
      
      = params[0
      
        ];

        
      
      
        return
      
       decodeSampledBitmapFromResource(getResources(), data, 100, 100
      
        ));

    }



    
      
      
        //
      
      
         Once complete, see if ImageView is still around and set bitmap.
      
      
            @Override

    
      
      
        protected
      
      
        void
      
      
         onPostExecute(Bitmap bitmap) {

        
      
      
        if
      
       (imageViewReference != 
      
        null
      
       && bitmap != 
      
        null
      
      
        ) {

            
      
      
        final
      
       ImageView imageView =
      
         imageViewReference.get();

            
      
      
        if
      
       (imageView != 
      
        null
      
      
        ) {

                imageView.setImageBitmap(bitmap);

            }

        }

    }

}
      
    

对于 ImageView 的软引用( WeakReference )保证了 AsyncTask 不会阻止 ImageView 和任何它引用的对象被垃圾回收器回收。这样的话,在任务结束后, ImageView 是否仍然存在就没有保证了,所以你必须在 onPostExecute() 中检查一下引用是否存在。这个 ImageView 也许已经不存在了,就比如说,用户转到了其他的activity,或者一个配置的变更在任务完成之前发生了。

要开始异步地加载这个位图,简单地创建一个任务的实例并执行它:

      
        public
      
      
        void
      
       loadBitmap(
      
        int
      
      
         resId, ImageView imageView) {

    BitmapWorkerTask task 
      
      = 
      
        new
      
      
         BitmapWorkerTask(imageView);

    task.execute(resId);

}
      
    

二). 处理并发

当一些普通的View组件,如: ListView GridView 等和 AsyncTask 配合使用时,会引入另一个之前章节讲过的问题。为了让存储使用更高效,这些组件会在用户滚动窗口时回收自己的子View。如果没一个子View都激活一个 AsyncTask ,那么当执行完毕后,相关联的view是否会因为另一个子view也引用同样的对象而不被回收,这一方面是没有保证的。另外,异步任务结束的顺序是否和开始的顺序保持一致,这一点也未必。

在这篇博客: Multithreading for Performance 中进一步讨论了处理并发的问题,并提供了一种解决方案,这个方案能让 ImageView 存储一个最新的 AsyncTask 引用,同时在任务执行完毕后可以对其进行检查。还是像之前章节那样类似的方法,对 AsyncTask 进行一些扩展。

创建一个专用的 Drawable 子类,用来存储“WorkerTask”的引用。在这个例子中,一个 BitmapDrawable 被使用到,这样的话一个“占位符式的”图片就能在任务完成之前被显示:

      
        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();

    }

}
      
    

在执行 BitmapWorkerTask 之前,创建一个 AsyncDrawable 并将它和目标 ImageView 绑定起来:

      
        public
      
      
        void
      
       loadBitmap(
      
        int
      
      
         resId, ImageView imageView) {

    
      
      
        if
      
      
         (cancelPotentialWork(resId, imageView)) {

        
      
      
        final
      
       BitmapWorkerTask task = 
      
        new
      
      
         BitmapWorkerTask(imageView);

        
      
      
        final
      
       AsyncDrawable asyncDrawable =

                
      
        new
      
      
         AsyncDrawable(getResources(), mPlaceHolderBitmap, task);

        imageView.setImageDrawable(asyncDrawable);

        task.execute(resId);

    }

}
      
    

代码中所引用的这个 cancelPotentialWork 方法用来检查是否另一个正在运行的任务已经关联了这个 ImageView 。如果是的话,它尝试通过调用 cancel() 方法取消之前的任务。在一些个别情况中,新的任务数据会和已经存在的任务相符合,那么就没有其他的事情取药发生。下面的代码是 cancelPotentialWork 方法的实现:

      
        public
      
      
        static
      
      
        boolean
      
       cancelPotentialWork(
      
        int
      
      
         data, ImageView imageView) {

    
      
      
        final
      
       BitmapWorkerTask bitmapWorkerTask =
      
         getBitmapWorkerTask(imageView);



    
      
      
        if
      
       (bitmapWorkerTask != 
      
        null
      
      
        ) {

        
      
      
        final
      
      
        int
      
       bitmapData =
      
         bitmapWorkerTask.data;

        
      
      
        if
      
       (bitmapData !=
      
         data) {

            
      
      
        //
      
      
         Cancel previous task
      
      

            bitmapWorkerTask.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
      
      
        ;

}
      
    

在上述代码中,一个辅助的方法, getBitmapWorkerTask(),被用来获取与任务相关联的一个特定 ImageView

      
        private
      
      
        static
      
      
         BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {

   
      
      
        if
      
       (imageView != 
      
        null
      
      
        ) {

       
      
      
        final
      
       Drawable drawable =
      
         imageView.getDrawable();

       
      
      
        if
      
       (drawable 
      
        instanceof
      
      
         AsyncDrawable) {

           
      
      
        final
      
       AsyncDrawable asyncDrawable =
      
         (AsyncDrawable) drawable;

           
      
      
        return
      
      
         asyncDrawable.getBitmapWorkerTask();

       }

    }

    
      
      
        return
      
      
        null
      
      
        ;

}
      
    

最后一步是修改 BitmapWorkerTask 中的 onPostExecute()方法,这样它就能检查任务是否取消了以及当前的任务是否和 ImageView 所关联的数据相匹配:

      
        class
      
       BitmapWorkerTask 
      
        extends
      
       AsyncTask<Integer, Void, Bitmap>
      
         {

    ...



    @Override

    
      
      
        protected
      
      
        void
      
      
         onPostExecute(Bitmap bitmap) {

        
      
      
        if
      
      
         (isCancelled()) {

            bitmap 
      
      = 
      
        null
      
      
        ;

        }



        
      
      
        if
      
       (imageViewReference != 
      
        null
      
       && bitmap != 
      
        null
      
      
        ) {

            
      
      
        final
      
       ImageView imageView =
      
         imageViewReference.get();

            
      
      
        final
      
       BitmapWorkerTask bitmapWorkerTask =
      
        

                    getBitmapWorkerTask(imageView);

            
      
      
        if
      
       (
      
        this
      
       == bitmapWorkerTask && imageView != 
      
        null
      
      
        ) {

                imageView.setImageBitmap(bitmap);

            }

        }

    }

}
      
    

现在这个实现对于 ListView GridView 和其它需要回收子view的组件来说,就变的更加合适了。只需要调用 loadBitmap()就可以对你的 ImageView 设置图片。例如,在一个 GridView 的实现中,是在其对应适配器的 getView() 方法中执行。

【Android Developers Training】 57. 在UI线程之外处理图像


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论