1. 程式人生 > >Android有效避免程式OOM-圖片壓縮和三級快取

Android有效避免程式OOM-圖片壓縮和三級快取

前言

我們都知道現在的手機應用APP真的是給我們的生活帶來了巨大的便利,應用中的圖片也是精美絕倫特別好看,並且隨著科技的進步,相機的解析度也越來越高了,手機拍出來的照片可能達到十幾兆很正常,圖片這麼大,在實際的開發過程中,還經常會遇到圖片的載入等消耗記憶體比較大的情況,因為圖片看起來更加生動形象,引人注意,給使用者一種視覺衝擊,效果自然就好。雖然手機現在效能越來越好了,但是因為圖片的載入,特別是圖片比較多的時候仍然是一個非常耗費記憶體的工作,這樣的話我們就不得不考慮如何解決圖片載入消耗記憶體的的問題了,如何解決效能圖片記憶體佔用情況呢?

  • 使用圖片壓縮技術

一個ImageView顯示一張圖片,特別是當ImageView的尺寸比較小的時候,圖片比較大的時候,這時候圖片的大小對視覺效果影響不大,但是這個時候圖片的大小對記憶體佔用影響就大了,我們需要對圖片進行壓縮顯示縮圖即可。BitmapFactory這個類提供了豐富多彩的API供我們使用
這裡寫圖片描述

BitmapFactory有多種生成Bitmap的方式,其中根據不同的情景有decodeByteArray,decodeFile,decodeResource,decodeStream等多種方式生成Bitmap物件,其中decodeByteArray方法可以對圖片的位元組資料進行處理,decodeFile可以根據圖片的路徑處理,decodeResource根據資原始檔中的檔案進行,decodeStream根據流進行處理,比如網路中的圖片。

BitmapFactory.Options這個類就比較有意思了,其中這個類有一個布林值引數inJustDecodeBounds,當inJustDecodeBounds等於true時候,解析程式並不載入當前的圖片進入記憶體,返回的Bitmap等於NULL,雖然Bitmap為空,但是能夠從options中獲取到當前圖片的各種屬性資訊,比如圖片的寬高等。當inJustDecodeBounds為false時候就一切正常,圖片載入進入記憶體了。我們可以根據這一點在圖片載入進入記憶體前對圖片進行壓縮處理,然後將inJustDecodeBounds=false,進行顯示即可。

我這裡寫好了一個圖片的壓縮類,如下,首先設定options.inJustDecodeBounds=true,這樣的話資源就不會載入進入記憶體中,但是我們可以獲取到圖片的寬高資訊,對圖片進行壓縮處理。

需要對圖片的壓縮比例做一個說明,假如我們得到的圖片寬高是500*400畫素,目標寬高是100*100,那麼壓縮比例如果是按照寬度的5來壓縮的話,那麼高度就會變成400/5=80畫素,小於目標的寬高導致圖片失真,如果按照高度的壓縮比4來壓縮的話,圖片是125*100滿足要求。所以這裡圖片的壓縮比要選擇比率比較小的一個進行壓縮。

等獲取到圖片的壓縮比例之後options.inJustDecodeBounds = false,這樣就能夠獲取到Bitmap物件了,返回此物件到方法的呼叫處即可。

public class ImageCompress {
    /**
     * 根據需要的圖片寬高和圖片的位元組陣列獲取Bitmap物件
     * @param bs     圖片資源
     * @param width  目標寬
     * @param height 目標高
     * @return
     */
    public static Bitmap getBitmap(byte[] bs, int width, int height) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        //inJustDecodeBounds = true時,資源不載入進入記憶體,獲取圖片寬高等資訊對圖片進行處理
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeByteArray(bs, 0, bs.length, options);
        //獲取圖片的壓縮比例
        options.inSampleSize = getCalcSize(options, width, height);
        options.inJustDecodeBounds = false;
        Bitmap bitmap = BitmapFactory.decodeByteArray(bs, 0, bs.length, options);
        return bitmap;
    }

    /**
     * 獲取圖片的壓縮比例
     * @param options
     * @param picwidth
     * @param picheight
     * @return
     */
    private static int getCalcSize(BitmapFactory.Options options, int picwidth, int picheight) {
        //原始比例
        int calcSize = 1;
        int width = options.outWidth;
        int height = options.outHeight;
        if (width > picheight || height > picheight) {
            int widthRate = Math.round((float) width / picwidth);
            int HeightRate = Math.round((float) height / picheight);
            //這裡根據三目運算子 返回的應該是壓縮比例比較小的一個比例,
            //因為這樣的話能夠保證寬高都是大於或者等於目標寬高,不會低於目標寬高導致失真
            calcSize = widthRate > HeightRate ? HeightRate : widthRate;
            return calcSize;
        }
        return 0;
    }
}
  • 圖片的三級快取處理技術

