本來本身寫了一份關於android顯示圖片的文章,可是以爲寫的不少點不到位,恰好在網上看到那麼一份以爲不錯,就轉載過來。我是直接看android-develper的官方手冊。英文理解能力不是很好,不少不明白的點,這裏都提到了。如下是鏈接地址:html
Android高效顯示圖片詳解(一)android
http://blog.csdn.net/zhiying201039/article/details/8653786ios
http://blog.csdn.net/zhiying201039/article/details/8665598網絡
http://blog.csdn.net/zhiying201039/article/details/8682419app
----------------------------------分割線------------------------------------------------異步
Android高效顯示圖片詳解(一)async
說明:ide
本講義分爲三部分,較爲詳細的介紹了Android平臺下圖片顯示,加載等操做的處理原則與辦法,以供你們共同窗習,轉載請註明出處 「From 移動微技」。
前提與解釋:
安卓平臺做爲一款移動端的應用操做平臺,其內存容量是十分有限的,內存資源是十分珍貴的,是沒法與傳統的桌面平臺相比的,所以,在安卓平臺下一樣的圖片操做與處理都要十分謹慎,不然你的程序能夠迅速地消耗可用內存的預算,最終由於OutOfMemory致使程序崩潰掉。如下有三個緣由說明了咱們爲何要謹慎:
(1)安卓平臺下對應用可以使用的系統資源都作出了限制,標準安卓系統下,一個應用程序可用的最大內存爲16M,一些第三方ROM
可能會上調這一限制,可是做爲應用來講必定要控制本身的內存用量,這並非能夠無限制使用的。
(2)一張高分辨圖片的內容耗用量是驚人的,例如,Galaxy Nexus的攝像頭在拍攝2592X1936像素(5百萬像素)。若是位圖使用
的是配置ARGB_8888
(默認的Android 2.3開始),那麼此圖像加載到內存佔用約19MB的內存(2592 * 1936 * 4字節),直接就耗
盡了在某些設備上的每一個應用程序的內存上限。
(3)安卓應用程序的一些控件常常須要幾個位圖一塊兒加載。例如ListView,GridView,ViewPager等控件,而且在使用中還要快速
的滑動,要及時對圖片進行更新與回收,更加增長了圖片處理的難度。
解決辦法:
一,如何去加載與顯示大圖:
其實,在安卓這樣內存有限的平臺上,是沒有必要按照原始尺寸把一張大圖徹底加載進來的,只須要加載與咱們顯示控件相匹配的尺寸就行,多了只會浪費咱們寶貴的內存。所以在加載圖片時,咱們按照咱們須要顯示的大小對原始圖片再採樣就OK了。同時咱們也能夠根據咱們所可以使用的內存大小來對圖片進行解碼,按照咱們可以承受的尺寸與分辨率來處理,保證圖片所佔用的內存在咱們可支配的範圍以內,也就避免了OOM的問題。
第一步:咱們須要獲取原始圖片的相關尺寸,分辨率等數據
能夠利用BitmapFactory的Options來達到這一目的,解碼圖片時能夠先把inJustDecodeBounds的值設爲true,這樣並無真正的去解碼圖片,不佔用內存,可是咱們卻能夠在這個過程當中獲取圖片的寬,高以及類型,代碼以下:
[html] view plaincopy
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myp_w_picpath, options);
int p_w_picpathHeight = options.outHeight;
int p_w_picpathWidth = options.outWidth;
String p_w_picpathType = options.outMimeType;
第二步:獲取原始的圖片尺寸後,根據目標計算縮放比例係數,代碼以下:
[html] view plaincopy
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of p_w_picpath
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
// Calculate ratios of height and width to requested height and width
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
// Choose the smallest ratio as inSampleSize value, this will guarantee
// a final p_w_picpath with both dimensions larger than or equal to the
// requested height and width.
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
官方文檔中說,inSampleSize這個屬性最好是2的倍數,這樣處理更快,效率更高。。。
第三步:開始對圖片進行解碼,代碼以下:
[html] view plaincopy
public static 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);
}
注意,真正解碼時須要把inJustDecodeBounds屬性重置爲false,這樣就能夠把一張十分巨大的圖輕鬆顯示在一個100x100的ImageView中了
[html] view plaincopy
mImageView.setImageBitmap(decodeSampledBitmapFromResource(getResources(), R.id.myp_w_picpath, 100, 100));
固然,你也能夠用來加載顯示其餘來源的圖片,而不是例子中資源文件中的,下一講咱們研究ListView,GridView中的多圖片併發顯示問題。
上節課咱們介紹瞭如何加載和顯示大圖,這節課咱們就要把這個技巧與實際開發聯繫起來,在實際的開發過程當中,最多見的場景就是用ListView,GridView等集合顯示控件
來呈現圖片,這節課,咱們就要用這些控件來高效的顯示圖片。
實際的使用環境中,若是圖片來源是SD卡或者網絡,那那麼加載圖片的過程必定不要放在UI線程中,這樣會嚴重的阻塞UI線程,出現ANR,程序就廢了。所以咱們首先要實現異步加載。
第一步:利用AsyncTask實現圖片的異步加載
將decodeSampledBitmapFromResource方法放入Task的doInBackground中後臺執行。不熟悉AsyncTask的同窗能夠學習AsyncTask的相關知識,這裏再也不過多介紹。
代碼:
[html] view plaincopy
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
private final WeakReference<ImageView> p_w_picpathViewReference;
private int data = 0;
public BitmapWorkerTask(ImageView p_w_picpathView) {
// Use a WeakReference to ensure the ImageView can be garbage collected
p_w_picpathViewReference = new WeakReference<ImageView>(p_w_picpathView);
}
// Decode p_w_picpath in background.
@Override
protected Bitmap doInBackground(Integer... params) {
data = params[0];
return decodeSampledBitmapFromResource(getResources(), data, 100, 100));
}
// Once complete, see if ImageView is still around and set bitmap.
@Override
protected void onPostExecute(Bitmap bitmap) {
if (p_w_picpathViewReference != null && bitmap != null) {
final ImageView p_w_picpathView = p_w_picpathViewReference.get();
if (p_w_picpathView != null) {
p_w_picpathView.setImageBitmap(bitmap);
}
}
}
}
注意,這裏對ImageView使用 WeakReference弱引用的目的是確保 AsyncTask不會妨礙系統對ImageView必要時候的垃圾回收。不然可能會出現內存泄露,同時,咱們必定要在Task執行完畢後對ImageView的存在性進行判斷,由於不能保證Task執行完畢後,ImageView還會存在。
下來咱們按照下面的代碼就可使用這個Task了:
[html] view plaincopy
public void loadBitmap(int resId, ImageView p_w_picpathView) {
BitmapWorkerTask task = new BitmapWorkerTask(p_w_picpathView);
task.execute(resId);
}
第二步:處理併發狀況
ListView與GridView這種多子視圖的控件會出現兩個問題,
第一,一個ListView會有衆多的ChildView,爲了更高效的利用內存,控件會自動回收掉被用戶滑動過去,不在當前有顯示的ChildView,若是每個ChildView都開啓一個Task去加載圖片,這樣就不能保證開啓Task的ChildView在Task執行完畢後沒有被回收掉(頗有可能用戶滑動到其餘地方去了)。
第二,由於每張圖片的處理時間是不一樣的,所以一樣不能保證加載完成的次序與開始的次序一致。
下來咱們開始着手解決這些問題,咱們要讓ImageView與Task造成一種綁定的關係。
咱們先來建立一個特殊的Drawable,這個Drawable有兩個功能,一個是與Task造成一種綁定的關係,另外也充當了ImageView的臨時佔位圖像,該Drawable的代碼以下:
[html] view plaincopy
static class AsyncDrawable extends BitmapDrawable {
private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
public AsyncDrawable(Resources res, Bitmap bitmap,
BitmapWorkerTask bitmapWorkerTask) {
super(res, bitmap);
bitmapWorkerTaskReference =
new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
}
public BitmapWorkerTask getBitmapWorkerTask() {
return bitmapWorkerTaskReference.get();
}
}
在該Drawable中經過弱引用能與對應的Task造成一種一一對應的捆綁關係。
咱們能夠這樣使用它,在執行Task以前,先建立一個對應的Drawable,並把它當成將要呈現實際圖片的ImageView佔位圖片,同時也與ImageView造成了綁定關係。
[html] view plaincopy
public void loadBitmap(int resId, ImageView p_w_picpathView) {
if (cancelPotentialWork(resId, p_w_picpathView)) {
final BitmapWorkerTask task = new BitmapWorkerTask(p_w_picpathView);
final AsyncDrawable asyncDrawable =
new AsyncDrawable(getResources(), mPlaceHolderBitmap, task);
p_w_picpathView.setImageDrawable(asyncDrawable);
task.execute(resId);
}
}
固然,咱們須要判斷下ImageView以前是否已經綁定了,若是以前綁定過但與本次的圖片不一樣,那咱們就要按最新的須要重新綁定下,若是以前與如今的一致,則保持原狀,再也不重新綁定,代碼中的cancelPotentialWork就是作這個工做的,其代碼以下:
[html] view plaincopy
public static boolean cancelPotentialWork(int data, ImageView p_w_picpathView) {
final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(p_w_picpathView);
if (bitmapWorkerTask != null) {
final int bitmapData = bitmapWorkerTask.data;
if (bitmapData != data) {
// Cancel previous task
bitmapWorkerTask.cancel(true);
} else {
// The same work is already in progress
return false;
}
}
// No task associated with the ImageView, or an existing task was cancelled
return true;
}
[html] view plaincopy
private static BitmapWorkerTask getBitmapWorkerTask(ImageView p_w_picpathView) {
if (p_w_picpathView != null) {
final Drawable drawable = p_w_picpathView.getDrawable();
if (drawable instanceof AsyncDrawable) {
final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
return asyncDrawable.getBitmapWorkerTask();
}
}
return null;
}
最後,咱們在Task的onPostExecute函數中,把加載的圖片更新到視圖中去,在更新前咱們須要檢查下Task是否被取消,而且當前的Task是不是那個與ImageView關聯的Task,一致則咱們把圖片更新到ImageView上去,代碼以下:
[html] view plaincopy
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
...
@Override
protected void onPostExecute(Bitmap bitmap) {
if (isCancelled()) {
bitmap = null;
}
if (p_w_picpathViewReference != null && bitmap != null) {
final ImageView p_w_picpathView = p_w_picpathViewReference.get();
final BitmapWorkerTask bitmapWorkerTask =
getBitmapWorkerTask(p_w_picpathView);
if (this == bitmapWorkerTask && p_w_picpathView != null) {
p_w_picpathView.setImageBitmap(bitmap);
}
}
}
}
最後,實際的使用也至關簡單,只須要在你的ListView適配器的getView函數中調用上面的loadBitmap函數就OK了~
下一節咱們來講說緩存,加入緩存讓這個機制更增強大。。
感謝收看! 多多好評,在此謝過!
Android高效顯示圖片詳解(三)地址:http://blog.csdn.net/zhiying201039/article/details/8682419
用戶在使用ListView或GridView時,控件會自動把用戶滑過的已不在當前顯示區域的ChildView回收掉,固然也會把該子視圖上的bitmap回收掉以釋放內存,所以,爲了保證一個流暢,快速的操做體驗,咱們應當避免反覆的對同一張圖片進行加載,好比說用戶在往下看圖的過程當中又向上滑回去看圖,這時對於已經上面已經加載過的圖片咱們就沒有必要讓它再加載一遍了,應該能很快的把圖片顯示出來,這裏咱們要使用緩存來達到這一目的。
一,使用Memory Cache:
內存緩存速度快,同時爲了更加適應實際應用的場景,咱們使用LruCache來達到按使用頻率緩存的目的,把最近使用的加入緩存,較長時間不用的則會剔除掉釋放出空間。
緩存的代碼以下:
[html] view plaincopy
private LruCache<String, Bitmap> mMemoryCache;
@Override
protected void onCreate(Bundle savedInstanceState) {
...
// Get max available VM memory, exceeding this amount will throw an
// OutOfMemory exception. Stored in kilobytes as LruCache takes an
// int in its constructor.
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// Use 1/8th of the available memory for this memory cache.
final int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
// The cache size will be measured in kilobytes rather than
// number of items.
return bitmap.getByteCount() / 1024;
}
};
...
}
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
public Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key);
}
那麼咱們在loadBitmap的時候就能夠先檢查下緩存中保存的是否有該圖片,有則直接取出使用,再也不進行加載。
新的代碼以下:
[html] view plaincopy
public void loadBitmap(int resId, ImageView p_w_picpathView) {
final String p_w_picpathKey = String.valueOf(resId);
final Bitmap bitmap = getBitmapFromMemCache(p_w_picpathKey);
if (bitmap != null) {
mImageView.setImageBitmap(bitmap);
} else {
mImageView.setImageResource(R.drawable.p_w_picpath_placeholder);
BitmapWorkerTask task = new BitmapWorkerTask(mImageView);
task.execute(resId);
}
}
固然,咱們也要在加載圖片是及時的維護緩存,把剛使用到的圖片add進緩存中去。
新的代碼以下:
[html] view plaincopy
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
...
// Decode p_w_picpath in background.
@Override
protected Bitmap doInBackground(Integer... params) {
final Bitmap bitmap = decodeSampledBitmapFromResource(
getResources(), params[0], 100, 100));
addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);
return bitmap;
}
...
}
在使用內存作緩存的基礎上,咱們還可使用Disk控件作爲緩存,構成一種二級緩存的結構,設想這種狀況,若是App在使用的過程被忽然來電打斷,那麼此時有可能就會引發系統內存的回收,當用戶再次切換到App時,App就要進行次很明顯的圖片再次加載的過程。這個時候,咱們就須要用到Disk了,由於足夠持久。
下面是是原來的基礎上增長使用Disk Cache 的例子:
[html] view plaincopy
private DiskLruCache mDiskLruCache;
private final Object mDiskCacheLock = new Object();
private boolean mDiskCacheStarting = true;
private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB
private static final String DISK_CACHE_SUBDIR = "thumbnails";
@Override
protected void onCreate(Bundle savedInstanceState) {
...
// Initialize memory cache
...
// Initialize disk cache on background thread
File cacheDir = getDiskCacheDir(this, DISK_CACHE_SUBDIR);
new InitDiskCacheTask().execute(cacheDir);
...
}
class InitDiskCacheTask extends AsyncTask<File, Void, Void> {
@Override
protected Void doInBackground(File... params) {
synchronized (mDiskCacheLock) {
File cacheDir = params[0];
mDiskLruCache = DiskLruCache.open(cacheDir, DISK_CACHE_SIZE);
mDiskCacheStarting = false; // Finished initialization
mDiskCacheLock.notifyAll(); // Wake any waiting threads
}
return null;
}
}
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
...
// Decode p_w_picpath in background.
@Override
protected Bitmap doInBackground(Integer... params) {
final String p_w_picpathKey = String.valueOf(params[0]);
// Check disk cache in background thread
Bitmap bitmap = getBitmapFromDiskCache(p_w_picpathKey);
if (bitmap == null) { // Not found in disk cache
// Process as normal
final Bitmap bitmap = decodeSampledBitmapFromResource(
getResources(), params[0], 100, 100));
}
// Add final bitmap to caches
addBitmapToCache(p_w_picpathKey, bitmap);
return bitmap;
}
...
}
public void addBitmapToCache(String key, Bitmap bitmap) {
// Add to memory cache as before
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
// Also add to disk cache
synchronized (mDiskCacheLock) {
if (mDiskLruCache != null && mDiskLruCache.get(key) == null) {
mDiskLruCache.put(key, bitmap);
}
}
}
public Bitmap getBitmapFromDiskCache(String key) {
synchronized (mDiskCacheLock) {
// Wait while disk cache is started from background thread
while (mDiskCacheStarting) {
try {
mDiskCacheLock.wait();
} catch (InterruptedException e) {}
}
if (mDiskLruCache != null) {
return mDiskLruCache.get(key);
}
}
return null;
}
// Creates a unique subdirectory of the designated app cache directory. Tries to use external
// but if not mounted, falls back on internal storage.
public static File getDiskCacheDir(Context context, String uniqueName) {
// Check if media is mounted or storage is built-in, if so, try and use external cache dir
// otherwise use internal cache dir
final String cachePath =
Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||
!isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() :
context.getCacheDir().getPath();
return new File(cachePath + File.separator + uniqueName);
}