編輯:Android編程入門
在Android的開發中,我們經常回去處理一些圖片相關的問題,比如當加載圖片到內存中產生的OOM(OutOfMemory)異常、圖片加載到內存中占多大內存的問題、jpg png兩種常見的圖片的原理及區別。
圖片加載到內存所占內存大小的問題
在講OOM異常前需要對圖片的加載有所了解,所以在這裡就先介紹圖片加載的問題。
圖片加載到內存中的大小,不是直接由圖片的存儲大小來決定的。比如一個10k大小的png格式的圖片加載到內存可能就不止10k了。那應該怎麼計算呢?
圖片加載到內存中的大小=圖片的寬×圖片的高×該圖片一個像素所占的位數/8
舉個例子:一個1024*1024像素的圖片,每個像素是32位,那麼他的大小就是1024×1024×32÷8=4M。通常圖片保存成jpg、png格式是經過壓縮處理的,它的存儲大小可能就只有幾k。這就是為什麼我們在加載一個10多k的圖片是會出現OOM異常的原因。
加載較大的圖片
在展示高分辨率圖片的時候,最好先將圖片進行壓縮。壓縮後的圖片大小應該和用來展示它的控件大小相近,在一個很小的ImageView上顯示一張超大的圖片不會帶來任何視覺上的好處,但卻會占用很多的內存,而且在性能上還可能會帶來負面影響。下面我們就來看一看,如何對一張大圖片進行適當的壓縮,讓它能夠以最佳大小顯示的同時,還能防止OOM的出現。
BitmapFactory這個類提供了多個解析方法(decodeByteArray, decodeFile, decodeResource等)用於創建Bitmap對象,我們應該根據圖片的來源選擇合適的方法。比如SD卡中的圖片可以使用decodeFile方法,網絡上的圖片可以使用decodeStream方法,資源文件中的圖片可以使用decodeResource方法。這些方法會嘗試為已經構建的bitmap分配內存,這時就會很容易導致OOM出現。為此每一種解析方法都提供了一個可選的BitmapFactory.Options參數,將這個參數的inJustDecodeBounds屬性設置為true就可以讓解析方法禁止為bitmap分配內存,返回值也不再是一個Bitmap對象,而是null。雖然Bitmap是null了,但是BitmapFactory.Options的outWidth、outHeight和outMimeType屬性都會被賦值。這個技巧讓我們可以在加載圖片之前就獲取到圖片的長寬值和MIME類型,從而根據情況對圖片進行壓縮。如下代碼所示:
BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(getResources(), R.id.myimage, options); int imageHeight = options.outHeight; int imageWidth = options.outWidth; String imageType = options.outMimeType;
在加載圖片時,最好每次都先檢查一下圖片的大小,除非你能確保這個圖片不會導致OOM異常。
通過上面的代碼我們能得到圖片的大小,下面我們來對圖片進行壓縮處理。通過設置BitmapFactory.Options中inSampleSize的值就可以實現。比如我們有一張2048*1536像素的圖片,將inSampleSize的值設置為4,就可以把這張圖片壓縮成512*384像素。原本加載這張圖片需要占用13M的內存,壓縮後就只需要占用0.75M了(假設圖片是ARGB_8888類型,即每個像素點占用4個字節)。下面的方法可以根據傳入的寬和高,計算出合適的inSampleSize值:
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { // 源圖片的高度和寬度 final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { // 計算出實際寬高和目標寬高的比率 final int heightRatio = Math.round((float) height / (float) reqHeight); final int widthRatio = Math.round((float) width / (float) reqWidth); // 選擇寬和高中最小的比率作為inSampleSize的值,這樣可以保證最終圖片的寬和高 // 一定都會大於等於目標的寬和高。 inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio; } return inSampleSize; }
使用這個方法,首先你要將BitmapFactory.Options的inJustDecodeBounds屬性設置為true,解析一次圖片。然後將BitmapFactory.Options連同期望的寬度和高度一起傳遞到到calculateInSampleSize方法中,就可以得到合適的inSampleSize值了。之後再解析一次圖片,使用新獲取到的inSampleSize值,並把inJustDecodeBounds設置為false,就可以得到壓縮後的圖片了。
大量圖片的緩存處理
在你應用程序的UI界面加載一張圖片是一件很簡單的事情,但是當你需要在界面上加載一大堆圖片的時候,情況就變得復雜起來。在很多情況下,(比如使用ListView, GridView 或者 ViewPager 這樣的組件),屏幕上顯示的圖片可以通過滑動屏幕等事件不斷地增加,最終導致OOM。
為了保證內存的使用始終維持在一個合理的范圍,通常會把被移除屏幕的圖片進行回收處理。此時垃圾回收器也會認為你不再持有這些圖片的引用,從而對這些圖片進行GC操作。用這種思路來解決問題是非常好的,可是為了能讓程序快速運行,在界面上迅速地加載圖片,你又必須要考慮到某些圖片被回收之後,用戶又將它重新滑入屏幕這種情況。這時重新去加載一遍剛剛加載過的圖片無疑是性能的瓶頸,你需要想辦法去避免這個情況的發生。
這個時候,使用內存緩存技術可以很好的解決這個問題,它可以讓組件快速地重新加載和處理圖片。下面我們就來看一看如何使用內存緩存技術來對圖片進行緩存,從而讓你的應用程序在加載很多圖片的時候可以提高響應速度和流暢性。
我們可以使用LruCache來處理這個問題,例子如下:
private LruCache<String, Bitmap> mMemoryCache; @Override protected void onCreate(Bundle savedInstanceState) { // 獲取到可用內存的最大值,使用內存超出這個值會引起OutOfMemory異常。 // LruCache通過構造函數傳入緩存值,以KB為單位。 int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); // 使用最大可用內存值的1/8作為緩存的大小。 int cacheSize = maxMemory / 8; mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { // 重寫此方法來衡量每張圖片的大小,默認返回圖片數量。 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); }
另外,在github上有個很好用的解決大量圖片緩存的框架—xUtils,其中BitmapUtils模塊就是用來解決這個問題的。
Android - 內容提供者(Content Provider)內容提供者組件通過請求從一個應用程序向其他的應用程序提供數據。這些請求由類 Conten
每一個Android應用在啟動的時候都會創建一個線程,這個線程被稱為主線程或者UI線程,Android應用的所有操作默認都會運行在這個線程中。但是當我們想要進行數據請求,
前言 這是“基礎自測”系列的第三篇文章,以Android開發需要熟悉的20個技術點為切入點,本
1. ViewRoot ViewRoot是連接WindowManager與DecorView的紐帶,View的整個繪制流程的三大步(