上面使用了圖片的壓縮技術,使得單張圖片進行了壓縮,但是當我們在ListView或者GridView中載入圖片太多的時候,程式佔用總記憶體會隨著圖片的增加而增加,最終導致記憶體溢位,所以僅僅圖片壓縮是不夠的,還是會出現記憶體溢位的情況。

下面設想一種場景,當我們在ListView中滑動載入圖片,有的圖片已經劃出螢幕了,我們就得考慮到圖片的回收問題,使用GC回收,但是當我們又劃回來的時候又得考慮到圖片的重新載入,再次下載的話肯定是不僅浪費使用者流量,還還降低效能,這個時候使用三級快取技術就會提升效能不少。我們這裡的三級快取一般包含三個層級,首先是記憶體快取,然後是SD卡快取,最後是網路下載。下面我們一個一個來說。

1 記憶體快取

記憶體快取主要是LruCache類,這個類的演算法原理是最近最少使用演算法,即把最近經常使用的物件使用強的引用快取到一個LinkedHashMap中,把最近最少使用的物件在快取值達到峰值之前移除記憶體。我們需要設定一個合適的快取大小作為程式的快取,如果過大會導致OOM,過小就會頻繁的回收和建立物件,降低程式的效能。

或許有的小夥伴會說到使用弱引用或者軟引用也可以在記憶體缺乏時候釋放資源啊,但是因為從API9開始GC垃圾回收器更加傾向於回收弱引用和軟引用的物件,這樣的話可能正在使用時候因為記憶體不夠就將資源回收了,豈不是使用者體驗很差了嘛,所以不建議使用這種方式。

下面是使用LruCache快取的類,我們一般設定為當前程式的八分之一作為快取大小,這個設定的值不宜多大不宜過小,上面已經說過原因了。一般情況下系統給我們的程式分配的記憶體大小是16M,在高配置手機中會有32M的記憶體,那麼在八分之一的快取就會有4M大小。


public class MemoryCache {
    private LruCache<String, Bitmap> mLruCache;
    //當前程式分配的記憶體大小
    private int memorySize = 0;
    //快取的記憶體大小
    private int cacheSize = 0;

    public MemoryCache() {
        memorySize = (int) Runtime.getRuntime().maxMemory();
        //設定快取大小為程式記憶體的八分之一
        cacheSize = memorySize / 8;
        mLruCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                // return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
                return bitmap.getByteCount() / 1024;
            }
        };
    }

    /**
     * 向LruCache中新增Bitmap
     *
     * @param url
     * @param bitmap
     */
    public void addBitmapToLruCache(String url, Bitmap bitmap) {
        if (!TextUtils.isEmpty(url)) {
            if (bitmap != null) {
                mLruCache.put(url, bitmap);
            }
        }
    }

    /**
     * 從LruCache中獲取圖片Bitmap
     *
     * @param url
     * @return
     */
    public Bitmap getBitmapFromLruCache(String url) {
        if (!TextUtils.isEmpty(url) && mLruCache.get(url) != null) {
            return mLruCache.get(url);
        }
        return null;
    }

    /**
     * 刪除某一個Bitmap
     *
     * @param key
     */
    public synchronized void removeBitmapFromLruCache(String key) {
        if (key != null) {
            Bitmap bitmap = mLruCache.get(key);
            if (bitmap != null) {
                bitmap.recycle();
            }
        }
    }

    /**
     * 清除LruCache中的內容
     */
    public void clear() {
        if (mLruCache.size() > 0) {
            mLruCache.evictAll();
        }
        mLruCache = null;
    }
}

2 SD卡快取

因為我們的記憶體中會因為程式資源不足而被清除掉,這個時候如果我們想要在不重新下載圖片的基礎上快速顯示圖片的話,我們就要進行SD卡快取了,SD卡的快取策略其實比較簡單,下面一個類有註解,比較簡單,主要的就是新增方法addFileToSDCard和獲取方法getFileFromSDCard。

public class SDCardCache {
    private static final String TAG = "aaa";
    private static final String SDPATH = Environment.getExternalStorageDirectory().getAbsolutePath();
    //SD卡是否掛載正常
    private boolean isMounted = false;
    //SD卡快取的目錄檔案
    private String file = "fileCache";
    //快取的總目錄
    private File dirFile = null;

