思路:
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 的屏幕上不能录制屏幕... 好伤心, 以后再补