概述
第三方開源的圖片框架很多审丘,這里自己去寫一個(gè)的目的是通過這樣一個(gè)寫的過程,拓展自己對(duì)架構(gòu)設(shè)計(jì)的理解勾给,包括設(shè)計(jì)模式滩报,線程,策略播急,緩存等等渣锦。另外大型的框架例如Glide顽决,代碼很完善,擴(kuò)展性很高,但是閱讀起來有難度仓坞,而實(shí)際上,這些框架底層實(shí)現(xiàn)原理都是類似的顷扩,所以通過構(gòu)建一個(gè)簡(jiǎn)單框架的過程更加有助于對(duì)其原理的理解火邓,算是為閱讀復(fù)雜的第三方源碼打下一個(gè)基礎(chǔ)。
github地址:https://github.com/renzhenming/ImageLoader.git
今天的框架要實(shí)現(xiàn)一下的功能:
1.根據(jù)用戶需求可以靈活配置(建造者模式)
2.支持高并發(fā)烂叔,圖片加載的優(yōu)先級(jí)
3.支持可以選擇不同的加載策略谨胞,對(duì)加載策略進(jìn)行擴(kuò)展
4.二級(jí)緩存 加載圖片時(shí)內(nèi)存中已經(jīng)加載了,則從內(nèi)存中加載蒜鸡,不存在去外置卡中5.加載胯努,外置還不存在則從網(wǎng)絡(luò)下載
6.并對(duì)緩存策略可以擴(kuò)展
7.支持從加載過程中顯示默認(rèn)加載圖片
8.支持加載失敗時(shí) 顯示默認(rèn)錯(cuò)誤圖片
9.圖片顯示自適應(yīng)昼牛。從網(wǎng)絡(luò)加載下來的圖片經(jīng)最佳比例壓縮后顯示不能失真變形
10.支持請(qǐng)求轉(zhuǎn)發(fā),下載
用到的模式:
1.生產(chǎn)者 消費(fèi)者模式
2.建造者模式
3.單例模式
4.模板方法模式
5.策略模式
用到的知識(shí)點(diǎn)
1.內(nèi)存緩存 LruCache技術(shù)
2.硬盤緩存技術(shù)DiskLruCache技術(shù)
3.圖片下載時(shí)請(qǐng)求轉(zhuǎn)發(fā)
框架構(gòu)建流程
如上圖康聂,首先使用Builder設(shè)計(jì)模式構(gòu)建ImageLoaderConfig贰健,這個(gè)類處理圖片加載框架的全局配置信息,包括加載策略恬汁,緩存策略伶椿,線程數(shù),以及加載中一些圖片的配置氓侧,封裝成了DisplayConfig對(duì)象脊另;SimpleImageLoader是對(duì)外暴露的主類,它持有配置對(duì)象的引用约巷,所以它可以調(diào)用所以圖片加載中所涉及的配置偎痛,然后將這些配置封裝成BitmapRequest對(duì)象,一個(gè)BitmapRequest對(duì)象對(duì)應(yīng)一次網(wǎng)絡(luò)請(qǐng)求独郎,在SimpleImageLoader初始化的同時(shí)還會(huì)初始化全局的請(qǐng)求隊(duì)列RequestQueue踩麦,BitmapRequest對(duì)象會(huì)被加入隊(duì)列中,這時(shí)候分發(fā)器開始工作氓癌,將BitmapRequest按照一定協(xié)議分發(fā)給不同的加載器谓谦,加載器拿到請(qǐng)求后先從緩存BitmapCache中獲取,有緩存則直接展示然后再去加載網(wǎng)絡(luò)圖片贪婉,加載網(wǎng)絡(luò)圖片的時(shí)候又涉及到加載策略的選擇反粥,根據(jù)不同策略進(jìn)行不同的加載。
關(guān)鍵代碼
1.配置管理
ImageLoaderConfig ,配置管理類疲迂,我們把一些關(guān)鍵的配置信息單獨(dú)封裝起來才顿,以Builder模式構(gòu)建,用戶可以自由的配置自己需要的尤蒿,同時(shí)提高擴(kuò)展性郑气,這正是Builder設(shè)計(jì)模式的優(yōu)點(diǎn).ImageLoaderConfig 負(fù)責(zé)處理整個(gè)框架的配置信息,目前包括緩存策略优质,加載策略竣贪,加載中顯示的占位圖,加載失敗展示的占位圖等等
package com.rzm.imageloader.config;
import com.rzm.imageloader.cache.BitmapCache;
import com.rzm.imageloader.policy.LoadPolicy;
/**
* Author:renzhenming
* Time:2018/6/13 7:21
* Description:This is ImageLoaderConfig
* 以建造者模式實(shí)現(xiàn)巩螃,管理ImageLoader配置信息
*/
public class ImageLoaderConfig {
/**
* 圖片顯示配置 TODO 初始化
*/
private DisplayConfig displayConfig;
/**
* 緩存策略
*/
private BitmapCache bitmapCache;
/**
* 加載策略
*/
private LoadPolicy loadPolicy;
/**
* 默認(rèn)線程數(shù)
*/
private int threadCount = Runtime.getRuntime().availableProcessors();
private ImageLoaderConfig(){}
/**
* 建造者模式
*/
public static class Builder{
/**
* Builder持有外部類的引用演怎,在new的時(shí)候創(chuàng)建出來
*/
private ImageLoaderConfig config;
public Builder(){
config = new ImageLoaderConfig();
}
/**
* 設(shè)置緩存策略
* @param bitmapCache
* @return
*/
public Builder setCachePolicy(BitmapCache bitmapCache){
config.bitmapCache = bitmapCache;
return this;
}
/**
* 設(shè)置加載策略
* @param loadPolicy
* @return
*/
public Builder setLoadPolicy(LoadPolicy loadPolicy){
config.loadPolicy = loadPolicy;
return this;
}
/**
* 設(shè)置線程數(shù)
* @param threadCount
* @return
*/
public Builder setThreadCount(int threadCount){
config.threadCount = threadCount;
return this;
}
/**
* 設(shè)置加載過程中的圖片
* @param resId
* @return
*/
public Builder setLoadingImage(int resId){
if (config.displayConfig == null){
throw new NullPointerException("you have not set DisplayConfig,DisplayConfig is null");
}
config.displayConfig.loadingImage = resId;
return this;
}
/**
* 設(shè)置加載失敗顯示的圖片
* @param resId
* @return
*/
public Builder setErrorImage(int resId){
if (config.displayConfig == null){
throw new NullPointerException("you have not set DisplayConfig,DisplayConfig is null");
}
config.displayConfig.errorImage = resId;
return this;
}
/**
* 構(gòu)建
* @return
*/
public ImageLoaderConfig build(){
return config;
}
}
public DisplayConfig getDisplayConfig() {
return displayConfig;
}
public BitmapCache getBitmapCache() {
return bitmapCache;
}
public LoadPolicy getLoadPolicy() {
return loadPolicy;
}
public int getThreadCount() {
return threadCount;
}
}
package com.rzm.commonlibrary.general.imageloader.config;
/**
* Author:renzhenming
* Time:2018/6/13 7:24
* Description:This is DisplayConfig
* 圖片顯示配置類,單獨(dú)拿出來作為一個(gè)單獨(dú)類有利于擴(kuò)展避乏,仿Glide
*/
public class DisplayConfig {
/**
* 加載過程中的占位圖片
*/
public int loadingImage = -1;
/**
* 加載失敗顯示的圖片
*/
public int errorImage = -1;
}
2.SimpleImageLoader
以單例形式構(gòu)建的交互類爷耀,持有ImageLoaderConfig 配置引用,負(fù)責(zé)將每一個(gè)網(wǎng)絡(luò)請(qǐng)求封裝成BitmapRequest對(duì)象拍皮,SimpleImageLoader初始化的時(shí)候會(huì)構(gòu)建出一個(gè)全局的阻塞式隊(duì)列歹叮,BitmapRequest會(huì)被加入這個(gè)隊(duì)列中跑杭,此時(shí)分發(fā)器開始工作,分發(fā)器的構(gòu)建啟發(fā)于Android消息隊(duì)列中的looper咆耿,負(fù)責(zé)將每個(gè)Request請(qǐng)求從隊(duì)列中取出交給負(fù)責(zé)處理這個(gè)請(qǐng)求的加載器
package com.rzm.commonlibrary.general.imageloader.loader;
import android.graphics.Bitmap;
import android.widget.ImageView;
import com.rzm.commonlibrary.general.imageloader.config.DisplayConfig;
import com.rzm.commonlibrary.general.imageloader.config.ImageLoaderConfig;
import com.rzm.commonlibrary.general.imageloader.request.BitmapRequest;
import com.rzm.commonlibrary.general.imageloader.request.RequestQueue;
/**
* Author:renzhenming
* Time:2018/6/13 7:22
* Description:This is SimpleImageLoader
*/
public class SimpleImageLoader {
/**
* 持有配置信息對(duì)象的引用
*/
private ImageLoaderConfig config;
private static volatile SimpleImageLoader instance;
/**
* 請(qǐng)求隊(duì)列
*/
private RequestQueue requestQueue;
private SimpleImageLoader(){}
private SimpleImageLoader(ImageLoaderConfig config){
this.config = config;
//初始化請(qǐng)求隊(duì)列
requestQueue = new RequestQueue(config.getDispatcherCount());
//開啟請(qǐng)求隊(duì)列
requestQueue.start();
}
/**
* 用于在Application中初始化ImageLoader 設(shè)置配置信息
* 必須德谅,否則調(diào)用空參getInstance()會(huì)拋異常
* @param config
* @return
*/
public static SimpleImageLoader init(ImageLoaderConfig config){
if (instance == null){
synchronized (SimpleImageLoader.class){
if (instance == null){
instance = new SimpleImageLoader(config);
}
}
}
return instance;
}
/**
* 用于在代碼中獲取單例對(duì)象
* @return
*/
public static SimpleImageLoader getInstance(){
if (instance == null){
throw new UnsupportedOperationException("SimpleImageLoader haven't been init with ImageLoaderConfig,call init(ImageLoaderConfig config) in your application");
}
return instance;
}
/**
* 顯示圖片
* @param imageView
* @param url
*/
public void display(ImageView imageView,String url){
display(imageView,url,null,null);
}
/**
* 顯示圖片
* @param imageView
* @param url
*/
public void display(ImageView imageView,String url,DisplayConfig displayConfig){
display(imageView,url,displayConfig,null);
}
/**
* 顯示圖片
* @param imageView
* @param url
*/
public void display(ImageView imageView,String url,ImageListener listener){
display(imageView,url,null,listener);
}
/**
* 顯示圖片,用于針對(duì)特殊圖片配置特殊的配置信息
* @param imageView
* @param url
* @param displayConfig
* @param listener
*/
public void display(ImageView imageView,String url,DisplayConfig displayConfig,ImageListener listener){
if (imageView == null){
throw new NullPointerException("ImageView cannot be null");
}
//封裝成一個(gè)請(qǐng)求對(duì)象
BitmapRequest request= new BitmapRequest(imageView,url,displayConfig,listener);
//加入請(qǐng)求隊(duì)列
requestQueue.addRequest(request);
}
/**
* 監(jiān)聽圖片萨螺,設(shè)置后期處理窄做,仿Glide
*/
public static interface ImageListener{
void onComplete(ImageView imageView, Bitmap bitmap,String url);
}
/**
* 獲取全局配置
* @return
*/
public ImageLoaderConfig getConfig() {
return config;
}
}
3.BitmapRequest請(qǐng)求對(duì)象
一個(gè)BitmapRequest對(duì)象中封裝了這次請(qǐng)求的所有相關(guān)信息,包括這個(gè)請(qǐng)求的圖片顯示邏輯的配置DisplayConfig慰技,當(dāng)前全局的加載策略LoadPolicy椭盏,請(qǐng)求圖片的網(wǎng)絡(luò)地址和這個(gè)地址需要展示的控件對(duì)象ImageView,另外我對(duì)BitmapRequest對(duì)象重寫了hashCode和equals方法,實(shí)現(xiàn)了Comparable接口吻商,以實(shí)現(xiàn)這個(gè)對(duì)象的比較邏輯掏颊,為加載策略服務(wù)
package com.rzm.commonlibrary.general.imageloader.request;
import android.widget.ImageView;
import com.rzm.commonlibrary.general.imageloader.config.DisplayConfig;
import com.rzm.commonlibrary.general.imageloader.loader.SimpleImageLoader;
import com.rzm.commonlibrary.general.imageloader.policy.LoadPolicy;
import com.rzm.commonlibrary.general.imageloader.utils.Md5Util;
import java.lang.ref.SoftReference;
/**
* Author:renzhenming
* Time:2018/6/13 7:23
* Description:This is BitmapRequest
*/
public class BitmapRequest implements Comparable<BitmapRequest>{
/**
* 展示配置
*/
private DisplayConfig disPlayConfig;
/**
* 加載策略
*/
private LoadPolicy loadPolicy = SimpleImageLoader.getInstance().getConfig().getLoadPolicy();
/**
* 序列號(hào),用于順序比較
*/
private int serialNum;
/**
* 持有ImageView的軟引用
*/
private SoftReference<ImageView> imageViewSoftReference;
/**
* 圖片路徑
*/
private String imageUrl;
/**
* 圖片路徑的md5值
*/
private String imageUrlMd5;
/**
* 下載完成的監(jiān)聽
*/
private SimpleImageLoader.ImageListener imageListener;
public BitmapRequest() {
}
public BitmapRequest(ImageView imageView, String imageUrl) {
this(imageView,imageUrl,null,null);
}
public BitmapRequest(ImageView imageView,String imageUrl,DisplayConfig displayConfig) {
this(imageView,imageUrl,displayConfig,null);
}
public BitmapRequest(ImageView imageView,String imageUrl,SimpleImageLoader.ImageListener imageListener) {
this(imageView,imageUrl,null,imageListener);
}
public BitmapRequest(ImageView imageView, String imageUrl, DisplayConfig displayConfig,
SimpleImageLoader.ImageListener imageListener){
this.imageViewSoftReference = new SoftReference<ImageView>(imageView);
if (imageUrl != null) {
imageView.setTag(imageUrl);
imageUrlMd5 = Md5Util.toMD5(imageUrl);
}
this.imageUrl = imageUrl;
if (displayConfig != null){
this.disPlayConfig = displayConfig;
}
if (imageListener != null) {
this.imageListener = imageListener;
}
}
/**
* 請(qǐng)求的先后順序是根據(jù)加載的策略進(jìn)行的艾帐,不同的策略比較的條件也不同乌叶,所以
* 這里要把比較的邏輯交割具體的策略去做,策略有多種掩蛤,所以通過接口調(diào)用枉昏,增強(qiáng)擴(kuò)展性
* @return
*/
@Override
public int compareTo(BitmapRequest o) {
return loadPolicy.compareTo(o,this);
}
public int getSerialNum() {
return serialNum;
}
public void setSerialNum(int serialNum) {
this.serialNum = serialNum;
}
/**
* 獲取這個(gè)請(qǐng)求對(duì)應(yīng)的ImageView
* @return
*/
public ImageView getImageView(){
if (imageViewSoftReference == null)
return null;
return imageViewSoftReference.get();
}
public DisplayConfig getDisPlayConfig() {
return disPlayConfig;
}
public LoadPolicy getLoadPolicy() {
return loadPolicy;
}
public SoftReference<ImageView> getImageViewSoftReference() {
return imageViewSoftReference;
}
public String getImageUrl() {
return imageUrl;
}
public String getImageUrlMd5() {
return imageUrlMd5;
}
public SimpleImageLoader.ImageListener getImageListener() {
return imageListener;
}
/**
* BitmapRequest會(huì)被加入請(qǐng)求隊(duì)列中陈肛,在隊(duì)列中有需要做判斷當(dāng)前請(qǐng)求是否存在
* 那么就涉及到這個(gè)對(duì)象的比較揍鸟,所以需要重寫hashCode和equals方法
*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BitmapRequest request = (BitmapRequest) o;
return serialNum == request.serialNum &&
loadPolicy.equals(request.loadPolicy);
}
@Override
public int hashCode() {
int result = loadPolicy != null ? loadPolicy.hashCode():0;
result = 66*result+serialNum;
return result;
}
}
4.分發(fā)器實(shí)現(xiàn)原理
RequestDispatcher是一個(gè)繼承了Thread的線程對(duì)象,隊(duì)列創(chuàng)建后會(huì)根據(jù)配置創(chuàng)建出指定數(shù)量的分發(fā)器句旱,當(dāng)隊(duì)列中有請(qǐng)求對(duì)象后就從隊(duì)列中取出對(duì)象交給加載器阳藻,根據(jù)一定的協(xié)議選擇合適的加載器進(jìn)行網(wǎng)絡(luò)請(qǐng)求,當(dāng)隊(duì)列中沒有對(duì)象時(shí)谈撒,會(huì)進(jìn)入休眠狀態(tài)
package com.rzm.commonlibrary.general.imageloader.request;
import android.text.TextUtils;
import android.util.Log;
import com.rzm.commonlibrary.general.imageloader.loader.Loader;
import com.rzm.commonlibrary.general.imageloader.loader.LoaderManager;
import java.util.concurrent.BlockingQueue;
/**
* Author:renzhenming
* Time:2018/6/13 7:24
* Description:This is RequestDispatcher
*/
public class RequestDispatcher extends Thread{
/**
* 從隊(duì)列中轉(zhuǎn)發(fā)請(qǐng)求需要持有隊(duì)列的引用
*/
private BlockingQueue<BitmapRequest> blockingQueue;
public RequestDispatcher(BlockingQueue<BitmapRequest> blockingQueue) {
this.blockingQueue = blockingQueue;
}
/**
* 阻塞式隊(duì)列腥泥,轉(zhuǎn)發(fā)器開啟,從隊(duì)列中取請(qǐng)求隊(duì)列啃匿,如果沒有則會(huì)阻塞當(dāng)前線程蛔外,所以這里
* 是在子線程開啟的
*/
@Override
public void run() {
while(!isInterrupted()){
try {
BitmapRequest request = blockingQueue.take();
//處理請(qǐng)求對(duì)象,交給loader
String schema = parseSchema(request.getImageUrl());
//獲取加載器
Loader loader = LoaderManager.getInstance().getLoader(schema);
if (loader == null){
Log.d("TAG",request.getImageUrl() + "沒有找到對(duì)應(yīng)的加載器");
return;
}
loader.load(request);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 根據(jù)圖片url判斷加載類型
* @param imageUrl
* @return
*/
private String parseSchema(String imageUrl) {
if (TextUtils.isEmpty(imageUrl)){
return null;
}
if (imageUrl.contains("://")){
//形如 http://xxx 或者file://xxx溯乒,這樣截取后
//可以獲得http file等前綴夹厌,根據(jù)這個(gè)前綴獲取相應(yīng)
//的加載器
return imageUrl.split("://")[0];
}else{
Log.d("TAG","不持支的圖片類型");
}
return null;
}
}
5.加載器實(shí)現(xiàn)原理
目前該框架支持網(wǎng)絡(luò)圖片和本地圖片加載,不同的加載器根據(jù)不同的url進(jìn)行選擇裆悄,為了提高擴(kuò)展性矛纹,設(shè)置接口和抽象類的實(shí)現(xiàn)方式,下面是頂層接口
package com.rzm.commonlibrary.general.imageloader.loader;
import com.rzm.commonlibrary.general.imageloader.request.BitmapRequest;
/**
* Author:renzhenming
* Time:2018/6/13 7:21
* Description:This is Loader
*/
public interface Loader {
/**
* 加載圖片
* @param request
*/
void load(BitmapRequest request);
}
網(wǎng)絡(luò)加載器和本地加載器實(shí)現(xiàn)邏輯有所不同光稼,但是有一些公共的操作存在或南,比如加載前顯示加載中占位圖孩等,加載失敗顯示失敗圖片等等,這些操作可以放在一個(gè)公共的基類中實(shí)現(xiàn)采够,所以這里創(chuàng)建了一個(gè)抽象類作為上層類處理公共邏輯
package com.rzm.commonlibrary.general.imageloader.loader;
import android.graphics.Bitmap;
import android.widget.ImageView;
import com.rzm.commonlibrary.general.imageloader.cache.BitmapCache;
import com.rzm.commonlibrary.general.imageloader.config.DisplayConfig;
import com.rzm.commonlibrary.general.imageloader.request.BitmapRequest;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Author:renzhenming
* Time:2018/6/13 7:22
* Description:This is AbstractLoader
* 加載器策略不同肄方,則不同的加載器實(shí)現(xiàn)方式不同,但是他們有相同的操作蹬癌,比如顯示
* 占位圖等扒秸,所以這些相同操作在抽象一層出來
*/
public abstract class AbstractLoader implements Loader {
private static final String TAG = "AbstractLoader";
private AtomicInteger integer = new AtomicInteger(0);
/**
* 加載器加載圖片的邏輯是先緩存后網(wǎng)絡(luò),所以需要持有緩存對(duì)象的引用
*/
private BitmapCache bitmapCache = SimpleImageLoader.getInstance().getConfig().getBitmapCache();
/**
* 同樣因?yàn)橐幚盹@示時(shí)的邏輯冀瓦,所以需要持有顯示配置對(duì)象的引用
*/
private DisplayConfig displayConfig = SimpleImageLoader.getInstance().getConfig().getDisplayConfig();
@Override
public void load(BitmapRequest request) {
//從緩存中獲取Bitmap
Bitmap bitmap= null;
if (bitmapCache != null) {
bitmap = bitmapCache.get(request);
}
if (bitmap == null){
//顯示加載中圖片
showLoadingImg(request);
//開始加載網(wǎng)絡(luò)圖伴奥,加載的邏輯不同加載器有所不同,所以交給各自
//加載器實(shí)現(xiàn)翼闽,抽象
bitmap = onLoad(request);
if (bitmap == null){
//加載失敗重試三次
while(integer.incrementAndGet() <=3){
bitmap = onLoad(request);
if (bitmap != null){
break;
}
}
integer.set(0);
}
if (bitmap == null){
}
//加入緩存
if (bitmapCache != null && bitmap != null)
cacheBitmap(request,bitmap);
}else{
//有緩存
}
deliveryToUIThread(request,bitmap);
}
public abstract Bitmap onLoad(BitmapRequest request);
protected void deliveryToUIThread(final BitmapRequest request, final Bitmap bitmap) {
ImageView imageView = request.getImageView();
if(imageView!=null) {
imageView.post(new Runnable() {
@Override
public void run() {
updateImageView(request, bitmap);
}
});
}
}
private void updateImageView(final BitmapRequest request, final Bitmap bitmap) {
ImageView imageView = request.getImageView();
//加載正常 防止圖片錯(cuò)位
if(bitmap != null && imageView.getTag().equals(request.getImageUrl())){
imageView.setImageBitmap(bitmap);
}
//有可能加載失敗
if(bitmap == null && displayConfig!=null&&displayConfig.errorImage!=-1){
imageView.setImageResource(displayConfig.errorImage);
}
//監(jiān)聽
//回調(diào) 給圓角圖片 特殊圖片進(jìn)行擴(kuò)展
if(request.getImageListener() != null){
request.getImageListener().onComplete(imageView, bitmap, request.getImageUrl());
}
}
/**
* 緩存圖片
* @param request
* @param bitmap
*/
private void cacheBitmap(BitmapRequest request, Bitmap bitmap) {
if (request != null && bitmap != null){
synchronized (AbstractLoader.class){
bitmapCache.put(request,bitmap);
}
}
}
/**
* 顯示加載中占位圖,需要判斷用戶有沒有配置
* @param request
*/
private void showLoadingImg(BitmapRequest request) {
if (hasLoadingPlaceHolder()){
final ImageView imageView = request.getImageView();
if (imageView != null){
imageView.post(new Runnable() {
@Override
public void run() {
imageView.setImageResource(displayConfig.loadingImage);
}
});
}
}
}
/**
* 是否設(shè)置了加載中圖片
* @return
*/
private boolean hasLoadingPlaceHolder() {
return displayConfig != null && displayConfig.loadingImage > 0;
}
}
接下來是具體的加載器實(shí)現(xiàn)
package com.rzm.commonlibrary.general.imageloader.loader;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.text.TextUtils;
import com.rzm.commonlibrary.general.imageloader.request.BitmapRequest;
import com.rzm.commonlibrary.general.imageloader.utils.BitmapDecoder;
import com.rzm.commonlibrary.general.imageloader.utils.ImageViewHelper;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
/**
* Author:renzhenming
* Time:2018/6/13 7:22
* Description:This is UrlLoader
* 網(wǎng)絡(luò)圖片加載器
*/
public class UrlLoader extends AbstractLoader {
@Override
public Bitmap onLoad(BitmapRequest request) {
try {
String imageUrl = request.getImageUrl();
if (TextUtils.isEmpty(imageUrl)){
return null;
}
URL url = new URL(imageUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
if (conn.getResponseCode() != 200){
return null;
}
InputStream inputStream = conn.getInputStream();
//轉(zhuǎn)化成BufferedInputStream
final BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
//標(biāo)記一下拾徙,reset后會(huì)重置到這個(gè)位置
bufferedInputStream.mark(inputStream.available());
BitmapDecoder decoder = new BitmapDecoder() {
@Override
public Bitmap decodeBitmapWithOptions(BitmapFactory.Options options) {
//第一次讀取,因?yàn)樵O(shè)置了inJustDecodeBounds為true感局,所以尼啡,這里decodeStream之后,會(huì)將寬高
//信息存儲(chǔ)在options中询微;第二次讀取崖瞭,因?yàn)樵O(shè)置了inJustDecodeBounds為false.所以會(huì)將流全部讀取
Bitmap bitmap = BitmapFactory.decodeStream(bufferedInputStream,null,options);
if (options.inJustDecodeBounds){
//表示時(shí)第一次執(zhí)行,此時(shí)只是為了獲取Bounds
try {
//第一次讀取圖片寬高信息撑毛,讀完之后书聚,要為第二次讀取做準(zhǔn)備,將流重置
bufferedInputStream.reset();
} catch (IOException e) {
e.printStackTrace();
}
}else{
try {
bufferedInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return bitmap;
}
};
//傳入控件的寬高藻雌,設(shè)置圖片適應(yīng)控件
return decoder.decodeBitmap(ImageViewHelper.getImageViewWidth(request.getImageView()),
ImageViewHelper.getImageViewHeight(request.getImageView()));
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
package com.rzm.commonlibrary.general.imageloader.loader;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import com.rzm.commonlibrary.general.imageloader.request.BitmapRequest;
import com.rzm.commonlibrary.general.imageloader.utils.BitmapDecoder;
import com.rzm.commonlibrary.general.imageloader.utils.ImageViewHelper;
import java.io.File;
/**
* Author:renzhenming
* Time:2018/6/13 7:22
* Description:This is LocalLoader
* 本地圖片加載器
*/
public class LocalLoader extends AbstractLoader {
@Override
public Bitmap onLoad(BitmapRequest request) {
//得到本地圖片的路徑
final String path = Uri.parse(request.getImageUrl()).getPath();
File file = new File(path);
if (!file.exists() || !file.isFile()){
return null;
}
BitmapDecoder decoder = new BitmapDecoder() {
@Override
public Bitmap decodeBitmapWithOptions(BitmapFactory.Options options) {
return BitmapFactory.decodeFile(path,options);
}
};
return decoder.decodeBitmap(ImageViewHelper.getImageViewWidth(request.getImageView()),
ImageViewHelper.getImageViewHeight(request.getImageView()));
}
}
package com.rzm.commonlibrary.general.imageloader.utils;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
/**
* 圖片解碼器
*/
public abstract class BitmapDecoder {
/**
* 壓縮圖片
* @param width imageView的寬度
* @param height imageView的高度
* @return
*/
public Bitmap decodeBitmap(int width,int height){
BitmapFactory.Options options = new BitmapFactory.Options();
//設(shè)置為true 只讀取圖片的寬高雌续,不需要將整個(gè)圖片都加載到內(nèi)存
options.inJustDecodeBounds = true;
decodeBitmapWithOptions(options);
//經(jīng)過上面一次操作,此時(shí)options中已經(jīng)有了寬高信息
calculateSampleSizeWithOptions(options,width,height);
//第二次就可以得到縮放后的bitmap了
return decodeBitmapWithOptions(options);
}
/**
* 將圖片寬高和控件寬高進(jìn)行比較胯杭,得到縮放值驯杜,信息仍然存儲(chǔ)在options中
* @param options
* @param viewWidth
* @param viewHeight
*/
private void calculateSampleSizeWithOptions(BitmapFactory.Options options,int viewWidth,int viewHeight) {
//計(jì)算縮放比例
//圖片的原始寬高
int width = options.outWidth;
int height = options.outHeight;
int inSampleSize = 1;
//當(dāng)圖片的寬高大于控件的寬高時(shí)才需要壓縮
if (width > viewWidth || height > viewHeight){
//計(jì)算出寬高的縮放比例
int widthRatio = Math.round((float) width/(float)viewWidth);
int heightRatio = Math.round((float)height/(float)viewHeight);
//取寬高縮放比較大的值為圖片的縮放比
inSampleSize = Math.max(widthRatio,heightRatio);
}
//設(shè)置到options中做个,options保存的是配置信息
//當(dāng)inSampleSize為2鸽心,圖片的寬高會(huì)縮放為原來的1/2
options.inSampleSize = inSampleSize;
//每個(gè)像素2個(gè)字節(jié)
options.inPreferredConfig = Bitmap.Config.RGB_565;
//寬高已經(jīng)計(jì)算出來了,inJustDecodeBounds值可以復(fù)位了
options.inJustDecodeBounds = false;
//當(dāng)系統(tǒng)內(nèi)存不足時(shí).可以回收bitmap
options.inPurgeable = true;
options.inInputShareable = true;
}
/**
* 將流的處理通過抽象方法暴露出來居暖,降低解碼器和外部的耦合
* @param options
*/
public abstract Bitmap decodeBitmapWithOptions(BitmapFactory.Options options);
}
6.本地緩存
package com.rzm.commonlibrary.general.imageloader.cache;
import android.graphics.Bitmap;
import android.util.LruCache;
import com.rzm.commonlibrary.general.imageloader.request.BitmapRequest;
/**
* Author:renzhenming
* Time:2018/6/13 7:20
* Description:This is MemoryCache
*/
public class MemoryCache implements BitmapCache{
private LruCache<String,Bitmap> mLruCache;
public MemoryCache(){
int maxSize = (int) (Runtime.getRuntime().maxMemory()/1024/8);
mLruCache = new LruCache<String,Bitmap>(maxSize){
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes()*value.getHeight();
}
};
}
@Override
public Bitmap get(BitmapRequest request) {
if (mLruCache == null) return null;
return mLruCache.get(request.getImageUrlMd5());
}
@Override
public void put(BitmapRequest request, Bitmap bitmap) {
if (mLruCache == null) return;
mLruCache.put(request.getImageUrlMd5(),bitmap);
}
@Override
public void remove(BitmapRequest request) {
if (mLruCache == null) return;
mLruCache.remove(request.getImageUrlMd5());
}
}
7.硬盤緩存
package com.rzm.commonlibrary.general.imageloader.disk;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import com.rzm.commonlibrary.general.imageloader.cache.BitmapCache;
import com.rzm.commonlibrary.general.imageloader.request.BitmapRequest;
import com.rzm.commonlibrary.general.imageloader.utils.IOUtil;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* Author:renzhenming
* Time:2018/6/13 7:20
* Description:This is DiskCache
*/
public class DiskCache implements BitmapCache {
private static volatile DiskCache mDiskCache;
//緩存路徑
private String mCacheDir = "Image";
//MB
private static final int MB = 1024 * 1024;
//jackwharton的杰作
private DiskLruCache mDiskLruCache;
private DiskCache(Context context)
{
iniDiskCache(context);
}
public static DiskCache getInstance(Context context) {
if(mDiskCache==null)
{
synchronized (DiskCache.class)
{
if(mDiskCache==null)
{
mDiskCache=new DiskCache(context);
}
}
}
return mDiskCache;
}
private void iniDiskCache(Context context) {
//得到緩存的目錄 android/data/data/com.xxx/cache/Image
File directory=getDiskCache(mCacheDir,context);
if(!directory.exists())
{
directory.mkdirs();
}
try {
//最后一個(gè)參數(shù) 指定緩存容量
mDiskLruCache=DiskLruCache.open(directory,1,1,50*MB);
} catch (IOException e) {
e.printStackTrace();
}
}
private File getDiskCache(String mCacheDir, Context context) {
//默認(rèn)緩存路徑
return new File(context.getCacheDir(),mCacheDir);
//return new File(Environment.getExternalStorageDirectory(),mCacheDir);
}
@Override
public void put(BitmapRequest request, Bitmap bitmap) {
if (mDiskLruCache == null) return;
DiskLruCache.Editor edtor=null;
OutputStream os=null;
try {
//路徑必須是合法字符
edtor=mDiskLruCache.edit(request.getImageUrlMd5());
os=edtor.newOutputStream(0);
if(persistBitmap2Disk(bitmap,os))
{
edtor.commit();
}else {
edtor.abort();
}
} catch (IOException e) {
e.printStackTrace();
}
}
private boolean persistBitmap2Disk(Bitmap bitmap, OutputStream os) {
BufferedOutputStream bos=new BufferedOutputStream(os);
bitmap.compress(Bitmap.CompressFormat.JPEG,100,bos);
try {
bos.flush();
} catch (IOException e) {
e.printStackTrace();
}finally {
IOUtil.closeQuietly(bos);
}
return true;
}
@Override
public Bitmap get(BitmapRequest request) {
if (mDiskLruCache == null) return null;
try {
DiskLruCache.Snapshot snapshot=mDiskLruCache.get(request.getImageUrlMd5());
if(snapshot!=null)
{
InputStream inputStream=snapshot.getInputStream(0);
return BitmapFactory.decodeStream(inputStream);
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
@Override
public void remove(BitmapRequest request) {
if (mDiskLruCache == null) return;
try {
mDiskLruCache.remove(request.getImageUrlMd5());
} catch (IOException e) {
e.printStackTrace();
}
}
}