    /**
     * 構造方法中檢查SD卡掛載情況 檢查建立快取目錄
     */
    public SDCardCache() {
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
            isMounted = true;
            dirFile = new File(SDPATH, file);
            if (!dirFile.exists()) {
                dirFile.mkdirs();
            }
        } else {
            Log.i(TAG, "SDCardCache: sdcard出錯了");
        }
    }

    /**
     * 向SD卡中新增圖片
     *
     * @param bs  圖片資源
     * @param url 圖片的url地址
     */
    public void addFileToSDCard(byte[] bs, String url) {
        if (isMounted) {
            if (!dirFile.exists()) {
                return;
            }
            String fileName = url.substring(url.indexOf("/") + 1);
            try {
                File file = new File(dirFile, fileName);
                Log.i(TAG, "addFileToSDCard: " + file.getAbsolutePath());
                FileOutputStream outputStream = new FileOutputStream(file);
                outputStream.write(bs, 0, bs.length);
                outputStream.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 從SD卡中獲取圖片
     *
     * @param url
     * @return
     */
    public Bitmap getFileFromSDCard(String url) {
        Bitmap bitmap = null;
        if (isMounted && !TextUtils.isEmpty(url)) {
            String fileName = url.substring(url.indexOf("/") + 1);
            File file = new File(dirFile, fileName);
            bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
            return bitmap;
        }
        return null;
    }

    /**
     * 從SD卡中移除某一個固定的圖片
     *
     * @param url
     * @return
     */
    private boolean removeFromSDCard(String url) {
        if (isMounted && !TextUtils.isEmpty(url)) {
            String fileName = url.substring(url.indexOf("/") + 1);
            File file = new File(dirFile, fileName);
            if (file.exists()) {
                return file.delete();
            }
        }
        return false;
    }

    /**
     * 清除檔案中的快取
     */
    private void chear() {
        if (isMounted) {
            File[] files = dirFile.listFiles();
            for (File file : files) {
                file.delete();
            }
        }
    }
}

3 網路獲取

網路下載就不應該叫做快取了,應該叫做下載,這裡我使用了執行緒池和介面回撥。

public class WebCache {
    /**
     * 根據url從網路下載,這裡使用了執行緒池和介面回撥,因為等我們獲取到資源時候還不一定立馬進行處理,在回撥中處理
     *
     * @param poolExecutor
     * @param url
     * @param callback
     */
    public void getWebCache(ThreadPoolExecutor poolExecutor, final String url, final CallBacks callback) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    byte[] bs = getBytes(url);
                    if (bs != null) {
                        callback.getResult(bs);
                    } else {
                        Log.i("aaa", "run: 獲取資料為空");
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };
        poolExecutor.execute(runnable);
    }

    /**
     * 通過流的方式獲取圖片的二進位制資料
     *
     * @param stringUrl
     * @return
     */
    private byte[] getBytes(String stringUrl) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

        try {
            URL url = new URL(stringUrl);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            //設定主機讀取時間超時
            connection.setReadTimeout(10000);
            //設定主機連線時間超時
            connection.setConnectTimeout(10000);
            //設定請求方法為GET
            connection.setRequestMethod("GET");
            //設定以後可以使用conn.getInputStream().read();
            connection.setDoInput(true);
            //連線
            connection.connect();
            if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
                int len = 0;
                byte[] bytes = new byte[1024];
                InputStream inputStream = connection.getInputStream();
                while ((len = inputStream.read(bytes)) != -1) {
                    outputStream.write(bytes, 0, len);
                    outputStream.flush();
                }
                if (inputStream != null) {
                    inputStream.close();
                }
                return outputStream.toByteArray();
            }

        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    interface CallBacks {
        void getResult(byte[] bs);
    }
}

4 三級快取CacheManager類

具體的使用場景是這樣的,如果我們有一個圖片的url地址,首先會檢查記憶體快取中是否快取了這個圖片,如果有的話就獲取顯示,沒的話就會走下一步SD卡快取;如果SD卡快取中有這個資源的話就獲取顯示,然後快取到記憶體快取中,如果沒有的話就走下一個網路快取;如果走進了網路快取的話,直接通過網路下載,然後分別快取進入記憶體快取和SD卡快取中即可,CacheManager類我是用了最常用的單例模式,如下

public class CacheManager {
    private MemoryCache mMemoryCache = new MemoryCache();
    private SDCardCache mSDCardCache = new SDCardCache();
    private WebCache mWebCache = new WebCache();
    private Handler mHandler = new Handler();
    private ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 10, 3, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(128));

    /**
     * 構造方法私有化
     */
    private CacheManager() {
    }

    /**
     * 單例模式
     * @return
     */
    public static CacheManager getInstance() {
        return SingleTonHolder.INSTANCE;
    }

    private static class SingleTonHolder {
        private static final CacheManager INSTANCE = new CacheManager();
    }

    public void getCache(final String url, final ImageView imageView) {
        if (mMemoryCache.getBitmapFromLruCache(url) != null) {
            Log.i("aaa", "1 記憶體中執行了。。。");
            imageView.setImageBitmap(mMemoryCache.getBitmapFromLruCache(url));
        } else if (mSDCardCache.getFileFromSDCard(url) != null) {
            Log.i("aaa", "2 SD卡中執行了。。。");
            imageView.setImageBitmap(mSDCardCache.getFileFromSDCard(url));
            mMemoryCache.addBitmapToLruCache(url, mSDCardCache.getFileFromSDCard(url));
        } else {
            Log.i("aaa", "3 網路下載資料執行了");
            mWebCache.getWebCache(poolExecutor, url, new WebCache.CallBacks() {
                @Override
                public void getResult(byte[] bs) {
                    final Bitmap bitmap = ImageCompress.getBitmap(bs, 400, 400);
                    mSDCardCache.addFileToSDCard(Bitmap2Bytes(bitmap), url);
                    mMemoryCache.addBitmapToLruCache(url, bitmap);
                    mHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            imageView.setImageBitmap(bitmap);
                            Log.i("aaa", "run: " + "圖片顯示了");
                        }
                    });
                }
            });
        }
    }

    public byte[] Bitmap2Bytes(Bitmap bm) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        bm.compress(Bitmap.CompressFormat.PNG, 100, baos);
        return baos.toByteArray();
    }
}

