圖片顯示:
我們需要根據需求去加載圖片的大小。
例如在列表中僅用於預覽時加載縮略圖(thumbnails )。
只有當用戶點擊具體條目想看詳細信息的時候,這時另啟動一個fragment/activity/對話框等等,去顯示整個圖片
圖片大小:
直接使用ImageView顯示bitmap會占用較多資源,特別是圖片較大的時候,可能導致崩潰。
使用BitmapFactory.Options設置inSampleSize, 這樣做可以減少對系統資源的要求。
屬性值inSampleSize表示縮略圖大小為原始圖片大小的幾分之一,即如果這個值為2,則取出的縮略圖的寬和高都是原始圖片的1/2,圖片大小就為原始大小的1/4。
BitmapFactory.Options bitmapFactoryOptions = new BitmapFactory.Options(); bitmapFactoryOptions.inJustDecodeBounds = true; bitmapFactoryOptions.inSampleSize = 2; // 這里一定要將其設置回false,因為之前我們將其設置成了true // 設置inJustDecodeBounds為true后,decodeFile並不分配空間,即,BitmapFactory解碼出來的Bitmap為Null,但可計算出原始圖片的長度和寬度
options.inJustDecodeBounds = false; Bitmap bmp = BitmapFactory.decodeFile(sourceBitmap, options);
圖片像素:
ALPHA_8:每個像素占用1byte內存
ARGB_4444:每個像素占用2byte內存
ARGB_8888:每個像素占用4byte內存 (默認)
RGB_565:每個像素占用2byte內存
publicstaticBitmapreadBitMap(Contextcontext, intresId) { BitmapFactory.Optionsopt = newBitmapFactory.Options(); opt.inPreferredConfig = Bitmap.Config.RGB_565; opt.inPurgeable = true; opt.inInputShareable = true; //獲取資源圖片
InputStreamis = context.getResources().openRawResource(resId); returnBitmapFactory.decodeStream(is, null, opt); }
圖片回收:
使用Bitmap過后,就需要及時的調用Bitmap.recycle()方法來釋放Bitmap占用的內存空間,而不要等Android系統來進行釋放。
下面是釋放Bitmap的示例代碼片段。
// 先判斷是否已經回收
if(bitmap != null && !bitmap.isRecycled()){
// 回收並且置為null
bitmap.recycle();
bitmap = null;
}
System.gc();
捕獲異常:
經過上面這些優化后還會存在報OOM的風險,所以下面需要一道最后的關卡——捕獲OOM異常:
Bitmap bitmap = null; try { // 實例化Bitmap bitmap = BitmapFactory.decodeFile(path); } catch (OutOfMemoryError e) { // 捕獲OutOfMemoryError,避免直接崩潰 } if (bitmap == null) { // 如果實例化失敗 返回默認的Bitmap對象 return defaultBitmapMap; }
二、緩存
Bitmap緩存分為兩種:
一種是內存緩存,一種是硬盤緩存。
內存緩存(LruCache):
以犧牲寶貴的應用內存為代價,內存緩存提供了快速的Bitmap訪問方式。系統提供的LruCache類是非常適合用作緩存Bitmap任務的,它將最近被引用到的對象存儲在一個強引用的LinkedHashMap中,並且在緩存超過了指定大小之后將最近不常使用的對象釋放掉。
注意:以前有一個非常流行的內存緩存實現是SoftReference(軟引用)或者WeakReference(弱引用)的Bitmap緩存方案,然而現在已經不推薦使用了。自Android2.3版本(API Level 9)開始,垃圾回收器更着重於對軟/弱引用的回收,這使得上述的方案無效。
硬盤緩存(DiskLruCache):
一個內存緩存對加速訪問最近瀏覽過的Bitmap非常有幫助,但是你不能 局限於內存中的可用圖片。GridView這樣有着更大的數據集的組件可以很輕易消耗掉內存緩存。你的應用有可能在執行其他任務(如打電話)的時候被打 斷,並且在后台的任務有可能被殺死或者緩存被釋放。一旦用戶重新聚焦(resume)到你的應用,你得再次處理每一張圖片。
在這種情況下,硬盤緩存可以用來存儲Bitmap並在圖片被內存緩存釋放后減小圖片加載的時間(次數)。當然,從硬盤加載圖片比內存要慢,並且應該在后台線程進行,因為硬盤讀取的時間是不可預知的。
注意:如果訪問圖片的次數非常頻繁,那么ContentProvider可能更適合用來存儲緩存圖片,例如Image Gallery這樣的應用程序。
三、一些好用的開源框架
1、 Android-Universal-Image-Loader 圖片緩存(Git地址:https://github.com/nostra13/Android-Universal-Image-Loader)
這個開源庫存在的特征:
- 多線程下載圖片,圖片可以來源於網絡,文件系統,項目文件夾assets中以及drawable中等
- 支持隨意的配置ImageLoader,例如線程池,圖片下載器,內存緩存策略,硬盤緩存策略,圖片顯示選項以及其他的一些配置
- 支持圖片的內存緩存,文件系統緩存或者SD卡緩存
- 支持圖片下載過程的監聽
- 根據控件(ImageView)的大小對Bitmap進行裁剪,減少Bitmap占用過多的內存
- 較好的控制圖片的加載過程,例如暫停圖片加載,重新開始加載圖片,一般使用在ListView,GridView中,滑動過程中暫停加載圖片,停止滑動的時候去加載圖片
- 提供在較慢的網絡下對圖片進行加載
在使用之前,我們先來了解一下Android-Universal-Image-Loader中的三大組件:ImageLoaderConfiguration、ImageLoader、DisplayImageOptions。
博客(http://www.cnblogs.com/kissazi2/p/3886563.html)中有對這三者的詳細解讀。
ImageLoaderConfiguration是針對圖片緩存的全局配置,主要有線程類、緩存大小、磁盤大小、圖片下載與解析、日志方面的配置。
ImageLoader是具體下載圖片,緩存圖片,顯示圖片的具體執行類,它有兩個具體的方法displayImage(...)、loadImage(...),但是其實最終他們的實現都是displayImage(...)。
DisplayImageOptions用於指導每一個Imageloader根據網絡圖片的狀態(空白、下載錯誤、正在下載)顯示對應的圖片,是否將緩存加載到磁盤上,下載完后對圖片進行怎么樣的處理。
下面是我的項目中實際使用到的例子:
ImgConfig .java(在這個文件中對img加載屬性進行了統一的配置)
/**
圖片配置文件
*/
public class ImgConfig { public static void initImgConfig(Context context) { File cacheDir =new File(StorageUtil.getDirByType(context, StorageUtil.TYPE_IMG_CACHE_DIR));
//ImageLoaderConfiguration是針對圖片緩存的全局配置,主要有線程類、緩存大小、磁盤大小、圖片下載與解析、日志方面的配置。 ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder( context) .memoryCacheExtraOptions(480, 800) // max width, max height,即保存的每個緩存文件的最大長寬 .threadPoolSize(3) // 線程池內加載的數量 .threadPriority(Thread.NORM_PRIORITY - 2) .denyCacheImageMultipleSizesInMemory() .memoryCache(new UsingFreqLimitedMemoryCache(2 * 1024 * 1024)) // You can pass your own memory cache // 將保存的時候的URI名稱用MD5 加密 .tasksProcessingOrder(QueueProcessingType.LIFO) // 緩存的文件數量 .diskCache(new UnlimitedDiskCache(cacheDir)) // 自定義緩存路徑 .defaultDisplayImageOptions(DisplayImageOptions.createSimple()) .imageDownloader( new BaseImageDownloader(context, 5 * 1000, 30 * 1000)) .writeDebugLogs() // Remove for release app .build();// 開始構建 ImageLoader.getInstance().init(config); } //人物頭像的加載
//DisplayImageOptions用於指導每一個Imageloader根據網絡圖片的狀態(空白、下載錯誤、正在下載)顯示對應的圖片,是否將緩存加載到磁盤上,下載完后對圖片進行怎么樣的處理。
public static DisplayImageOptions getPortraitOption(){ DisplayImageOptions options = new DisplayImageOptions.Builder() .showImageOnLoading(R.drawable.default_portrait_female_little) //加載圖片時的圖片 .showImageForEmptyUri(R.drawable.default_portrait_female_little) //沒有圖片資源時的默認圖片 .showImageOnFail(R.drawable.default_portrait_female_little) //加載失敗時的圖片 .cacheInMemory(true) //啟用內存緩存 .cacheOnDisk(true) //啟用外存緩存 .considerExifParams(true) //啟用EXIF和JPEG圖像格式 .build(); return options; } //人物頭像的加載 public static DisplayImageOptions getPortraitLargeOption(){ DisplayImageOptions options = new DisplayImageOptions.Builder() .showImageOnLoading(R.drawable.default_portrait_fmale_large) //加載圖片時的圖片 .showImageForEmptyUri(R.drawable.default_portrait_fmale_large) //沒有圖片資源時的默認圖片 .showImageOnFail(R.drawable.default_portrait_fmale_large) //加載失敗時的圖片 .cacheInMemory(true) //啟用內存緩存 .cacheOnDisk(true) //啟用外存緩存 .considerExifParams(true) //啟用EXIF和JPEG圖像格式 .build(); return options; } //大圖的加載 public static DisplayImageOptions getBigImgOption(){ DisplayImageOptions options = new DisplayImageOptions.Builder() .showImageOnLoading(R.drawable.default_big_img) //加載圖片時的圖片 .showImageForEmptyUri(R.drawable.default_big_img) //沒有圖片資源時的默認圖片 .showImageOnFail(R.drawable.default_big_img) //加載失敗時的圖片 .cacheInMemory(true) //啟用內存緩存 .cacheOnDisk(true) //啟用外存緩存 .considerExifParams(true) //啟用EXIF和JPEG圖像格式 .displayer(new RoundedBitmapDisplayer(20)) //設置顯示風格這里是圓角矩形 .build(); return options; } //相冊的加載 public static DisplayImageOptions getAlbumImgOption(){ DisplayImageOptions options = new DisplayImageOptions.Builder() .showImageOnLoading(R.drawable.default_album) //加載圖片時的圖片 .showImageForEmptyUri(R.drawable.default_album) //沒有圖片資源時的默認圖片 .showImageOnFail(R.drawable.default_album) //加載失敗時的圖片 .cacheInMemory(true) //啟用內存緩存 .cacheOnDisk(true) //啟用外存緩存 .considerExifParams(true) //啟用EXIF和JPEG圖像格式 .bitmapConfig(Config.RGB_565) //設置圖片編碼格式 .build(); return options; } //相冊的加載 public static DisplayImageOptions getAlbumImgDefOption(){ DisplayImageOptions options = new DisplayImageOptions.Builder() .showImageOnLoading(R.drawable.default_album) //加載圖片時的圖片 .showImageForEmptyUri(R.drawable.default_album) //沒有圖片資源時的默認圖片 .showImageOnFail(R.drawable.default_album) //加載失敗時的圖片 .cacheInMemory(false) //啟用內存緩存 .cacheOnDisk(false) //啟用外存緩存 .considerExifParams(true) //啟用EXIF和JPEG圖像格式 .bitmapConfig(Config.RGB_565) .build(); return options; } //Card圖片的加載 public static DisplayImageOptions getCardImgOption(){ DisplayImageOptions options = new DisplayImageOptions.Builder() .showImageOnLoading(R.drawable.default_big_img) //加載圖片時的圖片 .showImageForEmptyUri(R.drawable.default_big_img) //沒有圖片資源時的默認圖片 .showImageOnFail(R.drawable.default_big_img) //加載失敗時的圖片 .cacheInMemory(false) //啟用內存緩存 .cacheOnDisk(true) //啟用外存緩存 .considerExifParams(true) //啟用EXIF和JPEG圖像格式 .bitmapConfig(Config.ARGB_8888) .build(); return options; } //BannerCard圖片的加載 public static DisplayImageOptions getBannerImgOption(){ DisplayImageOptions options = new DisplayImageOptions.Builder() .showImageOnLoading(R.drawable.default_banner_img) //加載圖片時的圖片 .showImageForEmptyUri(R.drawable.default_banner_img) //沒有圖片資源時的默認圖片 .showImageOnFail(R.drawable.default_banner_img) //加載失敗時的圖片 .cacheInMemory(true) //啟用內存緩存 .cacheOnDisk(true) //啟用外存緩存 .considerExifParams(true) //啟用EXIF和JPEG圖像格式 .build(); return options; } }
使用:
//配置application
public void initConfig(BeautyDiaryApplication application) { synchronized (sLock) { this.application = application; context = application.getBaseContext(); packageName = context.getPackageName(); versionCode = getVersionCode(context, BeautyDiaryApplication.class); versionName = getVersionName(context, BeautyDiaryApplication.class); imei = Util.getImei(getBaseContext()); try { sLock.notifyAll(); } catch (Exception e) { } ImgConfig.initImgConfig(application); } }
//加載、展示圖片
//第一個參數: 圖片url
//第二個參數: 要設置在哪個view上
//第三個參數: 加載圖片配置(imgconfig中的方法)
ImageLoader.getInstance().displayImage(StorageUtil.getPid2Url(entity.getPortrait(), StorageUtil.PIC_TYPE_LARGE),
portraitIv,ImgConfig.getPortraitLargeOption());
2、 Android 網絡通信框架Volley