Android學習之內存優化(一)—— 圖片處理


在Android應用里,最耗費內存的就是圖片資源。而且在Android系統中,讀取位圖Bitmap時,分給虛擬機中的圖片的堆棧大小只有8M,如果超出了,就會出現OutOfMemory異常。所以,對於圖片的內存優化,是Android應用開發中比較重要的內容。
 
Bitmap類的構造方法都是私有的,所以開發者不能直接new出一個Bitmap對象,只能通過BitmapFactory類的各種靜態方法來實例化一個Bitmap。
 
對於圖片,內存優化中有兩個手段,一是減少圖片本身所占的內存、二是緩存經常使用的圖片,避免重復創建Bitmap文件,增加內存的開支。
 一、減少
   下面來看看幾個處理圖片的方法:

 

  圖片顯示:

  我們需要根據需求去加載圖片的大小。

  例如在列表中僅用於預覽時加載縮略圖(thumbnails )。

  只有當用戶點擊具體條目想看詳細信息的時候,這時另啟動一個fragment/activity/對話框等等,去顯示整個圖片

 

  圖片大小:

  直接使用ImageView顯示bitmap會占用較多資源,特別是圖片較大的時候,可能導致崩潰。 
  使用BitmapFactory.Options設置inSampleSize, 這樣做可以減少對系統資源的要求。 
  屬性值inSampleSize表示縮略圖大小為原始圖片大小的幾分之一,即如果這個值為2,則取出的縮略圖的寬和高都是原始圖片的1/2,圖片大小就為原始大小的1/4。 

 

        BitmapFactory.Options bitmapFactoryOptions = new BitmapFactory.Options(); bitmapFactoryOptions.inJustDecodeBounds = true; bitmapFactoryOptions.inSampleSize = 2; // 這里一定要將其設置回false,因為之前我們將其設置成了true // 設置inJustDecodeBounds為true后,decodeFile並不分配空間,即,BitmapFactory解碼出來的Bitmap為Null,但可計算出原始圖片的長度和寬度 
        options.inJustDecodeBounds = false; Bitmap bmp = BitmapFactory.decodeFile(sourceBitmap, options); 

 

  圖片像素:

  Android中圖片有四種屬性,分別是:
  ALPHA_8:每個像素占用1byte內存 
  ARGB_4444:每個像素占用2byte內存 
  ARGB_8888:每個像素占用4byte內存 (默認)
  RGB_565:每個像素占用2byte內存 
 
  Android默認的顏色模式為ARGB_8888,這個顏色模式色彩最細膩,顯示質量最高。但同樣的,占用的內存也最大。 所以在對圖片效果不是特別高的情況下使用  RGB_565(565沒有透明度屬性),如下:
 
               
 publicstaticBitmapreadBitMap(Contextcontext, intresId) { BitmapFactory.Optionsopt = newBitmapFactory.Options(); opt.inPreferredConfig = Bitmap.Config.RGB_565; opt.inPurgeable = true; opt.inInputShareable = true; //獲取資源圖片 
            InputStreamis = context.getResources().openRawResource(resId); returnBitmapFactory.decodeStream(is, null, opt); }
 
               

 

 
              

  圖片回收:

  使用Bitmap過后,就需要及時的調用Bitmap.recycle()方法來釋放Bitmap占用的內存空間,而不要等Android系統來進行釋放。

  下面是釋放Bitmap的示例代碼片段。

        // 先判斷是否已經回收
        if(bitmap != null && !bitmap.isRecycled()){
            // 回收並且置為null
            bitmap.recycle();
            bitmap = null;
        }
        System.gc();

  捕獲異常:

  經過上面這些優化后還會存在報OOM的風險,所以下面需要一道最后的關卡——捕獲OOM異常:

        Bitmap bitmap = null;
        try {
            // 實例化Bitmap
            bitmap = BitmapFactory.decodeFile(path);
        } catch (OutOfMemoryError e) {
            // 捕獲OutOfMemoryError,避免直接崩潰
        }
        if (bitmap == null) {
            // 如果實例化失敗 返回默認的Bitmap對象
            return defaultBitmapMap;
        }


