Android加載大圖長圖方案簡析

0190828104408.png

本文只是簡要分析安卓端自帶壓縮與加載方案

1,高效加載加載大圖

展示高分辨率圖片的時候,最好先將圖片進行壓縮。

BitmapFactory這個類提供了多個解析方法(decodeByteArray, decodeFile, decodeResource等)用于創建Bitmap對象,我們應該根據圖片的來源選擇合適的方法。比如SD卡中的圖片可以使用decodeFile方法,網絡上的圖片可以使用decodeStream方法,資源文件中的圖片可以使用decodeResource方法。

色彩模式->色彩模式是數字世界中表示顏色的一種算法,在Bitmap里用Config來表示。

ARGB_8888:每個像素占四個字節,A、R、G、B 分量各占8位,是 Android 的默認設置;

RGB_565:每個像素占兩個字節,R分量占5位,G分量占6位,B分量占5位;

ARGB_4444:每個像素占兩個字節,A、R、G、B分量各占4位,成像效果比較差;

Alpha_8: 只保存透明度,共8位,1字節;

安卓自帶壓縮方案流程

1.比例大小壓縮

BitmapFactory每一種解析方法都提供了一個可選的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;

決定是把整張圖片加載到內存中還是加載一個壓縮版的圖片到內存中。以下幾個因素是我們需要考慮的:

  • 預估一下加載整張圖片所需占用的內存。

  • 為了加載這一張圖片你所愿意提供多少內存。

  • 用于展示這張圖片的控件的實際大小。

  • 當前設備的屏幕尺寸和分辨率。

通過設置BitmapFactory.Options中inSampleSize的值就可以實現。比如我們有一張20481536像素的圖片,將inSampleSize的值設置為4,就可以把這張圖片壓縮成512384像素。原本加載這張圖片需要占用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,就可以得到壓縮后的圖片了。

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
        int reqWidth, int reqHeight) {
    // 第一次解析將inJustDecodeBounds設置為true,來獲取圖片大小
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);
    // 調用上面定義的方法計算inSampleSize值
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
    // 使用獲取到的inSampleSize值再次解析圖片
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
}

下面的代碼非常簡單地將任意一張圖片壓縮成100*100的縮略圖,并在ImageView上展示。

mImageView.setImageBitmap(
    decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));

2.質量壓縮

所用方法與類與比例壓縮基本相同

質量壓縮:根據傳遞進去的質量大小,采用系統自帶的壓縮算法,將圖片壓縮成JPEG格式

    private Bitmap compressImage(Bitmap image) {

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        image.compress(Bitmap.CompressFormat.JPEG, 100, baos);//質量壓縮方法,這里100表示不壓縮,把壓縮后的數據存放到baos中
        int options = 100;
        while ( baos.toByteArray().length / 1024>100) { //循環判斷如果壓縮后圖片是否大于100kb,大于繼續壓縮     
            baos.reset();//重置baos即清空baos
            image.compress(Bitmap.CompressFormat.JPEG, options, baos);//這里壓縮options%,把壓縮后的數據存放到baos中
            options -= 10;//每次都減少10
        }
        ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());//把壓縮后的數據baos存放到ByteArrayInputStream中
        Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);//把ByteArrayInputStream數據生成圖片
        return bitmap;
    }

使用圖片壓縮庫進行壓縮

JNI使用Jpeg庫

Android和IOS 中圖片處理使用了一個叫做skia的開源圖形處理引擎。他位于android源碼的/external/skia 目錄。我們平時在java層使用一個圖片處理的函數實際上底層就是調用了這個開源引擎中的相關的函數。

android在進行jpeg壓縮編碼的時候,考慮到了效率問題使用了定長編碼方式進行編碼(因為當時的手機性能都比較低),而IOS使用了變長編碼的算法——哈夫曼算法。而且IOS對skia引擎也做了優化。所有我們看到同樣的圖片在ios上壓縮會好一點。

1、下載開源的libjpeg,進行移植、編譯得到libjpeg.so
2、使用jni編寫一個函數用來圖片壓縮
3、在函數中添加一個開關選項,可以讓我們選擇是否使用哈夫曼算法。
4、打包,搞成sdk供我們以后使用。

具體可查看Android使用libjpeg實現圖片壓縮

Luban魯班壓縮

Luban魯班壓縮