5 三級快取的使用

上面講解了那麼多的圖片壓縮和三級快取,下面終於到了最後的使用階段了,其實使用是最最簡單的了。如果想要顯示一張圖片到ImageView中的話,只需要一句話就可呼叫,我們比較一下和Glide的使用方式,是不是有點相似呢。

public void onClick(View view) {
        /**
         * 三級快取呼叫方式
         */
        CacheManager.getInstance().getCache(imageUrl3, (ImageView) findViewById(R.id.image));
        /**
         * Glide的呼叫方式
         */
        //Glide.with(this).load(imageUrl3).into((ImageView) findViewById(R.id.image));
    }

總結:有了上面的圖片壓縮策略和三級快取策略,無論是載入單張或者多張圖片,就不用擔心程式OOM了。(完)

相關推薦

Android有效避免程式OOM-圖片壓縮三級快取

前言 我們都知道現在的手機應用APP真的是給我們的生活帶來了巨大的便利,應用中的圖片也是精美絕倫特別好看,並且隨著科技的進步,相機的解析度也越來越高了,手機拍出來的照片可能達到十幾兆很正常,圖片這麼大,在實際的開發過程中,還經常會遇到圖片的載入等

Android高效載入大圖、多圖解決方案,有效避免程式OOM

本篇文章主要內容來自於Android Doc,我翻譯之後又做了些加工,英文好的朋友也可以直接去讀原文。高效載入大圖片我們在編寫Android程式的時候經常要用到許多圖片,不同圖片總是會有不同的形狀、不同的大小,但在大多數情況下,這些圖片都會大於我們程式所需要的大小。比如說系統

Android高效載入大圖、多圖解決方案,有效避免程式OOM .

高效載入大圖片 我們在編寫Android程式的時候經常要用到許多圖片,不同圖片總是會有不同的形狀、不同的大小,但在大多數情況下,這些圖片都會大於我們程式所需要的大小。比如說系統圖片庫裡展示的圖片大都是用手機攝像頭拍出來的,這些圖片的解析度會比我們手機螢幕的解析度高得多。大家應該知道,我們編寫的應用程式都

Android高效加載大圖、多圖解決方案,有效避免程序OOM

view idv alc ash ces cal android手機 ons 多圖 前言:Android手機分配給給個應用的內存空間都是有限的,當圖片像素>屏幕像素時,會造成內存浪費,嚴重時更會造成oom,當圖片像素<屏幕像素時,又會導致展示出來的圖片失真,因此

34模塊-zip【文件壓縮和解壓、圖片壓縮編輯】

rotate border title onclick scale max line htm port Zip模塊管理文件壓縮和解壓,通過plus.zip可獲取壓縮管理對象。比較常用的就是 對圖片進行壓縮、轉碼、旋轉操作了 <!DOCTYPE html><

android基於libjpeg-turbo的圖片壓縮框架