二、緩存

  Bitmap緩存分為兩種:

  一種是內存緩存,一種是硬盤緩存。

 

  內存緩存(LruCache):

  以犧牲寶貴的應用內存為代價,內存緩存提供了快速的Bitmap訪問方式。系統提供的LruCache類是非常適合用作緩存Bitmap任務的,它將最近被引用到的對象存儲在一個強引用的LinkedHashMap中,並且在緩存超過了指定大小之后將最近不常使用的對象釋放掉。

  注意:以前有一個非常流行的內存緩存實現是SoftReference(軟引用)或者WeakReference(弱引用)的Bitmap緩存方案,然而現在已經不推薦使用了。自Android2.3版本(API Level 9)開始,垃圾回收器更着重於對軟/弱引用的回收,這使得上述的方案無效。

 

  硬盤緩存(DiskLruCache):

  一個內存緩存對加速訪問最近瀏覽過的Bitmap非常有幫助,但是你不能 局限於內存中的可用圖片。GridView這樣有着更大的數據集的組件可以很輕易消耗掉內存緩存。你的應用有可能在執行其他任務(如打電話)的時候被打 斷,並且在后台的任務有可能被殺死或者緩存被釋放。一旦用戶重新聚焦(resume)到你的應用,你得再次處理每一張圖片。

在這種情況下,硬盤緩存可以用來存儲Bitmap並在圖片被內存緩存釋放后減小圖片加載的時間(次數)。當然,從硬盤加載圖片比內存要慢,並且應該在后台線程進行,因為硬盤讀取的時間是不可預知的。

  注意:如果訪問圖片的次數非常頻繁,那么ContentProvider可能更適合用來存儲緩存圖片,例如Image Gallery這樣的應用程序。

