开发App必定涉及到图片加载、图片处理,那就必须会用到三方的图片框架,要么选择本身封装。至于主流的三方图片框架,就不得不说老牌的ImageLoader、现在更流行的Glide、Picasso和Fresco。但三方的框架本文不会过多介绍。java
Glide等框架,毕竟是大神及团队花费很大精力开发和维护的开源框架,他们的设计思路、性能优化、代码规范等等很值得咱们学习,以前一段时间也研究过Glide的源码(不得不禁衷佩服)。android
今天,将本身对于图片加载的思路想法,也借鉴了开源框架的一些好的点,封装了一个图片加载框架——JsLoader。(github地址:github.com/shuaijia/Js…)与你们分享。git
文章目录: github
至于图片的网络请求,我这里仍是使用Android原生提供的HttpUrlConnection;请求网络图片时,开启子线程进行操做,使用线程池对线程进行统一管理;线程间通讯仍是用了Handler;提到图片加载,你们确定会马上想到图片的三级缓存(内存—外存—网络),但我这里提供一个新的思路——四级缓存,与三级缓存不一样的是内存又分为了两级,这些稍后会详细介绍到。缓存
本文目的在于和你们分享一个图片框架的封装思路,至于代码的优化,如使用OkHttp替换HttpUrlConnection,使用RxJava替换Handler等,或者有别的不足的地方,也但愿你们可以反馈给我,咱们一块儿进步。安全
先看下总体流程图:性能优化
public class MyThreadFactory {
//Android的线程池类
private static ThreadPoolExecutor threadPoolExecutor=null;
//获取当前用户的手机的CPU的核心数
private static int num= Runtime.getRuntime().availableProcessors();
//用于存储提交任务的任务队列
private static BlockingDeque<Runnable> workQueue=new LinkedBlockingDeque<>(num*50);
private MyThreadFactory(){
}
public static ThreadPoolExecutor getThreadPoolExecutor(){
if(null==threadPoolExecutor){
threadPoolExecutor=new ThreadPoolExecutor(num*2, num*4, 8, TimeUnit.SECONDS, workQueue, new ThreadPoolExecutor.CallerRunsPolicy());
// threadPoolExecutor=new ThreadPoolExecutor(1, 1, 8, TimeUnit.SECONDS, workQueue, new ThreadPoolExecutor.CallerRunsPolicy());
}
return threadPoolExecutor;
}
}
复制代码
当前类是一个线程池的管理类。因为当前的线程池,在整个项目中不须要建立多个对象,直接使用单例模式进行建立。bash
补充:Android中的线程池 在Android中使用线程池的类是:ThreadPoolExecutor;微信
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(int corePoolSize, int maxinumPoolSize, long keepAliveTime, TimeUnit unit, BlockingDeque<Runnable> workQueue, ThreadFactory threadFactory);
复制代码
参数:网络
在上文展现的类中,咱们获取了手机的CPU核心数num,本线程池的核心线程数为CPU数的2倍,最大线程数为CPU核心数的4倍。
private static final HashMap<String,Bitmap> mHardBitmapCache=new LinkedHashMap<String,Bitmap>(
M_LINK_SIZE/2,0.75f,true){
/**
* 这个方法是是put或putAll时调用,默认返回false,表示添加数据时不移除最旧的数据.
* @param eldest
* @return
*/
@Override
protected boolean removeEldestEntry(Entry<String, Bitmap> eldest) {
if (size() > M_LINK_SIZE) {
// 当map的size大于30时,把最近不经常使用的key放到mSoftBitmapCache中,从而保证mHardBitmapCache的效率
Bitmap value = eldest.getValue();
if (value != null) {
mWeakBitmapCache.put(eldest.getKey(),new SoftReference<Bitmap>(value));
}
return true;
}
return false;
}
};
复制代码
定义的内存中的一级缓存,即保存做为强引用的位置的HashMap。
此处HashMap使用的是LinkedHashMap。LinkedHashMap 是HashMap的一个子类,保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先获得的记录确定是先插入的。也能够在构造时用带参数,按照应用次数排序。在遍历的时候会比HashMap慢,不过有种状况例外,当HashMap容量很大,实际数据较少时,遍历起来可能会比 LinkedHashMap慢,由于LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而HashMap的遍历速度和他的容量有关。
正是因为LinkedHashMap具备记忆功能,最近插入的最新访问,就符合了咱们的最近最多使用的原则。但因为其遍历速度慢,咱们对其容量进行设定,最多30和元素。
重写removeEldestEntry方法,当map的size大于30时,把最近不经常使用的key放到mSoftBitmapCache中(也就是内存第二级缓存),从而保证mHardBitmapCache的效率。
这里咱们在Map中是以Url和Bitmap为Key-Value存储的,因为LinkedHashMap存放少,并且插入移出快,因此这里用的是Bitmap的强引用。
若是LinkedHashMap中包含咱们须要的图片,则将图片直接返回。可是注意:此时咱们认为此图使用频率更高,所以咱们须要先将该元素移出,在加入(这是因为该map后插入的遍历时先读取)。
mHardBitmapCache.remove(netUrlKey);
mHardBitmapCache.put(netUrlKey,usefulBitmap);
复制代码
此为内存的一级缓存。
若是内存的LinkedHashMap中未获取到咱们想要的图片的话,在二级缓存中进行查找。
private static Map<String, SoftReference<Bitmap>> mWeakBitmapCache = new ConcurrentHashMap<String, SoftReference<Bitmap>>(
M_LINK_SIZE / 2);
复制代码
这时就用到了ConcurrentHashMap,它的最大特色就是线程安全、高并发、存储量大。因为存储量大,因此咱们存放Bitmap时就须要使用其软引用了。
若是此map中含有须要的图片,则先取出其软引用,在从软引用中获取Bitmap对象返回。再将其移至一级缓存中。
内存的读取总体代码以下:
/**
* 这里定义的操做方法完成的是从内存中的Map中获取图片的对象
* 既然已经在内存中了,默认已经完成了压缩
*
* @param netUrlKey 做为图片在Map中惟一标志的网络图片URL
* @return
*/
public static Bitmap getBitmapFromRAM(String netUrlKey){
if(mHardBitmapCache.containsKey(netUrlKey)){
Bitmap usefulBitmap=mHardBitmapCache.get(netUrlKey);
if(null!=usefulBitmap){
//若是存在正在内存中的Bitmap图片,将图片的使用级别向前提,并返回Bitmap对象
mHardBitmapCache.remove(netUrlKey);
mHardBitmapCache.put(netUrlKey,usefulBitmap);
return usefulBitmap;
}else{
//这里的状况是虽然在集合中包含对应的Key可是经过key得不到对应的Bitmap,此时将
//key从Map中清楚,并返回null
mHardBitmapCache.remove(netUrlKey);
return null;
}
}else{
//若是在强引用中不包含对应的key,那么在软引用中进行查找
if(mWeakBitmapCache.containsKey(netUrlKey)){
SoftReference<Bitmap> usefulSoftBitmap=mWeakBitmapCache.get(netUrlKey);
if(null!=usefulSoftBitmap){
//从软应用中获取出对应的Bitmap对象
Bitmap usefulBitmap = usefulSoftBitmap.get();
if(null!=usefulBitmap){
//将软引用中的低级别图片转移到强引用中
mHardBitmapCache.put(netUrlKey,usefulBitmap);
return usefulBitmap;
}else{
//软引用中包含key可是获取不到图片
mWeakBitmapCache.remove(netUrlKey);
return null;
}
}else{
//软引用中包含key可是获取不到图片
mWeakBitmapCache.remove(netUrlKey);
return null;
}
}else{
//软引用中也不包括这个key,那么从判断SD卡中是否存在这个资源图片
return null;
}
}
}
复制代码
特别声明:在存放入内存前,会将图片进行压缩。
内存中没有图片的话,就去文件中查找:
/**
* 获取已经保存的数据的位置的路径
*
* @param netUrlorPath
* @return
*/
private static String getSavedPath(String netUrlorPath) {
String savedPath = null;
if (StorageUtil.isPhoneHaveSD()) {
// 建立以SD卡根目录为路径的File对象
File fileBySD = new File(StorageUtil.getPathBySD());
// 建立SD卡根目录下以当前应用包名为文件夹的文件对象,并验证是否存在当前目录
File fileBySDSon = new File(fileBySD, PackageUtil.getAppPackageName());
// File fileBySDSon=new File(fileBySD,"AA");
if (fileBySDSon.exists()) {
String md5Url = EncryptUtil.md5(netUrlorPath);
// 以包名为文件夹的对象存在的时候,经过将文件对象和图片的名称的拼接构建文件对象
File imageFile = new File(fileBySDSon, URLEncoder.encode(md5Url));
if (imageFile.exists()) {
// 图片文件对象存在的时候获取当前的图片对象对应的路径
savedPath = imageFile.getAbsolutePath();
} else {
return null;
}
} else {
return null;
}
} else {
// 建立以Cache根目录为路径的File对象
File fileByCache = new File(StorageUtil.getPathBycache());
// 建立SD卡根目录下以当前应用包名为文件夹的文件对象,并验证是否存在当前目录
File fileByCacheSon = new File(fileByCache, PackageUtil.getAppPackageName());
// File fileByCacheSon=new File(fileByCache,"AA");
if (fileByCacheSon.exists()) {
String md5Url = EncryptUtil.md5(netUrlorPath);
// 以包名为文件夹的对象存在的时候,经过将文件对象和图片的名称的拼接构建文件对象
File imageFile = new File(fileByCacheSon, URLEncoder.encode(md5Url));
if (imageFile.exists()) {
// 图片文件对象存在的时候获取当前的图片对象对应的路径
savedPath = imageFile.getAbsolutePath();
} else {
return null;
}
} else {
return null;
}
}
return savedPath;
}
复制代码
上方代码是根据图片url获取到图片在文件中的路径。
全部的缓存图片,会保存在本包名文件夹下,以url的md5值为名字的文件中,判断到有此文件的话,将文件路径返回。
/**
* 这里完成的操做是判断传递进来的路径是否包括Bitmap对象,若是存在将Bitmap对象返回 不然返回null
*
* @param saveTime
* 图片的保存时间
* @param netUrl
* 网络图片的网络路径做为文件名称
* @return
*/
public static Bitmap getBitmapFromSD(long saveTime, String netUrl) {
long nativeSaveTime = saveTime > 0 ? saveTime : DATA_DEFAULT_SAVETIME;
long actualSaveTime = 0L;
if (null == netUrl) {
return null;
}
String imageSavePath = getSavedPath(netUrl);
// System.out.println("已经存储的图片的路径::" + imageSavePath);
if (null == imageSavePath) {
return null;
}
File imageFile = new File(imageSavePath);
if (!imageFile.exists()) {
// throw new StructException("须要的文件不存在!");
return null;
}
actualSaveTime = System.currentTimeMillis() - imageFile.lastModified();
if (actualSaveTime > nativeSaveTime) {
imageFile.delete();
//System.out.println("文件超时了!");
return null;
}
/**
* 这里的逻辑是当文件对象存在的时候将该文件对象获取出来,并生成Bitmap对象并返回
*/
// Bitmap sdBitmap= BitmapFactory.decodeFile(imageSavePath);
// 从SD卡中获取图片的时候直接进行图片的压缩处理防止OOM
//System.out.println("保存的图片的连接:" + imageSavePath);
Bitmap sdBitmap = ImageUtil.getCompressBitmapBYScreen(imageSavePath);
return sdBitmap;
}
复制代码
判断到文件中有咱们须要的图片,会拿到文件路径。可是,咱们有设定文件有效时间,超过该时间则视为超时,返回null,不然读取该文件。根据图片的路径和当前手机的默认屏幕分辨率进行图片压缩再返回。
文件中有该图片,那就将该图片移植内存中,以提升优先级,并且内存两级中都放入该图片。
以上都没拿到图片的话,那只能从网络来获取啦!
对http仍是https进行判断,分别对应使用HttpUrlConnection和HttpsUrlConnection。他们代码相似,就只贴其中一个了。
public static InputStream getHttpIOByGet(String netUrl) throws IOException {
// System.out.println("网络的连接:"+netUrl);
URL url = new URL(netUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
int code = conn.getResponseCode();
// System.out.println("返回码::"+code);
if (code == 200) {
InputStream is = conn.getInputStream();
return is;
}else{
return null;
}
}
复制代码
返回码200,表示请求成功,就将输入流返回,不然返回null。
Bitmap bitmap= BitmapFactory.decodeStream(inputStream);
复制代码
获取输入流后,使用上方代码获取Bitmap对象,缘由你们懂的。
获取到图片后,再依次存入sd卡和内存中,由于是好是操做,就在子线程中进行了。
new Thread(){
@Override
public void run() {
//3.一、从网络获取图片
//3.二、将图片压缩后的保存到SD卡或机身内存中
FileUtil.putBitmapToSD(netUrl, finalThreeCacheBitmap);
//3.四、将图片保存到Map中
CacheRAM.putBitmapToRAM(netUrl, finalThreeCacheBitmap);
}
}.start();
复制代码
这里主要想介绍下图片的压缩:由于图片加载很容易形成OOM,因此图片压缩处理显得尤其重要。
提供集中压缩方式:
这里就再也不贴代码了,能够去个人github中查看。github.com/shuaijia/Js…
allprojects {
repositories {
...
maven { url 'https://www.jitpack.io' }
}
}
dependencies {
compile 'com.github.shuaijia:JsImageLoader:v1.0'
}
复制代码
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
复制代码
JsLoader.with(this)
.load("https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=699359866,1092793192&fm=27&gp=0.jpg")
.defaultImg(R.mipmap.default)
.errorImg(R.mipmap.error)
.into(imageView);
复制代码
因为本人水平有限,难免有不对或不足的地方,但愿你们可以提出,咱们共同进步。
更多精彩内容,请关注个人微信公众号——Android机动车