Bitmap,表示位圖,由像素點構成。Bitmap的承載容器是jpg、png等格式的文件,是對bitmap的壓縮。當jpg、png等文件須要展現在手機上的控件時,就會解析成Bitmap並繪製到view上。一般處理圖片時要避免過多的內存使用,畢竟移動設備的內存有限。 那麼加載一張圖片須要佔用多大內存呢?考慮到效率加載圖片時緩存策略是怎樣的呢?java
原始計算公式以下:android
Bitmap的內存 = 分辨率 * 像素點的大小git
詳細理解參考Android中一張圖片佔據的內存大小是如何計算github
Bitmap的加載,可過系統提供的BitmapFactory四個方法:decodeFile、decodeResource、decodeStream、decodeByteArray,對應處理從 文件、資源、流、字節數組 來加載Bitmap。算法
如何優化加載呢?由公式可見想要減小圖片加載成Bitmap時佔用的內存,兩個方法:數組
針對第二點,下降分辨率,BitmapFactory.Options也提供了對應的方法,步驟以下:緩存
代碼以下:安全
private void initView() {
//R.mipmap.blue放在Res的xxh(480dpi)中,測試手機dpi也是480
//一、inJustDecodeBounds設爲true,並加載圖片
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.mipmap.blue, options);
//二、獲取原始寬高信息
int outWidth = options.outWidth;
int outHeight = options.outHeight;
Log.i(TAG, "initView: outWidth="+outWidth+",outHeight="+outHeight);
//三、原始寬高信息 和 view的大小 計算並設置採樣率
ViewGroup.LayoutParams layoutParams = ivBitamp.getLayoutParams();
int inSampleSize = getInSampleSize(layoutParams.width, layoutParams.height, outWidth, outHeight);
options.inSampleSize = inSampleSize;
Log.i(TAG, "initView: inSampleSize="+options.inSampleSize);
//四、inJustDecodeBounds設爲false,並加載圖片
options.inJustDecodeBounds = false;
Bitmap bitmap= BitmapFactory.decodeResource(getResources(), R.mipmap.blue, options);
Log.i(TAG, "initView: size="+bitmap.getByteCount());
int density = bitmap.getDensity();
Log.i(TAG, "initView: density="+density);
Log.i(TAG, "initView: original size="+337*222*4);
Log.i(TAG, "initView: calculated size="+ (337/inSampleSize) *(222/inSampleSize)* density/480 *4);
//繪製到view
ivBitamp.setImageBitmap(bitmap);
}
/** * 計算採樣率 * @param width view的寬 * @param height view的高 * @param outWidth 圖片原始的寬 * @param outHeight 圖片原始的高 * @return */
private int getInSampleSize(int width, int height, int outWidth, int outHeight) {
int inSampleSize = 1;
if (outWidth>width || outHeight>height){
int halfWidth = outWidth / 2;
int halfHeight = outHeight / 2;
//保證採樣後的寬高都不小於目標快高,不然會拉伸而模糊
while (halfWidth/inSampleSize >=width
&& halfHeight/inSampleSize>=height){
//採樣率通常取2的指數
inSampleSize *=2;
}
}
return inSampleSize;
}
}
複製代碼
緩存策略在Android中應用普遍。使用緩存能夠節省流量、提升效率。網絡
加載圖片時,通常會從網絡加載,而後緩存在存儲設備上,這樣下次就不用請求網絡了。而且一般也會緩存一份到內存中,這樣下次能夠直接取內存中的緩存,要比從存儲設備中取快不少。因此通常是先從內存中取,內存沒有就取存儲設備,也沒有才會請求網絡,這就是所謂的「三級緩存」。此策略一樣適用其餘文件類型。app
緩存策略中的操做有 添加緩存、獲取緩存、刪除緩存。添加和獲取比較好理解,刪除緩存是啥意思?由於緩存大小是有限制的,像移動設備的 內存 和 設備存儲都是有限的,不能無限制的添加,只能限定一個最大緩存,到達最大時就會刪除一部分緩存。可是刪除哪一部分緩存呢?刪除 緩存建立時間最老的嗎,若是它常常用到呢,好像不太完美,固然這也是一種緩存算法。
目前經典的緩存算法是LRU(Least Recently Used),最近最少使用。具體就是 當緩存滿時,會先刪除那些 近期 最少使用 的緩存。使用LRU算法的緩存有兩種,LruCache和DiskLruCache,LruCache是使用內存緩存,DiskLruCache是實現磁盤緩存。
LruCache是泛型類,使用方法以下: 提供最大緩存容量,建立LruCache實例,並重寫其sizeOf方法來計算緩存對象的大小。最大容量和緩存對象大小單位要一致。
private void testLruCache() {
//當前進程的最大內存,單位M
long maxMemory = Runtime.getRuntime().maxMemory() / 1024 / 1024;
//取進程內存的1/8
int cacheMaxSize = (int) (maxMemory/8);
//建立Bitmap實例
mBitmapLruCache = new LruCache<String, Bitmap>(cacheMaxSize){
@Override
protected int sizeOf(String key, Bitmap value) {
//緩存對象bitmap的大小,單位M
return value.getByteCount()/1024/1024;
}
@Override
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
//移除舊緩存時會調用,能夠在這裏進行像資源回收的工做。
//evicted爲true,表示此處移除是由於快滿了要騰出空間
}
};
//添加緩存
mBitmapLruCache.put("1",mBitmap);
//獲取緩存
Bitmap bitmap = mBitmapLruCache.get("1");
ivBitamp.setImageBitmap(bitmap);
//刪除緩存,通常不會用,由於快滿時會自動刪近期最少使用的緩存,就是它的核心功能
mBitmapLruCache.remove("1");
}
複製代碼
可見使用很簡單,那麼LruCache是怎麼完成 刪除「近期最少使用」 的呢?看下LruCache的代碼:
public class LruCache<K, V> {
//此map以強引用的方式存儲緩存對象
private final LinkedHashMap<K, V> map;
//當前緩存的大小(帶單位的)
private int size;
//緩存最大容量(帶單位的)
private int maxSize;
...
public LruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
//LinkedHashMap是按照 訪問順序 排序的,因此get、put操做都會把要存的k-v放在隊尾
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}
/** * 獲取緩存,同時會把此k-v放在鏈表的尾部 */
public final V get(K key) {
if (key == null) {
throw new NullPointerException("key == null");
}
V mapValue;
//get是線程安全的操做
synchronized (this) {
//LinkedHashMap的get方法中調afterNodeAccess,會移到鏈表尾部
mapValue = map.get(key);
if (mapValue != null) {
hitCount++;
return mapValue;
}
missCount++;
}
...
}
/** * 緩存key-value,value會存在 隊尾 * @return 以前也是這個key存的value */
public final V put(K key, V value) {
if (key == null || value == null) {
//不容許 null key、null value
throw new NullPointerException("key == null || value == null");
}
V previous;
//可見put操做是線程安全的
synchronized (this) {
putCount++;
size += safeSizeOf(key, value);
//強引用存入map(不會被動地被系統回收),其由於是LinkedHashMap,會放在隊尾
previous = map.put(key, value);
if (previous != null) {
//若是前面已這個key,那麼替換後調整下當前緩存大小
size -= safeSizeOf(key, previous);
}
}
if (previous != null) {
entryRemoved(false, key, previous, value);
}
//從新調整大小
trimToSize(maxSize);
return previous;
}
/** * 比較 當前已緩存的大小 和最大容量,決定 是否刪除 */
private void trimToSize(int maxSize) {
while (true) {
K key;
V value;
synchronized (this) {
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new IllegalStateException(getClass().getName()
+ ".sizeOf() is reporting inconsistent results!");
}
if (size <= maxSize) {
//大小還沒超過最大值
break;
}
//已經達到最大容量
//由於是訪問順序,因此遍歷的最後一個就是最近沒有訪問的,那麼就能夠刪掉它了!
Map.Entry<K, V> toEvict = null;
for (Map.Entry<K, V> entry : map.entrySet()) {
toEvict = entry;
}
// END LAYOUTLIB CHANGE
if (toEvict == null) {
break;
}
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= safeSizeOf(key, value);
evictionCount++;
}
//由於是爲了騰出空間,因此這個回調第一個參數是true
entryRemoved(true, key, value, null);
}
}
protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
...
}
複製代碼
由以上代碼及註釋,可見LruCache的算法實現是依靠 設置了訪問順序的LinkedHashMap。由於是訪問順序模式,get、put操做都會調整k-v到鏈表尾部。在緩存將滿時,遍歷LinkedHashMap,由於是訪問順序模式,因此遍歷的最後一個就是最近沒有使用的,而後刪除便可。
DiskLruCache是實現磁盤緩存,因此須要設備存儲的讀寫權限;通常是從網絡請求圖片後緩存到磁盤中,因此還須要網絡權限。
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
複製代碼
DiskLruCache,不是官方提供,因此須要引入依賴:
implementation 'com.jakewharton:disklrucache:2.0.2'
複製代碼
經過查看源碼,發現LinkedHashMap內部也是維護了訪問順序的LinkedHashMap,原理上和LruCache是一致的。只是使用上有點點複雜,畢竟涉及文件的讀寫。
具體使用及注意點以下代碼:
private void testDiskLruCache(String urlString) {
long maxSize = 50*1024*1024;
try {
//1、建立DiskLruCache
//第一個參數是要存放的目錄,這裏選擇外部緩存目錄(若app卸載此目錄也會刪除);
//第二個是版本通常設1;第三個是緩存節點的value數量通常也是1;
//第四個是最大緩存容量這裏取50M
mDiskLruCache = DiskLruCache.open(getExternalCacheDir(), 1, 1, maxSize);
//2、緩存的添加:一、經過Editor,把圖片的url轉成key,經過edit方法獲得editor,而後獲取輸出流,就能夠寫到文件系統了。
DiskLruCache.Editor editor = mDiskLruCache.edit(hashKeyFormUrl(urlString));
if (editor != null) {
//參數index取0(由於上面的valueCount取的1)
OutputStream outputStream = editor.newOutputStream(0);
boolean downSuccess = downloadPictureToStream(urlString, outputStream);
if (downSuccess) {
//二、編輯提交,釋放編輯器
editor.commit();
}else {
editor.abort();
}
//三、寫到文件系統,會檢查當前緩存大小,而後寫到文件
mDiskLruCache.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
//3、緩存的獲取
try {
String key = hashKeyFormUrl(urlString);
DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
FileInputStream inputStream = (FileInputStream)snapshot.getInputStream(0);
// Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
// mIvBitamp.setImageBitmap(bitmap);
//注意,通常須要採樣加載,但文件輸入流是有序的文件流,採樣時兩次decodeStream影響文件流的文職屬性,致使第二次decode是獲取是null
//爲解決此問題,可用文件描述符
FileDescriptor fd = inputStream.getFD();
//採樣加載(就是前面講的bitmap的高效加載)
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds=true;
BitmapFactory.decodeFileDescriptor(fd,null,options);
ViewGroup.LayoutParams layoutParams = mIvBitamp.getLayoutParams();
options.inSampleSize = getInSampleSize(layoutParams.width, layoutParams.height, options.outWidth, options.outHeight);
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fd, null, options);
//存入內容緩存,繪製到view。(下次先從內存緩存獲取,沒有就從磁盤緩存獲取,在沒有就請求網絡--"三級緩存")
mBitmapLruCache.put(key,bitmap);
runOnUiThread(new Runnable() {
@Override
public void run() {
mIvBitamp.setImageBitmap(mBitmapLruCache.get(key));
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
/** * 下載圖片到文件輸入流 */
private boolean downloadPictureToStream(String urlString, OutputStream outputStream) {
URL url = null;
HttpURLConnection urlConnection = null;
BufferedInputStream in = null;
BufferedOutputStream out = null;
try {
url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
in = new BufferedInputStream(urlConnection.getInputStream());
out = new BufferedOutputStream(outputStream);
int b;
while ((b=in.read()) != -1) {
//寫入文件輸入流
out.write(b);
}
return true;
} catch (IOException e) {
e.printStackTrace();
}finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
try {
if (in != null) {in.close();}
if (out != null) {out.close();}
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
/** * 圖片的url轉成key,使用MD5 */
private String hashKeyFormUrl(String url) {
try {
MessageDigest digest = MessageDigest.getInstance("MD5");
return byteToHexString(digest.digest(url.getBytes()));
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
private String byteToHexString(byte[] bytes) {
StringBuffer stringBuffer = new StringBuffer();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(0XFF & bytes[i]);
if (hex.length()==1) {
stringBuffer.append(0);
}
stringBuffer.append(hex);
}
return stringBuffer.toString();
}
複製代碼
前面說的 Bitmap的高效加載、LruCache、DiskLruCache,是一個圖片加載框架必備的功能點。下面就來封裝一個ImageLoader。首先羅列 實現的要點:
說明,
詳細以下
public class ImageLoader {
private static final String TAG = "ImageLoader";
private static final long KEEP_ALIVE_TIME = 10L;
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_THREAD_SIZE = CPU_COUNT + 1;
private static final int THREAD_SIZE = CPU_COUNT * 2 + 1;
private static final int VIEW_TAG_URL = R.id.view_tag_url;
private static final Object object = new Object();
private ThreadPoolExecutor mExecutor;
private Handler mMainHandler;
private Context mApplicationContext;
private static volatile ImageLoader mImageLoader;
private LruCache<String, Bitmap> mLruCache;
private DiskLruCache mDiskLruCache;
/** * 磁盤緩存最大容量,50M */
private static final long DISK_LRU_CACHE_MAX_SIZE = 50 * 1024 * 1024;
/** * 當前進程的最大內存,取進程內存的1/8 */
private static final long MEMORY_CACHE_MAX_SIZE = Runtime.getRuntime().maxMemory() / 8;
public ImageLoader(Context context) {
if (context == null) {
throw new RuntimeException("context can not be null !");
}
mApplicationContext = context.getApplicationContext();
initLruCache();
initDiskLruCache();
initAsyncLoad();
}
public static ImageLoader with(Context context){
if (mImageLoader == null) {
synchronized (object) {
if (mImageLoader == null) {
mImageLoader = new ImageLoader(context);
}
}
}
return mImageLoader;
}
private void initAsyncLoad() {
mExecutor = new ThreadPoolExecutor(CORE_THREAD_SIZE, THREAD_SIZE,
KEEP_ALIVE_TIME, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(), new ThreadFactory() {
private final AtomicInteger count = new AtomicInteger(1);
@Override
public Thread newThread(Runnable runnable) {
return new Thread(runnable, "load bitmap thread "+ count.getAndIncrement());
}
});
mMainHandler = new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(@NonNull Message msg) {
LoadResult result = (LoadResult) msg.obj;
ImageView imageView = result.imageView;
Bitmap bitmap = result.bitmap;
String url = result.url;
if (imageView == null || bitmap == null) {
return;
}
//此判斷是 避免 ImageView在列表中複用致使圖片錯位的問題
if (url.equals(imageView.getTag(VIEW_TAG_URL))) {
imageView.setImageBitmap(bitmap);
}else {
Log.w(TAG, "handleMessage: set image bitmap,but url has changed,ignore!");
}
}
};
}
private void initLruCache() {
mLruCache = new LruCache<String, Bitmap>((int) MEMORY_CACHE_MAX_SIZE){
@Override
protected int sizeOf(String key, Bitmap value) {
//緩存對象bitmap的大小,單位要和MEMORY_CACHE_MAX_SIZE一致
return value.getByteCount();
}
@Override
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
//移除舊緩存時會調用,能夠在這裏進行像資源回收的工做。
}
};
}
private void initDiskLruCache() {
File externalCacheDir = mApplicationContext.getExternalCacheDir();
if (externalCacheDir != null) {
long usableSpace = externalCacheDir.getUsableSpace();
if (usableSpace < DISK_LRU_CACHE_MAX_SIZE){
//剩餘空間不夠了
Log.e(TAG, "initDiskLruCache: "+"UsableSpace="+usableSpace+" , not enough(target 50M),cannot creat diskLruCache!");
return;
}
}
//1、建立DiskLruCache
//第一個參數是要存放的目錄,這裏選擇外部緩存目錄(若app卸載此目錄也會刪除);
//第二個是版本通常設1;第三個是緩存節點的value數量通常也是1;
//第四個是最大緩存容量這裏取50M
try {
this.mDiskLruCache = DiskLruCache.open(mApplicationContext.getExternalCacheDir(), 1, 1, DISK_LRU_CACHE_MAX_SIZE);
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "initDiskLruCache: "+e.getMessage());
}
}
/** * 緩存bitmap到內存 * @param url url * @param bitmap bitmap */
private void addBitmapMemoryCache(String url, Bitmap bitmap) {
String key = UrlKeyTransformer.transform(url);
if (mLruCache.get(key) == null && bitmap != null) {
mLruCache.put(key,bitmap);
}
}
/** * 從內存緩存加載bitmap * @param url url * @return */
private Bitmap loadFromMemoryCache(String url) {
return mLruCache.get(UrlKeyTransformer.transform(url));
}
/** * 從磁盤緩存加載bitmap(並添加到內存緩存) * @param url url * @param requestWidth 要求的寬 * @param requestHeight 要求的高 * @return bitmap */
private Bitmap loadFromDiskCache(String url, int requestWidth, int requestHeight) throws IOException {
if (Looper.myLooper()==Looper.getMainLooper()) {
Log.w(TAG, "loadFromDiskCache from Main Thread may cause block !");
}
if (mDiskLruCache == null) {
return null;
}
DiskLruCache.Snapshot snapshot = null;
String key = UrlKeyTransformer.transform(url);
snapshot = mDiskLruCache.get(key);
if (snapshot != null) {
FileInputStream inputStream = (FileInputStream)snapshot.getInputStream(0);
//Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
//通常須要採樣加載,但文件輸入流是有序的文件流,採樣時兩次decodeStream影響文件流的位置屬性,
//致使第二次decode是獲取是null,爲解決此問題,可用文件描述符。
FileDescriptor fd = inputStream.getFD();
Bitmap bitmap = BitmapSampleDecodeUtil.decodeFileDescriptor(fd, requestWidth, requestHeight);
addBitmapMemoryCache(url,bitmap);
return bitmap;
}
return null;
}
/** * 從網路加載圖片 到磁盤緩存(而後再從磁盤中採樣加載) * @param urlString urlString * @param requestWidth 要求的寬 * @param requestHeight 要求的高 * @return Bitmap */
private Bitmap loadFromHttp(String urlString, int requestWidth, int requestHeight) throws IOException {
//線程檢查,不能是主線程
if (Looper.myLooper()==Looper.getMainLooper()) {
throw new RuntimeException("Do not loadFromHttp from Main Thread!");
}
if (mDiskLruCache == null) {
return null;
}
DiskLruCache.Editor editor = null;
editor = mDiskLruCache.edit(UrlKeyTransformer.transform(urlString));
if (editor != null) {
OutputStream outputStream = editor.newOutputStream(0);
if (downloadBitmapToStreamFromHttp(urlString, outputStream)) {
editor.commit();
}else {
editor.abort();
}
mDiskLruCache.flush();
}
return loadFromDiskCache(urlString, requestWidth, requestHeight);
}
/** * 從網絡下載圖片到文件輸入流 * @param urlString * @param outputStream * @return */
private boolean downloadBitmapToStreamFromHttp(String urlString, OutputStream outputStream) {
URL url = null;
HttpURLConnection urlConnection = null;
BufferedInputStream in = null;
BufferedOutputStream out = null;
try {
url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
in = new BufferedInputStream(urlConnection.getInputStream());
out = new BufferedOutputStream(outputStream);
int b;
while ((b=in.read()) != -1) {
//寫入文件輸入流
out.write(b);
}
return true;
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "downloadBitmapToStreamFromHttp,failed : "+e.getMessage());
}finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
IoUtil.close(in);
IoUtil.close(out);
}
return false;
}
/** * 從網絡直接下載bitmap(無緩存、無採樣) * @param urlString * @return */
private Bitmap downloadBitmapFromUrlDirectly(String urlString) {
URL url;
HttpURLConnection urlConnection = null;
BufferedInputStream bufferedInputStream = null;
try {
url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
bufferedInputStream = new BufferedInputStream(urlConnection.getInputStream());
return BitmapFactory.decodeStream(bufferedInputStream);
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "downloadBitmapFromUrlDirectly,failed : "+e.getMessage());
}finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
IoUtil.close(bufferedInputStream);
}
return null;
}
public Bitmap loadBitmap(String url){
return loadBitmap(url,0,0);
}
/** * 同步 加載bitmap * * 不能在主線程執行。加載時 先從內存緩存獲取,有就返回bitmap,若沒有就從磁盤緩存獲取; * 磁盤緩存有就返回bitmap並緩存到內存緩存,沒有就請求網絡; * 網絡請求回來,就緩存到磁盤緩存,而後從磁盤緩存獲取返回。 * * @return Bitmap */
public Bitmap loadBitmap(String url, int requestWidth, int requestHeight){
Bitmap bitmap = loadFromMemoryCache(url);
if (bitmap != null) {
Log.d(TAG, "loadBitmap: loadFromMemoryCache, url:"+url);
return bitmap;
}
try {
bitmap = loadFromDiskCache(url, requestWidth, requestHeight);
} catch (IOException e) {
e.printStackTrace();
}
if (bitmap != null) {
Log.d(TAG, "loadBitmap: loadFromDiskCache, url:"+url);
return bitmap;
}
try {
bitmap = loadFromHttp(url, requestWidth, requestHeight);
} catch (IOException e) {
e.printStackTrace();
}
if (bitmap != null){
Log.d(TAG, "loadBitmap: loadFromHttp, url:"+url);
return bitmap;
}
if (mDiskLruCache == null) {
Log.d(TAG, "loadBitmap: diskLruCache is null,load bitmap from url directly!");
bitmap = downloadBitmapFromUrlDirectly(url);
}
return bitmap;
}
public void loadBitmapAsync(final String url, final ImageView imageView){
loadBitmapAsync(url,imageView,0,0);
}
/** * 異步 加載bitmap * 外部可在任意線程執行,由於內部實現是在子線程(線程池)加載, * 而且內部會經過Handler切到主線程,只須要傳入view,內部就可直接繪製Bitmap到view。 * @param url * @param imageView * @param requestWidth * @param requestHeight */
public void loadBitmapAsync(final String url, final ImageView imageView, final int requestWidth, final int requestHeight){
if (url == null || url.isEmpty() || imageView == null) {
return;
}
// 標記當前imageView要繪製圖片的url
imageView.setTag(VIEW_TAG_URL, url);
mExecutor.execute(new Runnable() {
@Override
public void run() {
Bitmap loadBitmap = loadBitmap(url, requestWidth, requestHeight);
Message message = Message.obtain();
message.obj = new LoadResult(loadBitmap, url, imageView);
mMainHandler.sendMessage(message);
}
});
}
}
複製代碼
/** * Bitmap採樣壓縮加載工具 * @author hufeiyang */
public class BitmapSampleDecodeUtil {
private static final String TAG = "BitmapSampleDecodeUtil";
/** * 對資源圖片的採樣 * @param resources resources * @param resourcesId 資源id * @param requestWidth view的寬 * @param requestHeight view的高 * @return 採樣後的bitmap */
public static Bitmap decodeSampleResources(Resources resources, int resourcesId, int requestWidth, int requestHeight){
if (resources == null || resourcesId<=0) {
return null;
}
//一、inJustDecodeBounds設爲true,並加載圖片
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(resources, resourcesId, options);
//二、獲取原始寬高信息
int outWidth = options.outWidth;
int outHeight = options.outHeight;
//三、原始寬高信息 和 view的大小 計算並設置採樣率
options.inSampleSize = getInSampleSize(requestWidth, requestHeight, outWidth, outHeight);
//四、inJustDecodeBounds設爲false,並加載圖片
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(resources, resourcesId, options);
}
/** * 對文件描述符的採樣加載 * @param fileDescriptor fileDescriptor * @param requestWidth view的寬 * @param requestHeight view的高 * 注意,文件輸入流是有序的文件流,採樣時兩次decodeStream影響文件流的文職屬性,致使第二次decode是獲取是null。 * 爲解決此問題,可用本方法對文件流的文件描述符 加載。 */
public static Bitmap decodeFileDescriptor(FileDescriptor fileDescriptor, int requestWidth, int requestHeight){
if (fileDescriptor == null) {
return null;
}
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds=true;
BitmapFactory.decodeFileDescriptor(fileDescriptor,null,options);
options.inSampleSize = getInSampleSize(requestWidth, requestHeight, options.outWidth, options.outHeight);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
}
/** * 計算採樣率 * @param width view的寬 * @param height view的高 * @param outWidth 圖片原始的寬 * @param outHeight 圖片原始的高 * @return */
private static int getInSampleSize(int width, int height, int outWidth, int outHeight) {
int inSampleSize = 1;
if (width==0 || height ==0){
return inSampleSize;
}
if (outWidth>width || outHeight>height){
int halfWidth = outWidth / 2;
int halfHeight = outHeight / 2;
//保證採樣後的寬高都不小於目標快高,不然會拉伸而模糊
while (halfWidth/inSampleSize >=width
&& halfHeight/inSampleSize>=height){
inSampleSize *=2;
}
}
Log.d(TAG, "getInSampleSize: inSampleSize="+inSampleSize);
return inSampleSize;
}
}
複製代碼
/** * 圖片的url轉成key * @author hufeiyang */
public class UrlKeyTransformer {
/** * 圖片的url轉成key * @param url * @return MD5轉換後的key */
public static String transform(String url) {
if (url == null || url.isEmpty()) {
return null;
}
try {
MessageDigest digest = MessageDigest.getInstance("MD5");
return byteToHexString(digest.digest(url.getBytes()));
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
private static String byteToHexString(byte[] bytes) {
StringBuffer stringBuffer = new StringBuffer();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(0XFF & bytes[i]);
if (hex.length()==1) {
stringBuffer.append(0);
}
stringBuffer.append(hex);
}
return stringBuffer.toString();
}
}
複製代碼
public class LoadResult {
public Bitmap bitmap;
/** * bitmap對應的url */
public String url;
public ImageView imageView;
public LoadResult(Bitmap bitmap, String url, ImageView imageView) {
this.bitmap = bitmap;
this.url = url;
this.imageView = imageView;
}
}
複製代碼
具體代碼gitHub地址:SimpleImageLoader
最後,歡迎留言討論,若是你喜歡這篇文章,請幫忙 點贊、收藏和轉發,感謝!
歡迎關注個人 公 衆 號:
![]()