Android自定义图片加载框架
大神原网址: http://blog.csdn.net/lmj623565791/article/details/41874561
思路:
1. 压缩图片
压缩本地图片: 获得imageview想要显示的大小 -> 设置合适的inSampleSize
压缩网络图片:
a. 硬盘缓存开启 -> 直接下载存到sd卡,然后采用本地的压缩方案
b. 硬盘缓存关闭 -> 使用BitmapFactory.decodeStream(is, null, opts);
2. 图片加载架构
图片压缩加载完 -> 放入LruCache -> 设置到ImageView上
1、单例,包含一个LruCache用于管理我们的图片;
2、任务队列,我们每来一次加载图片的请求,我们会封装成Task存入我们的TaskQueue;
3、包含一个后台线程,这个线程在第一次初始化实例的时候启动,然后会一直在后台运行;
任务呢?还记得我们有个任务队列么,有队列存任务,得有人干活呀;
所以,当每来一次加载图片请求的时候,我们同时发一个消息到后台线程,后台线程去使用线程池去TaskQueue去取一个任务执行;
4、调度策略;3中说了,后台线程去TaskQueue去取一个任务,这个任务不是随便取的,有策略可以选择,一个是FIFO(先进先出),一个是LIFO(后进先出),我倾向于后者。
代码简析:
三个主要文件:
- ImageSizeUtil.java
- package com.carloz.diy.imageloader;
- import android.graphics.BitmapFactory;
- import android.util.DisplayMetrics;
- import android.view.ViewGroup;
- import android.widget.ImageView;
- /**
- * Created by root on 15-11-13.
- */
- public class ImageSizeUtil {
- public static class ImageSize {
- int width;
- int height;
- }
- /**
- * 获得imageview想要显示的大小
- *
- * 可以看到,我们拿到imageview以后:
- * 首先企图通过getWidth获取显示的宽;有些时候,这个getWidth返回的是0;
- * 那么我们再去看看它有没有在布局文件中书写宽;
- * 如果布局文件中也没有精确值,那么我们再去看看它有没有设置最大值;
- * 如果最大值也没设置,那么我们只有拿出我们的终极方案,使用我们的屏幕宽度;
- * 总之,不能让它任性,我们一定要拿到一个合适的显示值。
- * 可以看到这里或者最大宽度,我们用的反射,而不是getMaxWidth();
- * 维萨呢,因为getMaxWidth竟然要API 16,我也是醉了;为了兼容性,我们采用反射的方案。反射的代码就不贴了。
- * @param imageView
- * @return imageView的大小
- */
- public static ImageSize getImageViewSize(ImageView imageView) {
- ImageSize imageSize = new ImageSize();
- DisplayMetrics displayMetrics = imageView.getContext().getResources().getDisplayMetrics();
- ViewGroup.LayoutParams layoutParams = imageView.getLayoutParams();
- int width = imageView.getWidth();
- if(width <= 0) width = layoutParams.width;
- // if(width <= 0) width = imageView.getMaxWidth();
- if(width <= 0) width = displayMetrics.widthPixels;
- int height = imageView.getHeight();
- if(height <= 0) height = layoutParams.height;
- // if(height <= 0) height = imageView.getMaxHeight();
- if(height <= 0) height = displayMetrics.heightPixels;
- imageSize.width = width;
- imageSize.height = height;
- return imageSize;
- }
- /**
- * 计算BitmapFactory.Options options 中的 inSampleSize, 加载图片的大小的重要参数
- * 根据需求的宽和高以及图片实际的宽和高计算inSampleSize
- * 1. 如果 inSampleSize > 1,返回一个较小的图像保存在内存中.
- * 例如,insamplesize = = 4返回一个图像是1 / 4的宽度/高度的图像
- * 2. 如果 inSampleSize <= 1, 返回原始图像
- * @param options 保存着实际图片的大小
- * @param reqWidth 压缩后图片的 width
- * @param reqHeight 压缩后图片的 height
- * @return options里面存了实际的宽和高;reqWidth和reqHeight就是我们之前得到的想要显示的大小;
- * 经过比较,得到一个合适的inSampleSize;
- */
- public static int caculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
- int inSampleSize = 1;
- int width = options.outWidth, height=options.outHeight;
- if(width > reqWidth || height > reqHeight) {
- int widthRadio = Math.round(width/reqWidth);
- int heightRadio = Math.round(height/reqHeight);
- inSampleSize = Math.max(widthRadio, heightRadio);
- }
- return inSampleSize;
- }
- }
- DownloadImgUtils.java
- package com.carloz.diy.imageloader;
- import android.graphics.Bitmap;
- import android.graphics.BitmapFactory;
- import android.widget.ImageView;
- import java.io.BufferedInputStream;
- import java.io.File;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.InputStream;
- import java.net.HttpURLConnection;
- import java.net.MalformedURLException;
- import java.net.URL;
- /**
- * Created by root on 15-11-16.
- */
- public class DownloadImgUtils {
- public static Bitmap downloadImageByUrl(String imgUrl, ImageView imageView) {
- if (null == imgUrl) return null;
- try {
- URL url = new URL(imgUrl);
- HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
- InputStream is = new BufferedInputStream(httpConn.getInputStream());
- is.mark(is.available()); // 在InputStream中设置一个标记位置.
- // 参数readlimit 表示多少字节可以读取.
- // 调用reset()将重新流回到标记的位置
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeStream(is, null, options);
- // 获取imageview想要显示的宽和高
- ImageSizeUtil.ImageSize imageSize = ImageSizeUtil.getImageViewSize(imageView);
- options.inSampleSize = ImageSizeUtil.caculateInSampleSize(options, imageSize.width, imageSize.height);
- options.inJustDecodeBounds = false;
- is.reset(); // Resets this stream to the last marked location.
- Bitmap bitmap = BitmapFactory.decodeStream(is, null, options);
- httpConn.disconnect();
- is.close();
- return bitmap;
- } catch (MalformedURLException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- return null;
- }
- /**
- * 根据url下载图片在指定的文件
- * @param urlStr
- * @param file
- * @return
- */
- public static boolean downloadImageByUrl(String urlStr, File file) {
- FileOutputStream fos = null;
- InputStream is = null;
- try {
- URL url = new URL(urlStr);
- HttpURLConnection conn = (HttpURLConnection) url.openConnection();
- is = conn.getInputStream();
- fos = new FileOutputStream(file);
- byte[] buf = new byte[512];
- int len = 0;
- while ((len = is.read(buf)) != -1) {
- fos.write(buf, 0, len);
- }
- fos.flush();
- return true;
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- try {
- if (is != null)
- is.close();
- } catch (IOException e) {
- }
- try {
- if (fos != null)
- fos.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- return false;
- }
- }
- MyImageLoader.java
- package com.carloz.diy.imageloader;
- import android.content.Context;
- import android.graphics.Bitmap;
- import android.graphics.BitmapFactory;
- import android.os.Environment;
- import android.os.Handler;
- import android.os.Looper;
- import android.os.Message;
- import android.util.Log;
- import android.util.LruCache;
- import android.widget.ImageView;
- import java.io.File;
- import java.security.MessageDigest;
- import java.security.NoSuchAlgorithmException;
- import java.util.LinkedList;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- import java.util.concurrent.Semaphore;
- /**
- * Created by root on 15-11-13.
- */
- public class MyImageLoader {
- public static final String TAG = "MyImageLoader";
- private static MyImageLoader mInstance;
- private LruCache<String, Bitmap> mLruCache; // 图片缓存的核心对象
- private ExecutorService mThreadPool; // 线程池
- private static final int DEFAULT_THREAD_COUNT = 1;
- private LinkedList<Runnable> mTaskQueue; // 任务队列
- private Thread mBackstageThread; // 后台轮询线程
- private Handler mBackstageThreadHandler;
- // Semaphore, 它负责协调各个线程, 以保证它们能够正确、合理的使用公共资源。
- // 也是操作系统中用于控制进程同步互斥的量。
- // Semaphore分为单值和多值两种,前者只能被一个线程获得,后者可以被若干个线程获得。
- // 停车场系统中,车位是公共资源,每辆车好比一个线程,看门人起的就是信号量的作用
- private Semaphore mBackstageThreadSemaphore;
- private Semaphore mBackstageThreadHandlerSemaphore = new Semaphore(0);
- private static final Object syncObject = new Object(); // 单例模式 && synchronized
- public enum QueueType {FIFO, LIFO}
- private QueueType mType = QueueType.LIFO;
- private boolean isDiskCacheEnable = true; // 硬盘缓存可用
- // UI Thread
- private Handler mUIHandler;
- /**
- * 单例模式
- *
- * @param threadCount
- * @return
- */
- public static MyImageLoader getInstance(int threadCount, QueueType type) {
- if (mInstance == null) {
- synchronized (syncObject) {
- if (mInstance == null) {
- mInstance = new MyImageLoader(threadCount, type);
- }
- }
- }
- return mInstance;
- }
- public static MyImageLoader getInstance() {
- if (mInstance == null) {
- synchronized (syncObject) {
- if (mInstance == null) {
- mInstance = new MyImageLoader(DEFAULT_THREAD_COUNT, QueueType.LIFO);
- }
- }
- }
- return mInstance;
- }
- private MyImageLoader(int threadCount, QueueType type) {
- init(threadCount, type);
- }
- private void init(int threadCount, QueueType type) {
- initBackThread();
- // get the max available memory
- int maxMemory = (int) Runtime.getRuntime().maxMemory();
- int cacheMemory = maxMemory / 8;
- // 继承LruCache时,必须要复写sizeof方法,用于计算每个条目的大小
- // the size of bitmap can not over cacheMemory
- mLruCache = new LruCache<String, Bitmap>(cacheMemory) {
- @Override
- protected int sizeOf(String key, Bitmap value) {
- return value.getByteCount();
- }
- };
- // create thread pool that
- // reuses a fixed number of threads operating off a shared unbounded queue.
- mThreadPool = Executors.newFixedThreadPool(threadCount);
- mTaskQueue = new LinkedList<>();
- mType = type;
- mBackstageThreadSemaphore = new Semaphore(threadCount);
- }
- /**
- * 初始化后台轮询线程
- */
- private void initBackThread() {
- mBackstageThread = new Thread() {
- @Override
- public void run() {
- Looper.prepare();
- mBackstageThreadHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- mThreadPool.execute(getTask()); // 线程池去取出一个任务进行执行
- try {
- mBackstageThreadSemaphore.acquire();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- };
- mBackstageThreadHandlerSemaphore.release(); // 释放一个信号量
- Looper.loop();
- }
- };
- mBackstageThread.start();
- }
- public void loadImage(String path, final ImageView imageView, boolean isFromNet) {
- imageView.setTag(path);
- if (mUIHandler == null) {
- mUIHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- ImageBeanHolder holder = (ImageBeanHolder) msg.obj;
- Bitmap bm = holder.bitmap;
- ImageView iv = holder.imageView;
- String path2 = holder.path;
- if (iv.getTag().toString().equals(path2)) {
- iv.setImageBitmap(bm);
- }
- }
- };
- }
- Bitmap bitmap = getBitmapFromLruCache(path); // 根据path在缓存中获取bitmap
- if (bitmap != null) {
- refreshBitmap(path, imageView, bitmap);
- } else {
- addTask(buildTask(path, imageView, isFromNet));
- }
- }
- private void refreshBitmap(String path, final ImageView imageView, Bitmap bitmap) {
- Message msg = Message.obtain();
- ImageBeanHolder holder = new ImageBeanHolder();
- holder.bitmap = bitmap;
- holder.path = path;
- holder.imageView = imageView;
- msg.obj = holder;
- mUIHandler.sendMessage(msg);
- }
- /**
- * 就是runnable加入TaskQueue,与此同时使用mBackstageThreadHandler(这个handler还记得么,
- * 用于和我们后台线程交互。)去发送一个消息给后台线程,叫它去取出一个任务执行
- *
- * @param runnable
- */
- private synchronized void addTask(Runnable runnable) {
- mTaskQueue.add(runnable);
- try {
- if (mBackstageThreadHandler == null)
- mBackstageThreadHandlerSemaphore.acquire();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- mBackstageThreadHandler.sendEmptyMessage(0x110); //
- }
- /**
- * 就是根据Type从任务队列头或者尾进行取任务
- *
- * @return
- */
- private Runnable getTask() {
- if (mType == QueueType.FIFO) {
- return mTaskQueue.removeFirst();
- } else if (mType == QueueType.LIFO) {
- return mTaskQueue.removeLast();
- }
- return null;
- }
- /**
- * 我们新建任务,说明在内存中没有找到缓存的bitmap;我们的任务就是去根据path加载压缩后的bitmap返回即可,然后加入LruCache,设置回调显示。
- * 首先我们判断是否是网络任务?
- * 如果是,首先去硬盘缓存中找一下,(硬盘中文件名为:根据path生成的md5为名称)。
- * 如果硬盘缓存中没有,那么去判断是否开启了硬盘缓存:
- * 开启了的话:下载图片,使用loadImageFromLocal本地加载图片的方式进行加载(压缩的代码前面已经详细说过);
- * 如果没有开启:则直接从网络获取(压缩获取的代码,前面详细说过);
- * 如果不是网络图片:直接loadImageFromLocal本地加载图片的方式进行加载
- * 经过上面,就获得了bitmap;然后加入addBitmapToLruCache,refreashBitmap回调显示图片
- *
- * @param path
- * @param imageView
- * @param isFromNet
- * @return
- */
- private Runnable buildTask(final String path, final ImageView imageView, final boolean isFromNet) {
- return new Runnable() {
- @Override
- public void run() {
- Bitmap bitmap = null;
- if (isFromNet) {
- File file = getDiskCacheDir(imageView.getContext(), md5(path));
- if (file.exists()) { // 如果本地已经缓存了该文件
- bitmap = loadImageFromLocal(file.getAbsolutePath(), imageView);
- if (bitmap == null)
- Log.d(TAG, "load image failed from local: " + path);
- } else { // 需要从网络下载
- if (isDiskCacheEnable) { // 检测是否开启硬盘缓存
- boolean downloadState = DownloadImgUtils.downloadImageByUrl(path, file);
- if (downloadState) {
- bitmap = loadImageFromLocal(file.getAbsolutePath(), imageView);
- }
- if (bitmap == null)
- Log.d(TAG, "download image failed to diskcache(" + path + ")");
- } else { // 直接从网络加载到imageView
- bitmap = DownloadImgUtils.downloadImageByUrl(path, imageView);
- if (bitmap == null)
- Log.d(TAG, "download image failed to memory(" + path + ")");
- }
- }
- } else {
- bitmap = loadImageFromLocal(path, imageView);
- }
- addBitmapToLruCache(path, bitmap);
- refreshBitmap(path, imageView, bitmap);
- mBackstageThreadSemaphore.release();
- }
- };
- }
- /**
- * 使用loadImageFromLocal本地加载图片的方式进行加载
- *
- * @param path
- * @param imageView
- * @return
- */
- private Bitmap loadImageFromLocal(final String path, final ImageView imageView) {
- Bitmap bitmap = null;
- // 1、获得图片需要显示的大小
- ImageSizeUtil.ImageSize imageSize = ImageSizeUtil.getImageViewSize(imageView);
- // 2、压缩图片
- bitmap = decodeSampledBitmapFromPath(path, imageSize.width, imageSize.height);
- return bitmap;
- }
- /**
- * 将图片加入LruCache
- *
- * @param path
- * @param bitmap
- */
- protected void addBitmapToLruCache(String path, Bitmap bitmap) {
- if (getBitmapFromLruCache(path) == null) {
- if (bitmap != null)
- mLruCache.put(path, bitmap);
- }
- }
- /**
- * 根据图片需要显示的宽和高对图片进行压缩
- *
- * @param path
- * @param width
- * @param height
- * @return
- */
- protected Bitmap decodeSampledBitmapFromPath(String path, int width, int height) {
- // 获得图片的宽和高,并不把图片加载到内存中
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeFile(path, options);
- options.inSampleSize = ImageSizeUtil.caculateInSampleSize(options, width, height);
- // 使用获得到的InSampleSize再次解析图片
- options.inJustDecodeBounds = false;
- Bitmap bitmap = BitmapFactory.decodeFile(path, options);
- if (null == bitmap)
- Log.d(TAG, "options.inSampleSize = " + options.inSampleSize + ", " + path);
- return bitmap;
- }
- /**
- * 获得缓存图片的地址
- *
- * @param context
- * @param uniqueName
- * @return
- */
- public File getDiskCacheDir(Context context, String uniqueName) {
- String cachePath;
- if (false && Environment.MEDIA_MOUNTED.equals(Environment
- .getExternalStorageState())) {
- cachePath = context.getExternalCacheDir().getPath();
- } else {
- cachePath = context.getCacheDir().getPath();
- }
- return new File(cachePath + File.separator + uniqueName);
- }
- /**
- * 根据path在缓存中获取bitmap
- *
- * @param key
- * @return
- */
- private Bitmap getBitmapFromLruCache(String key) {
- return mLruCache.get(key);
- }
- /**
- * 利用签名辅助类,将字符串字节数组
- *
- * @param str
- * @return
- */
- public String md5(String str) {
- byte[] digest = null;
- try {
- MessageDigest md = MessageDigest.getInstance("md5");
- digest = md.digest(str.getBytes());
- return bytes2hex02(digest);
- } catch (NoSuchAlgorithmException e) {
- e.printStackTrace();
- }
- return null;
- }
- /**
- * 方式二
- *
- * @param bytes
- * @return
- */
- public String bytes2hex02(byte[] bytes) {
- StringBuilder sb = new StringBuilder();
- String tmp = null;
- for (byte b : bytes) {
- // 将每个字节与0xFF进行与运算,然后转化为10进制,然后借助于Integer再转化为16进制
- tmp = Integer.toHexString(0xFF & b);
- if (tmp.length() == 1)// 每个字节8为,转为16进制标志,2个16进制位
- {
- tmp = "0" + tmp;
- }
- sb.append(tmp);
- }
- return sb.toString();
- }
- private class ImageBeanHolder {
- Bitmap bitmap;
- ImageView imageView;
- String path;
- }
- }
以上就是整个框架
使用方法: mImageLoader.loadImage(path, iv_popup, true);
在 4.0 的屏幕上不能录制屏幕... 好伤心, 以后再补
Android自定义图片加载框架的更多相关文章
- Android之图片加载框架Fresco基本使用(一)
PS:Fresco这个框架出的有一阵子了,也是现在非常火的一款图片加载框架.听说内部实现的挺牛逼的,虽然自己还没研究原理.不过先学了一下基本的功能,感受了一下这个框架的强大之处.本篇只说一下在xml中 ...
- android Glide图片加载框架的初探
一.Glide图片加载框架的简介 谷歌2014年开发者论坛会上介绍的图片加载框架,它让我们在处理不管是网路下载的图片还是本地的图片,减轻了很多工作量, 二.开发步骤: 1.添加链接库 compile ...
- android glide图片加载框架
项目地址: https://github.com/bumptech/glide Glide作为安卓开发常用的图片加载库,有许多实用而且强大的功能,那么,今天就来总结一番,这次把比较常见的都写出来,但并 ...
- Android之图片加载框架Fresco基本使用(二)
PS:最近看到很多人都开始写年终总结了,时间过得飞快,又到年底了,又老了一岁. 学习内容: 1.进度条 2.缩放 3.ControllerBuilder,ControllerListener,Post ...
- Android 三大图片加载框架的对比——ImageLoader,Picasso,Glide
一.ImageLaoder介绍 << Universal ImageLoader 是很早开源的图片缓存,在早期被很多应用使用 多线程下载图片,图片可以来源于网络,文件系统,项目文件夹ass ...
- Android图片加载框架最全解析(六),探究Glide的自定义模块功能
不知不觉中,我们的Glide系列教程已经到了第六篇了,距离第一篇Glide的基本用法发布已经过去了半年的时间.在这半年中,我们通过用法讲解和源码分析配合学习的方式,将Glide的方方面面都研究了个遍, ...
- Android中常见的图片加载框架
图片加载涉及到图片的缓存.图片的处理.图片的显示等.而随着市面上手机设备的硬件水平飞速发展,对图片的显示要求越来越高,稍微处理不好就会造成内存溢出等问题.很多软件厂家的通用做法就是借用第三方的框架进行 ...
- 一起写一个Android图片加载框架
本文会从内部原理到具体实现来详细介绍如何开发一个简洁而实用的Android图片加载缓存框架,并在内存占用与加载图片所需时间这两个方面与主流图片加载框架之一Universal Image Loader做 ...
- Android项目框架之图片加载框架的选择
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 从Android爆发以后,自定义的控件如EditTextWithDelete.ActionBar.P ...
随机推荐
- 关于css中z-index 的应用
我想很多人在应用中的会碰到这个问题,设置 z-index无效:无论设置为多高的数字都没有效果: 原因是在设置z-index之前必须满足一下两个条件: 1,给设置z-index的元素设置相应的定位值,p ...
- 利用http协议实现图片窃取
在http协议里有一个referer,用来标示站点来源,大家都遇到这样的情况.转载了一篇博客,图片显示不正常,就和头信息里这个有关 原理:在webserver里面.依据http协议里面refered头 ...
- iOS开发中一些常用的方法
1.压缩图片 #pragma mark 处理图片 - (void)useImage:(UIImage *)image { NSLog(@"with-----%f heught-----%f& ...
- android学习资料
在线查看android源码 1. https://github.com/android 2. http://grepcode.com/project/repository.grepcode.com ...
- Cisco交换机设置管理IP
需要准备一根CONSOLE线和带串行接口的电脑. (图1) 用CONSOLE线连接好电脑与交换机(交换机的CONSOLE口一般都有表示). 然后按照图1点“开始→程序→超级终端”会弹出来一个窗口(图2 ...
- hdu1002大数相加
A + B Problem II Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Tot ...
- MyJFrame(文本)界面的建立
import java.awt.Color;import java.awt.Component;import java.awt.Container;import java.awt.FlowLayout ...
- Java开发之I/O读取文件实例详解
在java开发或者android开发中,读取文件是不可避免的,以下对java开发中读取文件做了归纳和详解: 1.按字节读取文件内容2.按字符读取文件内容3.按行读取文件内容 4.随机读取文件内容 pa ...
- Beyond REST: How to build a HATEOAS API in Java with Spring MVC, Jersey (JAX-RS) and VRaptor
http://zeroturnaround.com/rebellabs/beyond-rest-how-to-build-a-hateoas-api-in-java-with-spring-mvc-j ...
- [整理]Oracle LOCK 机制
数据库是一个多用户使用的共享资源.当多个用户并发地存取数据时,在数据库中就会产生多个事务同时存取同一数据的情况.若对并发操作不加控制就可能会读取和存储不正确的数据,破坏数据库的一致性.锁机制用于管理对 ...