自己动手从网络加载,缓存,压缩图片

首先是思路:

为什么要缓存图片?
:因为每次界面显示时都会从网络加载图片,如果已经加载过了,而还去加载图像的话,就会浪费客户的流量,流量就是钱,所有已经加载的图片不应该再次加载,所以要缓存起来,而且从缓存读取图片会速度很快;

为什么要压缩图片?
: 如果不对图像进行压缩,由于加载的图片过大,一:会导致程序反映过慢,进而影响客户的体验,二:有可能会让程序崩溃即内存溢出;

加载:
由于对listview进行了优化(即item 的复用),即当listview进行滑动时隐藏的item会在下面进行出现(不会出现新的item);而这时会出现item头像的乱闪,就想”动画”一样;所以需要加载缓存;
加载,缓存压缩图像的思路:
1.每个item都会发起一个加载任务请求,所以需要一个任务队列 taskQueue
2.需要一个取任务的Handler:pollHandler
3.当加载完成后需要一个更新UI的Handker:uiHandler
4.从时刻循环等待着取任务的一个线程:Thread:pollThread
5.因为i加载量可能比较大,需要线程池进行多线程并发执行加载任务
线程池:ExecutorService :exec;
6.任务队列发起网络加载任务;
7.加载完毕的图像进行压缩和缓存

/**
 * 工具类:加载图像
 * @author fz
 *
 */
public class ImageLoader {
    //下载完毕后,message里面的what
    public static final int LOADER_FINISH=101;
    public static final int      NEW_TASK=102;

    //线程池中的线程数量 (具体多少根据设备(几核手机)来定)
     public static int Thread_count;
   //上下文
     public static Context context;
     //内存缓存
     public static LruCache<String, Bitmap> lcache;
     //任务队列 (Deque:双向队列:未来可以根据需求来决定是FIFO还是LIFO)
      public static LinkedBlockingDeque<Runnable> taskQueue;
      //线程池
      public static ExecutorService exec;
      //从任务队列中取任务的handler
      public static Handler pollHandler;
      //任务下载完成后,用来刷新UI(listview)的 UI的Handler
      public static Handler uihandler;
      //"养活" Pollhandler
      public static Thread pollThread;

      //磁盘缓存
      public static DiskLruCache diskcache;


      //标示的作用,确保Imageloader 只进行一次初始化操作即可
      public static boolean isFirstTime= true;



      /**
       * 初始化几个属性
       * @param c
       */
      public static void init(Context c){
          if(!isFirstTime){
              return;
          }
          context=c;
          Thread_count=getNumberOfCores();

          //初始化缓存空间
          lcache=new LruCache<String, Bitmap>((int) (Runtime.getRuntime().maxMemory()/8)){
             @Override
            protected int sizeOf(String key, Bitmap value) {
                // TODO Auto-generated method stub
                return value.getHeight()*value.getRowBytes();
            }
          };
          //初始化磁盘缓存
          try {
            diskcache=DiskLruCache.open(directory(), appVersion(), 1, 1024*1024*10);
        } catch (IOException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }

          //初始化任务队列
          taskQueue=new LinkedBlockingDeque<Runnable>();
          exec=Executors.newFixedThreadPool(Thread_count);
          //初始化Handler
          //pollhandler,uihandler
          uihandler=new Handler(Looper.getMainLooper()){
              @Override
            public void handleMessage(Message msg) {

                if(msg.what==101){
                    //TODO msg.obj 到底放什么?
                    //msg.obj 放什么能解决Listview的加载"动画"效果
                    ValueObject vo=(ValueObject) msg.obj;
                    ImageView iv=vo.iv;
                    String url=vo.url;
                    Bitmap b=vo.bitmap;
                    if(iv.getTag().toString().equals(url)){
                        iv.setImageBitmap(b);
                    }

                }else{
                super.handleMessage(msg);
                }
            }
          };
          /**
           * 初始化工作线程("养活"pollHandler)
           * 任务队列中只要有任务pollHandler就会取任务
           */
          pollThread=new Thread(){
              public void run() {
            Looper.prepare();
            pollHandler=new Handler(){
                @Override
                public void handleMessage(Message msg) {
                    if(msg.what==NEW_TASK){
                        try {
                            //现在任务队列中中被放入了新任务;
                            //去队列中去取任务
                            Runnable task = taskQueue.takeFirst();
                            //将取出的任务放到线程池中去执行
                            exec.execute(task);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }

                    }else{
                    super.handleMessage(msg);
                    }
                }
            };
                  Looper.loop();
              };
          };
          //取任务的线程启动
          pollThread.start();
          //表示 ,初始化完成了
          isFirstTime=false;
      }
      /**
       * 获得程序的版本号
       * @return
       */
      private static int appVersion() {
        try {
        PackageInfo info=context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
        return info.versionCode;
        } catch (NameNotFoundException e) {
            e.printStackTrace();
        }
        return 1;
    }

