編輯:Android開發實例
這篇是基於上一篇 實戰Android仿人人客戶端之對從服務器端(網絡)獲取的圖片進行本地進行雙緩存處理的業務流程圖 來進行講解,沒看過的可以先閱讀下上一篇博文,其實我個人覺得圖片雙緩存處理這塊,一張流程圖已足以說明一切。至於編碼實現,不同的人有不同的實現方式,下面我就和大家聊一聊我的實現方式:
一、圖片雙緩存處理,類圖如下:
二、網絡圖片本地雙緩存的編碼實現(以獲取用戶圖像為例):
1、發出需要顯示用戶圖像的請求
- String headUrl = user.getHeadurl();
- LogUtil.i(TAG, "headUrl = " + user.getHeadurl());
- // 用戶圖像的大小48x48,單位為dip,轉換為px
- int widthPx = DensityUtil.dip2px(mContext, 48);
- // 要一張圓角高質量的圖片
- ImageInfo imgInfo = new ImageInfo(mLeftPanelLayout.ivUserIcon, headUrl, widthPx, widthPx, true, false);
- mImageLoader.displayImage(imgInfo);
注:mLeftPanelLayout.ivUserIcon為ImageView;ImageInfo對象封裝了圖片請求參數。
2、根據URL從內存緩存中獲取Bitmap對象,找到了Bitmap對象,用ImageView對象顯示圖像,到這裡終止。
- Bitmap bitmap = memoryCache.get(url);
- if (bitmap != null) {
- imageView.setImageBitmap(bitmap);
- }
注:memoryCache是MemoryCache(內存緩存類)的對象引用。
3 、沒有從緩存中找到了Bitmap對象,則根據URL從文件緩存中獲取File對象,將File對象解碼(解析)成Bitmap對象,用ImageView對象顯示用戶圖像,到這裡終止。
- final File file = fileCache.getFile(url);
- if(file.exists()){
- String pathName = file.getAbsolutePath();
- System.out.println("pathName = " + pathName);
- System.out.println("file.length() = " + file.length());
- bitmap = BitmapFactory.decodeFile(pathName);
- imageView.setImageBitmap(bitmap);
- }
注:fileCache為文件緩存類的引用
4、沒有從文件緩存中找到File對象,則開啟網絡請求業務線程。
- // 開啟線程加載圖片
- try {
- AsyncBaseRequest asyncRequest = new AsyncHttpGet(url, null, null, new ResultCallback() {
- @Override
- public void onSuccess(Object obj) {
- }
- @Override
- public void onFail(int errorCode) {
- System.out.println("Loading image error. errorCode = " + errorCode);
- }
- });
- mDefaultThreadPool.execute(asyncRequest);
- mAsyncRequests.add(asyncRequest);
- } catch (IOException e) {
- e.printStackTrace();
- }
5、網絡請求返回的圖片數據流可能會很大,直接解碼生成Bitmap對象,可能會造成OOM。因此,要根據指定的壓縮比例,獲得合適的Bitmap
- Bitmap bitmap = BitmapUtil.decodeStream((InputStream) obj, imgInfo.getWidth(), imgInfo.getHeight());
6、上一步處理過後,可能解碼生成的Bitmap對象還會很大,可能還會造成OOM,因此,對Bitmap對象再次進行質量壓縮。
- if (imgInfo.isCompress()) {
- // 對Bitmap進行質量壓縮
- bitmap = BitmapUtil.compressBitmap(bitmap);
- }
7、進行本地文件緩存
- try {
- fileCache.writeToFile(inStream, file);
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
8、進行本地內存緩存
- // 將數據流將其轉換成Bitmap
- bitmap = BitmapFactory.decodeStream(inStream);
- // 存入內存緩存中
- memoryCache.put(url, bitmap);
9、用ImageView對象顯示用戶圖像,到這裡終止。
- // 用ImageView對象顯示圖片
- final Bitmap btm = bitmap;
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- imageView.setImageBitmap(btm);
- }
- });
加載圖片的完整方法,代碼如下:
- /**
- * 加載圖片
- * @param imgInfo 圖片信息
- */
- public void displayImage(final ImageInfo imgInfo) {
- final ImageView imageView = imgInfo.getImageView();
- final String url = imgInfo.getUrl();
- imageViews.put(imageView, url);
- // 從內存緩存中查找
- Bitmap bitmap = memoryCache.get(url);
- if (bitmap != null) {
- imageView.setImageBitmap(bitmap);
- } else {
- // 從文件緩存中查找
- final File file = fileCache.getFile(url);
- if (file.exists()) {
- String pathName = file.getAbsolutePath();
- System.out.println("pathName = " + pathName);
- System.out.println("file.length() = " + file.length());
- bitmap = BitmapFactory.decodeFile(pathName);
- imageView.setImageBitmap(bitmap);
- } else {
- // 開啟線程加載圖片
- try {
- AsyncBaseRequest asyncRequest = new AsyncHttpGet(url, null, null, new ResultCallback() {
- @Override
- public void onSuccess(Object obj) {
- if (obj == null || !(obj instanceof InputStream)) {
- System.out.println("Loading image return Object is null or not is InputStream.");
- return;
- }
- try {
- // 根據指定的壓縮比例,獲得合適的Bitmap
- Bitmap bitmap = BitmapUtil.decodeStream((InputStream) obj, imgInfo.getWidth(), imgInfo.getHeight());
- if (imgInfo.isRounded()) {
- // 將圖片變成圓角
- // bitmap = BitmapUtil.drawRoundCorner(bitmap, 8);
- bitmap = BitmapUtil.drawRoundBitmap(bitmap, 8);
- }
- if (imgInfo.isCompress()) {
- // 對Bitmap進行質量壓縮
- bitmap = BitmapUtil.compressBitmap(bitmap);
- }
- // 將Bitmap轉換成ByteArrayInputStream
- ByteArrayOutputStream outStream = new ByteArrayOutputStream();
- bitmap.compress(Bitmap.CompressFormat.PNG, 100, outStream);
- ByteArrayInputStream inStream = new ByteArrayInputStream(outStream.toByteArray());
- // 將進行質量壓縮後的數據寫入文件(文件緩存)
- fileCache.writeToFile(inStream, file);
- // 存入內存緩存中
- memoryCache.put(url, bitmap);
- // 防止圖片錯位
- String tag = imageViews.get(imageView);
- if (tag == null || !tag.equals(url)) {
- System.out.println("tag is null or url and ImageView disaccord.");
- return;
- }
- // 用ImageView對象顯示圖片
- final Bitmap btm = bitmap;
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- imageView.setImageBitmap(btm);
- }
- });
- } catch (IOException e) {
- // 這裡不做處理,因為默認顯示的圖片在xml組件配置裡已設置
- e.printStackTrace();
- }
- }
- @Override
- public void onFail(int errorCode) {
- System.out.println("Loading image error. errorCode = " + errorCode);
- }
- });
- mDefaultThreadPool.execute(asyncRequest);
- mAsyncRequests.add(asyncRequest);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
三、在上述業務處理過程中,遇到的問題及解決思路(記錄處理過程)
1、根據指定的壓縮比例,獲得合適的Bitmap,閱讀如下代碼:
- /**
- * 根據指定的壓縮比例,獲得合適的Bitmap
- * @param inStream InputStream
- * @param width 指定的寬度
- * @param height 指定的高度
- */
- public static Bitmap decodeStream(InputStream inStream, int width, int height) {
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeStream(inStream, null, options);
- int w = options.outWidth;
- int h = options.outHeight;
- // 從服務器端獲取的圖片大小為:80x120
- // 我們想要的圖片大小為:40x40
- // 縮放比:120/40 = 3,也就是說我們要的圖片大小為原圖的1/3
- // 縮放比。由於是固定比例縮放,只用高或者寬其中一個數據進行計算即可
- int ratio = 1; // 默認為不縮放
- if (w >= h && w > width) {
- ratio = (int) (w / width);
- } else if (w < h && h > height) {
- ratio = (int) (h / height);
- }
- if (ratio <= 0) {
- ratio = 1;
- }
- System.out.println("圖片的縮放比例值ratio = " + ratio);
- options.inJustDecodeBounds = false;
- // 屬性值inSampleSize表示縮略圖大小為原始圖片大小的幾分之一,即如果這個值為2,
- // 則取出的縮略圖的寬和高都是原始圖片的1/2,圖片大小就為原始大小的1/4。
- options.inSampleSize = ratio;
- return BitmapFactory.decodeStream(inStream, null, options);
- }
注:inStream為從網絡獲取後,直接傳進來的。
運行上面的後,返回的Bitmap對象為null。究其原因,在設置 options.inJustDecodeBounds = true後,我們調用了BitmapFactory.decodeStream(inStream, null, options)方法獲取圖片的大小,但是該方法在執行完後,應該在內部把傳進去的InputStream關閉掉了。第二次的時候就讀不到數據了。解決思路,將從網絡獲取到的數據流先保存起來。解決方法一:
- /**
- * 根據指定的壓縮比例,獲得合適的Bitmap(方法一)
- * @param file File
- * @param width 指定的寬度
- * @param height 指定的高度
- * @return Bitmap
- */
- public static Bitmap decodeStream(File file, int width, int height) {
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeFile(file.getAbsolutePath(), options);
- int w = options.outWidth;
- int h = options.outHeight;
- // 從服務器端獲取的圖片大小為:80x120
- // 我們想要的圖片大小為:40x40
- // 縮放比:120/40 = 3,也就是說我們要的圖片大小為原圖的1/3
- // 縮放比。由於是固定比例縮放,只用高或者寬其中一個數據進行計算即可
- int ratio = 1; // 默認為不縮放
- if (w >= h && w > width) {
- ratio = (int) (w / width);
- } else if (w < h && h > height) {
- ratio = (int) (h / height);
- }
- if (ratio <= 0) {
- ratio = 1;
- }
- System.out.println("圖片的縮放比例值ratio = " + ratio);
- options = new BitmapFactory.Options();
- options.inJustDecodeBounds = false;
- // 屬性值inSampleSize表示縮略圖大小為原始圖片大小的幾分之一,即如果這個值為2,
- // 則取出的縮略圖的寬和高都是原始圖片的1/2,圖片大小就為原始大小的1/4。
- options.inSampleSize = ratio;
- return BitmapFactory.decodeFile(file.getAbsolutePath(), options);
- }
解決方法二:
- /**
- * 根據指定的壓縮比例,獲得合適的Bitmap(方法二)
- * @param inStream InputStream
- * @param width 指定的寬度
- * @param height 指定的高度
- * @return Bitmap
- * @throws IOException
- */
- public static Bitmap decodeStream(InputStream inStream, int width, int height) throws IOException {
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- // 從輸入流讀取數據
- byte[] data = StreamTool.read(inStream);
- BitmapFactory.decodeByteArray(data, 0, data.length, options);
- int w = options.outWidth;
- int h = options.outHeight;
- // 從服務器端獲取的圖片大小為:80x120
- // 我們想要的圖片大小為:40x40
- // 縮放比:120/40 = 3,也就是說我們要的圖片大小為原圖的1/3
- // 縮放比。由於是固定比例縮放,只用高或者寬其中一個數據進行計算即可
- int ratio = 1; // 默認為不縮放
- if (w >= h && w > width) {
- ratio = (int) (w / width);
- } else if (w < h && h > height) {
- ratio = (int) (h / height);
- }
- if (ratio <= 0) {
- ratio = 1;
- }
- System.out.println("圖片的縮放比例值ratio = " + ratio);
- options.inJustDecodeBounds = false;
- // 屬性值inSampleSize表示縮略圖大小為原始圖片大小的幾分之一,即如果這個值為2,
- // 則取出的縮略圖的寬和高都是原始圖片的1/2,圖片大小就為原始大小的1/4。
- options.inSampleSize = ratio;
- return BitmapFactory.decodeByteArray(data, 0, data.length);
- }
解決方法三:從網絡返回的數據流中只讀取圖片的信息(寬度和高度),計算壓縮比例,之後再次從網絡讀取數據按第一次計算出的壓縮比例,獲得合適的Bitmap。(這個是下下策,要訪問兩次網絡)
2、對Bitmap進行質量壓縮,閱讀如下代碼:
- /**
- * 對Bitmap進行質量壓縮
- * @param bitmap Bitmap
- * @return ByteArrayInputStream
- */
- public static Bitmap compressBitmap(Bitmap bitmap) {
- ByteArrayOutputStream outStream = new ByteArrayOutputStream();
- // 圖片質量默認值為100,表示不壓縮
- int quality = 100;
- // PNG是無損的,將會忽略質量設置。因此,這裡設置為JPEG
- bitmap.compress(Bitmap.CompressFormat.JPEG, quality, outStream);
- // 判斷壓縮後圖片的大小是否大於100KB,大於則繼續壓縮
- while (outStream.toByteArray().length / 1024 > 100) {
- outStream.reset();
- // 壓縮quality%,把壓縮後的數據存放到baos中
- bitmap.compress(Bitmap.CompressFormat.JPEG, quality, outStream);
- quality -= 10;
- }
- System.out.println("quality = " + quality);
- byte[] data = outStream.toByteArray();
- return BitmapFactory.decodeByteArray(data, 0, data.length);
- }
注意: bitmap.compress(Bitmap.CompressFormat.PNG, quality, outStream);如果這麼寫,是沒有壓縮效果的。因為PNG是無損的,將會忽略質量設置。
四、上述講解中涉及到的類,完整的源文件如下:
加載(裝載)圖片類
- package com.everyone.android.bitmap;
- import java.io.ByteArrayInputStream;
- import java.io.ByteArrayOutputStream;
- import java.io.File;
- import java.io.IOException;
- import java.io.InputStream;
- import java.text.SimpleDateFormat;
- import java.util.Collections;
- import java.util.LinkedHashMap;
- import java.util.List;
- import java.util.Map;
- import android.graphics.Bitmap;
- import android.graphics.BitmapFactory;
- import android.os.Handler;
- import android.widget.ImageView;
- import com.everyone.android.AppBaseActivity;
- import com.everyone.android.callback.ResultCallback;
- import com.everyone.android.entity.ImageInfo;
- import com.everyone.android.net.AsyncBaseRequest;
- import com.everyone.android.net.AsyncHttpGet;
- import com.everyone.android.net.DefaultThreadPool;
- import com.everyone.android.utils.BitmapUtil;
- /**
- * 功能描述:加載(裝載)圖片
- *
- * 在以前,一個非常流行的內存緩存的實現是使用SoftReference or WeakReference ,但是這種辦法現在並不推薦。
- * 從Android 2.3開始,垃圾回收器會更加積極的去回收軟引用和弱引用引用的對象,這樣導致這種做法相當的無效。
- * 另外,在Android 3.0之前,圖片數據保存在本地內存中,它們不是以一種可預見的方式來釋放的,
- * 這樣可能會導致應用內存的消耗量出現短暫的超限,應用程序崩潰 。
- *
- * @author android_ls
- */
- public class ImageLoader {
- /**
- * 內存緩存
- */
- private MemoryCache memoryCache;
- /**
- * 文件緩存
- */
- private FileCache fileCache;
- /**
- * 存放圖片的顯示視圖ImageView和圖片的URL
- */
- private Map<ImageView, String> imageViews = Collections.synchronizedMap(new LinkedHashMap<ImageView, String>());
- private List<AsyncBaseRequest> mAsyncRequests;
- private DefaultThreadPool mDefaultThreadPool;
- private Handler mHandler;
- public ImageLoader(AppBaseActivity activity) {
- this.memoryCache = new MemoryCache();
- this.fileCache = new FileCache(activity.getContext());
- this.mAsyncRequests = activity.getAsyncRequests();
- this.mDefaultThreadPool = activity.getDefaultThreadPool();
- this.mHandler = activity.getHandler();
- }
- /**
- * 加載圖片
- * @param imgInfo 圖片信息
- */
- public void displayImage(final ImageInfo imgInfo) {
- final ImageView imageView = imgInfo.getImageView();
- final String url = imgInfo.getUrl();
- imageViews.put(imageView, url);
- // 從內存緩存中查找
- Bitmap bitmap = memoryCache.get(url);
- if (bitmap != null) {
- imageView.setImageBitmap(bitmap);
- } else {
- // 從文件緩存中查找
- final File file = fileCache.getFile(url);
- if (file.exists()) {
- String pathName = file.getAbsolutePath();
- System.out.println("pathName = " + pathName);
- System.out.println("file.length() = " + file.length());
- SimpleDateFormat mDateFormat = new SimpleDateFormat ("yyyy年MM月dd日 HH:mm:ss");
- System.out.println("file.lastModified() = " + mDateFormat.format(file.lastModified()));
- bitmap = BitmapFactory.decodeFile(pathName);
- imageView.setImageBitmap(bitmap);
- } else {
- // 開啟線程加載圖片
- try {
- AsyncBaseRequest asyncRequest = new AsyncHttpGet(url, null, null, new ResultCallback() {
- @Override
- public void onSuccess(Object obj) {
- if (obj == null || !(obj instanceof InputStream)) {
- System.out.println("Loading image return Object is null or not is InputStream.");
- return;
- }
- try {
- // 根據指定的壓縮比例,獲得合適的Bitmap
- Bitmap bitmap = BitmapUtil.decodeStream((InputStream) obj, imgInfo.getWidth(), imgInfo.getHeight());
- if (imgInfo.isRounded()) {
- // 將圖片變成圓角
- // bitmap = BitmapUtil.drawRoundCorner(bitmap, 8);
- bitmap = BitmapUtil.drawRoundBitmap(bitmap, 8);
- }
- if (imgInfo.isCompress()) {
- // 對Bitmap進行質量壓縮
- bitmap = BitmapUtil.compressBitmap(bitmap);
- }
- // 將Bitmap轉換成ByteArrayInputStream
- ByteArrayOutputStream outStream = new ByteArrayOutputStream();
- bitmap.compress(Bitmap.CompressFormat.PNG, 100, outStream);
- ByteArrayInputStream inStream = new ByteArrayInputStream(outStream.toByteArray());
- // 將進行質量壓縮後的數據寫入文件(文件緩存)
- fileCache.writeToFile(inStream, file);
- // 存入內存緩存中
- memoryCache.put(url, bitmap);
- // 防止圖片錯位
- String tag = imageViews.get(imageView);
- if (tag == null || !tag.equals(url)) {
- System.out.println("tag is null or url and ImageView disaccord.");
- return;
- }
- // 用ImageView對象顯示圖片
- final Bitmap btm = bitmap;
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- imageView.setImageBitmap(btm);
- }
- });
- } catch (IOException e) {
- // 這裡不做處理,因為默認顯示的圖片在xml組件配置裡已設置
- e.printStackTrace();
- }
- }
- @Override
- public void onFail(int errorCode) {
- System.out.println("Loading image error. errorCode = " + errorCode);
- }
- });
- mDefaultThreadPool.execute(asyncRequest);
- mAsyncRequests.add(asyncRequest);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
- }
內存緩存類
- package com.everyone.android.bitmap;
- import java.util.Collections;
- import java.util.Iterator;
- import java.util.LinkedHashMap;
- import java.util.Map;
- import java.util.Map.Entry;
- import android.graphics.Bitmap;
- import android.util.Log;
- /**
- * 功能描述:內存緩存類
- *
- * @author android_ls
- */
- public class MemoryCache {
- /**
- * 打印LOG的TAG
- */
- private static final String TAG = "MemoryCache";
- /**
- * 放入緩存時是個同步操作
- * LinkedHashMap構造方法的最後一個參數true代表這個map裡的元素將按照最近使用次數由少到多排列,
- * 這樣的好處是如果要將緩存中的元素替換,則先遍歷出最近最少使用的元素來替換以提高效率
- */
- private Map<String, Bitmap> cacheMap = Collections.synchronizedMap(new LinkedHashMap<String, Bitmap>(10, 1.5f, true));
- // 緩存只能占用的最大堆內存
- private long maxMemory;
- public MemoryCache() {
- // 使用25%的可用的堆大小
- maxMemory = Runtime.getRuntime().maxMemory() / 4;
- Log.i(TAG, "MemoryCache will use up to " + (maxMemory / 1024 / 1024) + "MB");
- }
- /**
- * 根據key獲取相應的圖片
- * @param key
- * @return Bitmap
- */
- public Bitmap get(String key) {
- if (!cacheMap.containsKey(key)){
- return null;
- }
- return cacheMap.get(key);
- }
- /**
- * 添加圖片到緩存
- * @param key
- * @param bitmap
- */
- public synchronized void put(String key, Bitmap bitmap) {
- checkSize();
- cacheMap.put(key, bitmap);
- Log.i(TAG, "cache size=" + cacheMap.size() + " bitmap size = " + getBitmapSize(bitmap));
- }
- /**
- * 嚴格控制堆內存,如果超過將首先替換最近最少使用的那個圖片緩存
- */
- private void checkSize() {
- long count = 0;
- Iterator<Entry<String, Bitmap>> iterator = cacheMap.entrySet().iterator();
- while (iterator.hasNext()) {
- Entry<String, Bitmap> entry = iterator.next();
- count += getBitmapSize(entry.getValue());
- }
- Log.i(TAG, "cache size=" + count + " length=" + cacheMap.size());
- if (count > maxMemory) {
- while (iterator.hasNext()) {
- Entry<String, Bitmap> entry = iterator.next();
- count -= getBitmapSize(entry.getValue());
- iterator.remove();
- if (count <= maxMemory) {
- System.out.println("夠用了,不用在刪除了");
- break;
- }
- }
- Log.i(TAG, "Clean cache. New size " + cacheMap.size());
- }
- }
- /**
- * 獲取bitmap的字節大小
- * @param bitmap
- * @return
- */
- private long getBitmapSize(Bitmap bitmap) {
- if (bitmap == null) {
- return 0;
- }
- return bitmap.getRowBytes() * bitmap.getHeight();
- }
- /**
- * 清空緩存
- */
- public void clear() {
- cacheMap.clear();
- }
- }
網絡下載文件本地緩存類
- package com.everyone.android.bitmap;
- import java.io.File;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.InputStream;
- import java.text.SimpleDateFormat;
- import java.util.Arrays;
- import java.util.Comparator;
- import android.content.Context;
- import android.os.StatFs;
- /**
- * 功能描述:網絡下載文件本地緩存類
- *
- * @author android_ls
- */
- public class FileCache {
- /**
- * 本地與我們應用程序相關文件存放的根目錄
- */
- private static final String ROOT_DIR_PATH = "CopyEveryone";
- /**
- * 下載文件存放的目錄
- */
- private static final String IMAGE_DOWNLOAD_CACHE_PATH = ROOT_DIR_PATH + "/Download/cache";
- /**
- * 默認的磁盤緩存大小(20MB)
- */
- private static final int DEFAULT_DISK_CACHE_SIZE = 1024 * 1024 * 20;
- /**
- * 緩存文件存放目錄
- */
- private File cacheDir;
- /**
- * 緩存根目錄
- */
- private String cacheRootDir;
- private Context mContext;
- public FileCache(Context context) {
- mContext = context;
- if (android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED)) {
- cacheRootDir = android.os.Environment.getExternalStorageDirectory().getAbsolutePath();
- } else {
- cacheRootDir = mContext.getCacheDir().getAbsolutePath();
- }
- cacheDir = new File(cacheRootDir + File.separator + IMAGE_DOWNLOAD_CACHE_PATH);
- // 檢測文件緩存目錄是否存在,不存在則創建
- if (!cacheDir.exists()) {
- cacheDir.mkdirs();
- }
- }
- /**
- * 獲取下載的文件要存放的緩存目錄
- * /mnt/sdcard/CopyEveryone/Download/cache
- * @return 緩存目錄的全路徑
- */
- public String getCacheDirPath() {
- return cacheDir.getAbsolutePath();
- }
- /**
- * 根據URL從文件緩存中獲取文件
- * @param url url的hashCode為緩存的文件名
- */
- public File getFile(String url) {
- if (!cacheDir.exists()) {
- cacheDir.mkdirs();
- }
- String filename = String.valueOf(url.hashCode());
- File file = new File(cacheDir, filename);
- return file;
- }
- /**
- * 計算存儲可用的大小
- * @return
- */
- public long getAvailableMemorySize() {
- StatFs stat = new StatFs(cacheRootDir);
- long blockSize = stat.getBlockSize();
- long availableBlocks = stat.getAvailableBlocks();
- return availableBlocks * blockSize;
- }
- /**
- * 將指定的數據寫入文件
- * @param inputStream InputStream
- * @param outputStream OutputStream
- * @throws IOException
- */
- public synchronized void writeToFile(InputStream inputStream, File file) throws IOException {
- int fileSize = inputStream.available();
- System.out.println("fileSize = " + fileSize);
- long enabledMemory = getAvailableMemorySize();
- System.out.println("當前可用硬盤: " + (enabledMemory/1024/1024)); // 單位:MB
- // 當前可用存儲空間不足20M
- if(DEFAULT_DISK_CACHE_SIZE > enabledMemory){
- if (fileSize > enabledMemory) {
- // 檢測可用空間大小,若不夠用則刪除最早的文件
- File[] files = cacheDir.listFiles();
- Arrays.sort(files, new FileLastModifSort());
- int length = files.length;
- for (int i = 0; i < length; i++) {
- files[i].delete();
- length = files.length;
- enabledMemory = getAvailableMemorySize();
- System.out.println("當前可用內存: " + enabledMemory);
- if (fileSize <= enabledMemory) {
- System.out.println("夠用了,不用在刪除了");
- break;
- }
- }
- }
- } else {
- int count = 0;
- File[] files = cacheDir.listFiles();
- for (int i = 0; i < files.length; i++) {
- count += files[i].length();
- }
- System.out.println("file cache size = " + count);
- // 使用的空間大於上限
- enabledMemory = DEFAULT_DISK_CACHE_SIZE - count;
- if(fileSize > enabledMemory){
- Arrays.sort(files, new FileLastModifSort());
- int length = files.length;
- for (int i = 0; i < length; i++) {
- count -= files[i].length();
- files[i].delete();
- length = files.length;
- enabledMemory = DEFAULT_DISK_CACHE_SIZE - count;
- if (fileSize <= enabledMemory) {
- System.out.println("夠用了,不用在刪除了");
- break;
- }
- }
- }
- }
- if(enabledMemory == 0){
- return;
- }
- // 將數據寫入文件保存
- FileOutputStream outStream = new FileOutputStream(file);
- byte[] buffer = new byte[1024];
- int len = 0;
- while ((len = inputStream.read(buffer)) != -1) {
- outStream.write(buffer, 0, len);
- }
- outStream.flush();
- outStream.close();
- inputStream.close();
- // 設置最後修改的時間
- long newModifiedTime = System.currentTimeMillis();
- file.setLastModified(newModifiedTime);
- System.out.println("file.length() = " + file.length());
- SimpleDateFormat mDateFormat = new SimpleDateFormat ("yyyy年MM月dd日 HH:mm:ss");
- System.out.println("writeToFile file.lastModified() = " + mDateFormat.format(file.lastModified()));
- }
- /**
- * 根據文件的最後修改時間進行排序
- * @author android_ls
- *
- */
- class FileLastModifSort implements Comparator<File> {
- public int compare(File file1, File file2) {
- if (file1.lastModified() > file2.lastModified()) {
- return 1;
- } else if (file1.lastModified() == file2.lastModified()) {
- return 0;
- } else {
- return -1;
- }
- }
- }
- /**
- * 清空緩存的文件
- */
- public void clear() {
- if (!cacheDir.exists()) {
- return;
- }
- File[] files = cacheDir.listFiles();
- if (files != null) {
- for (File f : files) {
- f.delete();
- }
- }
- }
- }
圖片信息實體類
- package com.everyone.android.entity;
- import android.widget.ImageView;
- /**
- * 功能描述:圖片信息實體類
- *
- * @author android_ls
- */
- public class ImageInfo {
- private int id; // 唯一標識
- private ImageView imageView; // 用於顯示的組件
- private String url; // 網絡URL
- private int width; // 寬度
- private int height; // 高度
- private boolean rounded; // 是否要轉換成圓角
- private boolean compress; // 是否要進行質量壓縮
- public ImageInfo(ImageView imageView, String url) {
- this.imageView = imageView;
- this.url = url;
- }
- public ImageInfo() {
- }
- public ImageInfo(ImageView imageView, String url, int width, int height, boolean rounded, boolean compress) {
- this.imageView = imageView;
- this.url = url;
- this.width = width;
- this.height = height;
- this.rounded = rounded;
- this.compress = compress;
- }
- public ImageInfo(ImageView imageView, String url, boolean rounded) {
- this.imageView = imageView;
- this.url = url;
- this.rounded = rounded;
- }
- public ImageInfo(ImageView imageView, String url, int width, int height) {
- this.imageView = imageView;
- this.url = url;
- this.width = width;
- this.height = height;
- }
- public ImageInfo(ImageView imageView, String url, int width, int height, boolean rounded) {
- this.imageView = imageView;
- this.url = url;
- this.width = width;
- this.height = height;
- this.rounded = rounded;
- }
- public boolean isCompress() {
- return compress;
- }
- public void setCompress(boolean compress) {
- this.compress = compress;
- }
- public int getId() {
- return id;
- }
- public void setId(int id) {
- this.id = id;
- }
- public ImageView getImageView() {
- return imageView;
- }
- public void setImageView(ImageView imageView) {
- this.imageView = imageView;
- }
- public String getUrl() {
- return url;
- }
- public void setUrl(String url) {
- this.url = url;
- }
- public int getWidth() {
- return width;
- }
- public void setWidth(int width) {
- this.width = width;
- }
- public int getHeight() {
- return height;
- }
- public void setHeight(int height) {
- this.height = height;
- }
- public boolean isRounded() {
- return rounded;
- }
- public void setRounded(boolean rounded) {
- this.rounded = rounded;
- }
- }
Bitmap加工處理工具類
- package com.everyone.android.utils;
- import java.io.ByteArrayOutputStream;
- import java.io.File;
- import java.io.IOException;
- import java.io.InputStream;
- import android.graphics.Bitmap;
- import android.graphics.Bitmap.Config;
- import android.graphics.BitmapFactory;
- import android.graphics.Canvas;
- import android.graphics.Color;
- import android.graphics.Paint;
- import android.graphics.PorterDuff;
- import android.graphics.PorterDuff.Mode;
- import android.graphics.PorterDuffXfermode;
- import android.graphics.Rect;
- import android.graphics.RectF;
- import android.graphics.drawable.BitmapDrawable;
- import android.graphics.drawable.Drawable;
- /**
- * 功能描述:Bitmap加工處理工具類
- * @author android_ls
- *
- */
- public class BitmapUtil {
- /**
- * 將圖片變成圓角(方法一)
- * @param bitmap Bitmap
- * @param pixels 圓角的弧度
- * @return 圓角圖片
- */
- public static Bitmap drawRoundBitmap(Bitmap bitmap, float pixels) {
- Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Config.ARGB_8888);
- Canvas canvas = new Canvas(output);
- final Paint paint = new Paint();
- final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
- final RectF rectF = new RectF(rect);
- paint.setAntiAlias(true);
- canvas.drawARGB(0, 0, 0, 0);
- // paint.setColor()的參數,除不能為Color.TRANSPARENT外,可以任意寫
- paint.setColor(Color.RED);
- canvas.drawRoundRect(rectF, pixels, pixels, paint);
- paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
- canvas.drawBitmap(bitmap, rect, rect, paint);
- return output;
- }
- /**
- * 將圖片變成圓角(方法二)
- * @param bitmap Bitmap
- * @param pixels 圓角的弧度
- * @return 圓角圖片
- */
- public static Bitmap drawRoundCorner(Bitmap bitmap, float pixels) {
- Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(output);
- RectF outerRect = new RectF(0, 0, bitmap.getWidth(), bitmap.getHeight());
- Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
- // paint.setColor()的參數,除不能為Color.TRANSPARENT外,可以任意寫
- paint.setColor(Color.WHITE);
- canvas.drawRoundRect(outerRect, pixels, pixels, paint);
- paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
- Drawable drawable = new BitmapDrawable(bitmap);
- drawable.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight());
- canvas.saveLayer(outerRect, paint, Canvas.ALL_SAVE_FLAG);
- drawable.draw(canvas);
- canvas.restore();
- return output;
- }
- /**
- * 對Bitmap進行質量壓縮
- * @param bitmap Bitmap
- * @return ByteArrayInputStream
- */
- public static Bitmap compressBitmap(Bitmap bitmap) {
- ByteArrayOutputStream outStream = new ByteArrayOutputStream();
- // 圖片質量默認值為100,表示不壓縮
- int quality = 100;
- // PNG是無損的,將會忽略質量設置。因此,這裡設置為JPEG
- bitmap.compress(Bitmap.CompressFormat.JPEG, quality, outStream);
- // 判斷壓縮後圖片的大小是否大於100KB,大於則繼續壓縮
- while (outStream.toByteArray().length / 1024 > 100) {
- outStream.reset();
- // 壓縮quality%,把壓縮後的數據存放到baos中
- bitmap.compress(Bitmap.CompressFormat.JPEG, quality, outStream);
- quality -= 10;
- }
- System.out.println("quality = " + quality);
- byte[] data = outStream.toByteArray();
- return BitmapFactory.decodeByteArray(data, 0, data.length);
- }
- /**
- * 根據指定的壓縮比例,獲得合適的Bitmap(方法一)
- * @param file File
- * @param width 指定的寬度
- * @param height 指定的高度
- * @return Bitmap
- */
- public static Bitmap decodeStream(File file, int width, int height) {
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeFile(file.getAbsolutePath(), options);
- int w = options.outWidth;
- int h = options.outHeight;
- // 從服務器端獲取的圖片大小為:80x120
- // 我們想要的圖片大小為:40x40
- // 縮放比:120/40 = 3,也就是說我們要的圖片大小為原圖的1/3
- // 縮放比。由於是固定比例縮放,只用高或者寬其中一個數據進行計算即可
- int ratio = 1; // 默認為不縮放
- if (w >= h && w > width) {
- ratio = (int) (w / width);
- } else if (w < h && h > height) {
- ratio = (int) (h / height);
- }
- if (ratio <= 0) {
- ratio = 1;
- }
- System.out.println("圖片的縮放比例值ratio = " + ratio);
- options = new BitmapFactory.Options();
- options.inJustDecodeBounds = false;
- // 屬性值inSampleSize表示縮略圖大小為原始圖片大小的幾分之一,即如果這個值為2,
- // 則取出的縮略圖的寬和高都是原始圖片的1/2,圖片大小就為原始大小的1/4。
- options.inSampleSize = ratio;
- return BitmapFactory.decodeFile(file.getAbsolutePath(), options);
- }
- /**
- * 根據指定的壓縮比例,獲得合適的Bitmap(方法二)
- * @param inStream InputStream
- * @param width 指定的寬度
- * @param height 指定的高度
- * @return Bitmap
- * @throws IOException
- */
- public static Bitmap decodeStream(InputStream inStream, int width, int height) throws IOException {
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- // 從輸入流讀取數據
- byte[] data = StreamTool.read(inStream);
- BitmapFactory.decodeByteArray(data, 0, data.length, options);
- int w = options.outWidth;
- int h = options.outHeight;
- // 從服務器端獲取的圖片大小為:80x120
- // 我們想要的圖片大小為:40x40
- // 縮放比:120/40 = 3,也就是說我們要的圖片大小為原圖的1/3
- // 縮放比。由於是固定比例縮放,只用高或者寬其中一個數據進行計算即可
- int ratio = 1; // 默認為不縮放
- if (w >= h && w > width) {
- ratio = (int) (w / width);
- } else if (w < h && h > height) {
- ratio = (int) (h / height);
- }
- if (ratio <= 0) {
- ratio = 1;
- }
- System.out.println("圖片的縮放比例值ratio = " + ratio);
- options.inJustDecodeBounds = false;
- // 屬性值inSampleSize表示縮略圖大小為原始圖片大小的幾分之一,即如果這個值為2,
- // 則取出的縮略圖的寬和高都是原始圖片的1/2,圖片大小就為原始大小的1/4。
- options.inSampleSize = ratio;
- return BitmapFactory.decodeByteArray(data, 0, data.length);
- }
- /**
- * 根據指定的壓縮比例,獲得合適的Bitmap(會出錯的方法,僅用於測試)
- * @param inStream
- * @param width
- * @param height
- * @return
- * @throws IOException
- */
- public static Bitmap decodeStreamError(InputStream inStream, int width, int height) throws IOException {
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeStream(inStream, null, options);
- int w = options.outWidth;
- int h = options.outHeight;
- // 從服務器端獲取的圖片大小為:80x120
- // 我們想要的圖片大小為:40x40
- // 縮放比:120/40 = 3,也就是說我們要的圖片大小為原圖的1/3
- // 縮放比。由於是固定比例縮放,只用高或者寬其中一個數據進行計算即可
- int ratio = 1; // 默認為不縮放
- if (w >= h && w > width) {
- ratio = (int) (w / width);
- } else if (w < h && h > height) {
- ratio = (int) (h / height);
- }
- if (ratio <= 0) {
- ratio = 1;
- }
- System.out.println("圖片的縮放比例值ratio = " + ratio);
- options.inJustDecodeBounds = false;
- // 屬性值inSampleSize表示縮略圖大小為原始圖片大小的幾分之一,即如果這個值為2,
- // 則取出的縮略圖的寬和高都是原始圖片的1/2,圖片大小就為原始大小的1/4。
- options.inSampleSize = ratio;
- return BitmapFactory.decodeStream(inStream, null, options);
- }
- }
- package com.everyone.android.utils;
- import android.content.Context;
- /**
- * 功能描述:單位轉換工具類
- * @author android_ls
- *
- */
- public class DensityUtil {
- /**
- * 將單位為dip的值轉換成單位為px的值
- * @param context Context
- * @param dipValue dip值
- * @return px值
- */
- public static int dip2px(Context context, float dipValue) {
- final float scale = context.getResources().getDisplayMetrics().density;
- return (int) (dipValue * scale + 0.5f);
- }
- /**
- * 將單位為px的值轉換成單位為dip的值
- * @param context Context
- * @param pxValue 像素值
- * @return dip值
- */
- public static int px2dip(Context context, float pxValue) {
- final float scale = context.getResources().getDisplayMetrics().density;
- return (int) (pxValue / scale + 0.5f);
- }
- /**
- * 將px值轉換為sp值,保證文字大小不變
- *
- * @param pxValue
- * @param fontScale(DisplayMetrics類中屬性scaledDensity)
- * @return
- */
- public static int px2sp(Context context, float pxValue) {
- final float scale = context.getResources().getDisplayMetrics().density;
- return (int) (pxValue / scale + 0.5f);
- }
- /**
- * 將sp值轉換為px值,保證文字大小不變
- *
- * @param spValue
- * @param fontScale(DisplayMetrics類中屬性scaledDensity)
- * @return
- */
- public static int sp2px(Context context, float spValue) {
- final float scale = context.getResources().getDisplayMetrics().density;
- return (int) (spValue * scale + 0.5f);
- }
- }
數據流處理工具類數據流處理工具類數據流處理工具類
- package com.everyone.android.utils;
- import java.io.ByteArrayOutputStream;
- import java.io.IOException;
- import java.io.InputStream;
- /**
- * 功能描述:數據流處理工具類
- * @author android_ls
- */
- public final class StreamTool {
- /**
- * 從輸入流讀取數據
- *
- * @param inStream
- * @return
- * @throws IOException
- * @throws Exception
- */
- public static byte[] read(InputStream inStream) throws IOException {
- ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
- byte[] buffer = new byte[1024];
- int len = 0;
- while ((len = inStream.read(buffer)) != -1) {
- outSteam.write(buffer, 0, len);
- }
- outSteam.close();
- inStream.close();
- return outSteam.toByteArray();
- }
- }
五、網絡模塊修改的文件源碼:
網絡請求線程基類
- package com.everyone.android.net;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.Serializable;
- import java.net.HttpURLConnection;
- import java.util.Map;
- import org.json.JSONException;
- import com.everyone.android.callback.ParseCallback;
- import com.everyone.android.callback.ResultCallback;
- import com.everyone.android.utils.Constant;
- import com.everyone.android.utils.LogUtil;
- import com.everyone.android.utils.StreamTool;
- /**
- * 功能描述:網絡請求線程基類
- * @author android_ls
- *
- */
- public abstract class AsyncBaseRequest implements Runnable, Serializable {
- /**
- *
- */
- private static final long serialVersionUID = 1L;
- /**
- * LOG打印標簽
- */
- private static final String TAG = "AsyncBaseRequest";
- /**
- * 網絡連接超時,默認值為5秒
- */
- protected int connectTimeout = 5 * 1000;
- /**
- * 網絡數據讀取超時,默認值為5秒
- */
- protected int readTimeout = 5 * 1000;
- private boolean interrupted;
- public boolean isInterrupted() {
- return interrupted;
- }
- public void setInterrupted(boolean interrupted) {
- this.interrupted = interrupted;
- }
- protected void setConnectTimeout(int connectTimeout) {
- this.connectTimeout = connectTimeout;
- }
- protected void setReadTimeout(int readTimeout) {
- this.readTimeout = readTimeout;
- }
- protected String requestUrl;
- protected Map<String, String> parameter;
- private ParseCallback parseHandler;
- private ResultCallback requestCallback;
- protected HttpURLConnection mHttpURLConn;
- protected InputStream mInStream;
- public AsyncBaseRequest(String url, Map<String, String> parameter, ParseCallback handler, ResultCallback requestCallback) {
- this.parseHandler = handler;
- this.requestUrl = url;
- this.parameter = parameter;
- this.requestCallback = requestCallback;
- }
- /**
- * 發送網絡請求
- *
- * @return 網絡請求返回的InputStream數據流
- * @throws IOException
- */
- protected abstract InputStream getRequestResult() throws IOException;
- @Override
- public void run() {
- if (interrupted) {
- LogUtil.i(TAG, "訪問網絡前中斷業務處理線程(終止)");
- return;
- }
- try {
- mInStream = getRequestResult();
- if (mInStream != null) {
- if (interrupted) {
- LogUtil.i(TAG, "讀取數據前中斷業務處理線程(終止)");
- return;
- }
- Object obj = null;
- if(parseHandler != null){
- byte[] data = StreamTool.read(mInStream);
- if (interrupted) {
- LogUtil.i(TAG, "解析數據前中斷業務處理線程(終止)");
- return;
- }
- String result = new String(data);
- obj = parseHandler.parse(result);
- }
- if (interrupted) {
- LogUtil.i(TAG, "刷新UI前中斷業務處理線程(終止)");
- return;
- }
- if(obj != null){
- requestCallback.onSuccess(obj);
- } else {
- requestCallback.onSuccess(mInStream);
- }
- } else {
- LogUtil.i(TAG, "get InputStream By HttpURLConnection return result is NULL.");
- requestCallback.onFail(Constant.NETWORK_REQUEST_RETUN_NULL); // 網絡請求返回NULL
- }
- } catch (JSONException e) {
- requestCallback.onFail(Constant.NETWORK_REQUEST_RESULT_PARSE_ERROR); // 網絡請求返回結果解析出錯
- e.printStackTrace();
- } catch (IOException e) {
- requestCallback.onFail(Constant.NETWORK_REQUEST_IOEXCEPTION_CODE); // IO異常標識
- e.printStackTrace();
- }
- }
- public HttpURLConnection getRequestConn() {
- return mHttpURLConn;
- }
- }
通過HTTP協議發送GET請求
- package com.everyone.android.net;
- import java.io.IOException;
- import java.io.InputStream;
- import java.net.HttpURLConnection;
- import java.net.URL;
- import java.net.URLEncoder;
- import java.util.Map;
- import org.apache.http.protocol.HTTP;
- import com.everyone.android.callback.ParseCallback;
- import com.everyone.android.callback.ResultCallback;
- /**
- * 功能描述:通過HTTP協議發送GET請求
- * @author android_ls
- *
- */
- public class AsyncHttpGet extends AsyncBaseRequest {
- /**
- *
- */
- private static final long serialVersionUID = 2L;
- public AsyncHttpGet(String url, Map<String, String> parameter, ParseCallback handler, ResultCallback requestCallback) throws IOException {
- super(url, parameter, handler, requestCallback);
- }
- @Override
- protected InputStream getRequestResult() throws IOException {
- StringBuilder sb = new StringBuilder(requestUrl);
- if (parameter != null && !parameter.isEmpty()) {
- sb.append('?');
- for (Map.Entry<String, String> entry : parameter.entrySet()) {
- sb.append(entry.getKey()).append('=').append(URLEncoder.encode(entry.getValue(), HTTP.UTF_8)).append('&');
- }
- sb.deleteCharAt(sb.length() - 1);
- }
- URL url = new URL(sb.toString());
- mHttpURLConn = (HttpURLConnection) url.openConnection();
- mHttpURLConn.setConnectTimeout(connectTimeout);
- mHttpURLConn.setRequestMethod("GET");
- if (mHttpURLConn.getResponseCode() == HttpURLConnection.HTTP_OK) {
- return mHttpURLConn.getInputStream();
- }
- return null;
- }
- }
六、運行後的效果圖:
轉自:http://blog.csdn.net/android_ls/article/details/8797740
Android IMF(Input Method Framework)是自An
android:gravity:設置的是控件自身上面的內容位置 android:layout_gravity:設置控件本身相對於父控件的顯示位置。 看下如下代碼段
Android應用程序可以在許多不同地區的許多設備上運行。為了使應用程序更具交互性,應用程序應該處理以適合應用程序將要使用的語言環境方面的文字,數字,文件等。在本章中,我
JSON代表JavaScript對象符號。它是一個獨立的數據交換格式,是XML的最佳替代品。本章介紹了如何解析JSON文件,並從中提取所需的信息。Android提供了四個