Android-認識Bitmap
學習自
- Android開發藝術探索
例行廢話
在Android的各種APP中都被離不開各種各樣的圖片,有的圖片很大,有的圖片很小不管這樣圖片都是一種很吃記憶體的資源,而在Android中每個APP所持有的資源是非常有限的,所以我們要儘可能的“摳門”一點。本著能省則省的原則,有一個 300 x 300 的圖片現在在一個100 x 100 的ImageView中是一個完全不必要的事情。所以我們為了更節省資源和避免OOM,我們必須對圖片進行處理。在Android中 Bitmap
就代表一個圖片資源。
BitmapFactory和Options
我們記載點陣圖資源都是通過 BitmapFactory
進行載入的,這個類提供了以下的方法。
方法名 | 描述 |
---|---|
decodeResource | 從資源中載入圖片 |
decodeByteArray | 從byte陣列中載入圖片 |
decodeFile | 從檔案中載入圖片 |
decodeStream | 從流中載入資源 |
Options
Options類是BitmapFactory的一個巢狀類,所有的對Bitmap進行特殊處理的操作同時通過這個類的引數和屬性達成的。其中一個Bitmap所佔用的記憶體的大小是由下面的幾個引數影響的。
- inPreferredConfig Bitmap的色彩模式,不同的色彩模式,每個畫素所佔據的位元組的數量是不一樣的。
- inSampleSize 取樣率,取樣率越大,Bitmap的寬和高就越小,所佔用的記憶體也就越小。
inPreferredConfig
通過 inPreferredConfig 屬性可以設定Bitmap載入的色彩模式,主要有以下幾種色彩模式
- ALPHA_8 每個畫素佔據1byte
- ARGB_4444 每個畫素佔據2byte
- ARGB_8888 每個畫素佔據4byte
- RGB_565 每個畫素佔據2byte
ARGB_8888 的顯式效果最好,同時也是Android預設的色彩模式,但是也最為消耗記憶體。 假設一張1024 x 1024,模式為ARGB_8888的圖片,那麼它佔有的記憶體就是: 1024 x 1024 x 4 byte
//ARGB_8888 模式載入的Bitmap
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.wallbackground)
Log.e("TAG", "Width:${bitmap.width} Helight:${bitmap.height}")
Log.e("TAG", "Original:" + getBitmapSize(bitmap))
//Log資訊如下
//Width:5040 Helight:2835
//Size:57153600
//RGB_565 這裡本想使用 ARGB_4444 的測彩模式的,但是列印出來的一直卻和模式的ARGB_8888相同
//可能也跟具體的裝置有關係吧,所以這裡就是用了 RGB_565 的色彩模式來驗證一下不同的色彩模式
//佔中的記憶體大小不同
val options = BitmapFactory.Options()
options.inPreferredConfig = Bitmap.Config.RGB_565
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.wallbackground,options)
Log.e("TAG", "Width:${bitmap.width} Helight:${bitmap.height}")
Log.e("TAG", "Size:" + getBitmapSize(bitmap))
//Log資訊如下
//Width:5040 Helight:2835
//Size:28576800
fun getBitmapSize(bitmap: Bitmap): Int {
return bitmap.rowBytes * bitmap.height
}
複製程式碼
inSampleSize 取樣率
取樣率可以Bitmap記憶體優化的重頭戲,上面也提到了,當使用一個大圖載入到一個小圖中的時候這種情況是完全不需要的,比如說 400 x 400 圖片要顯示在 200 x 200 的ImageView上的時候,那麼只需要記載200 x 200 大小的圖片就行了,而如果直接載入 400 x 400 的圖片就憑白地浪費了一倍的空間。所以我們需要通過設定Bitmap的取樣率將圖片縮小。當然最後經過取樣後的圖片最好不要小於 ImageView 的大小,否則就會造成拉伸效果。
現假設要載入一個400 x 400 的圖片,取樣率預設是 1
也就是載入原圖,如果取樣變為 2
那麼 圖片的寬高都會 / 2 所以所佔據的記憶體也就只有原圖的 1/4(1/取樣率的平方) 了。取樣率越大圖片的寬高越小,佔用的記憶體也就越小。 在官方文件中推薦將取樣率設定為2的N次冪(1,2,3,8..),比如說如果取樣率設為 3
那麼取樣率就是 2
,但是這真是一個參考,在一些機型上的執行效果並不是如此。
NOTE:
- 經過取樣後的Bitmap的大小不應該小於 ImageView的大小。
- 關於取樣率的計算的最終結果並不會完全地精確,會有一些上下的浮動。
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.wallbackground)
Log.e("TAG", "Size:" + getBitmapSize(bitmap))
//Log: Size:57153600
val options = BitmapFactory.Options()
options.inSampleSize = 2
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.wallbackground, options)
Log.e("TAG", "Size:" + getBitmapSize(bitmap))
//Log: 3573360
fun getBitmapSize(bitmap: Bitmap): Int {
return bitmap.rowBytes * bitmap.height
}
//Log Size:14293440
複製程式碼
優化Bitmap載入
我想通過上面的介紹你已經大概知道了如果優化Bitmap了吧
- 在載入圖片的時候首先獲取到圖片的寬和高和ImageView的寬和高(在這一步並不會載入Bitmap僅僅會獲取Bitmap的寬和高)
- 根據圖片的寬和高來和ImageView的取樣率
- 以指定的取樣率載入Bitmap
頁面的佈局檔案如下
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="cn.shycoder.studybitmap.MainActivity">
<Button
android:id="@+id/btnLoadImg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="onClick"
android:text="Load Img" />
<ImageView
android:id="@+id/iv"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_gravity="center_horizontal" />
</LinearLayout>
複製程式碼
Activity 的程式碼
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
fun onClick(view: View) {
loadImageFromResource(iv, R.drawable.wallbackground)
}
/**
* 從資源中得到一個合適大小的Bitmap
* */
private fun loadImageFromResource(imageView: ImageView, resId: Int) {
Log.e("TAG", "ImageView Width:${imageView.width} Height: ${imageView.height}")
//1. 獲取Bitmap的寬高
val options = BitmapFactory.Options()
// 通過設定此選項,載入Bitmap的時候,僅僅會獲取寬和高並不會真正地載入Bitmap
options.inJustDecodeBounds = true
BitmapFactory.decodeResource(this.resources, resId, options)
//2. 計算對應的圖片的取樣率
val inSampleSize = this.calculateInSampleSize(options, imageView.width, imageView.height)
//3.根據取樣率載入圖片
//記得取消這個選項
options.inJustDecodeBounds = false
options.inSampleSize = inSampleSize
val bitmap = BitmapFactory.decodeResource(this.resources, resId, options)
imageView.setImageBitmap(bitmap)
}
private fun calculateInSampleSize(options: BitmapFactory.Options, requireWidth: Int,
requireHeight: Int): Int {
val width = options.outWidth
val height = options.outHeight
var inSampleSize = 1
//如果Bitmap的大小是大於ImageView的大小的
if (width > requireWidth && height > requireHeight) {
//取樣率的增加
while ((width / (inSampleSize + 1) > requireWidth)
&& (height / (inSampleSize + 1) > requireHeight)) {
inSampleSize += 1
}
}
return inSampleSize
}
}
複製程式碼
圖片對比
// 原圖所佔記憶體:60466176
// 經過取樣壓縮後:15148960:
複製程式碼