Light a lightweight image compress framework for Android based on libJpeg. 一個基於libJpeg的壓縮圖片框架, 支援配合rxjava使用。 可以一行程式碼解決圖片下載->壓縮->顯

Android性能優化之圖片壓縮優化

bit nat nsa lose 3.2 透明度 之間 修復 基準 1 分類Android圖片壓縮結合多種壓縮方式,常用的有尺寸壓縮、質量壓縮、采樣率壓縮以及通過JNI調用libjpeg庫來進行壓縮。 參考此方法:Android-BitherCompress 備註:對於資源

Android有效地展示大圖片(三)

圖片快取 只下載一張圖片在你的UI上時非常簡單的。但是如果你需要一次性下很多圖片就不這麼容易了。在很多情況下(比如ListView,GridView或者是ViewPager),要展示在螢幕上的圖片加上即將要展示的圖片,這個數量可是沒有什麼大小限制的。 以上提到的控制元件,為了

Android 有效的展示大圖片(四)

下面是Android對bitmap的記憶體管理的進化過程: 在Android2.2之前的版本中,當垃圾回收執行緒開始時,你的app的執行緒就會掛起。這就會導致使用者體驗降級。Android2.3之後使得垃圾回收機制可以併發執行。這也就意味著當一個bitmap沒有指向自己的引用時,可以被垃圾回收機

Android--圖片載入處理(記憶體溢位三級快取

最簡單的解決辦法,用現成的框架,推薦glide和picasso用法:在build.gradle中加入:repositories { mavenCentral() maven { url 'https://maven.google.com' } } dependenc

Android中利用Picasso實現圖片壓縮指定任意尺寸

之前做專案時,有個需求是指定照片壓縮到任意寬高尺寸上傳給伺服器。當時我自己寫了個圖片壓縮方法,但是不夠完美,小問題不斷(比如OOM之類的)。後來看到了神器Picasso不光能載入網路圖片,還能以任意尺寸載入本地圖片。於是我想,既然Picasso能任意尺寸載入本地圖片,那它肯

Android:拍照功能及將圖片壓縮存入指定路徑的方法

package com.example.administrator.testapplication; import android.app.Activity; import android.content.Intent; import android.graphics.B

Android 取得應用程式的啟動次數執行時間等資訊

使用情景:最近有個需求是統計後臺應用執行時間,如果一個應用在後臺執行超過一定時間就Kill掉程序,達到省電的目的。此時就可以使用PkgUsageStats這個類來實現啦!通過com.android.internal.os.PkgUsageStats這個類可以得到一個應用

Android第三方開源庫:圖片壓縮

CompressHelper 原圖: 許可權: <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permissi

jquery 前端實現圖片壓縮上傳

        手機端上傳圖片時,有時候圖片會是一張比較大的圖片,上傳一張的大的圖片會消耗比較大的資源影響效率,這個時候就需要對上傳的圖片進行壓縮了。然而圖片的壓縮有很多種的實現方式,我這裡主要是通過畫布,拆分瓦片的形式來壓縮圖片。   (這個主要為個人筆記記錄)    

Android圖片中的三級快取, 為什麼要使用三級快取

    如今的 Android App 經常會需要網路互動,通過網路獲取圖片是再正常不過的事了 假如每次啟動的時候都從網路拉取圖片的話,勢必會消耗很多流量。在當前的狀況下,對於非wifi使用者來說,

利用LruCacheDiskLruCache使用圖片載入的三級快取

在上一篇文章當中,我們學習了DiskLruCache的概念和基本用法,但僅僅是掌握理論知識顯然是不夠的,那麼本篇文章我們就來繼續進階一下,看一看在實戰當中應該怎樣合理使用DiskLruCache。還不熟悉DiskLruCache用法的朋友可以先去參考我的上一篇文章

圖片二級取樣,三級快取

private ImageView imageView; //handler更新UI介面 Handler handler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMess

RecyclerView解決條目錯亂以及圖片閃越+三級快取機制

RecyclerView導致條目錯亂的原因:viewHolder的複用,一個複用的ViewHolder他裡邊的View有些屬性已經被修改了,所以新的item在使用服用的viewHolder時,那些被修改的viewHolder裡邊的屬性還依然存在,所以會導致新的item也應用

淺談圖片載入的三級快取(一)

之前被人問及過,圖片的三級快取是什麼啊,來給我講講,圖片三級快取,好高大尚的名字,聽著挺厲害,應該是很厲害的技術,當時不會啊,也就沒什麼了,沒有說出來唄,前一階端用到了BitmapUtils的圖片快取框架,索性就自己找些知識點來研究一些圖片的三級快取是什麼吧。真