    /**
     * 存储路径
     * @return
     */
    private static File directory() {
        String path=context.getCacheDir().getPath();
        return new File(path, "imageloadercache");
    }
    /**
       * 获取url指向的图像,放到v中显示
       * @param url 图像路径
       * @param v 显示图像的imageview
       */
      public static void loadImage(final String url,final ImageView iv){
          //先判断是否已经初始化
          if(isFirstTime){
              throw new RuntimeException("ImageLoader未做初始化");
          }
          //将URL转为MD5格式的字符串
          final String MD5=getMD5(url);
          //设置Tag,到时候要与vo中 的url进行对比
          iv.setTag(MD5);
          Bitmap bm=lcache.get(MD5);
          //先判断,URL所指向的图像是否在缓存中有保存
          if(bm!=null){
              Log.i("TAG", "图像时从内存中缓存加载的");
              iv.setImageBitmap(bm);
              return;
          }
          //从磁盘缓存中取出url对应的图片
          try {
            Snapshot snap=diskcache.get(MD5);
            if(snap!=null){
                Log.i("TAG", "图像时从磁盘缓存中取的");
            InputStream is= snap.getInputStream(0);
                bm=BitmapFactory.decodeStream(is);
                //将从磁盘缓存中获取的图片放到内存缓存存储一份
                lcache.put(MD5, bm);
                iv.setImageBitmap(bm);
                return;
            }
        } catch (IOException e1) {
            e1.printStackTrace();
        }
          //从网络中下载,任务队列执行下载任务
          taskQueue.add(new Runnable() {
            @Override
            public void run() {
                // TODO 进行网络加载
                //发起网络连接,获得图像资源 
                try{
                    URL u=new URL(url);
                    HttpURLConnection con=(HttpURLConnection) u.openConnection();
                    con.setRequestMethod("GET");
                    con.setDoInput(true);
                    con.connect();
                    InputStream is=con.getInputStream();
                    //要对图像进行压缩
                    Bitmap bm=compress(is,iv);//压缩图像的方法,自己创建的;
                    is.close();//关闭流,
                    //放入缓存中
                    lcache.put( MD5, bm);
                    //将压缩后的图像放到磁盘缓存中
                    Editor or=diskcache.edit(MD5);
                    OutputStream out =or.newOutputStream(0);
                    bm.compress(CompressFormat.JPEG, 100, out);
                    or.commit();
                    //写日志
                    diskcache.flush();
                    //封装item的TAG的实体类
                    ValueObject vo=new ValueObject(iv, MD5, bm);
                    //发消息,表示加载完成
                    Message.obtain(uihandler,LOADER_FINISH,vo).sendToTarget();

                }catch(Exception e){
                    e.printStackTrace();
                }
            }
        });
          Message.obtain(pollHandler,NEW_TASK).sendToTarget();
      }
      /**
       * 根据要显示的imageview的大小对图像进行压缩
       * @param is 图像源
       * @param iv 要显示图像的imageview
       * @return  压缩后的图像
       */
      protected static Bitmap compress(InputStream is, ImageView iv) {
        Bitmap b=null;
        try{
            //1)先获得原始(图像源)图像的尺寸大小
            //借助options 来获取图像的大小
            //is------byte[]
            ByteArrayOutputStream out=new ByteArrayOutputStream();
            byte[] buff=new byte[4096];
            int len=-1;
            while((len=is.read(buff))!=-1){
                out.write(buff,0,len);
            }
            Options opts=new Options();
            //opts,一旦设置此属性true,BitmapFactory是不会有返回值得,是null;
            opts.inJustDecodeBounds=true;

            BitmapFactory.decodeByteArray(out.toByteArray(), 0, out.toByteArray().length, opts);
            int width=opts.outWidth;//图像的宽度(像素数)
            int height=opts.outHeight;//图像的高度(像素)
            //2)获取希望的宽高值
            int tagWidth=iv.getWidth();//imageview 的宽度
            int tagHeight=iv.getHeight();//imageview 的高度
            //如果imageview的宽高取不到
            if(tagWidth==0||tagHeight==0){
                //可以手动的指定值(80dp,100dp);
                //拿到设备的屏幕的宽高
                tagWidth=context.getResources().getDisplayMetrics().widthPixels;
                tagHeight=context.getResources().getDisplayMetrics().heightPixels;
            }
            //3)计算压缩比
            int sampleSize=1;
            if(width*1.0/tagWidth>1||height*1.0/tagHeight>1){
                sampleSize=(int) Math.ceil(Math.max(width*1.0/tagWidth, height*1.0/tagHeight));
            }
            //4) 压缩图像
            opts.inSampleSize=sampleSize;
            opts.inJustDecodeBounds=false;
            b=BitmapFactory.decodeByteArray(out.toByteArray(), 0, out.toByteArray().length, opts);
            out.close();

        }catch(Exception e){
            e.printStackTrace();
        }
        return b;
    }
    /**
       * 使用MD5加密
       * @param url 原字符串
       * @return  把MD5格式转换成16进制的字符串
       */
      private static String getMD5(String url) {
        String res="";
        try {
            MessageDigest md=MessageDigest.getInstance("md5");
            md.update(url.getBytes());
            byte[] bs=md.digest();
            StringBuilder sb=new StringBuilder();
            for(byte b:bs){
                String str=Integer.toHexString(b & 0xFF);
                if(str.length()==1){
                    sb.append("0");
                }
                sb.append(str);
            }
            res=sb.toString();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return res;
    }
      /**
       * 线程池中的线程数量
       * @return
       */
    private static int getNumberOfCores() {
        //判断手机cpu 是几核   
        File f=new File("/sys/devices/system/cpu/");
        File[] fs=f.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String filename) {
                if(filename.contains("cpu[0-9]")){
                    return true;
                }else{
                    return false;
                }
            }
        });
        //数组的长度就是核数
        if(fs.length>0){
            return fs.length;
        }else{
            return 1;
        }
    }
    /**
     * 封装 item 的标签,即用url作为每个发起加载任务的
     * imageview的Tag,用来标示是否已经加载
     * @author fz
     *
     */
      private static class ValueObject{
          ImageView iv;//发起下载任务的那个Imageview
          String url;
          Bitmap bitmap;
        public ValueObject(ImageView iv, String url, Bitmap bitmap) {
            super();
            this.iv = iv;
            this.url = url;
            this.bitmap = bitmap;
        }
      }
}    

其中增加了一点磁盘缓存的小知识:
为什么要使用磁盘缓存:
主要是可以减少网络的访问,节省流量,为客户着想;
什么时候会使用到磁盘缓存:
当程序完全关闭后,再次启动时,会使用;
*程序启动后读取图像的顺序:
内存(即缓存)如果没有–>外存(即SD卡)如果没有–>才会去从网络加载图像*
DiskLruCache(是个工具类,属于第三方工具类)
1.创建
DiskLruCache.open(
存储路径,
软件版本号,
1个key 对应几个value.//比如说一条新闻可以有几张图片
存储空间的大小);
2.存储
edit(key);方法,获得一个Editor对象,通过该Editor对象可以获得一个输出流,把你要保存的内容以流的形式
存储起来即可,当流操作完毕以后,要调用一下commit方法,进行最终的保存,保存后调用flush()打印一个日志,系统需求,
将来版本更新时会用到

3.读取
get(key);方法,获得一个Snap对象,通过该Snap对象获得一个输入流,对该输入流进行解析;即可获得DiskLruCache中key 所
对应的value

其中还有一段MD5的知识:
MD5是一种加密方式,有兴趣的可用度娘一下;

我还是一个Android的菜鸟,所以欢迎各位同行来进行交流及指点!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值