[Android实例]Listview异步加载图片之优化篇(有图有码有解释)

            ViewHolder viewHolder = null;

            if (convertView == null) {

                    convertView = LayoutInflater.from(mContext).inflate(

                                    R.layout.list_item, null);

                    viewHolder = new ViewHolder();

                    viewHolder.mTextView = (TextView) convertView

                                    .findViewById(R.id.tv_tips);

                    viewHolder.mImageView = (ImageView) convertView

                                    .findViewById(R.id.iv_image);

                    convertView.setTag(viewHolder);

            } else {

                    viewHolder = (ViewHolder) convertView.getTag();

            }

            String url = "";

            url = urlArrays[position % urlArrays.length];

             

            viewHolder.mImageView.setImageResource(R.drawable.ic_launcher);

             



            if (!mBusy) {

                    mImageLoader.DisplayImage(url, viewHolder.mImageView, false);

                    viewHolder.mTextView.setText("--" + position

                                    + "--IDLE ||TOUCH_SCROLL");

            } else {

                    mImageLoader.DisplayImage(url, viewHolder.mImageView, true);                

                    viewHolder.mTextView.setText("--" + position + "--FLING");

            }

            return convertView;

    }



    static class ViewHolder {

            TextView mTextView;

            ImageView mImageView;

    }

}




关键代码是ImageLoader的DisplayImage方法,再看ImageLoader的实现



public class ImageLoader {

    private MemoryCache memoryCache = new MemoryCache();

    private AbstractFileCache fileCache;

    private Map<ImageView, String> imageViews = Collections

                    .synchronizedMap(new WeakHashMap<ImageView, String>());

    // 线程池

    private ExecutorService executorService;



    public ImageLoader(Context context) {

            fileCache = new FileCache(context);

            executorService = Executors.newFixedThreadPool(5);

    }



    // 最主要的方法

    public void DisplayImage(String url, ImageView imageView, boolean isLoadOnlyFromCache) {

            imageViews.put(imageView, url);

            // 先从内存缓存中查找



            Bitmap bitmap = memoryCache.get(url);

            if (bitmap != null)

                    imageView.setImageBitmap(bitmap);

            else if (!isLoadOnlyFromCache){

                     

                    // 若没有的话则开启新线程加载图片

                    queuePhoto(url, imageView);

            }

    }



    private void queuePhoto(String url, ImageView imageView) {

            PhotoToLoad p = new PhotoToLoad(url, imageView);

            executorService.submit(new PhotosLoader(p));

    }



    private Bitmap getBitmap(String url) {

            File f = fileCache.getFile(url);

             

            // 先从文件缓存中查找是否有

            Bitmap b = null;

            if (f != null && f.exists()){

                    b = decodeFile(f);

            }

            if (b != null){

                    return b;

            }

            // 最后从指定的url中下载图片

            try {

                    Bitmap bitmap = null;

                    URL imageUrl = new URL(url);

                    HttpURLConnection conn = (HttpURLConnection) imageUrl

                                    .openConnection();

                    conn.setConnectTimeout(30000);

                    conn.setReadTimeout(30000);

                    conn.setInstanceFollowRedirects(true);

                    InputStream is = conn.getInputStream();

                    OutputStream os = new FileOutputStream(f);

                    CopyStream(is, os);

                    os.close();

                    bitmap = decodeFile(f);

                    return bitmap;

            } catch (Exception ex) {

                    Log.e("", "getBitmap catch Exception...\nmessage = " + ex.getMessage());

                    return null;

            }

    }



    // decode这个图片并且按比例缩放以减少内存消耗,虚拟机对每张图片的缓存大小也是有限制的

    private Bitmap decodeFile(File f) {

            try {

                    // decode image size

                    BitmapFactory.Options o = new BitmapFactory.Options();

                    o.inJustDecodeBounds = true;

                    BitmapFactory.decodeStream(new FileInputStream(f), null, o);



                    // Find the correct scale value. It should be the power of 2.

                    final int REQUIRED_SIZE = 100;

                    int width_tmp = o.outWidth, height_tmp = o.outHeight;

                    int scale = 1;

                    while (true) {

                            if (width_tmp / 2 < REQUIRED_SIZE

                                            || height_tmp / 2 < REQUIRED_SIZE)

                                    break;

                            width_tmp /= 2;

                            height_tmp /= 2;

                            scale *= 2;

                    }



                    // decode with inSampleSize

                    BitmapFactory.Options o2 = new BitmapFactory.Options();

                    o2.inSampleSize = scale;

                    return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);

            } catch (FileNotFoundException e) {

            }

