12.1 Bitmap的高效加載
在介紹Bitmap的高效加載之前,先說一下如何加載一個Bitmap, Bitmap在Android中指的是一張圖片,可以是png格式也可以是jpg等其他常見的圖片格式。那么如何加載一個圖片呢?BitmapFactory類提供了四類方法:decodeFile、decodeResource、decodeStream和decodeByteArray,分別用于支持從文件系統、資源、輸入流以及字節數組中加載出一個Bitmap對象,其中decodeFile和decodeResource又間接調用了decodeStream方法,這四類方法最終是在Android的底層實現的,對應著BitmapFactory類的幾個native方法。
如何高效地加載Bitmap呢?其實核心思想也很簡單,那就是采用BitmapFactory. Options來加載所需尺寸的圖片。這里假設通過ImageView來顯示圖片,很多時候ImageView并沒有圖片的原始尺寸那么大,這個時候把整個圖片加載進來后再設給ImageView,這顯然是沒必要的,因為ImageView并沒有辦法顯示原始的圖片。通過BitmapFactory.Options就可以按一定的采樣率來加載縮小后的圖片,將縮小后的圖片在ImageView中顯示,這樣就會降低內存占用從而在一定程度上避免OOM,提高了Bitmap加載時的性能。BitmapFactory提供的加載圖片的四類方法都支持BitmapFactory.Options參數,通過它們就可以很方便地對一個圖片進行采樣縮放。
通過BitmapFactory.Options來縮放圖片,主要是用到了它的inSampleSize參數,即采樣率。當inSampleSize為1時,采樣后的圖片大小為圖片的原始大小;當inSampleSize大于1時,比如為2,那么采樣后的圖片其寬/高均為原圖大小的1/2,而像素數為原圖的1/4,其占有的內存大小也為原圖的1/4。拿一張1024×1024像素的圖片來說,假定采用ARGB8888格式存儲,那么它占有的內存為1024×1024×4,即4MB,如果inSampleSize為2,那么采樣后的圖片其內存占用只有512×512×4,即1MB。可以發現采樣率inSampleSize必須是大于1的整數圖片才會有縮小的效果,并且采樣率同時作用于寬/高,這將導致縮放后的圖片大小以采樣率的2次方形式遞減,即縮放比例為1/(inSampleSize的2次方),比如inSampleSize為4,那么縮放比例就是1/16。有一種特殊情況,那就是當inSampleSize小于1時,其作用相當于1,即無縮放效果。另外最新的官方文檔中指出,inSampleSize的取值應該總是為2的指數,比如1、2、4、8、16,等等。如果外界傳遞給系統的inSampleSize不為2的指數,那么系統會向下取整并選擇一個最接近的2的指數來代替,比如3,系統會選擇2來代替,但是經過驗證發現這個結論并非在所有的Android版本上都成立,因此把它當成一個開發建議即可。
考慮以下實際的情況,比如ImageView的大小是100×100像素,而圖片的原始大小為200×200,那么只需將采樣率inSampleSize設為2即可。但是如果圖片大小為200×300呢?這個時候采樣率還應該選擇2,這樣縮放后的圖片大小為100×150像素,仍然是適合ImageView的,如果采樣率為3,那么縮放后的圖片大小就會小于ImageView所期望的大小,這樣圖片就會被拉伸從而導致模糊。
通過采樣率即可有效地加載圖片,那么到底如何獲取采樣率呢?獲取采樣率也很簡單,遵循如下流程:
(1)將BitmapFactory.Options的inJustDecodeBounds參數設為true并加載圖片。
(2)從BitmapFactory.Options中取出圖片的原始寬高信息,它們對應于outWidth和outHeight參數。
(3)根據采樣率的規則并結合目標View的所需大小計算出采樣率inSampleSize。
(4)將BitmapFactory.Options的inJustDecodeBounds參數設為false,然后重新加載圖片。
經過上面4個步驟,加載出的圖片就是最終縮放后的圖片,當然也有可能不需要縮放。這里說明一下inJustDecodeBounds參數,當此參數設為true時,BitmapFactory只會解析圖片的原始寬/高信息,并不會去真正地加載圖片,所以這個操作是輕量級的。另外需要注意的是,這個時候BitmapFactory獲取的圖片寬/高信息和圖片的位置以及程序運行的設備有關,比如同一張圖片放在不同的drawable目錄下或者程序運行在不同屏幕密度的設備上,這都可能導致BitmapFactory獲取到不同的結果,之所以會出現這個現象,這和Android的資源加載機制有關,相信讀者平日里肯定有所體會,這里就不再詳細說明了。
將上面的4個流程用程序來實現,就產生了下面的代碼:
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);
}
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
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;
}
}
return inSampleSize;
}
有了上面的兩個方法,實際使用的時候就很簡單了,比如ImageView所期望的圖片大小為100×100像素,這個時候就可以通過如下方式高效地加載并顯示圖片:
mImageView.setImageBitmap(
decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
除了BitmapFactory的decodeResource方法,其他三個decode系列的方法也是支持采樣加載的,并且處理方式也是類似的,但是decodeStream方法稍微有點特殊,這個會在后續內容中詳細介紹。通過本節的介紹,讀者應該能很好地掌握這種高效地加載圖片的方法了。
- 前言
- 第1章 Activity的生命周期和啟動模式
- 1.1 Activity的生命周期全面分析
- 1.1.1 典型情況下的生命周期分析
- 1.1.2 異常情況下的生命周期分析
- 1.2 Activity的啟動模式
- 1.2.1 Activity的LaunchMode
- 1.2.2 Activity的Flags
- 1.3 IntentFilter的匹配規則
- 第2章 IPC機制
- 2.1 Android IPC簡介
- 2.2 Android中的多進程模式
- 2.2.1 開啟多進程模式
- 2.2.2 多進程模式的運行機制
- 2.3 IPC基礎概念介紹
- 2.3.1 Serializable接口
- 2.3.2 Parcelable接口
- 2.3.3 Binder
- 2.4 Android中的IPC方式
- 2.4.1 使用Bundle
- 2.4.2 使用文件共享
- 2.4.3 使用Messenger
- 2.4.4 使用AIDL
- 2.4.5 使用ContentProvider
- 2.4.6 使用Socket
- 2.5 Binder連接池
- 2.6 選用合適的IPC方式
- 第3章 View的事件體系
- 3.1 View基礎知識
- 3.1.1 什么是View
- 3.1.2 View的位置參數
- 3.1.3 MotionEvent和TouchSlop
- 3.1.4 VelocityTracker、GestureDetector和Scroller
- 3.2 View的滑動
- 3.2.1 使用scrollTo/scrollBy
- 3.2.2 使用動畫
- 3.2.3 改變布局參數
- 3.2.4 各種滑動方式的對比
- 3.3 彈性滑動
- 3.3.1 使用Scroller7
- 3.3.2 通過動畫
- 3.3.3 使用延時策略
- 3.4 View的事件分發機制
- 3.4.1 點擊事件的傳遞規則
- 3.4.2 事件分發的源碼解析
- 3.5 View的滑動沖突
- 3.5.1 常見的滑動沖突場景
- 3.5.2 滑動沖突的處理規則
- 3.5.3 滑動沖突的解決方式
- 第4章 View的工作原理
- 4.1 初識ViewRoot和DecorView
- 4.2 理解MeasureSpec
- 4.2.1 MeasureSpec
- 4.2.2 MeasureSpec和LayoutParams的對應關系
- 4.3 View的工作流程
- 4.3.1 measure過程
- 4.3.2 layout過程
- 4.3.3 draw過程
- 4.4 自定義View
- 4.4.1 自定義View的分類
- 4.4.2 自定義View須知
- 4.4.3 自定義View示例
- 4.4.4 自定義View的思想
- 第5章 理解RemoteViews
- 5.1 RemoteViews的應用
- 5.1.1 RemoteViews在通知欄上的應用
- 5.1.2 RemoteViews在桌面小部件上的應用
- 5.1.3 PendingIntent概述
- 5.2 RemoteViews的內部機制
- 5.3 RemoteViews的意義
- 第6章 Android的Drawable
- 6.1 Drawable簡介
- 6.2 Drawable的分類
- 6.2.1 BitmapDrawable2
- 6.2.2 ShapeDrawable
- 6.2.3 LayerDrawable
- 6.2.4 StateListDrawable
- 6.2.5 LevelListDrawable
- 6.2.6 TransitionDrawable
- 6.2.7 InsetDrawable
- 6.2.8 ScaleDrawable
- 6.2.9 ClipDrawable
- 6.3 自定義Drawable
- 第7章 Android動畫深入分析
- 7.1 View動畫
- 7.1.1 View動畫的種類
- 7.1.2 自定義View動畫
- 7.1.3 幀動畫
- 7.2 View動畫的特殊使用場景
- 7.2.1 LayoutAnimation
- 7.2.2 Activity的切換效果
- 7.3 屬性動畫
- 7.3.1 使用屬性動畫
- 7.3.2 理解插值器和估值器 /
- 7.3.3 屬性動畫的監聽器
- 7.3.4 對任意屬性做動畫
- 7.3.5 屬性動畫的工作原理
- 7.4 使用動畫的注意事項
- 第8章 理解Window和WindowManager
- 8.1 Window和WindowManager
- 8.2 Window的內部機制
- 8.2.1 Window的添加過程
- 8.2.2 Window的刪除過程
- 8.2.3 Window的更新過程
- 8.3 Window的創建過程
- 8.3.1 Activity的Window創建過程
- 8.3.2 Dialog的Window創建過程
- 8.3.3 Toast的Window創建過程
- 第9章 四大組件的工作過程
- 9.1 四大組件的運行狀態
- 9.2 Activity的工作過程
- 9.3 Service的工作過程
- 9.3.1 Service的啟動過程
- 9.3.2 Service的綁定過程
- 9.4 BroadcastReceiver的工作過程
- 9.4.1 廣播的注冊過程
- 9.4.2 廣播的發送和接收過程
- 9.5 ContentProvider的工作過程
- 第10章 Android的消息機制
- 10.1 Android的消息機制概述
- 10.2 Android的消息機制分析
- 10.2.1 ThreadLocal的工作原理
- 10.2.2 消息隊列的工作原理
- 10.2.3 Looper的工作原理
- 10.2.4 Handler的工作原理
- 10.3 主線程的消息循環
- 第11章 Android的線程和線程池
- 11.1 主線程和子線程
- 11.2 Android中的線程形態
- 11.2.1 AsyncTask
- 11.2.2 AsyncTask的工作原理
- 11.2.3 HandlerThread
- 11.2.4 IntentService
- 11.3 Android中的線程池
- 11.3.1 ThreadPoolExecutor
- 11.3.2 線程池的分類
- 第12章 Bitmap的加載和Cache
- 12.1 Bitmap的高效加載
- 12.2 Android中的緩存策略
- 12.2.1 LruCache
- 12.2.2 DiskLruCache
- 12.2.3 ImageLoader的實現446
- 12.3 ImageLoader的使用
- 12.3.1 照片墻效果
- 12.3.2 優化列表的卡頓現象
- 第13章 綜合技術
- 13.1 使用CrashHandler來獲取應用的crash信息
- 13.2 使用multidex來解決方法數越界
- 13.3 Android的動態加載技術
- 13.4 反編譯初步
- 13.4.1 使用dex2jar和jd-gui反編譯apk
- 13.4.2 使用apktool對apk進行二次打包
- 第14章 JNI和NDK編程
- 14.1 JNI的開發流程
- 14.2 NDK的開發流程
- 14.3 JNI的數據類型和類型簽名
- 14.4 JNI調用Java方法的流程
- 第15章 Android性能優化
- 15.1 Android的性能優化方法
- 15.1.1 布局優化
- 15.1.2 繪制優化
- 15.1.3 內存泄露優化
- 15.1.4 響應速度優化和ANR日志分析
- 15.1.5 ListView和Bitmap優化
- 15.1.6 線程優化
- 15.1.7 一些性能優化建議
- 15.2 內存泄露分析之MAT工具
- 15.3 提高程序的可維護性