開發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機動車