可能是最接近微信朋友圈的圖片壓縮算法

接近微信朋友圈壓縮后的效果,具體看以下對比!

內容 原圖 Luban Wechat
截屏 720P 720*1280,390k 720*1280,87k 720*1280,56k
截屏 1080P 1080*1920,2.21M 1080*1920,104k 1080*1920,112k
拍照 13M(4:3) 3096*4128,3.12M 1548*2064,141k 1548*2064,147k
拍照 9.6M(16:9) 4128*2322,4.64M 1032*581,97k 1032*581,74k
滾動截屏 1080*6433,1.56M 1080*6433,351k 1080*6433,482k

github上算法邏輯的介紹拷貝過來了:

  1. 判斷圖片比例值,是否處于以下區間內;
  • [1, 0.5625) 即圖片處于 [1:1 ~ 9:16) 比例范圍內
  • [0.5625, 0.5) 即圖片處于 [9:16 ~ 1:2) 比例范圍內
  • [0.5, 0) 即圖片處于 [1:2 ~ 1:∞) 比例范圍內
  1. 判斷圖片最長邊是否過邊界值;
  • [1, 0.5625) 邊界值為:1664 * n(n=1), 4990 * n(n=2), 1280 * pow(2, n-1)(n≥3)
  • [0.5625, 0.5) 邊界值為:1280 * pow(2, n-1)(n≥1)
  • [0.5, 0) 邊界值為:1280 * pow(2, n-1)(n≥1)
  1. 計算壓縮圖片實際邊長值,以第2步計算結果為準,超過某個邊界值則:width / pow(2, n-1),height/pow(2, n-1)
  2. 計算壓縮圖片的實際文件大小,以第2、3步結果為準,圖片比例越大則文件越大。
    size = (newW * newH) / (width * height) * m;
  • [1, 0.5625) 則 width & height 對應 1664,4990,1280 * n(n≥3),m 對應 150,300,300;
  • [0.5625, 0.5) 則 width = 1440,height = 2560, m = 200;
  • [0.5, 0) 則 width = 1280,height = 1280 / scale,m = 500;注:scale為比例值
  1. 判斷第4步的size是否過小
  • [1, 0.5625) 則最小 size 對應 60,60,100
  • [0.5625, 0.5) 則最小 size 都為 100
  • [0.5, 0) 則最小 size 都為 100
  1. 將前面求到的值壓縮圖片 width, height, size 傳入壓縮流程,壓縮圖片直到滿足以上數值

使用圖片緩存技術

為了能夠選擇一個合適的緩存大小給LruCache, 有以下多個因素應該放入考慮范圍內,例如:

  • 你的設備可以為每個應用程序分配多大的內存?
  • 設備屏幕上一次最多能顯示多少張圖片?有多少圖片需要進行預加載,因為有可能很快也會顯示在屏幕上?
  • 你的設備的屏幕大小和分辨率分別是多少?一個超高分辨率的設備(例如 Galaxy Nexus) 比起一個較低分辨率的設備(例如 Nexus S),在持有相同數量圖片的時候,需要更大的緩存空間。
  • 圖片的尺寸和大小,還有每張圖片會占據多少內存空間。
  • 圖片被訪問的頻率有多高?會不會有一些圖片的訪問頻率比其它圖片要高?如果有的話,你也許應該讓一些圖片常駐在內存當中,或者使用多個LruCache 對象來區分不同組的圖片。
  • 你能維持好數量和質量之間的平衡嗎?有些時候,存儲多個低像素的圖片,而在后臺去開線程加載高像素的圖片會更加的有效。

下面是一個使用 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);
}

在這個例子當中,使用了系統分配給應用程序的八分之一內存來作為緩存大小。在中高配置的手機當中,這大概會有4兆(32/8)的緩存空間。一個全屏幕的 GridView 使用4張 800x480分辨率的圖片來填充,則大概會占用1.5兆的空間(8004804)。因此,這個緩存大小可以存儲2.5頁的圖片。

public void loadBitmap(int resId, ImageView imageView) {
    final String imageKey = String.valueOf(resId);
    final Bitmap bitmap = getBitmapFromMemCache(imageKey);
    if (bitmap != null) {
        imageView.setImageBitmap(bitmap);
    } else {
        imageView.setImageResource(R.drawable.image_placeholder);
        BitmapWorkerTask task = new BitmapWorkerTask(imageView);
        task.execute(resId);
    }
}