            return null;

    }



    // Task for the queue

    private class PhotoToLoad {

            public String url;

            public ImageView imageView;



            public PhotoToLoad(String u, ImageView i) {

                    url = u;

                    imageView = i;

            }

    }



    class PhotosLoader implements Runnable {

            PhotoToLoad photoToLoad;



            PhotosLoader(PhotoToLoad photoToLoad) {

                    this.photoToLoad = photoToLoad;

            }



            @Override

            public void run() {

                    if (imageViewReused(photoToLoad))

                            return;

                    Bitmap bmp = getBitmap(photoToLoad.url);

                    memoryCache.put(photoToLoad.url, bmp);

                    if (imageViewReused(photoToLoad))

                            return;

                    BitmapDisplayer bd = new BitmapDisplayer(bmp, photoToLoad);

                    // 更新的操作放在UI线程中

                    Activity a = (Activity) photoToLoad.imageView.getContext();

                    a.runOnUiThread(bd);

            }

    }



    /**

     * 防止图片错位

     * 

     * @param photoToLoad

     * @return

     */

    boolean imageViewReused(PhotoToLoad photoToLoad) {

            String tag = imageViews.get(photoToLoad.imageView);

            if (tag == null || !tag.equals(photoToLoad.url))

                    return true;

            return false;

    }



    // 用于在UI线程中更新界面

    class BitmapDisplayer implements Runnable {

            Bitmap bitmap;

            PhotoToLoad photoToLoad;



            public BitmapDisplayer(Bitmap b, PhotoToLoad p) {

                    bitmap = b;

                    photoToLoad = p;

            }



            public void run() {

                    if (imageViewReused(photoToLoad))

                            return;

                    if (bitmap != null)

                            photoToLoad.imageView.setImageBitmap(bitmap);

     

            }

    }



    public void clearCache() {

            memoryCache.clear();

            fileCache.clear();

    }



    public static void CopyStream(InputStream is, OutputStream os) {

            final int buffer_size = 1024;

            try {

                    byte[] bytes = new byte[buffer_size];

                    for (;;) {

                            int count = is.read(bytes, 0, buffer_size);

                            if (count == -1)

                                    break;

                            os.write(bytes, 0, count);

                    }

            } catch (Exception ex) {

                    Log.e("", "CopyStream catch Exception...");

            }

    }

}


  

  

先从内存中加载,没有则开启线程从SD卡或网络中获取,这里注意从SD卡获取图片是放在子线程里执行的,否则快速滑屏的话会不够流畅,这是优化一。于此同时,在adapter里有个busy变量,表示listview是否处于滑动状态,如果是滑动状态则仅从内存中获取图片,没有的话无需再开启线程去外存或网络获取图片,这是优化二。ImageLoader里的线程使用了线程池,从而避免了过多线程频繁创建和销毁,有的童鞋每次总是new一个线程去执行这是非常不可取的,好一点的用的AsyncTask类,其实内部也是用到了线程池。在从网络获取图片时,先是将其保存到sd卡,然后再加载到内存,这么做的好处是在加载到内存时可以做个压缩处理,以减少图片所占内存,这是优化三。  

  



而图片错位问题的本质源于我们的listview使用了缓存convertView,假设一种场景,一个listview一屏显示九个item,那么在拉出第十个item的时候,事实上该item是重复使用了第一个item,也就是说在第一个item从网络中下载图片并最终要显示的时候其实该item已经不在当前显示区域内了,此时显示的后果将是在可能在第十个item上输出图像,这就导致了图片错位的问题。所以解决之道在于可见则显示,不可见则不显示。在ImageLoader里有个imageViews的map对象,就是用于保存当前显示区域图像对应的url集,在显示前判断处理一下即可。



下面再说下内存缓冲机制,本例采用的是LRU算法,先看看MemoryCache的实现



  

public class MemoryCache {

    private static final String TAG = "MemoryCache";

    // 放入缓存时是个同步操作

    // LinkedHashMap构造方法的最后一个参数true代表这个map里的元素将按照最近使用次数由少到多排列,即LRU

    // 这样的好处是如果要将缓存中的元素替换,则先遍历出最近最少使用的元素来替换以提高效率

写在最后

今天关于面试的分享就到这里,还是那句话,有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如Handler机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。

最后在这里小编分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

【Android核心高级技术PDF文档,BAT大厂面试真题解析】

【算法合集】

【延伸Android必备知识点】

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

【Android核心高级技术PDF文档,BAT大厂面试真题解析】

[外链图片转存中…(img-RGdTVlTH-1714521196441)]

【算法合集】

[外链图片转存中…(img-NH3Jrt8l-1714521196442)]

【延伸Android必备知识点】

[外链图片转存中…(img-ckhZaIpu-1714521196442)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 14
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值