Bitmap是Android中一種重要的圖片處理機制,它能夠用來獲取圖片的相關信息,同時能夠對圖片進行裁剪、縮放等操做,也能夠指定圖片格式進行保存。相信對於OOM再熟悉不過了,OOM的產生是一個很是頭疼的事情,若是在加載圖片的時候未對大圖進行處理,它將會佔用很是大的內存,這樣就很是容易產生OOM。因此咱們必需要有意識的對大圖進行壓縮加載,這樣才能更好的保證App的正常運行與性能的穩定。android
那麼若是計算一張圖片加載過程當中所佔的內存大小呢?在這以前,咱們先來了解一下關於Bitmap兩個主要配置。git
這是用來指定Bitmap的圖片的壓縮格式,在Bitmap中是一個Enum結構,主要表現爲如下三種格式。github
public enum CompressFormat {
JPEG (0),
PNG (1),
WEBP (2);
CompressFormat(int nativeInt) {
this.nativeInt = nativeInt;
}
final int nativeInt;
}
複製代碼
這是關於Bitmap像素存儲的方式配置,不一樣的像素存儲,對圖片的質量也會有不一樣的影響。在Bitmap中是一個Enum結構,主要表現於如下四種格式。web
因此若是爲了防止OOM對圖片進行壓縮,通常會使用RGB_565格式,由於ALPHA_8只有透明度,對於正常圖片未意義;ARGB_4444顯示的圖片質量太差;ARGB_8888佔用的內存最多。算法
若是加載的圖片的寬度爲1080、高度爲67五、Config爲ARGB_8888。那麼它佔的內存爲:1080 x 675 x 4 = 2916000.折算成M爲2916000 / 1024 / 1024 = 2.78M。一種圖片就近3M,若是加載10張或者100張,所佔的內存可想而知。這樣的話會很容易將內存消耗殆盡,同時對於Android App來講根本就不須要這麼高清的圖片,因此咱們在加載圖片的時候能夠對其進行相應的處理,例如:對寬高進行縮放,亦或者將Config改我RGB_565。這樣不只有效的減少了內存的佔用,同時也不影響圖片的清晰度的展現。api
下面咱們來看下如何經過Bitmap與BitmapFactory來對圖片進行處理數組
對於使用Bitmap進行圖片的壓縮處理,它主要提供瞭如下有效方法。bash
status return | method name |
---|---|
boolean | compress(Bitmap.CompressFormat format, int quality, OutputStream stream) |
static Bitmap | createBitmap(DisplayMetrics display, int[] colors, int width, int height, Bitmap.Config config) |
static Bitmap | createBitmap(DisplayMetrics display, int[] colors, int offset, int stride, int width, int height, Bitmap.Config config) |
static Bitmap | createBitmap(Bitmap source, int x, int y, int width, int height) |
static Bitmap | createBitmap(Bitmap src) |
static Bitmap | createBitmap(DisplayMetrics display, int width, int height, Bitmap.Config config) |
static Bitmap | createBitmap(Bitmap source, int x, int y, int width, int height, Matrix m, boolean filter) |
static Bitmap | createBitmap(int width, int height, Bitmap.Config config) |
static Bitmap | createBitmap(int[] colors, int offset, int stride, int width, int height, Bitmap.Config config) |
static Bitamp | createBitmap(int[] colors, int width, int height, Bitmap.Config config) |
static Bitmap | createScaledBitmap(Bitmap src, int dstWidth, int dstHeight, boolean filter) |
compress方法經過指定圖片的CompressFormat格式與壓縮百分比來對圖片進行壓縮處理,同時將壓縮的圖片保存到指定的outputStream中。咱們來看下具體用法app
val fs = FileOutputStream(path)
val out = ByteArrayOutputStream()
scaleBitmap.compress(Bitmap.CompressFormat.JPEG, 30, out)
LogUtils.d("compressBitmap jpeg of byteCount %d.", out.toByteArray().size)
fs.write(out.toByteArray())
fs.close()
複製代碼
在這裏要注意quality表明百分比,值爲0~100,值越小壓縮的後的大小就越小。例如上面的示例,quality爲30,則表明對原圖進行壓縮70%,保留30%。同時Bitmap.CompressFormat在前面已經詳細介紹了,要注意不一樣的格式圖片的展現效果也不一樣,例如JPEG的格式圖片是沒有透明度的。ide
特別注意的:對於Bitmap.CompressFormat.PNG類型的格式,quality將失去效果,由於其格式是無損的壓縮;再者,使用compress方法並非對顯示處理的圖片進行了壓縮,它只是對原圖進行壓縮後保存到本地磁盤中,並不改變顯示的圖片。主要用途做用於保存圖片到本地,以便下次加載,減少圖片在本地磁盤所佔的磁盤大小。
對於createBitmap方法,Bitmap中提供了9種不一樣數據源的壓縮處理方法。分別有經過colors數組、Bitmap與DisplayMetrics等來決定。
例如使用colors數組
val colors = intArrayOf(Color.RED, Color.GREEN, Color.BLUE,
Color.GREEN, Color.BLUE, Color.RED,
Color.BLUE, Color.RED, Color.GREEN)
val displayMetricsBitmap = Bitmap.createBitmap(DisplayMetrics(),colors,3, 3,Bitmap.Config.ARGB_8888)
LogUtils.d("displayMetricsBitmap of byteCount %d and rowBytes %d", displayMetricsBitmap.byteCount, displayMetricsBitmap.rowBytes)
複製代碼
這裏建立了一個3 x 3的圖片,Config爲ARGB_8888,因此最終圖片在內存中的大小爲3 x 3 x 4 = 36字節。而圖片的展現效果是經過colors數組中的顏色也實現的,3 x 3 = 9 分別對應colors中的9個像素點的色值。因此colors的大小最小必須大於等於9,即寬*高的大小。爲什麼說最小,由於Bitmap還提供了offset與stride參數的重載方法。這兩個參數分別表明在colors中的開始點的偏移量與取值的步伐,即每一個取值點間的跨度。
在實際是使用createBitmap最多的仍是用它的Bitmap重載方法,主要用來對原圖片進行裁剪。咱們直接看它的使用方式:
//bitmap
val bitmapBitmap = Bitmap.createBitmap(scaleBitmap, 150, 0, 100, 100)
image_view?.setImageBitmap(scaleBitmap)
image_view_text.text = "width: " + scaleBitmap.width + " height: " + scaleBitmap.height
sub_image_view.setImageBitmap(bitmapBitmap)
sub_image_view_text.text = "startX: 150 startY: 0\n" + "width: " + bitmapBitmap.width + " height: " + bitmapBitmap.height
複製代碼
主要參數是原Bitmap,咱們全部的操做都是在原Bitmap中進行的。其中x = 150、y = 0表明從原Bitmap中的座標(150,0)開始進行裁剪;width = 100、height = 100,裁剪後返回新的的Bitmap,且大小爲100 x 100。
if (!source.isMutable() && x == 0 && y == 0 && width == source.getWidth() &&
height == source.getHeight() && (m == null || m.isIdentity())) {
return source;
}
複製代碼
注意,若是原Bitmap是不可變的,同時須要的圖參數與原圖片相同,那麼它會直接返回原Bitmap。是否可變能夠經過Bitmap.isMutable判斷。
看下上面的代碼所展現的效果圖:
最後經過傳遞Bitmap參數還有一個可選參數Matrix,它主要用於對Bitmap進行矩陣變換。
該方法相對上面兩種就簡單多了,它目的是對原Bitmap進行指定的寬高進行縮放,最終返回新的Bitmap。
注意:若是傳入的寬高與原Bitmap相同,它將返回原Bitmap對象。
//createScaledBitmap
val createScaledBitmap = Bitmap.createScaledBitmap(scaleBitmap, 500, 300, false)
image_view?.setImageBitmap(scaleBitmap)
image_view_text.text = "width: " + scaleBitmap.width + " height: " + scaleBitmap.height
sub_image_view.setImageBitmap(createScaledBitmap)
sub_image_view_text.text = "width: " + createScaledBitmap.width + " height: " + createScaledBitmap.height
複製代碼
再來看下其實現源碼
public static Bitmap createScaledBitmap(@NonNull Bitmap src, int dstWidth, int dstHeight,
boolean filter) {
Matrix m = new Matrix();
final int width = src.getWidth();
final int height = src.getHeight();
if (width != dstWidth || height != dstHeight) {
final float sx = dstWidth / (float) width;
final float sy = dstHeight / (float) height;
m.setScale(sx, sy);
}
return Bitmap.createBitmap(src, 0, 0, width, height, m, filter);
}
複製代碼
一目瞭然,內部就是使用到了Matrix,運用Matrix的知識進行寬高縮放;最後再調用前面所分析的createBitmap方法。只不過這裏指定了初始點(0,0)而已,再傳入原Bitmap的寬高與生成的Matrix。所以createBitmap方法中的注意點也是應用到createScaledBitmap中。
BitmapFactory主要用來解碼Bitmap,經過不一樣的資源類型,例如:files、streams與byte-arrays。既然是繼續資源的解碼,天然能夠在解碼的過程當中進行一些圖片壓縮處理。來看下它提供的主要解碼方法。
status return | method name |
---|---|
static Bitmap | decodeByteArray(byte[] data, int offset, int length, BitmapFactory.Options opts) |
static Bitmap | decodeByteArray(byte[] data, int offset, int length) |
static Bitmap | decodeFile(String pathName) |
static Bitmap | decodeFile(String pathName, BitmapFactory.Options opts) |
static Bitmap | decodeFileDescriptor(FileDescriptor fd) |
static Bitmap | decodeFileDescriptor(FileDescriptor fd, Rect outPadding, BitmapFactory.Options opts) |
static Bitmap | decodeResource(Resources res, int id, BitmapFactory.Options opts) |
static Bitmap | decodeResource(Resources res, int id) |
static Bitmap | decodeResourceStream(Resources res, TypedValue value, InputStream is, Rect pad, BitmapFactory.Options opts) |
static Bitmap | decodeStream(InputStream is) |
static Bitmap | decodeStream(InputStream is, Rect outPadding, BitmapFactory.Options opts) |
直接經過方法名就能很方便的分辨出使用哪一種資源類型進行圖片解碼操做。例如:decodeByteArray方法是經過byte數組做爲解析源,同時在解碼過程當中能夠經過設置offset與length來控制解碼的起始點與解碼的大小。所以若是可以精確控制offset與length也就可以作到圖片的裁剪效果。decodeFileDescriptor方法是經過文件描述符進行解碼Bitmap。通常用不到,下面詳細分析幾種經常使用的方法。
該方法是經過文件路徑來解碼出Bitmap
//decodeFile
val decodeFileOptions = BitmapFactory.Options()
val decodeFileBitmap = BitmapFactory.decodeFile(mRootPath +"bitmap", decodeFileOptions)
decodeFileOptions.inSampleSize = 2
val decodeFileScaleBitmap = BitmapFactory.decodeFile(mRootPath + "bitmap", decodeFileOptions)
image_view?.setImageBitmap(decodeFileBitmap)
image_view_text.text = "width: " + decodeFileBitmap.width + " height: " + decodeFileBitmap.height
sub_image_view.setImageBitmap(decodeFileScaleBitmap)
sub_image_view_text.text = "width: " + decodeFileScaleBitmap.width + " height: " + decodeFileScaleBitmap.height
複製代碼
下面的圖片比上面的圖片寬高都縮小了一半,對圖片進行了壓縮操做。經過代碼發現,下面的圖片解碼時設置了
decodeFileOptions.inSampleSize = 2
複製代碼
這裏就涉及到了靜態內部類BitmapFactory.Options,能夠看上面的表發現大多數方法都有這個參數,它是一個可選項。主要用途是在圖片解碼過程當中對圖片的原有屬性進行修改。它的參數配置大多數以in前綴開頭,下面列舉一些經常使用的配置設置屬性。
type | name | description |
---|---|---|
boolean | inJustDecodeBounds | 若是爲true,解碼後不會返回Bitmap對象,但Bitmap寬高將返回到options.outWidth與options.outHeight中;反之返回。主要用於只需獲取解碼後的Bitmap的大小。 |
boolean | inMutable | 爲true,表明返回可變屬性的Bitmap,反之不可變 |
boolean | inPreferQualityOverSpeed | 爲true,將在解碼過程當中犧牲解碼的速度來獲取更高質量的Bitmap |
Bitmap.Config | inPreferredConfig | 根據指定的Config來進行解碼,例如:Bitmap.Config.RGB_565等 |
int | inSampleSize | 若是值大於1,在解碼過程當中將按比例返回佔更小內存的Bitmap。例如值爲2,則對寬高進行縮放一半。 |
boolean | inScaled | 若是爲true,且inDesity與inTargetDensity都不爲0,那麼在加載過程當中將會根據inTargetDensityl來縮放,在drawn中不依靠於圖片自身的縮放屬性。 |
int | inDensity | Bitmap自身的密度 |
int | inTargetDensity | Bitmap drawn過程當中使用的密度 |
咱們在來看下decodeFile的源碼
public static Bitmap decodeFile(String pathName, Options opts) {
validate(opts);
Bitmap bm = null;
InputStream stream = null;
try {
stream = new FileInputStream(pathName);
bm = decodeStream(stream, null, opts);
} catch (Exception e) {
/* do nothing.
If the exception happened on open, bm will be null.
*/
Log.e("BitmapFactory", "Unable to decode stream: " + e);
} finally {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
// do nothing here
}
}
}
return bm;
}
複製代碼
內部根據文件路徑建立FileInputStream,最終調用decodeStream方法進解碼圖片。
至於decodeStream內部則是根據不一樣的InputStream類型調用不一樣的native方法。若是爲AssetManager.AssetInputStrea類型則調用
nativeDecodeAsset(asset, outPadding, opts);
複製代碼
不然調用
nativeDecodeStream(is, tempStorage, outPadding, opts);
複製代碼
還有對應的decodeResourceStream方法內部也是調用了decodeStream.
public static Bitmap decodeResourceStream(Resources res, TypedValue value,
InputStream is, Rect pad, Options opts) {
validate(opts);
if (opts == null) {
opts = new Options();
}
if (opts.inDensity == 0 && value != null) {
final int density = value.density;
if (density == TypedValue.DENSITY_DEFAULT) {
opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
} else if (density != TypedValue.DENSITY_NONE) {
opts.inDensity = density;
}
}
if (opts.inTargetDensity == 0 && res != null) {
opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
}
return decodeStream(is, pad, opts);
}
複製代碼
decodeResourceStream內部作了對Bitmap的密度適配,最後再調用decodeStream,這樣decodeStream也分析完畢。
decodeResource用法也很簡單,傳入相應本地的資源文件便可,須要縮放的話配置options參數。
val options = BitmapFactory.Options()
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.yaodaoji, options)
複製代碼
隨便看下它的源碼
public static Bitmap decodeResource(Resources res, int id, Options opts) {
validate(opts);
Bitmap bm = null;
InputStream is = null;
try {
final TypedValue value = new TypedValue();
is = res.openRawResource(id, value);
bm = decodeResourceStream(res, value, is, null, opts);
} catch (Exception e) {
/* do nothing.
If the exception happened on open, bm will be null.
If it happened on close, bm is still valid.
*/
} finally {
try {
if (is != null) is.close();
} catch (IOException e) {
// Ignore
}
}
if (bm == null && opts != null && opts.inBitmap != null) {
throw new IllegalArgumentException("Problem decoding into existing bitmap");
}
return bm;
}
複製代碼
經過res.openRawResource(id, value)來獲取InputStream,最後再調用decodeResourceStream(res, value, is, null, opts)方法。這樣就簡單了,又回到了上面分析的方法中去了。
下面來作個總結,對於圖片壓縮主要使用到Bitmap與BitmapFactory這兩個類,同時在使用這兩個類以前也要對Bitmap中的CompressFormat與Config;BitmapFactory中的BitmapFactory.Options有所瞭解。而後再結合他們中的方法進行相應的壓縮、裁剪操做。其實只要掌握幾個經常使用的方法在平常的使用中就足夠了。
最後若有有不足之處,但願指出!