編輯:關於Android編程
1.簡介
大家都知道,在我們Android 開發的過程中,對於圖片的處理,是非常重要的,而對於我們如果每次都重網絡去拉去圖片,那樣會造成,現在android應用中不可避免的要使用圖片,有些圖片是可以變化的,需要每次啟動時從網絡拉取,這種場景在有廣告位的應用以及純圖片應用(比如淘寶,qq的照片牆)中比較多。
現在有一個問題:假如每次啟動的時候都從網絡拉取圖片的話,勢必會消耗很多流量。在當前的狀況下,對於非wifi用戶來說,流量還是很貴的,一個很耗流量的應用,其用戶數量級肯定要受到影響。當然,我想,向百度美拍這樣的應用,必然也有其內部的圖片緩存策略。總之,圖片緩存是很重要而且是必須的。
2.圖片緩存的原理
實現圖片緩存也不難,需要有相應的cache策略。這裡我采用 內存-文件-網絡 三層cache機制,其中內存緩存包括強引用緩存和軟引用緩存(SoftReference),其實網絡不算cache,這裡姑且也把它劃到緩存的層次結構中。當根據url向網絡拉取圖片的時候,先從內存中找,如果內存中沒有,再從緩存文件中查找,如果緩存文件中也沒有,再從網絡上通過http請求拉取圖片。在鍵值對(key-value)中,這個圖片緩存的key是圖片url的hash值,value就是bitmap。所以,按照這個邏輯,只要一個url被下載過,其圖片就被緩存起來了。
關於Java中對象的軟引用(SoftReference),如果一個對象具有軟引用,內存空間足夠,垃圾回收器就不會回收它;如果內存空間不足了,就會回收這些對象的內存。只要垃圾回收器沒有回收它,該對象就可以被程序使用。軟引用可用來實現內存敏感的高 速緩存。使用軟引用能防止內存洩露,增強程序的健壯性。
3.實例源碼
(1)內存緩存
package com.zengtao.tools; import java.lang.ref.SoftReference; import java.util.LinkedHashMap; import android.annotation.SuppressLint; import android.app.ActivityManager; import android.content.Context; import android.graphics.Bitmap; import android.util.LruCache; /** * 內存緩存:兩層緩存 * * @author zengtao 2015年4月27日 上午10:39:23 */ public class MemoryCache { private final static int SOFT_CACHE_SIZE = 15; // 軟引用緩存容量 private static LruCachemLruCache; // 硬引用緩存 private static LinkedHashMap > mSoftCache; // 軟引用緩存 @SuppressLint("NewApi") public MemoryCache(Context context) { int memClass = ((ActivityManager) context .getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass(); int cacheSize = 1024 * 1024 * memClass / 4; // 獲取系統的1/4的空間 作為緩存大小 mLruCache = new LruCache (cacheSize) { @Override protected int sizeOf(String key, Bitmap value) { if (value != null) { return value.getRowBytes() * value.getHeight(); } return 0; } @Override protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) { if (oldValue != null) { // 硬引用緩存滿的時候,會根據lru算法把最近沒有被使用的圖片抓入軟引用 mSoftCache.put(key, new SoftReference (oldValue)); } } }; mSoftCache = new LinkedHashMap >( SOFT_CACHE_SIZE, 0.75f, true) { private static final long serialVersionUID = 1L; @Override protected boolean removeEldestEntry( java.util.Map.Entry > eldest) { if (size() > SOFT_CACHE_SIZE) { return true; } return false; } }; } /** * 存儲圖片到緩存 * * @param url * :key * @param bitmap * : 圖片 */ @SuppressLint("NewApi") public void saveBitmap(String url, Bitmap bitmap) { if (bitmap != null) { synchronized (bitmap) { mLruCache.put(url, bitmap); } } } /** * 獲取緩存圖片 * * @param url * :url * @return */ @SuppressLint("NewApi") public Bitmap getBitmap(String url) { Bitmap bitmap = null; // 從硬引用找 synchronized (mLruCache) { // 從硬引用中獲取 bitmap = mLruCache.get(url); if (bitmap != null) { // 如果找到了,將元素移動到linkendHashMap的最前面,從而保證lrd算法中的是最後刪除 mLruCache.remove(url); mLruCache.put(url, bitmap); return bitmap; } } // 硬引用沒找到,從軟引用找 synchronized (mSoftCache) { SoftReference softReference = mSoftCache.get(url); if (softReference != null) { bitmap = softReference.get(); // 如果找到了,重新添加到硬緩存中 mLruCache.put(url, bitmap); mSoftCache.remove(url); return bitmap; } else { mSoftCache.remove(url); } } return null; } public void clearCache() { mSoftCache.clear(); } }
package com.zengtao.tools; import java.io.File; import java.io.FileOutputStream; import java.io.OutputStream; import java.util.Arrays; import java.util.Comparator; import android.annotation.SuppressLint; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Environment; import android.os.StatFs; /** * 文件緩存 * * @author zengtao 2015年4月27日 上午11:49:52 */ public class FileCache { private final static String IMAGECACHE = "ImageCache"; private final static String lASTPATHNAME = ".cache"; // 文件名 private final static int MB = 1024 * 1024; private final static int CACHESIZE = 10; private final static int SDCARD_FREE_SPANCE_CACHE = 10; public FileCache() { removeCache(getDirectory()); } /** * 將圖片存入緩存 * * @param url * : 地址 * @param bitmap * : 圖片 */ public void saveBitmap(String url, Bitmap bitmap) { if (bitmap == null) { return; } if (SDCARD_FREE_SPANCE_CACHE > caluateSDCardFreeSpance()) { return; // 空間不足 } String fileName = convertUrlToFileName(url); String dirPath = getDirectory(); File dirFile = new File(dirPath); if (dirFile.exists()) { dirFile.mkdirs(); } File file = new File(dirPath + "/" + fileName); try { file.createNewFile(); OutputStream outputStream = new FileOutputStream(file); bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream); outputStream.flush(); outputStream.close(); } catch (Exception e) { System.out.println("文件未找到或者io異常"); } } /** * 獲取文件緩存圖片 * * @param url * : 地址 * @return : bitmap */ public Bitmap getBitmap(final String url) { Bitmap bitmap = null; final String path = getDirectory() + convertUrlToFileName(url); File file = new File(path); if (file.exists()) { bitmap = BitmapFactory.decodeFile(path); if (bitmap == null) { file.delete(); } else { updateFileTime(path); } } return bitmap; } /** * 獲取sdCard路徑 * * @return :路徑地址 */ private String getSDCardPath() { String path = ""; File file = null; boolean isSDCardExist = Environment.getExternalStorageState() .toString().equals(android.os.Environment.MEDIA_MOUNTED); // 判斷是否有sdCard if (isSDCardExist) { file = Environment.getExternalStorageDirectory(); } if (file != null) { path = file.toString(); } return path; } /** * 計算存儲目錄下的文件大小, * 當文件總大小大於規定的CACHE_SIZE或者sdcard剩余空間小於FREE_SD_SPACE_NEEDED_TO_CACHE的規定 * 那麼刪除40%最近沒有被使用的文件 */ private boolean removeCache(String dirPath) { File dir = new File(dirPath); File[] files = dir.listFiles(); if (files == null) { return true; } if (!android.os.Environment.getExternalStorageState().equals( android.os.Environment.MEDIA_MOUNTED)) { return false; } int dirSize = 0; for (int i = 0; i < files.length; i++) { if (files[i].getName().contains(lASTPATHNAME)) { dirSize += files[i].length(); } } if (dirSize > CACHESIZE * MB || SDCARD_FREE_SPANCE_CACHE > caluateSDCardFreeSpance()) { int removeFactor = (int) ((0.4 * files.length) + 1); Arrays.sort(files, new FileLastModifSort()); for (int i = 0; i < removeFactor; i++) { if (files[i].getName().contains(lASTPATHNAME)) { files[i].delete(); } } } if (caluateSDCardFreeSpance() <= CACHESIZE) { return false; } return true; } /** * 獲取緩存目錄 * * @return : 目錄 */ private String getDirectory() { return getSDCardPath() + "/" + IMAGECACHE; } /** * 將url轉換成文件名 * * @param url * : 地址 * @return : 文件名 */ private String convertUrlToFileName(final String url) { String[] strs = url.split("/"); return strs[strs.length - 1] + lASTPATHNAME; } /** * 計算sdCard上的空閒空間 * * @return : 大小 */ @SuppressLint("NewApi") private int caluateSDCardFreeSpance() { int freespance = 0; StatFs start = new StatFs(Environment.getExternalStorageDirectory() .getPath()); long blocksize = start.getBlockSizeLong(); long availableBlocks = start.getAvailableBlocksLong(); freespance = Integer.parseInt(blocksize * availableBlocks + ""); return freespance; } /** 修改文件的最後修改時間 **/ public void updateFileTime(String path) { File file = new File(path); long lastTime = System.currentTimeMillis(); file.setLastModified(lastTime); } /** 根據文件的最後修改時間進行排序 **/ private class FileLastModifSort implements Comparator{ public int compare(File arg0, File arg1) { if (arg0.lastModified() > arg1.lastModified()) { return 1; } else if (arg0.lastModified() == arg1.lastModified()) { return 0; } else { return -1; } } } }
package com.zengtao.tools; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.util.Log; /** * 網絡緩存 * * @author zengtao 2015年4月27日 下午3:32:34 */ public class HttpCache { private static final String LOG_TAG = "ImageGetFromHttp"; public static Bitmap downloadBitmap(String url) { final HttpClient client = new DefaultHttpClient(); final HttpGet getRequest = new HttpGet(url); try { HttpResponse response = client.execute(getRequest); final int statusCode = response.getStatusLine().getStatusCode(); if (statusCode != HttpStatus.SC_OK) { Log.w(LOG_TAG, "Error " + statusCode + " while retrieving bitmap from " + url); return null; } final HttpEntity entity = response.getEntity(); if (entity != null) { InputStream inputStream = null; try { inputStream = entity.getContent(); FilterInputStream fit = new FlushedInputStream(inputStream); return BitmapFactory.decodeStream(fit); } finally { if (inputStream != null) { inputStream.close(); inputStream = null; } entity.consumeContent(); } } } catch (IOException e) { getRequest.abort(); Log.w(LOG_TAG, "I/O error while retrieving bitmap from " + url, e); } catch (IllegalStateException e) { getRequest.abort(); Log.w(LOG_TAG, "Incorrect URL: " + url); } catch (Exception e) { getRequest.abort(); Log.w(LOG_TAG, "Error while retrieving bitmap from " + url, e); } finally { client.getConnectionManager().shutdown(); } return null; } /** * InputStream流有個小bug在慢速網絡的情況下可能產生中斷,可以考慮重寫FilterInputStream處理skip方法來解決這個bug * BitmapFactory類的decodeStream方法在網絡超時或較慢的時候無法獲取完整的數據,這裡我 * 們通過繼承FilterInputStream類的skip方法來強制實現flush流中的數據 * ,主要原理就是檢查是否到文件末端,告訴http類是否繼續。 * * @author zengtao 2015年4月27日 下午6:33:17 */ static class FlushedInputStream extends FilterInputStream { public FlushedInputStream(InputStream inputStream) { super(inputStream); } @Override public long skip(long n) throws IOException { long totalBytesSkipped = 0L; while (totalBytesSkipped < n) { long bytesSkipped = in.skip(n - totalBytesSkipped); if (bytesSkipped == 0L) { int b = read(); if (b < 0) { break; // we reached EOF } else { bytesSkipped = 1; // we read one byte } } totalBytesSkipped += bytesSkipped; } return totalBytesSkipped; } } }
@SuppressLint("HandlerLeak") private Handler handler = new Handler() { public void handleMessage(Message msg) { if (msg.arg1 == 0x1) { if (msg.obj != null) { image.setImageBitmap((Bitmap) msg.obj); } } }; }; class MyThread extends Thread { @Override public void run() { bitmap = getBitmap("http://a.hiphotos.baidu.com/image/pic/item/77c6a7efce1b9d16f5022b7ef1deb48f8d5464e3.jpg"); Message message = new Message(); message.arg1 = 0x1; message.obj = bitmap; handler.sendMessage(message); } } /*** 獲得一張圖片,從三個地方獲取,首先是內存緩存,然後是文件緩存,最後從網絡獲取 ***/ public Bitmap getBitmap(String url) { // 1.從內存緩存中獲取圖片 Bitmap resultBitmap = memoryCache.getBitmap(url); if (resultBitmap == null) { // 2.文件緩存中獲取 resultBitmap = fileCache.getBitmap(url); if (resultBitmap == null) { // 3.從網絡獲取 resultBitmap = HttpCache.downloadBitmap(url); if (resultBitmap != null) { fileCache.saveBitmap(url, resultBitmap); memoryCache.saveBitmap(url, resultBitmap); System.out.println("3.網絡緩存中獲取圖片"); } } else { // 添加到內存緩存 memoryCache.saveBitmap(url, resultBitmap); System.out.println("2.文件緩存中獲取圖片"); } } else { System.out.println("1.內存緩存中獲取圖片"); } return resultBitmap; }
以上就完成了一套緩存的設計,值得注意的是,當去網絡獲取圖片的時候,圖片過於龐大,一定要做去異步線程中獲取圖片,或者做本地緩存,這樣不會讓用戶感覺自己的app卡死,是的用戶體驗效果更加。
最近由於項目需要,研究了一些統計圖的做法,開始時,看了很多博文,大部分都是引用第三方的庫,雖然簡單,易上手,但是功能太死板,有很多要求都是不能滿足的,所以經過研究,自己使
訊飛輸入法怎麼設置手寫靈敏度?在輸入法中使用手寫的時候,有的時候感覺靈敏度不是很好。如果你想讓手機系統快速反應你手寫的輸入文字,那就來設置下手寫靈敏度吧,訊
之前對線程也寫過幾篇文章,不過倒是沒有針對android,因為java與android在線程方面大部分還是相同,不過本篇我們要介紹的是android的專屬類Handler
昨天看了下RenderScript的官方文檔,發現RenderScript這厮有點牛逼。無意中發現ScriptIntrinsic這個抽象類,有些很有用的子類。其中有個子類