有关图片加载的一些记录。这里针对的主要是会有大量的AdapterView需要快速滚动加载图片情况

一些如 异步加载,文件缓存,LruCache内存缓存Bitmap等的常规的通用方式就不在这里说,这些可以看谷歌给的例子

这里单说一些使使用了前边所说的方式,依然有时候加载不流畅的情况

1、线程优先级

可能有时候发现使用了线程池异步加载,但是在图片加载密度很大的时候,在部分性能不好的机子上,界面还是有点卡,那有可能的原因是子线程优先级太高了

因为正常创建的子线程的优先级都是 Thread.NORM_PRIORITY。当机子性能不好,cpu竞争,有时候会导致主线程卡顿

解决方案:降低优先级

线程池在构建的时候使用

new ThreadPoolExecutor(REMOTE_NUMBERS, REMOTE_NUMBERS*2, 60,                     TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>(),                     new InefficiencyThreadFactory());

其中,线程构建工厂使用

private static class InefficiencyThreadFactory implements ThreadFactory {         private final AtomicInteger mCount = new AtomicInteger(1);         public Thread newThread(Runnable r) {             Thread thread = new Thread(r, "Imageloader #" + mCount.getAndIncrement());             thread.setPriority(Thread.MIN_PRIORITY+1);             return thread;         }     }

其实,不止图片加载的线程需要这样操作。如果做后台不间断一直在处理数据的的线程,比如说遍历生成手机内所有应用的MD5码,如果线程为普通优先级,性能比较弱的设备上也可能会导致界面卡顿

2、File对象构建和检索

第一步的优化,发现界面流畅好多,但是本地缓存越来越多的时候,发现快速滑动的时候,还会出现卡顿情况,经测试是因为主线程中调用new File()和调用File.exists导致的

因为我在项目里边最开始的设定是,先判断本地是否有缓存,然后决定从本地decode还是从服务器加载,虽然后续步骤都是在子线程中操作的,但是之前的判断对文件检索的时候还是会耗时,导致卡顿。

解决方案: 把判断方法放入子线程中 。ps:看了一些开源的加载工具类,有的会做文件缓存队列。

遇到的一些其他问题

1、防重复提交导致的第一张图片加载不出来的问题

ps:如果说,你的项目中没有出现这个问题,可能有两个原因,一是你没有做防重复请求机制 ,二可能是你已经有更完善的解决方案了

问题产生的原因:

小问题1: ListView或者别的布局中很可能会出现两张相同图片同时显示的情况,也就可能会导致会在同一时间对同一个图片发起请求。但是又不应该对同一张图片同时做网络请求或者做读取磁盘的操作,这就涉及到防止重复提交的问题了。这样的小问题可以在,第二次请求前拦截,解决代码很简单,不算问题。

小问题2: 正常来说,AdapterView(Gallery,ListView) 加载图片大多人会放在getView的时候进行异步加载。但是,常用的猿媛们应该会发现,AdapterView显示在第一个位置组件在创建的时候或者nofifyDataChaged的时候,会多次(一般两次,受getViewTypeCount影响)调用getView。因为第一次调用的getView获得的组件只是用来父组件查看子组件的一些的布局参数,而不用来显示,第二次调用getView获取的组件才会放入父容器中用来显示。只是android这样的机制其实对程序员正常写代码的时候没有影响的,也不算问题。

恩恩,对的,你应该猜到了,两个都算是小问题的问题,碰到一个就出现了一个蛋疼的问题: 当小问题2中的情况下,第一个位置 第一次调用getView获得ImageView1,这时候ImageView1开始加载图片,加载的同时,第二次getView调用,并获得ImageView2,也开始加载图片。这两个ImageView是同一个位置创建的,请求的url肯定也会相同。但是,因为ImageView1正在加载图片,从而导致ImageView2被防重复请求机制拦截请求而加载失败。一段时间后,ImageView1加载图片成功,并设置成功。 结果是,ImageView1加载成功,但是并没有放入父类组件中用来显示。ImageView2才是父类真正用来显示的组件,却加载失败。 而导致第一个显示位置的图片加载不出来。

曾经也针对android这个机制出现的问题写了一个 解决方案 ,这个方案不仅麻烦,而且还只是曲折的解决了AdapterView中部分情况下的问题。如果LinearLayout中有两个或多个ImageView加载同一个图片,可能会导致只有第一张图片出来,其他的都因为防重复提交拦截而加载失败导致没法显示。