三、一些好用的開源框架

  1、 Android-Universal-Image-Loader 圖片緩存(Git地址:https://github.com/nostra13/Android-Universal-Image-Loader)

         這個開源庫存在的特征:

  1. 多線程下載圖片,圖片可以來源於網絡,文件系統,項目文件夾assets中以及drawable中等
  2. 支持隨意的配置ImageLoader,例如線程池,圖片下載器,內存緩存策略,硬盤緩存策略,圖片顯示選項以及其他的一些配置
  3. 支持圖片的內存緩存,文件系統緩存或者SD卡緩存
  4. 支持圖片下載過程的監聽
  5. 根據控件(ImageView)的大小對Bitmap進行裁剪,減少Bitmap占用過多的內存
  6. 較好的控制圖片的加載過程,例如暫停圖片加載,重新開始加載圖片,一般使用在ListView,GridView中,滑動過程中暫停加載圖片,停止滑動的時候去加載圖片
  7. 提供在較慢的網絡下對圖片進行加載

    在使用之前,我們先來了解一下Android-Universal-Image-Loader中的三大組件:ImageLoaderConfigurationImageLoader、DisplayImageOptions

      博客(http://www.cnblogs.com/kissazi2/p/3886563.html)中有對這三者的詳細解讀。

        ImageLoaderConfiguration是針對圖片緩存的全局配置,主要有線程類、緩存大小、磁盤大小、圖片下載與解析、日志方面的配置。

        ImageLoader是具體下載圖片,緩存圖片,顯示圖片的具體執行類,它有兩個具體的方法displayImage(...)、loadImage(...),但是其實最終他們的實現都是displayImage(...)。

        DisplayImageOptions用於指導每一個Imageloader根據網絡圖片的狀態(空白、下載錯誤、正在下載)顯示對應的圖片,是否將緩存加載到磁盤上,下載完后對圖片進行怎么樣的處理。

   下面是我的項目中實際使用到的例子:

ImgConfig .java(在這個文件中對img加載屬性進行了統一的配置)
/**
圖片配置文件


*/
public
class ImgConfig { public static void initImgConfig(Context context) { File cacheDir =new File(StorageUtil.getDirByType(context, StorageUtil.TYPE_IMG_CACHE_DIR));
//ImageLoaderConfiguration是針對圖片緩存的全局配置,主要有線程類、緩存大小、磁盤大小、圖片下載與解析、日志方面的配置。 ImageLoaderConfiguration config
= new ImageLoaderConfiguration.Builder( context) .memoryCacheExtraOptions(480, 800) // max width, max height,即保存的每個緩存文件的最大長寬 .threadPoolSize(3) // 線程池內加載的數量 .threadPriority(Thread.NORM_PRIORITY - 2) .denyCacheImageMultipleSizesInMemory() .memoryCache(new UsingFreqLimitedMemoryCache(2 * 1024 * 1024)) // You can pass your own memory cache // 將保存的時候的URI名稱用MD5 加密 .tasksProcessingOrder(QueueProcessingType.LIFO) // 緩存的文件數量 .diskCache(new UnlimitedDiskCache(cacheDir)) // 自定義緩存路徑 .defaultDisplayImageOptions(DisplayImageOptions.createSimple()) .imageDownloader( new BaseImageDownloader(context, 5 * 1000, 30 * 1000)) .writeDebugLogs() // Remove for release app .build();// 開始構建 ImageLoader.getInstance().init(config); } //人物頭像的加載
//DisplayImageOptions用於指導每一個Imageloader根據網絡圖片的狀態(空白、下載錯誤、正在下載)顯示對應的圖片,是否將緩存加載到磁盤上,下載完后對圖片進行怎么樣的處理。
public static DisplayImageOptions getPortraitOption(){ DisplayImageOptions options = new DisplayImageOptions.Builder() .showImageOnLoading(R.drawable.default_portrait_female_little) //加載圖片時的圖片 .showImageForEmptyUri(R.drawable.default_portrait_female_little) //沒有圖片資源時的默認圖片 .showImageOnFail(R.drawable.default_portrait_female_little) //加載失敗時的圖片 .cacheInMemory(true) //啟用內存緩存 .cacheOnDisk(true) //啟用外存緩存 .considerExifParams(true) //啟用EXIF和JPEG圖像格式 .build(); return options; } //人物頭像的加載 public static DisplayImageOptions getPortraitLargeOption(){ DisplayImageOptions options = new DisplayImageOptions.Builder() .showImageOnLoading(R.drawable.default_portrait_fmale_large) //加載圖片時的圖片 .showImageForEmptyUri(R.drawable.default_portrait_fmale_large) //沒有圖片資源時的默認圖片 .showImageOnFail(R.drawable.default_portrait_fmale_large) //加載失敗時的圖片 .cacheInMemory(true) //啟用內存緩存 .cacheOnDisk(true) //啟用外存緩存 .considerExifParams(true) //啟用EXIF和JPEG圖像格式 .build(); return options; } //大圖的加載 public static DisplayImageOptions getBigImgOption(){ DisplayImageOptions options = new DisplayImageOptions.Builder() .showImageOnLoading(R.drawable.default_big_img) //加載圖片時的圖片 .showImageForEmptyUri(R.drawable.default_big_img) //沒有圖片資源時的默認圖片 .showImageOnFail(R.drawable.default_big_img) //加載失敗時的圖片 .cacheInMemory(true) //啟用內存緩存 .cacheOnDisk(true) //啟用外存緩存 .considerExifParams(true) //啟用EXIF和JPEG圖像格式 .displayer(new RoundedBitmapDisplayer(20)) //設置顯示風格這里是圓角矩形 .build(); return options; } //相冊的加載 public static DisplayImageOptions getAlbumImgOption(){ DisplayImageOptions options = new DisplayImageOptions.Builder() .showImageOnLoading(R.drawable.default_album) //加載圖片時的圖片 .showImageForEmptyUri(R.drawable.default_album) //沒有圖片資源時的默認圖片 .showImageOnFail(R.drawable.default_album) //加載失敗時的圖片 .cacheInMemory(true) //啟用內存緩存 .cacheOnDisk(true) //啟用外存緩存 .considerExifParams(true) //啟用EXIF和JPEG圖像格式 .bitmapConfig(Config.RGB_565) //設置圖片編碼格式 .build(); return options; } //相冊的加載 public static DisplayImageOptions getAlbumImgDefOption(){ DisplayImageOptions options = new DisplayImageOptions.Builder() .showImageOnLoading(R.drawable.default_album) //加載圖片時的圖片 .showImageForEmptyUri(R.drawable.default_album) //沒有圖片資源時的默認圖片 .showImageOnFail(R.drawable.default_album) //加載失敗時的圖片 .cacheInMemory(false) //啟用內存緩存 .cacheOnDisk(false) //啟用外存緩存 .considerExifParams(true) //啟用EXIF和JPEG圖像格式 .bitmapConfig(Config.RGB_565) .build(); return options; } //Card圖片的加載 public static DisplayImageOptions getCardImgOption(){ DisplayImageOptions options = new DisplayImageOptions.Builder() .showImageOnLoading(R.drawable.default_big_img) //加載圖片時的圖片 .showImageForEmptyUri(R.drawable.default_big_img) //沒有圖片資源時的默認圖片 .showImageOnFail(R.drawable.default_big_img) //加載失敗時的圖片 .cacheInMemory(false) //啟用內存緩存 .cacheOnDisk(true) //啟用外存緩存 .considerExifParams(true) //啟用EXIF和JPEG圖像格式 .bitmapConfig(Config.ARGB_8888) .build(); return options; } //BannerCard圖片的加載 public static DisplayImageOptions getBannerImgOption(){ DisplayImageOptions options = new DisplayImageOptions.Builder() .showImageOnLoading(R.drawable.default_banner_img) //加載圖片時的圖片 .showImageForEmptyUri(R.drawable.default_banner_img) //沒有圖片資源時的默認圖片 .showImageOnFail(R.drawable.default_banner_img) //加載失敗時的圖片 .cacheInMemory(true) //啟用內存緩存 .cacheOnDisk(true) //啟用外存緩存 .considerExifParams(true) //啟用EXIF和JPEG圖像格式 .build(); return options; } }

 

使用:

 

//配置application

public
void initConfig(BeautyDiaryApplication application) { synchronized (sLock) { this.application = application; context = application.getBaseContext(); packageName = context.getPackageName(); versionCode = getVersionCode(context, BeautyDiaryApplication.class); versionName = getVersionName(context, BeautyDiaryApplication.class); imei = Util.getImei(getBaseContext()); try { sLock.notifyAll(); } catch (Exception e) { } ImgConfig.initImgConfig(application); } }
//加載、展示圖片
//第一個參數: 圖片url
//第二個參數: 要設置在哪個view上
//第三個參數: 加載圖片配置(imgconfig中的方法)

ImageLoader.getInstance().displayImage(StorageUtil.getPid2Url(entity.getPortrait(), StorageUtil.PIC_TYPE_LARGE),
portraitIv,ImgConfig.getPortraitLargeOption());

2、 Android 網絡通信框架Volley

項目地址:https://android.googlesource.com/platform/frameworks/volley
我們在程序中需要和網絡通信的時候,大體使用的東西莫過於AsyncTaskLoader,HttpURLConnection,AsyncTask,HTTPClient(Apache)等,在2013年的Google I/O發布了Volley。Volley是Android平台上的網絡通信庫,能使網絡通信更快,更簡單,更健壯。
特點:
(1)JSON,圖像等的異步下載;
(2)網絡請求的排序(scheduling)
(3)網絡請求的優先級處理
(4)緩存
(5)多級別取消請求
(6)和Activity和生命周期的聯動(Activity結束時同時取消所有網絡請求)
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM