Android多线程应用之-加载图片列表

原创 2015年11月21日 13:59:07

Android加载图片列表,估计在很多应用中都会涉及到;固然,有许多开源框架提供了很方便的使用方法;并且相当稳定,但是如果不会自己实现,那么我相信遇到问题后就不会自己解决,只会依托于百度或者谷歌;这样,我认为不是你想要的结果吧;这几天我就自己设计实现了一下这个需求:
首先,分析如下:需求是加载图片列表,那么我需要完成以下的基本过程:
(一):下载图片
(二):显示图片
结合安卓自身的特点,下载图片过程不能在主线程;列表经过复用组件后,前面下载的东西如果不经过保存在本地那么就会造成二次下载;因此也需要缓存;缓存方案选择现在成熟的内存缓存方案和磁盘缓存方案;显示图片时需要根据页面对图片进行一定的优化,以优化内存占用;
因此我的具体实现如下:
首先将下载,下载回调;本地缓存寻找,寻找回调;分别实现在2个不同的线程实例;
因此实现了以下几个接口

//下载接口
public interface DownloadPictureTaskinf {
    public Bitmap download(String url);
}
//下载回调
public interface DownloadPictureCallbackinf {
    public void downloadCallback(Bitmap bitmap,String url);
}
//本地查找接口
public interface GetLoaclPictureTaskinf {
    public Bitmap getLocalBitmap();
}
//本地查找回调接口
public interface GetLocalPictureCallbackinf {
    public void getLocalCallback(Bitmap bitmap, String url);
}

线程池设计如下:


public class ThreadPool {

    private static ThreadPool instance = new ThreadPool();

    public static ThreadPool getInstance() {
        return instance;
    }

    private ThreadPool() {
        //下载的线程池
        downloadservice = Executors.newFixedThreadPool(8);
        //获取本地的线程池
        getLocalservice = Executors.newFixedThreadPool(1);
    }

    private ExecutorService downloadservice, getLocalservice;
    private Set<String> downloadSet = new HashSet<String>();
    private Set<String> getlocalTask = new HashSet<String>();
    //提交下载任务
    public void submitDownloadRunnable(
            final DownloadPictureTaskinf downloadPictureTaskinf,
            final DownloadPictureCallbackinf downloadPictureCallbackinf,
            final String url) {
        if (!downloadSet.contains(url)) {
            synchronized (downloadSet) {
                if (!downloadSet.contains(url)) {
                    downloadservice.submit(new Runnable() {
                        @Override
                        public void run() {
                            Bitmap temp = null;
                            temp = downloadPictureTaskinf.download(url);
                            downloadPictureCallbackinf.downloadCallback(temp,
                                    url);
                            downloadSet.remove(url);
                        }
                    });
                }
            }
        }

    }
    //提交查找本地任务
    public void submitgetLocalRunnable(
            final GetLoaclPictureTaskinf getLoaclPictureTaskinf,
            final GetLocalPictureCallbackinf getLocalPictureCallbackinf,
            final String url) {
        if (!getlocalTask.contains(url)) {
            synchronized (getlocalTask) {
                if (!getlocalTask.contains(url)) {
                    getlocalTask.add(url);
                    getLocalservice.submit(new Runnable() {
                        @Override
                        public void run() {
                            Bitmap temp = null;
                            temp = getLoaclPictureTaskinf.getLocalBitmap();
                            getLocalPictureCallbackinf.getLocalCallback(temp,
                                    url);
                            getlocalTask.remove(url);
                        }
                    });
                }
            }
        }

    }

}

下载图片类如下:


public class DownloadPictureTask implements DownloadPictureTaskinf,
        GetLoaclPictureTaskinf, DownloadPictureCallbackinf,
        GetLocalPictureCallbackinf {

    private Handler handler;
    private FileCache cache;
    private String url;
    private DownloadPictureCallbackinf callbackinf;
    private GetLocalPictureCallbackinf callbackinf2;

    public DownloadPictureTask(Context context, FileCache cache, String url,
            DownloadPictureCallbackinf callbackinf,
            GetLocalPictureCallbackinf callbackinf2) {
        this.cache = cache;
        this.url = url;
        this.callbackinf = callbackinf;
        this.callbackinf2 = callbackinf2;
        handler = new Handler(context.getMainLooper());
    }
    //当本地没有时,将下载任务提交到下载的线程池中
    @Override
    public void getLocalCallback(final Bitmap bitmap, final String url) {
        Log.v("本地==", bitmap + ":" + url);
        if (bitmap == null) {
            Log.v("本地为空", url);
            // 启动下载任务
            ThreadPool.getInstance().submitDownloadRunnable(this, this, url);
        } else {
            handler.post(new Runnable() {
                @Override
                public void run() {
                    callbackinf2.getLocalCallback(bitmap, url);
                }
            });
        }

    }
    //下载成功回调
    @Override
    public void downloadCallback(final Bitmap bitmap, final String url) {
        handler.post(new Runnable() {
            @Override
            public void run() {
                callbackinf.downloadCallback(bitmap, url);
            }
        });
    }
   //从本地缓存查找实现
    @Override
    public Bitmap getLocalBitmap() {
        Log.v("本地任务开启", url);
        Bitmap bmp = cache.getCacheFromMemory(url);
        if (bmp == null) {
            bmp = BitmapFactory.decodeStream(cache.getCacheFromDisk(url));
        }
        return bmp;
    }
    //下载实现
    @Override
    public Bitmap download(String url) {
        Log.v("下载任务开启", url);
        Bitmap bmp = null;
        try {
            URL mUrl = new URL(url);
            InputStream ins = mUrl.openConnection().getInputStream();
            //加入到本地缓存
            bmp = cache.addCache(url, ins);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bmp;
    }
   //启动查找本地任务
    public void execute() {
        ThreadPool.getInstance().submitgetLocalRunnable(this, this, url);
    }

}

下面是缓存实现类:


/**
 * 借用了LruCache和DiskLruCache
 * 文件名就是下载链接的MD5值
 * @author 徐晔
 * @note 文件缓存类,集合了内存缓存和磁盘缓存
 */
public class FileCache {

    private final static int MEMMAXSIZE = 1024 * 30;
    private final static int DISKMAXSIZE = 10*1024 * 1024;
    /** 内存缓存 */
    private LruCache<String, Bitmap> memoryCache;
    /** 磁盘缓存 */
    private DiskLruCache diskCache;

    public FileCache(Context context, int appVersion) {
        memoryCache = new LruCache<String, Bitmap>(MEMMAXSIZE);
        try {
            diskCache = DiskLruCache.open(
                    Util.getDiskCacheDir(context, "cache"), appVersion, 1,
                    DISKMAXSIZE);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 添加缓存
     * 
     * @note 首先添加到磁盘中,然后添加到内存中
     */
    public Bitmap addCache(String key, InputStream in) {
        if(in==null){
            return null;
        }
        key = MD5Utils.getMD5(key);
        DiskLruCache.Editor editor = null;
        Bitmap bitmap = null;
        ByteArrayOutputStream baos=null;
        OutputStream mOutputStream=null;
        try {
            editor = diskCache.edit(key);
                baos=new ByteArrayOutputStream();
                if (editor != null) {
                     mOutputStream = editor.newOutputStream(0);
                }
                int b;
                while ((b = in.read()) != -1) {
                    if (editor != null) {
                        mOutputStream.write(b);
                    }
                    baos.write(b);
                }
                editor.commit();
                bitmap=BitmapDecoder.decoderFromByteArray(baos.toByteArray(), 300, 300);
                memoryCache.put(key, bitmap);
        } catch (Exception e) {
            try {
                editor.abort();
            } catch (Exception e1) {
                e1.printStackTrace();
            }
            e.printStackTrace();
        } finally {
            try {
//              diskCache.flush();
                baos.close();
                in.close();
            } catch (Exception e2) {
                e2.printStackTrace();
            }
        }
        return bitmap;
    }

    /**
     * 在内存中取出缓存
     * 
     */
    public InputStream getCacheFromDisk(String key) {
        key = MD5Utils.getMD5(key);
        InputStream ins = null;
        try {
            Snapshot snapshot = diskCache.get(key);
            if (snapshot != null) {
                ins = snapshot.getInputStream(0);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ins;
    }

    /**
     * 在磁盘中取出缓存
     */
    public Bitmap getCacheFromMemory(String key) {
        key = MD5Utils.getMD5(key);
        return memoryCache.get(key);
    }
}

图片配置类:


/**
 * 
 * @author 徐晔
 * @note 根据配置对图片进行编码
 */
public class BitmapDecoder {

    /**
     * 将流转化为Bitmap文件
     * 
     * @param inputStream
     *            :输入流
     * @param rect
     *            :
     * @param realwidth
     *            :想要获取图片的宽度
     * @param realheight
     *            :想要获取的图片的高度
     * @return
     * @throws Exception
     *             :主要指内存溢出异常
     */
    public static Bitmap decoderFromByteArray(byte[] butearray,
            int realwidth, int realheight) throws Exception {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        options.inPreferredConfig = Bitmap.Config.ARGB_4444;
        BitmapFactory.decodeByteArray(butearray, 0, butearray.length, options);
        options.inJustDecodeBounds = false;
        options.inSampleSize = calculateInSampleSize(options, realwidth,
                realheight);
        try {
            return BitmapFactory.decodeByteArray(butearray, 0, butearray.length, options);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 转化文件大小
     */
    private static int calculateInSampleSize(BitmapFactory.Options options,
            int reqWidth, int reqHeight) {
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;
        if (height > reqHeight || width > reqWidth) {
            if (width > height) {
                inSampleSize = Math.round((float) height / (float) reqHeight);
            } else {
                inSampleSize = Math.round((float) width / (float) reqWidth);
            }
            final float totalPixels = width * height;
            final float totalReqPixelsCap = reqWidth * reqHeight * 2;
            while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
                inSampleSize++;
            }
        }
        return inSampleSize;
    }

}

适配器:


public class ImageAdapter extends BaseAdapter {

    private LayoutInflater inflater;
    private FileCache cache;
    private DownloadPictureTask task;
    private Context context;

    public ImageAdapter(Context context) {
        this.context = context;
        inflater = (LayoutInflater) context
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        cache = new FileCache(context, 1);
    }

    @Override
    public int getCount() {
        return Consts.image.length;
    }

    @Override
    public String getItem(int position) {
        return Consts.image[position];
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        final ViewHolder viewHolder;
        if (convertView == null) {
            viewHolder = new ViewHolder();
            convertView = inflater.inflate(R.layout.image_item, null);
            viewHolder.ii_item = (ImageView) convertView
                    .findViewById(R.id.ii_item);
            convertView.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder) convertView.getTag();
        }
        final String key = (String) getItem(position);
        viewHolder.ii_item.setTag(key);
        viewHolder.ii_item.setBackgroundDrawable(null);
        //启动异步任务
        task = new DownloadPictureTask(context, cache, key,
                new DownloadPictureCallbackinf() {

                    @Override
                    public void downloadCallback(Bitmap bitmap, String url) {
                        if (viewHolder.ii_item != null
                                && viewHolder.ii_item.getTag().toString()
                                        .equals(url)) {
                            viewHolder.ii_item.setBackgroundDrawable(new BitmapDrawable(bitmap));
                        }
                    }
                }, new GetLocalPictureCallbackinf() {

                    @Override
                    public void getLocalCallback(Bitmap bitmap, String url) {
                        if (viewHolder.ii_item != null
                                && viewHolder.ii_item.getTag().toString()
                                        .equals(url)) {
                            viewHolder.ii_item.setBackgroundDrawable(new BitmapDrawable(bitmap));

                        }
                    }
                });
        task.execute();
        return convertView;
    }

    class ViewHolder {
        ImageView ii_item;
    }

}

主类文件:


public class ImageActiviy extends Activity {

    private GridView ai_list;
    private ImageAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_image);
        ai_list = (GridView) findViewById(R.id.ai_list);
        adapter = new ImageAdapter(this);
        ai_list.setAdapter(adapter);
    }

}

以下是截图:
这里写图片描述
这个就是加载图片列表的大概过程;其中缓存借用了LruCache和DiskLruCache,这样不但节省了网络流量,还大大的加快了图片的加载速度;优化了用户体验;需要注意的是,在对异步任务进行提交时,有没有现有的任务在运行;不管是下载图片还是从本地磁盘取文件,需要避免重复下载和重复读取一个文件的问题;这两者一个浪费流量;另一个会造成系统卡顿,因为同时提交多个读取文件的操作特别容易引起APP卡顿;因此我这边从本地缓存读取文件的异步任务最大运行数量为1;

第100章、WebView应用之Javascript调用Android(从零开始学Android)

在某些Android应用中,使用Javascript调用Android中的方法也是会用到的。   (1)用WebView来显示HTML代码;   (2)允许WebView执行JavaScript ...
  • jianghuiquan
  • jianghuiquan
  • 2013年03月18日 14:37
  • 3488

C# 系统应用之鼠标模拟技术及自动操作鼠标

游戏程序的操作不外乎两种——键盘输入控制和鼠标输入控制,几乎所有游戏中都使用鼠标来改变角色的位置和方向,本文主要是讲述如何使用C#调用Windows API函数实现鼠标模拟操作的功能.首先通过结合Fi...
  • Eastmount
  • Eastmount
  • 2014年10月13日 20:04
  • 5804

栈的应用——表达式求值

栈的应用——表达式求值  栈在数据结构中应用非常广泛,表达式求值就是最典型的例子之一。 1、        “表达式”  =  “第一操作数”  +  “运算符”  +  “第二操作数”   ...
  • K_zer0
  • K_zer0
  • 2017年02月07日 14:37
  • 239

机器学习系列(3)_逻辑回归应用之Kaggle泰坦尼克之灾

Kaggle上的大神们,也分享过一些experience,说几条我记得的哈: 『对数据的认识太重要了!』 『数据中的特殊点/离群点的分析和处理太重要了!』 『特征工程(feature enginee...
  • longxinchen_ml
  • longxinchen_ml
  • 2015年11月12日 13:52
  • 10590

【转】C# 系统应用之使用Pancel控件同一窗体切换页面

时间 2014-03-18 15:57:49  CSDN博客原文  http://blog.csdn.net/eastmount/article/details/21461275 该文章...
  • zjwen2007
  • zjwen2007
  • 2014年11月12日 19:36
  • 365

Android多线程应用之AnyncTask 的用法

在开发Android应用时必须遵守单线程模型的原则: Android UI操作并不是线程安全的并且这些操作必须在UI线程中执行。在单线程模型中始终要记住两条法则:  1. 不要阻塞UI线程  2. 确...
  • mmc_maodun
  • mmc_maodun
  • 2013年10月20日 21:04
  • 3402

android应用之2

1、获取时间      Time t=new Time(); // or Time t=new Time("GMT+8"); 鍔犱笂Time Zone璧勬枡銆�       t.setToNow(...
  • xxu0123456789
  • xxu0123456789
  • 2014年09月17日 08:57
  • 531

Android中多线程下载列表实现

实现了一下Android中的文件多线程下载模块,支持自定义线程数、断点续传、下载任务的删除,添加等功能,这里封装了一下,功能已全部实现。不过由于使用的是最简单的手动线程数组及消息通知实现,可能还存在某...
  • u011638883
  • u011638883
  • 2014年01月03日 13:49
  • 2632

C# 系统应用之ListView控件 (二).加载选中节点文件夹下文件信息

在项目中的前面一篇文章"C# 系统应用之TreeView控件 (一).显示树状磁盘文件目录及加载图标"中我讲述了如何使用TreeView控件树状实现显示"我的电脑"所有磁盘路径下的文件夹,并加载图标如...
  • Eastmount
  • Eastmount
  • 2014年03月14日 18:20
  • 5242

Android应用之——仿美团loading加载中动画

前言 想必用过美团客户端的用户对美团那个加载小人的动画印象很深刻,一个可爱的小人在那拼命的跑。这个动画实现的方法其实很多,今天这里就用frame动画来实现一下。 一、效果图 ...
  • qq_27073205
  • qq_27073205
  • 2015年05月27日 19:33
  • 637
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Android多线程应用之-加载图片列表
举报原因:
原因补充:

(最多只允许输入30个字)