編輯:關於Android編程
一 . 高效加載 Bitmap
BitMapFactory 提供了四類方法: decodeFile,decodeResource,decodeStream 和 decodeByteArray 分別用於從文件系統,資源,輸入流以及字節數組中加載出一個 Bitmap 對象。
高效加載 Bitmap 很簡單,即采用 BitMapFactory.options
來加載所需要尺寸圖片。BitMapFactory.options
就可以按照一定的采樣率來加載縮小後的圖片,將縮小後的圖片置於 ImageView 中顯示。
通過采樣率即可高效的加載圖片,遵循如下方式獲取采樣率:
BitmapFactory.Options
的 inJustDecodeBounds 參數設置為 true 並加載圖片BitmapFactory.Options
中取出圖片的原始寬高信息,即對應於 outWidth 和 outHeight 參數BitmapFactory.Options
的 injustDecodeBounds 參數設置為 false,然後重新加載圖片過上述四個步驟,加載出的圖片就是最終縮放後的圖片,當然也有可能沒有縮放。
代碼實現如下:
public Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { // First decode with inJustDecodeBounds=true to check dimensions final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, resId, options); // Calculate inSampleSize options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // Decode bitmap with inSampleSize set options.inJustDecodeBounds = false; return BitmapFactory.decodeResource(res, resId, options); } public int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { if (reqWidth == 0 || reqHeight == 0) { return 1; } // Raw height and width of image final int height = options.outHeight; final int width = options.outWidth; Log.d(TAG, "origin, w= " + width + " h=" + height); int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; // Calculate the largest inSampleSize value that is a power of 2 and // keeps both height and width larger than the requested height and width. while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) { inSampleSize *= 2; } } Log.d(TAG, "sampleSize:" + inSampleSize); return inSampleSize; }
實際使用就可以像下面這樣了,如加載 100*100 的圖片大小,就可以像下面這樣高效的加載圖片了:
mImageView.setImageBitmap( decodeSampledBitmapFromResource(getResource(),R.id.myimage,100,100));
二 . Android 中的緩存策略
目前常用的算法是 LRU,即近期最少使用算法,當緩存存滿時,會優先淘汰近期最少使用的緩存對象
2.1 LruCache
LruCache 是一個泛型類,其內部實現機制是 LinkedHashMap 以強引用的方式存儲外部的緩存對象,提供了 get()
和 put()
來完成緩存對象的存取。當緩存滿了,移除較早的緩存對象,再添加新的。LruCache 是線程安全的。
2.2 DiskLriCache
DiskLruCache 用於實現存儲設備緩存,即磁盤緩存。
2.2.1 DiskLruCache 的創建
由於它不屬於 Android SDK的一部分,所以不能通過構造方法來創建,提供了 open()
方法用於自身的創建
public static DiskLruCache open(File directory,int appversion,int valueCount,long maxSize);
典型的 DiskLruCache 的創建過程
private static final Disk_CACHE_SIZE = 1024*1024*50;//50M File diskCaCheDir = getDiskCacheDir(mContext,"bitmap"); if(!diskCacheDir.exists()){ diskCacheDir.mkdirs(); } mDiskLruCache = DiskLruCache.open(diskCaCheDir,1,1,Disk_CACHE_SIZE);
第三個參數表示單個節點所對應的數據,一般設置為1即可。
2.2.2 DiskLruCache 的緩存添加 緩存的添加操作是通過 Editor 完成的, Editor 表示一個緩存對象的編輯對象。DiskLruCache 不允許同時編輯一個緩存對象。
2.2.3 DiskLruCache 的緩存查找
緩存查找過程也需要將 url 轉換為 key,通過 DiskLruCache 的 get()
得到一個 Snapshot 對象,然後通過該對象即可得到緩存的文件輸入流,得到文件輸入流即可得到 Bitmap 對象了。為了避免加載過程中 OOM,一般不會直接加載原始圖片。在前面介紹通過 BitmapFactory.Options
來加載一張縮放後的圖片,但是那種方法對 FileInputStream 的縮放存在問題,原因是 FileInputStream 是一種有序的文件流,而兩次 decodeStream 調用影響了文件流的位置屬性,導致了第二次 decodeStream 時得到的是 null。為了解決這個問題,可以通過文件流得到其對應的文件描述符,然後通過 BitmapFactory.decodeFileDescriptor
方法來加載一張縮放過後的圖片。
Bitmap bitmap = null; String key = hashKeyFormUrl(url); DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key); if (snapShot != null) { FileInputStream fileInputStream = (FileInputStream)snapShot.getInputStream(DISK_CACHE_INDEX); // 獲取文件描述符 FileDescriptor fileDescriptor = fileInputStream.getFD(); // 通過 BitmapFactory.decodeFileDescriptor 來加載一張縮放後的圖片 bitmap = mImageResizer.decodeSampledBitmapFromFileDescriptor(fileDescriptor, reqWidth, reqHeight); if (bitmap != null) { addBitmapToMemoryCache(key, bitmap); } } return bitmap; }
三 . ImageLoader 的實現
具備的功能,即圖片的同步加載,異步加載,圖片的壓縮,內存緩存,磁盤緩存以及網絡拉取。
3.1 圖片壓縮功能
如前面所述。
3.2 內存緩存和磁盤緩存的實現
選擇 LruCache 和 DiskLruCache 來分別完成內存緩存和磁盤緩存的工作
3.3 同步加載和異步加載的接口設計
關於同步加載:從 loadBitmap 的實現可以看出,其工作過程遵循如下幾個步驟:先試著從內存緩存中讀取圖片,接著從磁盤緩存中讀取圖片,最後試著從網絡拉取圖片。另外該方法不能在主線程中調用,否則就會拋出異常。因為加載圖片是一個耗時的操作。
關於異步加載:從 bindBitmap 中可以看出,binfBitmap 會先試著從內存緩存中讀取結果,如果成功就直接返回,否則會從線程池中去調用 loadBitmap()
,當加載成功後,再講圖片,圖片地址以及需要綁定的 ImageView 封裝成一個 loaderResult 對象,通過 mMainHandler 向主線程發送一個消息,這樣就可以在主線程中給 ImageView 設置圖片了。圖片的異步加載是一個很有用的功能,很多時候調用者不想在單獨的線程中以同步的方式來加載圖片,並將圖片設置給需要的 ImageVIew, 從而ImageLoader 內部需要自己需要在內部線程中加載圖片,並且將圖片設置給所需要的 ImageView。
ImageLoader源碼可以點擊這裡:下載 查看ImageLoader的實現
四 . ImageLoader 的使用
核心是 ImageAdapter , 其中的 getView()
的核心方法如下:
@Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder = null; if (convertView == null) { convertView = mInflater.inflate(R.layout.image_list_item,parent, false); holder = new ViewHolder(); holder.imageView = (ImageView) convertView.findViewById(R.id.image); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } ImageView imageView = holder.imageView; final String tag = (String)imageView.getTag(); final String uri = getItem(position); if (!uri.equals(tag)) { imageView.setImageDrawable(mDefaultBitmapDrawable); } if (mIsGridViewIdle && mCanGetBitmapFromNetWork) { imageView.setTag(uri); // 這句話將圖片的復雜加載過程交給 ImageLoader 了 mImageLoader.bindBitmap(uri, imageView, mImageWidth, mImageWidth); } return convertView; }
對於上述代碼 ImageAdapter 來說, ImageLoader 的加載圖片的復雜過程,更不需要知道。
優化列表卡頓現象:
總結
以上就是這篇文章的全部內容了,希望本文的內容對給我Android開發者們能帶來一定的幫助,如果有疑問大家可以留言交流。
眾所周知Android的Tab控件不是很好用,因此Github上的PagerSlidingTabStrip項目被廣為使用,該項目地址為: 其示例圖如下:由於其d
前面分析了電話撥號界面及電話呼叫界面,由於Android的電話Phone設計的很復雜,因此先從UI層入手分析。想要了解Android的電話撥號UI,請查看Android電
前言: 哇喔從題目是不是看出了什麼端倪, 沒錯我打算要造好多好多POP小輪子, 今天是輪子01 , 演示圖片我也是挑了好久呢, 博主真是用心呢, 中午空閒時間發出來, 沒
一、概述 對於加載圖片,大家都不陌生,一般為了盡可能避免OOM都會按照如下做法: 對於圖片顯示:根據需要顯示圖片控件的大小對圖片進行壓縮顯示。如果圖片數量非常多:則會使