现在的解决方案:

下边是runnable中粗略的代码

/** * * @author boliu * @param <Request> * @param <Result> */ public class RequestRunnable<Request,Result> implements Runnable {     String url ;     Client<Request, Result> client;     public RequestRunnable(String url,Client<Request, Result> client) {         this.url = url;         this.client = client;     }     @Override     public void run() {         ReentrantLock lock = null;         try {             lock = getRequestLock(url);             if(lock!=null){                 lock.lock();// 如果已经有这个URL的请求正在执行,这个方法会被阻塞.当这个锁被另外个线程释放,这条线程就会被唤起             }             Result result;             result = preRun();// 获取锁之后先从缓存中检查是否已存在缓存(包括磁盘缓存,或者内存中的缓存)。因为可能在前边lock阻塞的时候,已经被其他线程加载完毕,并放入缓存了             if(result==null){                 if(!Thread.currentThread().isInterrupted()) {                     result = client.execute(getRequest(url));                 }else{                     throw new InterruptedException();                 }             }             postResult(result);         }catch (Exception e) {             //请求失败,设置失败图片         } finally {             if (lock != null&&lock.isHeldByCurrentThread()) {                 lock.unlock();             }         }     }     private void postResult(Result result) {         // 通过handler刷新设置图片     }    private Result preRun() {         // 获取缓存         return null;     }     private Request getRequest(String url){         // 通过url生成请求的对象         return null;     }     private static WeakHashMap<String,ReentrantLock> REQUET_LIST = new WeakHashMap<String,ReentrantLock>();    public static synchronized  ReentrantLock getRequestLock(String url){         if(url!=null){             ReentrantLock lock = REQUET_LIST.get(url);             if(lock==null){                 lock =  new ReentrantLock();                 REQUET_LIST.put(url, lock);             }             return lock;         }         return null;     } }  /**      * 请求过程自定义      * @param <Request>      * @param <Result>      */     public interface Client<Request,Result>{         Result execute(Request request);     } 

View Code

ok, 问题解决

2、快速滚动延迟加载方案--减少快速滑动中,不显示图片的加载(这条不想贴代码了,阐述的有点混乱)

如果涉及到快速滚动,也必然是AdapterView的图片加载,而AdapterView中又是在getView中请求图片的。

就会涉及到一个问题,如果快速滚动的时候,很可能从第一个位置在几秒内滚动到第几百的位置上。如果每个位置滑过的时候都进行了图片处理,则会有几百张图片要去加载,如果网络正常的话,你想看到你当前显示的组件的那些图片,少的也得一两分钟吧?用户怎么可能忍受? 所以,必须引进延迟加载。

如果只是简单认为,在OnScrollListener.SCROLL_STATE_FLING的状态下,getView 不调用图片加载方法的话,那可能当快速滑动停下来的时候,当前界面的图片也都加载不出来了。因为显示的在屏幕上的组件也是在SCROLL_STATE_FLING状态下调用getView出来的,也没有调用图片加载方法。  如果非要用这样来做延迟加载的,或许 在onScrollStateChanged 这个方法中,SCROLL_STATE_IDLE状态时,notifyDataChanged一下也许可以,但我没试过。

事实上,大多延迟加载都是基于下边两点来实现的

|- 大多图片加载都要引用线程池或自己维护线程队列来实现.所以,就算getView中调用了图片加载方法,任务也是放在队列中等待执行。

|- AdapterView中的getView获取的组件是复用的,虽然滚动了几个百个item,但实际上初始化的子组件只比显示出来的组件个数多几个。

如果没有维护图片加载队列且AdapterView没有复用组件,这条下边说的内容可以略过了

设定: 要加载的ImageView 为 iv,图片uri 为 uri

方案A:我的方案

在getView 中,调用iv.setTag(uri) ,缓存了要加载图片的路径

当在子线程请求方法执行之前,先比较(时间点在任务执行的时候)要请求的图片路径与 iv.getTag 是否相同,如果不相同,说明iv 已经被再次getView,且有新的图片要显示。则当前请求的uri没有必要继续请求,结束这个任务。如果是,则当前iv没有更新的图片来显示,则加载

只是这样做的话,线程池会按滚动顺序,依次队列中取出请求任务来判断然后执行。实际上,往往用户当前要看的图片的任务是最近才放入队列中的,如果按FIFO顺序,会请求不及时,所以,我做了一个LIFO队列

//LinkedBlockingDeque 这个类1.6才有 即2.3以上

private static class LoaderDeque<T> extends LinkedBlockingDeque<T>{         private static final long serialVersionUID = 700662893561342216L;        @Override         public boolean offer(T e) {             return super.offerFirst(e);         }        @Override         public T remove() {             return super.removeFirst();         }     }

使用其实和最开始讲到的一样

new ThreadPoolExecutor(REMOTE_NUMBERS, REMOTE_NUMBERS*2, 60,
                    TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>(),
                    new InefficiencyThreadFactory());

注意,设置Bitmap的时候,也一定要再次检查请求到的Bitmap的url和iv.getTag是否相同,相同才设置

这个处理方案,缺点是占用了ImageView 的tag 对象。

可优化的方法: 使用一个HashMap ,里边存入ImageView.hashCode 和 图片url 的键值对。从而释放了ImageView的tag引用。 注意,这里使用了ImageView.hashCode ,而没有直接使用ImageView,可以避免不必要的内存泄露。 如果想用ImageView做key,建议使用 WeakHashMap

方案B. 谷歌提供的图片加载示例代码的延迟方案

要加载图片的ImageView中设置了一个Drawable的包装类。这个包装类中用弱引用引用了一个BitmapWorkerTask(AsyncTask的子类)对象实例

当getView调用,并且开始加载图片时,会拿出Drawable 中的引用 BitmapWorkerTask 来比较(时间点是任务提交的时候),是否是当前任务,如果不是当前任务,说明iv需要重新加载,则把原来的task停止,并把由请求Uri生成的BitmapWorkerTask  通过Drawable包装类缓存,并设置到iv上。这样也避免了加载当前不显示的图片。

而且谷歌图片加载实例还预留了一个在快速滑动的时候,暂停加载的接口

关键的两个变量

boolean mPauseWork

Object mPauseWorkLock = new Object();

当AdapterView 为SCROLL_STATE_FLING状态的时候,将mPauseWork 设置为true

而在任务的网络请求或磁盘读取之前,判断是否暂停加载,如果暂停,则使用mPauseWorkLock.wait 阻塞线程。而当滑动状态发生改变为停止的时候,会把mPauseWork   设置为false,并且mPauseWorkLock .notifyAll 唤醒所有阻塞线程。

synchronized (mPauseWorkLock) {
               while (mPauseWork && !isCancelled()) {
                   try {
                       mPauseWorkLock.wait();
                   } catch (InterruptedException e) {}
               }
           }

//request

感觉篇幅太长了。延迟加载神马的代码就不上了。

3、貌似所有的问题解决了,还是卡--部分机型的文字渲染导致的卡顿

我这里还遇到过一个情况,所有的机型都测试流畅,只有部分机型卡顿。后来测试发现,如果ListView换成纯图片滑动的挺欢的,换成纯文本就会卡顿。代表机型有 谷歌2代,HTC C8812E

测试用例中,ListView 每一个item设置的文字为20个左右,且没有使用重复数据(我截取了大约2000字小说内容,按每20个文字截取一个字符串),滑动会卡顿严重,但是如果每个item中的文字换成相同的或者相同文字重复量大,则不再卡顿

后来测试HTC C8812E 只有第一次加载的时候会卡,滚动结束后,再次进入测试应用就不会再卡了。但是谷歌2代会一直卡。

因为这两款机型的运存都比较低,推测是,字符库缓存策略类似应用的res目录下资源文件的缓存策略。

大量不同文本的话,检查到缓存字符集中没有,就会不断从字符库中加载字符,但是又因为内存偏小,可以缓存的字符集有限,从而导致之前加载的回收,GC,然后再加载..

这类问题只能无视了,或者在这些机型上降低不同文字数量。但是不要担心你的图片加载策略了,因为纯图片加载没问题。

android listview gridview 性能优化相关推荐

  1. Android之开发性能优化简介

    原帖地址:http://www.eoeandroid.com/forum.php?mod=viewthread&tid=327340&extra=page%3D1%26filter%3 ...

  2. android 内存和性能优化汇总

    1.即时编译(Just-in-time Compilation,JIT),又称动态转译(Dynamic Translation),是一种通过在运行时将字节码翻译为机器码,从而改善字节码编译语言性能的技 ...

  3. 转:Android应用开发性能优化完全分析

    转自:http://blog.csdn.net/yanbober/article/details/48394201 1 背景 其实有点不想写这篇文章的,但是又想写,有些矛盾.不想写的原因是随便上网一搜 ...

  4. Android进阶:性能优化篇 Android进阶:性能优化篇

    Android进阶:性能优化篇 分类:Android 性能优化2011-08-09 17:06585人阅读评论(0)收藏举报 一.在使用Gallery控件时,如果载入的图片过多,过大,就很容易出现Ou ...

  5. Android应用开发性能优化完全分析

    1 背景 其实有点不想写这篇文章的,但是又想写,有些矛盾.不想写的原因是随便上网一搜一堆关于性能的建议,感觉大家你一总结.我一总结的都说到了很多优化注意事项,但是看过这些文章后大多数存在一个问题就是只 ...

  6. Android应用程序性能优化

    1 背景 其实有点不想写这篇文章的,但是又想写,有些矛盾.不想写的原因是随便上网一搜一堆关于性能的建议,感觉大家你一总结.我一总结的都说到了很多优化注意事项,但是看过这些文章后大多数存在一个问题就是只 ...

  7. 2021新型面试题-血虐面试官斩获字节跳动Offer!Android 精选版面试题级答案(Android+Java+算法+性能优化+四大组件...)

    前言 双非本科,自认为技术水平不差,8月从美图实习离职回学校,各种倒霉的事不断,到现在11月,为了找个好的环境复习,9月又在学校附近租了房,基本是没有面试通知就学不进去,前面由于过于自信,也没拿个保底 ...

  8. Android开发——布局性能优化的一些技巧(一)

    0. 前言 上一篇我们分析了为什么LinearLayout会比RelativeLayout性能更高,意义在于分析了这两种布局的实现源码,算是对一个小结论的证明过程,但是对布局性能的优化效果,对这两种布 ...

  9. android的UI性能优化

    设计师,开发人员,需求研究和测试都会影响到一个app最后的UI展示,所有人都很乐于去建议app应该怎么去展示UI.UI也是app和用户打交道的部分,直接对用户形成品牌意识,需要仔细的设计.无论你的ap ...

最新文章

  1. 不可不看的干货——机器人自主系统的技术构建:感知、决策和执行
  2. 几乎所有的成功都是厚积薄发
  3. 使用Tomcat Native提升Tomcat IO效率
  4. 4行代码AC——L1-026 I Love GPLT (5分)
  5. 汇编语言(五)之数组中正数和负数分离
  6. 设计模式原则之里氏替换原则
  7. 实际返回的行数超出请求的行数怎么解决_count(*)这么慢,我该怎么办?
  8. jquery背景动画插件使用
  9. Selenium+C#自动化脚本开发学习
  10. c++ 建立MFC应用程序
  11. Chrome将网页背景变成黑色(真正的夜间模式、深色模式)
  12. 读书笔记_013 《人间失格》
  13. 中国物联网、物联网卡进入蓬勃发展阶段
  14. 转:著名的100个管理定律点评9 - 成也细节,败也细节略
  15. 提供数据接口公司总结
  16. 磁盘分析管理软件:Disk Space Analyzer Pro mac版
  17. 文件操作和IO --- 文件操作
  18. 有道笔记不能连接网络/IE不能上网 - 解决办法
  19. 工艺夹具-减速箱体零件工艺规程及加工φ52H8孔夹具设计(论文+说明书+工艺卡+外文翻译+CAD图纸)
  20. 应用之星:免费的傻瓜式在线制作电子书平台

热门文章

  1. 脱壳工具:BlackDex的使用详解
  2. Windows远程桌面实现之十一:桌面屏幕通过各种直播服务端直播(RTSP, RTMP, HTTP-FLV, HLS)
  3. 【干货书】统计学与因果关系:应用实证研究方法
  4. iphone 升级 出现 This device isn't eligible for the requested build 解决方法
  5. 什么是JavaBean?(最简单易懂的定义,内附例子)
  6. 多智能体环境MPE simple_tag障碍物位置修改
  7. 福州大学数学与计算机科学学院研究生,福州大学数学与计算机科学学院/软件学院2019年招收专业学位硕士学位研究生调剂方案...
  8. 黑马探花交友----3.圈子-发布动态点赞评论
  9. 工行部分网点开始更换芯片卡 复制难度极高
  10. 翻译文件名称,如何操作将中文名称翻译成英文名称