BitmapWorkerTask 還要把新加載的圖片的鍵值對放到緩存中。

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
    // 在后臺加載圖片。
    @Override
    protected Bitmap doInBackground(Integer... params) {
        final Bitmap bitmap = decodeSampledBitmapFromResource(
                getResources(), params[0], 100, 100);
        addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);
        return bitmap;
    }
}

2,加載加載長圖,不壓縮

那么對于這種需求,該如何做呢?

首先不壓縮,按照原圖尺寸加載,那么屏幕肯定是不夠大的,并且考慮到內存的情況,不可能一次性整圖加載到內存中,所以肯定是局部加載,那么就需要用到一個類:

  • BitmapRegionDecoder

其次,既然屏幕顯示不完,那么最起碼要添加一個上下左右拖動的手勢,讓用戶可以拖動查看。

BitmapRegionDecoder

BitmapRegionDecoder主要用于顯示圖片的某一塊矩形區域

BitmapRegionDecoder bitmapRegionDecoder =  BitmapRegionDecoder.newInstance(inputStream, false);

顯示指定的區域

bitmapRegionDecoder.decodeRegion(rect, options);

參數一很明顯是一個rect,參數二是BitmapFactory.Options,你可以控制圖片的inSampleSize,inPreferredConfig等。

下面列出核心代碼塊

 try
        {
            InputStream inputStream = getAssets().open("tangyan.jpg");
 
            //獲得圖片的寬、高
            BitmapFactory.Options tmpOptions = new BitmapFactory.Options();
            tmpOptions.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(inputStream, null, tmpOptions);
            int width = tmpOptions.outWidth;
            int height = tmpOptions.outHeight;
 
            //設置顯示圖片的中心區域
            BitmapRegionDecoder bitmapRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inPreferredConfig = Bitmap.Config.RGB_565;
            Bitmap bitmap = bitmapRegionDecoder.decodeRegion(new Rect(width / 2 - 100, height / 2 - 100, width / 2 + 100, height / 2 + 100), options);
            mImageView.setImageBitmap(bitmap);
 
 
        } catch (IOException e)
        {
            e.printStackTrace();
        }

上述代碼,就是使用BitmapRegionDecoder去加載assets中的圖片,調用bitmapRegionDecoder.decodeRegion解析圖片的中間矩形區域,返回bitmap,最終顯示在ImageView上。

自定義顯示大圖控件

根據上面的分析呢,我們這個自定義控件思路就非常清晰了:

  • 提供一個設置圖片的入口
  • 重寫onTouchEvent,在里面根據用戶移動的手勢,去更新顯示區域的參數
  • 每次更新區域參數后,調用invalidate,onDraw里面去regionDecoder.decodeRegion拿到bitmap,去draw

自定義View及示例代碼

洋神的博客分享

文章參考

Android 高清加載長圖或大圖方案

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,967評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,273評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,870評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,742評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,527評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,010評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,108評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,250評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,769評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,656評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,853評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,371評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,103評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,472評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,717評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,487評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,815評論 2 372

推薦閱讀更多精彩內容

  • 摘要:對android 上圖片壓縮,其實總結起來基本可以分為兩類壓縮:尺寸壓縮和質量壓縮, 尺寸壓縮其實也可以理解...
    男爵是只貓丶閱讀 8,806評論 2 14
  • 1、高效加載大圖片 我們在編寫Android程序的時候經常要用到許多圖片,不同圖片總是會有不同的形狀、不同的大小,...
    閑庭閱讀 4,772評論 0 8
  • 7.1 壓縮圖片 一、基礎知識 1、圖片的格式 jpg:最常見的圖片格式。色彩還原度比較好,可以支持適當壓縮后保持...
    AndroidMaster閱讀 2,534評論 0 13
  • 為何要壓縮 1、體積的原因如果你的圖片是要準備上傳的,那動輒幾M的大小肯定不行的,況且圖片分辨率大于設備分辨率的話...
    mahongyin閱讀 810評論 0 1
  • 2017年4月1日 林玉珍“育心麗謙時間管理100天挑戰營”第76天 【早起】5:00 【學習】1.《易經》系辭下...
    林玉珍閱讀 280評論 0 0