生活随笔
收集整理的這篇文章主要介紹了
高效使用Bitmaps(二) 后台加载Bitmap
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
轉自:http://my.oschina.net/rengwuxian/blog/183802
為什么要在后臺加載Bitmap?
在Android中,使用BitmapFactory.decodeResource(), BitmapFactory.decodeStream() 等方法可以把圖片加載到Bitmap中。但由于這些方法是耗時的,所以多數情況下,這些方法應該放在非UI線程中,否則將有可能導致界面的卡頓,甚至是觸 發ANR。
一般情況下,網絡圖片的加載必須放在后臺線程中;而本地圖片就可以根據實際情況自行決定了,如果圖片不多不大的話,也可以在UI線程中操作來圖個方便。至于谷歌官方的說法,是只要是從硬盤或者從網絡加載Bitmap,統統不應該在主線程中進行。
基礎操作:使用AsyncTask
?
| 01 | class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { |
| 02 | ????private final WeakReference<ImageView> imageViewReference; |
| 03 | ????private int data = 0; |
| 05 | ????public BitmapWorkerTask(ImageView imageView) { |
| 06 | ????????// Use a WeakReference to ensure the ImageView can be garbage collected |
| 07 | ????????imageViewReference = new WeakReference<ImageView>(imageView); |
| 10 | ????// Decode image in background. |
| 12 | ????protected Bitmap doInBackground(Integer... params) { |
| 13 | ????????data = params[0]; |
| 14 | ????????return decodeSampledBitmapFromResource(getResources(), data, 100, 100)); |
| 17 | ????// Once complete, see if ImageView is still around and set bitmap. |
| 19 | ????protected void onPostExecute(Bitmap bitmap) { |
| 20 | ????????if (imageViewReference != null && bitmap != null) { |
| 21 | ????????????final ImageView imageView = imageViewReference.get(); |
| 22 | ????????????if (imageView != null) { |
| 23 | ????????????????imageView.setImageBitmap(bitmap); |
以上代碼摘自Android官方文檔,是一個后臺加載Bitmap并在加載完成后自動將Bitmap設置到ImageView的AsyncTask的實現。有了這個AsyncTask之后,異步加載Bitmap只需要下面的簡單代碼:
?
| 1 | public void loadBitmap(int resId, ImageView imageView) { |
| 2 | ????BitmapWorkerTask task = new BitmapWorkerTask(imageView); |
| 3 | ????task.execute(resId); |
然后,一句loadBitmap(R.id.my_image, mImageView) 就能實現本地圖片的異步加載了。
并發操作:在ListView和GridView中進行后臺加載
在實際中,影響性能的往往是ListView和GridView這種包含大量圖片的控件。在滑動過程中,大量的新圖片在短時間內一起被加載,對于沒有進行任何優化的程序,卡頓現象必然會隨之而來。通過使用后臺加載Bitmap的方式,這種問題將被有效解決。具體怎么做,我們來看看谷歌推薦的方法。
首先創建一個實現了Drawable接口的類,用來存儲AsyncTask的引用。在本例中,選擇了繼承BitmapDrawable,用來給ImageView設置一個預留的占位圖,這個占位圖用于在AsyncTask執行完畢之前的顯示。
?
| 01 | static class AsyncDrawable extends BitmapDrawable { |
| 02 | ????private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference; |
| 04 | ????public AsyncDrawable(Resources res, Bitmap bitmap, |
| 05 | ????????????BitmapWorkerTask bitmapWorkerTask) { |
| 06 | ????????super(res, bitmap); |
| 07 | ????????bitmapWorkerTaskReference = |
| 08 | ????????????new WeakReference<BitmapWorkerTask>(bitmapWorkerTask); |
| 11 | ????public BitmapWorkerTask getBitmapWorkerTask() { |
| 12 | ????????return bitmapWorkerTaskReference.get(); |
接下來,和上面類似,依然是使用一個loadBitmap()方法來實現對圖片的異步加載。不同的是,要在啟動AsyncTask之前,把AsyncTask傳給AsyncDrawable,并且使用AsyncDrawable為ImageView設置占位圖:
?
| 1 | public void loadBitmap(int resId, ImageView imageView) { |
| 2 | ????if (cancelPotentialWork(resId, imageView)) { |
| 3 | ????????final BitmapWorkerTask task = new BitmapWorkerTask(imageView); |
| 4 | ????????final AsyncDrawable asyncDrawable = |
| 5 | ????????????????new AsyncDrawable(getResources(), mPlaceHolderBitmap, task); |
| 6 | ????????imageView.setImageDrawable(asyncDrawable); |
| 7 | ????????task.execute(resId); |
然后在Adapter的getView()方法中調用loadBitmap()方法,就可以為每個Item中的ImageView進行圖片的動態加載了。
loadBitmap()方法的代碼中有兩個地方需要注意:第一,cancelPotentialWork()這個方法,它的作用是進行兩項檢查:首先檢查當前是否已經有一個AsyncTask正在為這個ImageView加載圖片,如果沒有就直接返回true。如果有,再檢查這個Task正在加載的資源是否與自己正要進行加載的資源相同,如果相同,那就沒有必要再進行多一次的加載了,直接返回false;而如果不同(為什么會不同?文章最后會有解釋),就取消掉這個正在進行的任務,并返回true。第二個需要注意的是,本例中的?BitmapWorkerTask?實際上和上例是有所不同的。這兩點我們分開說,首先我們看cancelPotentialWork()方法的代碼:
?
| 01 | public static boolean cancelPotentialWork(int data, ImageView imageView) { |
| 02 | ????final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); |
| 04 | ????if (bitmapWorkerTask != null) { |
| 05 | ????????final int bitmapData = bitmapWorkerTask.data; |
| 06 | ????????if (bitmapData != data) { |
| 08 | ????????????bitmapWorkerTask.cancel(true); |
| 10 | ????????????// 相同任務已經存在,直接返回false,不再進行重復的加載 |
| 11 | ????????????return false; |
| 14 | ????// 沒有Task和ImageView進行綁定,或者Task由于加載資源不同而被取消,返回true |
在cancelPotentialWork()的代碼中,首先使用getBitmapWorkerTask()方法獲取到與ImageView相關聯的Task,然后進行上面所說的判斷。好,我們接著來看這個getBitmapWorkerTask()是怎么寫的:
?
| 01 | private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) { |
| 02 | ???if (imageView != null) { |
| 03 | ???????final Drawable drawable = imageView.getDrawable(); |
| 04 | ???????if (drawable instanceof AsyncDrawable) { |
| 05 | ???????????final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; |
| 06 | ???????????return asyncDrawable.getBitmapWorkerTask(); |
從代碼中可以看出,該方法通過imageView獲取到與它內部的 Drawable對象,如果獲取到了并且該對象為AsyncDrawable的實例,就調用這個AsyncDrawable的 getBitmapWorkerTask()方法來獲取到它對應的Task,也就是通過一個 ImageView->Drawable->AsyncTask的鏈來獲取到ImageView所對應的AsyncTask。
好的,cancelPotentialWork()方法分析完了,我們回到剛才提到的第二個點:BitmapWorkerTask類的不同。這個類的改動在于onPostExecute()方法,具體請看下面代碼:
?
| 01 | class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { |
| 05 | ????protected void onPostExecute(Bitmap bitmap) { |
| 06 | ????????if (isCancelled()) { |
| 07 | ????????????bitmap = null; |
| 10 | ????????if (imageViewReference != null && bitmap != null) { |
| 11 | ????????????final ImageView imageView = imageViewReference.get(); |
| 12 | ????????????final BitmapWorkerTask bitmapWorkerTask = |
| 13 | ????????????????????getBitmapWorkerTask(imageView); |
| 14 | ????????????if (this == bitmapWorkerTask && imageView != null) { |
| 15 | ????????????????imageView.setImageBitmap(bitmap); |
從代碼中可以看出,在后臺加載完Bitmap之后,它 并不是直接把Bitmap設置給ImageView,而是先判斷這個ImageView對應的Task是不是自己(為什么會不同?文章最后會有解釋)。如果是自己,才會執行ImageView的setImageBitmap()方法。到此,一個并發的異步加載ListView(或GridView)中圖片的實現全部完成。
延伸:文中兩個“為什么會不同”的解答
首先,簡單說一下ListView中Item和Item對應的View的關系 (GridView中同理)。假設一個ListView含有100項,那么它的100個Item應該分別對應一個View用于顯示,這樣一共是100個 View。但Android實際上并沒有這樣做。出于內存考慮,Android只會為屏幕上可見的每個Item分配一個View。用戶滑動 ListView,當第一個Item移動到在可視范圍外后,他所對應的View將會被系統分配給下一個即將出現的Item。
回到問題。
我們不妨假設屏幕上顯示了一個ListView,并且它最多能顯示10個 Item,而用戶在最頂部的Item(不妨稱他為第1個Item)使用Task加載Bitmap的時候進行了滑動,并且直到第1個Item消失而第11個 Item已經在屏幕底部出現的時候,這個Task還沒有加載完成。那么此時,原先與第1個Item綁定的ImageView已經被重新綁定到了第11個 Item上,并且第11個Item觸發了getItem()方法。在getItem()方法中,ImageView第二次使用Task為自己加載Bitmap,但這時它需要加載的圖片資源已經變了(由第1個Item對應的資源變成了第11個Item對應的資源),因此在cancelPotentialWork()方法執行時會判斷兩個資源不一致。這就是為什么相同ImageView卻對應了不同的資源。
同理,一個Task持有了一個ImageView,但由于這個Task有可能已經過時,因此這個ImageView所對應的Task未必就是這個Task本身,也有可能是另一個更年輕的Task。
轉載于:https://www.cnblogs.com/xingfuzzhd/p/3470603.html
總結
以上是生活随笔為你收集整理的高效使用Bitmaps(二) 后台加载Bitmap的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。