本站已关停,现有内容仅作科研等非赢利用途使用。特此声明。
查看: 3779|回复: 2
打印 上一主题 下一主题

[Android分享]多线程提升性能和体验

[复制链接]
跳转到指定楼层
1#
发表于 2013-3-26 14:18:04 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 Andy.Ma 于 2013-3-26 14:23 编辑


在Android App中,任何耗费时间的操作都可能堵塞UI线程,你必须将这些耗费时间的操作放在额外的线程中。例如与网络交互的一些操作,就会包括不确定的延迟。

接下来,我们将用异步线程做一个下载图片的例子:界面上会呈现图片列表,图片将从网络上下载下来。首先我们将新建一个异步Task,将下载图片的操作放到后台中执行,确保App的UI线程不会被阻塞。

public class ImgLoadTask extends AsyncTask<String, Void, Bitmap> {

        private String url;
        private WeakReference<ImageView> mImgViewReference;
        
        public  ImgLoadTask(ImageView view){
                mImgViewReference = new WeakReference<ImageView>(view);
        }
        
        @Override
        protected Bitmap doInBackground(String... params) {
                url = params[0];
                ImgLoader loader = new ImgLoader();
                return loader.downloadBitmap(url);
        }
        
        @Override
        protected void onPostExecute(Bitmap result) {
               
                if(result!=null && mImgViewReference!=null){
                        
                        ImageView imgView = mImgViewReference.get();
                        if(imgView!=null){
                                imgView.setImageBitmap(result);
                        }
                }
        }

}

注意,以上代码用ImageView用了一个虚引用(WeakReference),目的是当task正在执行下载的时候,如果当前的Activity要被Kill或销毁掉时,GC可以回收这个ImageView.所以在onPostExecute方法中要检测mImgViewReference和imgView是否为null的情况。

当你把Task部署上去,并进行execute后,你发现图片显示出来了,但是当你滑动list的时候,图片的显示会错乱。这是因为Android框架考虑到使用内存效率的原因。当滑动列表的时候,ListView会重新使用以显示过的View。
所以如果你快速滑动(就是一个Fling的手势)列表,ImageView会被重复用到很多次。在每次正确的显示ImageView的时,都会触发一个下载图片的ImgLoadTask,ImgLoadTask会最终改变它显示的图像。

到底最终问题出现在什么地方呢?答案是下载顺序的问题引起的。

因为ImgLoadTask在下载图片是异步的,它无法保证先执行execute的task就先执行完,也就是说不能保证有序的执行。所以最终显示的图片可能是很早就被execute的task(这个Task可能花费了很多时间来下载)。

知道了问题出现的原因,接下来就解决这个问题。

解决这个问题,就要记住下载的顺序,来保证最后执行execute的task下载的图片被正确显示出来。在这里使用Drawable的子类,在Task执行过程中,他会临时绑定ImageView。代码如下:


static class DownloaderDrawable extends BitmapDrawable {
                 
                private WeakReference<ImgLoadTask> mTaskReference;

                public DownloaderDrawable (Bitmap defaultImg,ImgLoadTask task){
                        super(defaultImg);
                        mTaskReference = new WeakReference<ImgLoadTask>(task);
                }
               
                public ImgLoadTask getDrawableTask(){
                        return mTaskReference.get();
                }
        }
        
注意:Drawable为什么会持有ImageView的引用的原因,请参看之前分享的文章“[Android分享]Context引起的内存泄露分析”
参数defaultImg的作用是:当图片还没下载完或下载失败时显示的默认图片。当然你也可以根据不同情况继承不同的Drawable。


现在可以编写下载的入口代码了,如下:

public static void loadBitmap(Bitmap defaultImg,String url,ImageView imgView){
                if(cancelPotentialDownload(url,imgView)){
                        ImgLoadTask task = new ImgLoadTask(imgView);
                        DownloaderDrawable drawable=new DownloaderDrawable(defaultImg, task);
                        imgView.setImageDrawable(drawable);
                        task.execute(url);
                }
               
        }

在一个ImageView将要启动一下下载图片Task时,cancelPotentialDownload方法将会停止该ImageView上正在下载图片的Task。需要注意的是,这种做法并不能保证最新的下载的图片总会被显示,应为之前的Task可能已经Finish了。解决这个问题的方法将在Task的onPostExecute方法中解决,之后会进行介绍。下面是cancelPotentialDownload的实现:
public static boolean cancelPotentialDownload(String url,ImageView imgView){
                ImgLoadTask task = getImgLoadTask(imgView);
                if(task !=null){
                        String taskUrl = task.url;
                        if(url == null || !url.equals(taskUrl)){
                                task.cancel(true);
                        }else{
                                // The same URL is already being downloaded.
                                return false;
                        }
                }
               
                return true;
        }
        
        上面代码用cancel方法停止正在下载的Task。如果一个正在下载的Task的URL跟当前的URL相同,当前就不用启动新的Task。用这种实现方式需要注意的是,如果某一个ImageView被GC回收,它相关的Task并不会终止。你可以用RecyclerListener接口来控制。
        
        getImgLoadTask方法代码如下:
        public static ImgLoadTask getImgLoadTask(ImageView imgView){
               
                if(imgView!=null){
                 Drawable imgDrawable = imgView.getDrawable();
                 if(imgDrawable!=null){
                 
                         if(imgDrawable instanceof DownloaderDrawable){
                                 DownloaderDrawable downDrawable = (DownloaderDrawable)imgDrawable;
                                 return downDrawable.getDrawableTask();
                         }
                 
                 }
                 
                }
        
                return null;
        }
        
最后修改一下Task中的onPostExecute方法实现:
@Override
        protected void onPostExecute(Bitmap result) {

                if (result != null && mImgViewReference != null) {

                        ImageView imgView = mImgViewReference.get();
                        if (imgView != null) {

                                ImgLoadTask task = ImgLoader.getImgLoadTask(imgView);
                                if (task != null && this == task) {
                                        imgView.setImageBitmap(result);
                                }

                        }
                }
        }
        

        Ok,利用后台多线程来加载数据的介绍就到这里。但是这个代码还需要进一步优化处理,比如需要缓存图片。。。之后我会再分享个大家。
        
        有需要源码的可直接留言给我或者私信。THX

ChinaGDG.com
回复

使用道具 举报

2#
发表于 2013-3-26 22:38:11 | 只看该作者
https://github.com/thest1/LazyList

LazyList 一个很好的关于异步加载以及缓存的例子,只是很多人使用的时候不记得 clearCache()
ChinaGDG.com
回复 支持 反对

使用道具 举报

3#
 楼主| 发表于 2013-3-27 10:33:02 | 只看该作者
Pengxiao.du 发表于 2013-3-26 22:38
https://github.com/thest1/LazyList

LazyList 一个很好的关于异步加载以及缓存的例子,只是很多人使用的 ...


这个我看过,Memory cache使用LinkedHashMap实现的
ChinaGDG.com
回复 支持 反对

使用道具 举报

*滑动验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表