高性能圖片優(yōu)化方案

10.高性能圖片優(yōu)化方案

目錄介紹

  • 01.圖片基礎(chǔ)概念介紹
    • 1.1 圖片占用內(nèi)存介紹
    • 1.2 網(wǎng)絡(luò)圖片加載流程
    • 1.3 三方庫(kù)加載圖片邏輯
    • 1.4 BitmapFactory
    • 1.5 圖片大小VS內(nèi)存
    • 1.6 Bitmap能直接存儲(chǔ)嗎
    • 1.7 Bitmap創(chuàng)建流程
    • 1.8 圖片框架如何設(shè)計(jì)
  • 02.圖片內(nèi)存計(jì)算方式
    • 2.1 如何計(jì)算占用內(nèi)存
    • 2.2 上面計(jì)算內(nèi)存對(duì)嗎
    • 2.3 一個(gè)像素占用內(nèi)存
    • 2.4 使用API獲取內(nèi)存
    • 2.5 影響B(tài)itmap內(nèi)存因素
    • 2.6 加載xhdpi和xxhdpi圖片
    • 2.7 圖片一些注意事項(xiàng)
  • 03.大圖的內(nèi)存優(yōu)化
    • 3.0 圖片壓縮核心思想
    • 3.1 常見(jiàn)圖片壓縮
    • 3.2 圖片尺寸壓縮
    • 3.3 圖片質(zhì)量壓縮
    • 3.4 雙線性采樣壓縮
    • 3.5 高清圖分片加載
    • 3.6 魯班圖片綜合壓縮
  • 04.色彩格式及內(nèi)存優(yōu)化
    • 4.1 RGB顏色種類
    • 4.2 ARGB色彩模式
    • 4.3 改變色彩格式優(yōu)化
  • 05.圖片內(nèi)存緩存設(shè)計(jì)
    • 5.1 圖片內(nèi)存緩存思想
    • 5.2 Lru內(nèi)存緩存
    • 5.3 Lru緩存注意事項(xiàng)
    • 5.4 使用Lru磁盤(pán)緩存
  • 06.不同版本對(duì)Bitmap管理
    • 6.1 演變進(jìn)程
    • 6.2 管理Bitmap內(nèi)存
    • 6.3 提高Bitmap復(fù)用
  • 07.圖片其他方面優(yōu)化
    • 7.1 減少PNG圖片使用
    • 7.2 控件切割圓角優(yōu)化
    • 7.3 如何給圖片置灰色
    • 7.4 如何處理圖片旋轉(zhuǎn)呢
    • 7.5 保存圖片且刷相冊(cè)
    • 7.6 統(tǒng)一圖片域名優(yōu)化
    • 7.7 優(yōu)化H5圖片加載
    • 7.8 優(yōu)化圖片陰影效果
    • 7.9 圖片資源的壓縮

01.圖片基礎(chǔ)概念介紹

1.1 圖片占用內(nèi)存介紹

移動(dòng)設(shè)備的系統(tǒng)資源有限,所以應(yīng)用應(yīng)該盡可能的降低內(nèi)存的使用。

在應(yīng)用運(yùn)行過(guò)程中,Bitmap (圖片)往往是內(nèi)存占用最大的一個(gè)部分,Bitmap 圖片的加載和處理,通常會(huì)占用大量的內(nèi)存空間,所以在操作 Bitmap 時(shí),應(yīng)該盡可能的小心。

Bitmap 會(huì)消耗很多的內(nèi)存,特別是諸如照片等內(nèi)容豐富的大圖。例如,一個(gè)手機(jī)拍攝的 2700 * 1900 像素的照片,需要 5.1M 的存儲(chǔ)空間,但是在圖像解碼配置 ARGB_8888 時(shí),它加載到內(nèi)存需要 19.6M 內(nèi)存空間(2592 * 1936 * 4 bytes),從而迅速消耗掉該應(yīng)用的剩余內(nèi)存空間。

OOM 的問(wèn)題也是我們常見(jiàn)的嚴(yán)重問(wèn)題,OOM 的產(chǎn)生的一個(gè)主要場(chǎng)景就是在大圖片分配內(nèi)存的時(shí)候產(chǎn)生的,如果 APP 可用內(nèi)存緊張,這時(shí)加載了一張大圖,內(nèi)存空間不足以分配該圖片所需要的內(nèi)存,就會(huì)產(chǎn)生 OOM,所以控制圖片的高效使用是必備技能。

1.2 網(wǎng)絡(luò)圖片加載流程

這一部分壓縮和緩存圖片,在glide源碼分析的文章里已經(jīng)做出了比較詳細(xì)的說(shuō)明。在這里簡(jiǎn)單說(shuō)一下圖片請(qǐng)求加載過(guò)程……

在使用App的時(shí)候,會(huì)經(jīng)常需要加載一些網(wǎng)絡(luò)圖片,一般的操作步驟大概是這樣的:

  • 第一步從網(wǎng)絡(luò)加載圖片:一般都是通過(guò)網(wǎng)絡(luò)拉取的方式去服務(wù)器端獲取到圖片的文件流后,再通過(guò)BitmapFactory.decodeStream(InputStream)來(lái)加載圖片Bitmap。
  • 第二步這種壓縮圖片:網(wǎng)絡(luò)加載圖片方式加載一兩張圖片倒不會(huì)出現(xiàn)問(wèn)題,但是如果短時(shí)間內(nèi)加載十幾張或者幾十張圖片的時(shí)候,就很有可能會(huì)造成OOM(內(nèi)存溢出),因?yàn)楝F(xiàn)在的圖片資源大小都是非常大的,所以我們?cè)诩虞d圖片之前還需要進(jìn)行相應(yīng)的圖片壓縮處理。
  • 第三步變換圖片:比如需要裁剪,切割圓角,旋轉(zhuǎn),添加高斯模糊等屬性。
  • 第四步緩存圖片:但又有個(gè)問(wèn)題來(lái)了,在使用移動(dòng)數(shù)據(jù)的情況下,如果用戶每次進(jìn)入App的時(shí)候都會(huì)去進(jìn)行網(wǎng)絡(luò)拉取圖片,這樣就會(huì)非常的浪費(fèi)數(shù)據(jù)流量,這時(shí)又需要對(duì)圖片資源進(jìn)行一些相應(yīng)的內(nèi)存緩存以及磁盤(pán)緩存處理,這樣不僅節(jié)省用戶的數(shù)據(jù)流量,還能加快圖片的加載速度。
  • 第五步異步加載:雖然利用緩存的方式可以加快圖片的加載速度,但當(dāng)需要加載很多張圖片的時(shí)候(例如圖片墻瀑布流效果),就還需用到多線程來(lái)加載圖片,使用多線程就會(huì)涉及到線程同步加載與異步加載問(wèn)題。

1.3 三方庫(kù)加載圖片邏輯

先說(shuō)出結(jié)論,目前市面較為常用的大概是Glide,Picasso,F(xiàn)resco等。大概的處理圖片涉及主要邏輯有:

從網(wǎng)絡(luò)或者本地等路徑拉取圖片;然后解碼圖片;然后進(jìn)行壓縮;接著會(huì)有圖片常用圓角,模糊或者裁剪等處理;然后三級(jí)緩存加載的圖片;當(dāng)然加載圖片過(guò)程涉及同步加載和異步加載;最后設(shè)置到具體view控件上。

1.4 BitmapFactory

直接通過(guò)網(wǎng)絡(luò)請(qǐng)求將網(wǎng)絡(luò)圖片轉(zhuǎn)化成bitmap,在這將采用最原生的網(wǎng)絡(luò)請(qǐng)求方式HttpURLConnection方式進(jìn)行圖片獲取。

經(jīng)過(guò)測(cè)試,請(qǐng)求8張圖片,耗時(shí)毫秒值174。一般是通過(guò)get請(qǐng)求拉取圖片的。這種方法應(yīng)該是最基礎(chǔ)的網(wǎng)絡(luò)請(qǐng)求,大家也可以回顧一下,一般開(kāi)發(fā)中很少用這種方式加載圖片。具體可以看:ImageToolLib

如何加載一個(gè)圖片呢?可以看看BitmapFactory類為我們提供了四類方法來(lái)加載Bitmap:decodeFile、decodeResource、decodeStream、decodeByteArray;也就是說(shuō)Bitmap,Drawable,InputStream,Byte[] 之間是可以進(jìn)行轉(zhuǎn)換。

1.5 圖片大小VS內(nèi)存

搞清楚一個(gè)圖片概念,在電腦上看到的 png 格式或者 jpg 格式的圖片,png(jpg) 只是這張圖片的容器。是經(jīng)過(guò)相對(duì)應(yīng)的壓縮算法將原圖每個(gè)像素點(diǎn)信息轉(zhuǎn)換用另一種數(shù)據(jù)格式表示。

加載圖片顯示到手機(jī),通過(guò)代碼,將這張圖片加載進(jìn)內(nèi)存時(shí),會(huì)先解析(也就是解碼操作)圖片文件本身的數(shù)據(jù)格式,然后還原為位圖,也就是 Bitmap 對(duì)象。

圖片大小vs圖片內(nèi)存大小,一張 png 或者 jpg 格式的圖片大小,跟這張圖片加載進(jìn)內(nèi)存所占用的大小完全是兩回事。

1.6 Bitmap能直接存儲(chǔ)嗎

Bitmap基礎(chǔ)概念,Bitmap對(duì)象本質(zhì)是一張圖片的內(nèi)容在手機(jī)內(nèi)存中的表達(dá)形式。它將圖片的內(nèi)容看做是由存儲(chǔ)數(shù)據(jù)的有限個(gè)像素點(diǎn)組成;每個(gè)像素點(diǎn)存儲(chǔ)該像素點(diǎn)位置的ARGB值。每個(gè)像素點(diǎn)的ARGB值確定下來(lái),這張圖片的內(nèi)容就相應(yīng)地確定下來(lái)了。

Bitmap本質(zhì)上不能直接存儲(chǔ) 為什么?bitmap是一個(gè)對(duì)象,如果要存儲(chǔ)成本地可以查看的圖片文件,則必須對(duì)bitmap進(jìn)行編碼,然后通過(guò)io流寫(xiě)到本地file文件上。

1.7 Bitmap創(chuàng)建流程

對(duì)于圖片OOM,可以發(fā)現(xiàn)一個(gè)現(xiàn)象。heapsize(虛擬機(jī)的內(nèi)存配置)越大越不容易 OOM,Android8.0 及之后的版本更不容易 OOM,這個(gè)該如何理解呢?Bitmap對(duì)象內(nèi)存的變化:

  • 在 Android 8.0 之前,Bitmap 像素占用的內(nèi)存是在 Java heap 中分配的;8.0 及之后,Bitmap 像素占用的內(nèi)存分配到了 Native Heap。
  • 由于 Native heap 的內(nèi)存分配上限很大,32 位應(yīng)用的可用內(nèi)存在 3~4G,64 位上更大,虛擬內(nèi)存幾乎很難耗盡,所以推測(cè) OOM 時(shí) Java heap 中占用內(nèi)存較多的對(duì)象是 Bitmap” 成立的情況下,應(yīng)用更不容易 OOM。

搞清楚Bitmap對(duì)象內(nèi)存分配,Bitmap 的構(gòu)造方法是不公開(kāi)的,在使用 Bitmap 的時(shí)候,一般都是通過(guò) Bitmap、BitmapFactory 提供的靜態(tài)方法來(lái)創(chuàng)建 Bitmap 實(shí)例。以 Bitmap.createBitmap 說(shuō)明了 Bitmap 對(duì)象的主要?jiǎng)?chuàng)建過(guò)程分析,可以看到 Java Bitmap 對(duì)象是在 Native 層通過(guò) NewObject 創(chuàng)建的。

  • allocateJavaPixelRef,是 8.0 之前版本為 Bitmap 像素從 Java heap 申請(qǐng)內(nèi)存。其核心原理是Bitmap 的像素是保存在 Java 堆上。
  • allocateHeapBitmap,是 8.0 版本為 Bitmap 像素從 Native heap 申請(qǐng)內(nèi)存。其核心原理主要是通過(guò) calloc 為 Bitmap 的像素分配內(nèi)存,這個(gè)分配就在 Native 堆上。

1.8 圖片框架如何設(shè)計(jì)

大多數(shù)圖片框架加載流程,概括來(lái)說(shuō),圖片加載包含封裝,解析,下載,解碼,變換,緩存,顯示等操作。

圖片框架是如何設(shè)計(jì)的

  • 封裝參數(shù):從指定來(lái)源,到輸出結(jié)果,中間可能經(jīng)歷很多流程,所以第一件事就是封裝參數(shù),這些參數(shù)會(huì)貫穿整個(gè)過(guò)程;
  • 解析路徑:圖片的來(lái)源有多種,格式也不盡相同,需要規(guī)范化;比如glide可以加載file,io,id,網(wǎng)絡(luò)等各種圖片資源
  • 讀取緩存:為了減少計(jì)算,通常都會(huì)做緩存;同樣的請(qǐng)求,從緩存中取圖片(Bitmap)即可;
  • 查找文件/下載文件:如果是本地的文件,直接解碼即可;如果是網(wǎng)絡(luò)圖片,需要先下載;比如glide這塊是發(fā)起一個(gè)請(qǐng)求
  • 解碼:這一步是整個(gè)過(guò)程中最復(fù)雜的步驟之一,有不少細(xì)節(jié);比如glide中解析圖片數(shù)據(jù)源,旋轉(zhuǎn)方向,圖片頭等信息
  • 變換和壓縮:解碼出Bitmap之后,可能還需要做一些變換處理(圓角,濾鏡等),還要做圖片壓縮;
  • 緩存:得到最終bitmap之后,可以緩存起來(lái),以便下次請(qǐng)求時(shí)直接取結(jié)果;比如glide用到三級(jí)緩存
  • 顯示:顯示結(jié)果,可能需要做些動(dòng)畫(huà)(淡入動(dòng)畫(huà),crossFade等);比如glide設(shè)置顯示的時(shí)候可以添加動(dòng)畫(huà)效果

02.圖片內(nèi)存計(jì)算方式

2.1 如何計(jì)算占用內(nèi)存

如果圖片要顯示下Android設(shè)備上,ImageView最終是要加載Bitmap對(duì)象的,就要考慮單個(gè)Bitmap對(duì)象的內(nèi)存占用了,如何計(jì)算一張圖片的加載到內(nèi)存的占用呢?其實(shí)就是所有像素的內(nèi)存占用總和:

bitmap內(nèi)存大小 = 圖片長(zhǎng)度 x 圖片寬度 x 單位像素占用的字節(jié)數(shù)

起決定因素就是最后那個(gè)參數(shù)了,Bitmap常見(jiàn)有2種編碼方式:ARGB_8888和RGB_565,ARGB_8888每個(gè)像素點(diǎn)4個(gè)byte,RGB_565是2個(gè)byte,一般都采用ARGB_8888這種。那么常見(jiàn)的1080*1920的圖片內(nèi)存占用就是:1920 x 1080 x 4 = 7.9M

2.2 上面計(jì)算內(nèi)存對(duì)嗎

我看到好多博客都是這樣計(jì)算的,但是這樣算對(duì)嗎?有沒(méi)有哥們?cè)囼?yàn)過(guò)這種方法正確性?我覺(jué)得看博客要對(duì)博主表示懷疑,論證別人寫(xiě)的是否正確。

說(shuō)出我的結(jié)論:上面2.1這種說(shuō)法也對(duì),但是不全對(duì),沒(méi)有說(shuō)明場(chǎng)景,同時(shí)也忽略了一個(gè)影響項(xiàng):Density。接下來(lái)看看源代碼。

inDensity默認(rèn)為圖片所在文件夾對(duì)應(yīng)的密度;inTargetDensity為當(dāng)前系統(tǒng)密度。

加載一張本地資源圖片,那么它占用的內(nèi)存 = width * height * nTargetDensity/inDensity 一個(gè)像素所占的內(nèi)存。

@Nullable
public static Bitmap decodeResourceStream(@Nullable Resources res, @Nullable TypedValue value,
        @Nullable InputStream is, @Nullable Rect pad, @Nullable 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);
}

正確說(shuō)法,這個(gè)注意呢?計(jì)算公式如下所示

  • 對(duì)資源文件:width * height * nTargetDensity/inDensity * nTargetDensity/inDensity * 一個(gè)像素所占的內(nèi)存;
  • 別的:width * height * 一個(gè)像素所占的內(nèi)存;

2.3 一個(gè)像素占用內(nèi)存

一個(gè)像素占用多大內(nèi)存?Bitmap.Config用來(lái)描述圖片的像素是怎么被存儲(chǔ)的?

  • ARGB_8888: 每個(gè)像素4字節(jié). 共32位,默認(rèn)設(shè)置。
  • Alpha_8: 只保存透明度,共8位,1字節(jié)。
  • ARGB_4444: 共16位,2字節(jié)。
  • RGB_565:共16位,2字節(jié),只存儲(chǔ)RGB值。

2.4 使用API獲取內(nèi)存

Bitmap使用API獲取內(nèi)存

  • getByteCount(),方法是在API12加入的,代表存儲(chǔ)Bitmap的色素需要的最少內(nèi)存。API19開(kāi)始getAllocationByteCount()方法代替了getByteCount()。
  • getAllocationByteCount(),API19之后,Bitmap加了一個(gè)Api:getAllocationByteCount();代表在內(nèi)存中為Bitmap分配的內(nèi)存大小。

思考:getByteCount()與getAllocationByteCount()的區(qū)別?

一般情況下兩者是相等的;通過(guò)復(fù)用Bitmap來(lái)解碼圖片,如果被復(fù)用的Bitmap的內(nèi)存比待分配內(nèi)存的Bitmap大,那么getByteCount()表示新解碼圖片占用內(nèi)存的大?。ú⒎菍?shí)際內(nèi)存大小,實(shí)際大小是復(fù)用的那個(gè)Bitmap的大小),getAllocationByteCount()表示被復(fù)用Bitmap真實(shí)占用的內(nèi)存大?。磎Buffer的長(zhǎng)度)。

在復(fù)用Bitmap的情況下,getAllocationByteCount()可能會(huì)比getByteCount()大。

2.5 影響B(tài)itmap內(nèi)存因素

影響B(tài)itmap占用內(nèi)存的因素:

  • 圖片最終加載的分辨率;
  • 圖片的格式(PNG/JPEG/BMP/WebP);
  • 圖片所存放的drawable目錄;
  • 圖片屬性設(shè)置的色彩模式;
  • 設(shè)備的屏幕密度;

2.6 加載xhdpi和xxhdpi圖片

提個(gè)問(wèn)題,加載xhdpi和xxhdpi中相同的圖片,顯示在控件上會(huì)一樣嗎??jī)?nèi)存大小一樣嗎?為什么?

肯定是不一樣的。xhdpi:240dpi--320dpi,xxhdpi:320dpi--480dpi,

app中設(shè)置的圖片是如何從hdpi中查找的?

  • 首先計(jì)算dpi,比如手機(jī)分辨率是1920x1080,5.1寸的手機(jī)。那么得到的dpi公式是(√ ̄19202 + 10802)/5.1 =2202/5.1= 431dpi。這樣優(yōu)先查找xxhdpi
  • 如果xxhdpi里沒(méi)有查找圖片,如果沒(méi)有會(huì)往上找,遵循“先高再低”原則。如果xhdpi里有這個(gè)圖片會(huì)使用xhdpi里的圖片,這時(shí)候發(fā)現(xiàn)會(huì)比在xhdpi里的圖片放大了。

為何要引入不同hdpi的文件管理?比如:xxhdpi放94x94,xhdpi放74x74,hdpi放45x45,這樣不管是什么樣的手機(jī)圖片都能在指定的比例顯示。引入多種hdpi是為了讓這個(gè)圖片在任何手機(jī)上都是手機(jī)的這個(gè)比例。

2.7 圖片一些注意事項(xiàng)

同樣圖片顯示在大小不相同的ImageView上,內(nèi)存是一樣嗎?圖片占據(jù)內(nèi)存空間大小與圖片在界面上顯示的大小沒(méi)有關(guān)系。

圖片放在res不同目錄,加載的內(nèi)存是一樣的嗎?最終圖片加載進(jìn)內(nèi)存所占據(jù)的大小會(huì)不一樣,因?yàn)橄到y(tǒng)在加載 res 目錄下的資源圖片時(shí),會(huì)根據(jù)圖片存放的不同目錄做一次分辨率的轉(zhuǎn)換,而轉(zhuǎn)換的規(guī)則是:新圖的高度 = 原圖高度 * (設(shè)備的 dpi / 目錄對(duì)應(yīng)的 dpi )

03.大圖的內(nèi)存優(yōu)化

3.0 圖片壓縮核心思想

圖片尺寸壓縮的核心思想是通過(guò)減少圖片的像素?cái)?shù)量(分辨率)或調(diào)整圖片的質(zhì)量(壓縮率)來(lái)降低圖片文件的大小,從而節(jié)省存儲(chǔ)空間、減少內(nèi)存占用以及加快圖片加載和傳輸速度。

  1. 減少分辨率:原理是通過(guò)降低圖片的寬度和高度(即減少像素?cái)?shù)量),從而減少圖片的存儲(chǔ)大小。
  2. 降低質(zhì)量:原理是通過(guò)調(diào)整圖片的壓縮率(如 JPEG 的質(zhì)量參數(shù)),減少圖片文件的大小。
  3. 轉(zhuǎn)化圖片格式:不同的圖片格式(如 JPEG、PNG、WebP)具有不同的壓縮算法和特性,選擇合適的格式可以在保證質(zhì)量的同時(shí)減少文件大小。

3.1 常見(jiàn)圖片壓縮

常見(jiàn)壓縮方法Api

  • 降低質(zhì)量:Bitmap.compress(),質(zhì)量壓縮,不會(huì)對(duì)內(nèi)存產(chǎn)生影響;
  • 減少分辨率:BitmapFactory.Options.inSampleSize,內(nèi)存壓縮;

Bitmap.compress()質(zhì)量壓縮

質(zhì)量壓縮,不會(huì)對(duì)內(nèi)存產(chǎn)生影響。它是在保持像素的前提下改變圖片的位深及透明度等,來(lái)達(dá)到壓縮圖片的目的,不會(huì)減少圖片的像素。進(jìn)過(guò)它壓縮的圖片文件大小會(huì)變小,但是解碼成bitmap后占得內(nèi)存是不變的。

BitmapFactory.Options.inSampleSize內(nèi)存壓縮

解碼圖片時(shí),設(shè)置BitmapFactory.Options類的inJustDecodeBounds屬性為true,可以在Bitmap不被加載到內(nèi)存的前提下,獲取Bitmap的原始寬高。而設(shè)置BitmapFactory.Options的inSampleSize屬性可以真實(shí)的壓縮Bitmap占用的內(nèi)存,加載更小內(nèi)存的Bitmap。

設(shè)置inSampleSize之后,Bitmap的寬、高都會(huì)縮小inSampleSize倍。例如:一張寬高為2048x1536的圖片,設(shè)置inSampleSize為4之后,實(shí)際加載到內(nèi)存中的圖片寬高是512x384。占有的內(nèi)存就是0.75M而不是12M,足足節(jié)省了15倍。

備注:inSampleSize值的大小不是隨便設(shè)、或者越大越好,需要根據(jù)實(shí)際情況來(lái)設(shè)置。inSampleSize比1小的話會(huì)被當(dāng)做1,任何inSampleSize的值會(huì)被取接近2的冪值。

3.2 圖片尺寸壓縮

3.2.1 如何理解尺寸壓縮

通常在大多數(shù)情況下,圖片的實(shí)際大小都比需要呈現(xiàn)的尺寸大很多。例如,我們的原圖是一張 2700 * 1900 像素的照片,加載到內(nèi)存就需要 19.6M 內(nèi)存空間,但是,我們需要把它展示在一個(gè)列表頁(yè)中,組件可展示尺寸為 270 * 190,這時(shí),我們實(shí)際上只需要一張?jiān)瓐D的低分辨率的縮略圖即可(與圖片顯示所對(duì)應(yīng)的 UI 控件匹配),那么實(shí)際上 270 * 190 像素的圖片,只需要 0.2M 的內(nèi)存即可??梢钥吹剑瑑?yōu)化前后相差了 98 倍,原來(lái)顯示 1 張,現(xiàn)在可以顯示 98 張圖片,效果非常顯著。

既然在對(duì)原圖縮放可以顯著減少內(nèi)存大小,那么我們應(yīng)該如何操作呢?先加載到內(nèi)存,再進(jìn)行操作嗎,可以如果先加載到內(nèi)存,好像也不太對(duì),這樣只接占用了 19.6M + 0.2M 2份內(nèi)存了,而我們想要的是,在原圖不加載到內(nèi)存中,只接將縮放后的圖片加載到內(nèi)存中,可以實(shí)現(xiàn)嗎?

BitmapFactory 提供了從不同資源創(chuàng)建 Bitmap 的解碼方法:decodeByteArray()、decodeFile()、decodeResource() 等。

但是,這些方法在構(gòu)造位圖的時(shí)候會(huì)嘗試分配內(nèi)存,也就是它們會(huì)導(dǎo)致原圖直接加載到內(nèi)存了,不滿足我們的需求。我們可以通過(guò) BitmapFactory.Options 設(shè)置一些附加的標(biāo)記,指定解碼選項(xiàng),以此來(lái)解決該問(wèn)題。

如何操作呢?答案來(lái)了:將 inJustDecodeBounds 屬性設(shè)置為 true,可以在解碼時(shí)避免內(nèi)存的分配,它會(huì)返回一個(gè) null 的 Bitmap ,但是可以獲取 outWidth、outHeight 和 outMimeType 值。利用該屬性,我們就可以在圖片不占用內(nèi)存的情況下,在圖片壓縮之前獲取圖片的尺寸。

怎樣才能對(duì)圖片進(jìn)行壓縮呢?通過(guò)設(shè)置BitmapFactory.Options中inSampleSize的值就可以實(shí)現(xiàn)。其計(jì)算方式大概就是:計(jì)算出實(shí)際寬高和目標(biāo)寬高的比率,然后選擇寬和高中最小的比率作為inSampleSize的值,這樣可以保證最終圖片的寬和高。

3.2.2 設(shè)置BitmapFactory.Options屬性

BitmapFactory.Options.inSampleSize 的核心思想是通過(guò)降低圖片的分辨率來(lái)減少內(nèi)存占用。它是在解碼階段生效的,因此它可以在圖片加載到內(nèi)存之前就完成壓縮,避免不必要的內(nèi)存消耗。

大概步驟如下所示:

  1. 要將BitmapFactory.Options的inJustDecodeBounds屬性設(shè)置為true,解析一次圖片。注意這個(gè)地方是核心,這個(gè)解析圖片并沒(méi)有生成bitmap對(duì)象(也就是說(shuō)沒(méi)有為它分配內(nèi)存控件),而僅僅是拿到它的寬高等屬性。
  2. 然后將BitmapFactory.Options連同期望的寬度和高度一起傳遞到到calculateInSampleSize方法中,就可以得到合適的inSampleSize值了。這一步會(huì)壓縮圖片。
  3. 再解析一次圖片,使用新獲取到的inSampleSize值,并把inJustDecodeBounds設(shè)置為false,就可以得到壓縮后的圖片了。此時(shí)才正式創(chuàng)建了bitmap對(duì)象,由于前面已經(jīng)對(duì)它壓縮了,所以你會(huì)發(fā)現(xiàn)此時(shí)所占內(nèi)存大小已經(jīng)很少了。

具體的實(shí)現(xiàn)代碼:

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

思考:inJustDecodeBounds這個(gè)參數(shù)是干什么的?如果設(shè)置為true則表示decode函數(shù)不會(huì)生成bitmap對(duì)象,僅是將圖像相關(guān)的參數(shù)填充到option對(duì)象里,這樣我們就可以在不生成bitmap而獲取到圖像的相關(guān)參數(shù)了。

為何設(shè)置兩次inJustDecodeBounds屬性?

  • 第一次:設(shè)置為true則表示decode函數(shù)不會(huì)生成bitmap對(duì)象,僅是將圖像相關(guān)的參數(shù)填充到option對(duì)象里,這樣我們就可以在不生成bitmap而獲取到圖像的相關(guān)參數(shù)。
  • 第二次:將inJustDecodeBounds設(shè)置為false再次調(diào)用decode函數(shù)時(shí)就能生成bitmap了。而此時(shí)的bitmap已經(jīng)壓縮減小很多了,所以加載到內(nèi)存中并不會(huì)導(dǎo)致OOM。

3.3 圖片質(zhì)量壓縮

圖片質(zhì)量壓縮核心思想是:一種通過(guò)減少圖像文件中像素的細(xì)節(jié)和信息來(lái)降低圖像文件大小的技術(shù)。這種壓縮方法通常會(huì)犧牲一定程度的圖像質(zhì)量,以換取更小的文件大小。

  1. 減少細(xì)節(jié)和信息:圖片質(zhì)量壓縮通過(guò)減少圖像文件中的細(xì)節(jié)和信息來(lái)降低文件大小。這可能包括減少顏色深度、降低圖像分辨率、去除不可見(jiàn)的細(xì)節(jié)等。
  2. 壓縮算法:常用的圖片質(zhì)量壓縮算法是基于 JPEG 格式的壓縮。JPEG 壓縮算法通過(guò)調(diào)整圖像的色彩和細(xì)節(jié)來(lái)實(shí)現(xiàn)壓縮,可以通過(guò)控制壓縮質(zhì)量參數(shù)來(lái)調(diào)整壓縮比例。
  3. 質(zhì)量參數(shù):JPEG 壓縮算法中的質(zhì)量參數(shù)通常是一個(gè)介于 0 到 100 之間的值,表示壓縮的質(zhì)量等級(jí)。

在Android中,對(duì)圖片進(jìn)行質(zhì)量壓縮,通常我們的實(shí)現(xiàn)方式如下所示:

//quality 為0~100,0表示最小體積,100表示最高質(zhì)量,對(duì)應(yīng)體積也是最大
bitmap.compress(Bitmap.CompressFormat.JPEG, quality , outputStream);

在上述代碼中,我們選擇的壓縮格式是CompressFormat.JPEG,除此之外還有兩個(gè)選擇:

  • 其一,CompressFormat.PNG,PNG格式是無(wú)損的,它無(wú)法再進(jìn)行質(zhì)量壓縮,quality這個(gè)參數(shù)就沒(méi)有作用了,會(huì)被忽略,所以最后圖片保存成的文件大小不會(huì)有變化;
  • 其二,CompressFormat.WEBP,這個(gè)格式是google推出的圖片格式,它會(huì)比JPEG更加省空間,經(jīng)過(guò)實(shí)測(cè)大概可以優(yōu)化30%左右。

Android質(zhì)量壓縮邏輯,函數(shù)compress經(jīng)過(guò)一連串的java層調(diào)用之后,最后來(lái)到了一個(gè)native函數(shù):

具體看:Bitmap.cpp,最后調(diào)用了函數(shù)encoder->encodeStream(…)編碼保存本地。該函數(shù)是調(diào)用skia引擎來(lái)對(duì)圖片進(jìn)行編碼壓縮。

3.4 雙線性采樣壓縮

核心思路是什么:這種壓縮方法通過(guò)對(duì)圖像進(jìn)行插值計(jì)算,以平滑地減少圖像的像素?cái)?shù)量,同時(shí)盡可能保留圖像的細(xì)節(jié)和質(zhì)量。

雙線性采樣(Bilinear Resampling)在 Android 中的使用方式一般有兩種:

bm = Bitmap.createScaledBitmap(bitmap, bitmap.getWidth()/2, bitmap.getHeight()/2, true);

//或者直接使用 matrix 進(jìn)行縮放
Matrix matrix = new Matrix();
matrix.setScale(0.5f, 0.5f);
bm = Bitmap.createBitmap(bitmap, 0, 0, bit.getWidth(), bit.getHeight(), matrix, true);

看源碼可以知道createScaledBitmap函數(shù)最終也是使用第二種方式的matrix進(jìn)行縮放

雙線性采樣使用的是雙線性內(nèi)插值算法,這個(gè)算法不像鄰近點(diǎn)插值算法一樣,直接粗暴的選擇一個(gè)像素,而是參考了源像素相應(yīng)位置周?chē)?x2個(gè)點(diǎn)的值,根據(jù)相對(duì)位置取對(duì)應(yīng)的權(quán)重,經(jīng)過(guò)計(jì)算之后得到目標(biāo)圖像。

3.5 高清圖分片加載

如何理解高清圖分片加載的核心思想:將大尺寸的高清圖像分割成多個(gè)小塊(片段),然后根據(jù)顯示需求動(dòng)態(tài)加載和拼接這些片段,以實(shí)現(xiàn)高清圖像的顯示。這種技術(shù)可以幫助減少內(nèi)存占用和提高性能,同時(shí)保持高清圖像的清晰度。

這種技術(shù)常用于需要展示高清圖像的應(yīng)用場(chǎng)景,如圖片查看器、地圖應(yīng)用等,以實(shí)現(xiàn)高質(zhì)量的圖像顯示效果。

適用場(chǎng)景 : 當(dāng)一張圖片非常大 , 在手機(jī)中只需要顯示其中一部分內(nèi)容 , BitmapRegionDecoder 非常有用 。

主要作用 : BitmapRegionDecoder 可以從圖像中 解碼一個(gè)矩形區(qū)域 。相當(dāng)于手在滑動(dòng)的過(guò)程中,計(jì)算當(dāng)前顯示區(qū)域的圖片繪制出來(lái)。

基本使用流程 : 先創(chuàng)建,后解碼 。調(diào)用 newInstance 方法 , 創(chuàng)建 BitmapRegionDecoder 對(duì)象 ;然后調(diào)用 decodeRegion 方法 , 獲取指定 Rect 矩形區(qū)域的解碼后的 Bitmap 對(duì)象。

3.6 魯班圖片綜合壓縮

一般情況下圖片綜合壓縮的整體思路如下:

  • 第一步進(jìn)行采樣率壓縮;
  • 第二步進(jìn)行寬高的等比例壓縮(微信對(duì)原圖和縮略圖限制了最大長(zhǎng)寬或者最小長(zhǎng)寬);
  • 第三步就是對(duì)圖片的質(zhì)量進(jìn)行壓縮(一般75或者70);
  • 第四步就是采用webP的格式。

關(guān)于圖片壓縮的綜合案例如下,具體可以參考:CompressServer

04.色彩格式及內(nèi)存優(yōu)化

4.1 RGB顏色種類

RGB 色彩模式是工業(yè)界的一種顏色標(biāo)準(zhǔn),通過(guò)對(duì)紅(R)、綠(G)、藍(lán)(B)三個(gè)顏色通道的變化以及它們相互之間的疊加來(lái)得到各式各樣的顏色的,RGB即是代表紅、綠、藍(lán)三個(gè)通道的顏色,這個(gè)標(biāo)準(zhǔn)幾乎包括了人類視力所能感知的所有顏色,是運(yùn)用最廣的顏色系統(tǒng)之一。Android 中,像素的存儲(chǔ)方式使用的色彩模式正是 RGB 色彩模式。

  1. 基本原理:RGB 色彩模式基于三種原色(紅、綠、藍(lán)),通過(guò)不同強(qiáng)度的這三種顏色的組合來(lái)產(chǎn)生各種其他顏色。通過(guò)調(diào)整每種原色的亮度和飽和度,可以生成數(shù)百萬(wàn)種不同的顏色。
  2. 顏色表示:在 RGB 色彩模式中,每種顏色由一個(gè)三元組(R,G,B)表示,其中 R、G、B 的取值范圍通常是 0 到 255,表示每種顏色的強(qiáng)度。例如,(255, 0, 0) 表示純紅色,(0, 255, 0) 表示純綠色,(0, 0, 255) 表示純藍(lán)色。
  3. 顏色混合:通過(guò)調(diào)整不同原色的強(qiáng)度,可以混合出各種中間色。例如,紅色和綠色的混合會(huì)產(chǎn)生黃色。
  4. 色彩范圍:RGB 色彩模式可以表示的顏色范圍非常廣泛,可以生成幾乎所有可見(jiàn)顏色,包括各種飽和度和亮度的顏色。

4.2 ARGB色彩模式

ARGB 色彩模式是一種在計(jì)算機(jī)圖形學(xué)和圖像處理中常用的色彩表示方式,它是在 RGB 色彩模式的基礎(chǔ)上增加了一個(gè) Alpha 通道,用于表示像素的透明度。

在 Android 中,我們常見(jiàn)的一些顏色設(shè)置,都是 RGB 色彩模式來(lái)描述像素顏色的,并且他們都帶有透明度通道,也就是所謂的 ARGB。例如,我們常見(jiàn)的顏色定義如下:

//在代碼中定義顏色值:藍(lán)色
public final int blue=0xff0000ff;

//或者在xml中定義:
<drawable name="blue">#ff0000ff</drawable>  

以上設(shè)置中,顏色值都是使用 16 進(jìn)制的數(shù)字來(lái)表示的。以上顏色值都是帶有透明度(透明通道)的顏色值,格式是 AARRGGBB,透明度、紅色、綠色、藍(lán)色四個(gè)顏色通道,各占有 2 位,也就是一個(gè)顏色通道,使用了 1 個(gè)字節(jié)來(lái)存儲(chǔ)。

4.3 改變色彩格式優(yōu)化

Android 中有多種 RGB 模式,我們可以設(shè)置不同的格式,來(lái)控制圖片像素顏色的顯示質(zhì)量和存儲(chǔ)空間。

Android.graphics.Bitmap 類里有一個(gè)內(nèi)部類 Bitmap.Config 類,它定義了可以在 Android 中使用的幾種色彩格式:

public enum Config {
    ALPHA_8     (1),
    RGB_565     (3),
    @Deprecated
    ARGB_4444   (4),
    ARGB_8888   (5),
    RGBA_F16    (6),
    HARDWARE    (7);
}

解釋一下這幾個(gè)值分別代表了什么含義?我們已經(jīng)知道了:A 代表透明度、R 代表紅色、G 代表綠色、B 代表藍(lán)色。

  • ALPHA_8:表示,只存在 Alpha 通道,沒(méi)有存儲(chǔ)色彩值,只含有透明度,每個(gè)像素占用 1 個(gè)字節(jié)的空間。
  • RGB_565:表示,R 占用 5 位二進(jìn)制的位置,G 占用了6位,B 占用了 5 位。每個(gè)像素占用 2 個(gè)字節(jié)空間,并且不包含透明度。
  • ARGB_4444:表示,A(透明度)、R(紅色)、G(綠色)、B(藍(lán)色)4個(gè)通道各占用 4 個(gè) bit 位。每個(gè)像素占用 2 個(gè)字節(jié)空間。
  • ARGB_8888:表示,A(透明度)、R(紅色)、G(綠色)、B(藍(lán)色)4個(gè)通道各占用 8 個(gè) bit 位。每個(gè)像素占用 4 個(gè)字節(jié)空間。
  • RGBA_F16:表示,每個(gè)像素存儲(chǔ)在8個(gè)字節(jié)上。此配置特別適合廣色域和HDR內(nèi)容。
  • HARDWARE:特殊配置,當(dāng)位圖僅存儲(chǔ)在圖形內(nèi)存中時(shí)。 此配置中的位圖始終是不可變的。

那么開(kāi)發(fā)中一般選擇哪一種比較合適呢

  • Android 中的圖片在加載時(shí),默認(rèn)的色彩格式是 ARGB_8888,也就是每個(gè)像素占用 4 個(gè)字節(jié)空間,一張 2700 * 1900 像素的照片,加載到內(nèi)存就需要 19.6M 內(nèi)存空間(2592 * 1936 * 4 bytes)。
  • 如果圖片在 UI 組件中顯示時(shí),不需要太高的圖片質(zhì)量,例如顯示一張縮略圖(不透明圖片)等場(chǎng)景,這時(shí),我們就沒(méi)必要使用 ARGB_8888 的色彩格式了,只需要使用 RGB_565 模式即可滿足顯示的需要。
  • 那么,我們的優(yōu)化操作就可以是:將 2700 * 1900 像素的原圖,壓縮到原圖的低分辨率的縮略圖 270 * 190 像素的圖片,這時(shí)需要 0.2M 的內(nèi)存。也就是從 19.6M內(nèi)存,壓縮為 0.2 M內(nèi)存。
  • 我們還可以進(jìn)一步優(yōu)化色彩格式,由 ARGB_8888 改為 RGB_565 模式,這時(shí),目標(biāo)圖片需要的內(nèi)存就變?yōu)?270 * 190 * 2 = 0.1M 了。圖片內(nèi)存空間又減小了一倍。

05.圖片內(nèi)存緩存設(shè)計(jì)

5.1 圖片內(nèi)存緩存思想

圖片內(nèi)存緩存的設(shè)計(jì)是優(yōu)化應(yīng)用性能的關(guān)鍵。內(nèi)存緩存的核心思想是利用內(nèi)存的高速讀寫(xiě)特性,緩存最近使用的圖片,從而避免重復(fù)加載和減少磁盤(pán) I/O 操作。

  1. 快速訪問(wèn):內(nèi)存緩存的讀寫(xiě)速度遠(yuǎn)高于磁盤(pán)緩存和網(wǎng)絡(luò)加載,因此將最近使用的圖片保存在內(nèi)存中,可以顯著提升圖片加載速度。
  2. 有限資源管理:內(nèi)存資源有限,因此需要合理管理緩存大小,避免占用過(guò)多內(nèi)存導(dǎo)致應(yīng)用崩潰或性能下降。
  3. 淘汰策略:當(dāng)緩存達(dá)到上限時(shí),需要淘汰部分緩存項(xiàng)以釋放空間。常用的淘汰策略是 LRU(Least Recently Used,最近最少使用)。

5.2 Lru內(nèi)存緩存

LruCache 類特別適合用來(lái)緩存 Bitmap,它使用一個(gè)強(qiáng)引用的 LinkedHashMap 保存最近引用的對(duì)象,并且在緩存超出設(shè)定大小時(shí),刪除最近最少使用的對(duì)象。

給 LruCache 確定一個(gè)合適的緩存大小非常重要,我們需要考慮幾個(gè)因素:

  • 應(yīng)用剩余多少可用內(nèi)存?
  • 需要有多少?gòu)垐D片同時(shí)顯示到屏幕上?有多少圖片需要準(zhǔn)備好以便馬上顯示到屏幕?
  • 設(shè)備的屏幕大小和密度是多少?高密度的設(shè)備需要更大的緩存空間來(lái)緩存同樣數(shù)量的圖片。
  • Bitmap 的尺寸配置是多少,花費(fèi)多少內(nèi)存?
  • 圖片被訪問(wèn)的頻率如何?如果其中一些比另外一些訪問(wèn)更頻繁,那么我們可能希望在內(nèi)存中保存那些最常訪問(wèn)的圖片,或者根據(jù)訪問(wèn)頻率給 Bitmap 分組,為不同的 Bitmap 組設(shè)置多個(gè) LruCache 對(duì)象。
  • 是否可以在緩存圖片的質(zhì)量和數(shù)量之間尋找平衡點(diǎn)?有時(shí),保存大量低質(zhì)量的 Bitmap 會(huì)非常有用,加載更高質(zhì)量的圖片的任務(wù)可以交給另外一個(gè)后臺(tái)線程處理。
  • 緩存太小會(huì)導(dǎo)致額外的花銷(xiāo)卻沒(méi)有明顯的好處,緩存太大同樣會(huì)導(dǎo)致 java.lang.OutOfMemory 的異常,并且使得你的程序只留下小部分的內(nèi)存用來(lái)工作(緩存占用太多內(nèi)存,導(dǎo)致其他操作會(huì)因?yàn)閮?nèi)存不夠而拋出異常)。所以,我們需要分析實(shí)際情況之后,提出一個(gè)合適的解決方案。

LruCache是Android提供的一個(gè)緩存類,通常運(yùn)用于內(nèi)存緩存,LruCache是一個(gè)泛型類,它的底層是用一個(gè)LinkedHashMap以強(qiáng)引用的方式存儲(chǔ)外界的緩存對(duì)象來(lái)實(shí)現(xiàn)的。

為什么使用LinkedHashMap來(lái)作為L(zhǎng)ruCache的存儲(chǔ),是因?yàn)長(zhǎng)inkedHashMap有兩種排序方式,一種是插入排序方式,一種是訪問(wèn)排序方式,默認(rèn)情況下是以訪問(wèn)方式來(lái)存儲(chǔ)緩存對(duì)象的;LruCache提供了get和put方法來(lái)完成緩存的獲取和添加,當(dāng)緩存滿時(shí),會(huì)將最近最少使用的對(duì)象移除掉,然后再添加新的緩存對(duì)象。如下源碼所示,底層是LinkedHashMap。

public LruCache(int maxSize) {
    if (maxSize <= 0) {
        throw new IllegalArgumentException("maxSize <= 0");
    }
    this.maxSize = maxSize;
    this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}

在使用LruCache的時(shí)候,首先需要獲取當(dāng)前設(shè)備的內(nèi)存容量,通常情況下會(huì)將總?cè)萘康陌朔种蛔鳛長(zhǎng)ruCache的容量,然后重寫(xiě)LruCache的sizeof方法,sizeof方法用于計(jì)算緩存對(duì)象的大小,單位需要與分配的容量的單位一致;

// 獲取系統(tǒng)最大緩存
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// set LruCache size;
// 使用最大可用內(nèi)存值的1/8作為緩存的大小
int cacheSize = maxMemory / 8;
LruCache<String, Bitmap> memoryCache = new LruCache<String, Bitmap>(cacheSize) {
    @Override
    protected int sizeOf(@NonNull String uri, @NonNull Bitmap bitmap) {
        // 重寫(xiě)此方法來(lái)衡量每張圖片的大小,默認(rèn)返回圖片數(shù)量
        return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
    }
};
//插入對(duì)象
memoryCache.put(key, bitmap);
//取出對(duì)象
memoryCache.get(key);

如何淘汰緩存,這個(gè)就要看LinkedHashMap集合的特點(diǎn)呢!LinkedHashMap 構(gòu)造函數(shù)的第三個(gè)參數(shù):accessOrder,傳入true時(shí), 元素會(huì)按訪問(wèn)順序排列,最后訪問(wèn)的在遍歷器最后端。在進(jìn)行淘汰時(shí),移除遍歷器前端的元素,直至緩存總大小降低到指定大小以下。

5.3 Lru緩存注意事項(xiàng)

看一個(gè)真實(shí)的場(chǎng)景:假設(shè)我們的LruCache可以緩存80張,每次刷新從網(wǎng)絡(luò)獲取20張圖片且不重復(fù),那么在刷新第五次的時(shí)候,根據(jù)LruCache緩存的規(guī)則,第一次刷新的20張圖片就會(huì)從LruCache中移出,處于等待被系統(tǒng)GC的狀態(tài)。如果我們繼續(xù)刷新n次,等待被回收的張數(shù)就會(huì)累積到 20 * n 張。

會(huì)出現(xiàn)什么問(wèn)題:會(huì)出現(xiàn)大量的Bitmap內(nèi)存碎片,我們不知道系統(tǒng)什么時(shí)候會(huì)觸發(fā)GC回收掉這些無(wú)用的Bitmap,對(duì)于內(nèi)存是否會(huì)溢出,是否會(huì)頻繁GC導(dǎo)致卡頓等未知問(wèn)題。

解決方案該怎么做?

第一種:在3.0以后引入了 BitmapFactory.Options.inBitmap,如果設(shè)置此項(xiàng),需要解碼的圖片就會(huì)嘗試使用該Bitmap的內(nèi)存,這樣取消了內(nèi)存的動(dòng)態(tài)分配,提高了性能,節(jié)省了內(nèi)存。

第二種:把處于無(wú)用的狀態(tài)的Bitmap放入SoftReference。SoftReference引用的對(duì)象會(huì)在內(nèi)存溢出之前被回收。

關(guān)于Lru緩存案例和代碼可以參考:AppLruCache

5.4 使用Lru磁盤(pán)緩存

內(nèi)存緩存能夠提高訪問(wèn)最近用過(guò)的 Bitmap 的速度,但是我們無(wú)法保證最近訪問(wèn)過(guò)的 Bitmap 都能夠保存在緩存中。像類似 GridView 等需要大量數(shù)據(jù)填充的控件很容易就會(huì)用盡整個(gè)內(nèi)存緩存。另外,我們的應(yīng)用可能會(huì)被類似打電話等行為而暫停并退到后臺(tái),因?yàn)楹笈_(tái)應(yīng)用可能會(huì)被殺死,那么內(nèi)存緩存就會(huì)被銷(xiāo)毀,里面的 Bitmap 也就不存在了。一旦用戶恢復(fù)應(yīng)用的狀態(tài),那么應(yīng)用就需要重新處理那些圖片。

磁盤(pán)緩存可以用來(lái)保存那些已經(jīng)處理過(guò)的 Bitmap,它還可以減少那些不再內(nèi)存緩存中的 Bitmap 的加載次數(shù)。當(dāng)然從磁盤(pán)讀取圖片會(huì)比從內(nèi)存要慢,而且由于磁盤(pán)讀取操作時(shí)間是不可預(yù)期的,讀取操作需要在后臺(tái)線程中處理。

注意:如果圖片會(huì)被更頻繁的訪問(wèn),使用 ContentProvider 或許會(huì)更加合適,比如在圖庫(kù)應(yīng)用中。

注意:因?yàn)槌跏蓟疟P(pán)緩存涉及到 I/O 操作,所以它不應(yīng)該在主線程中進(jìn)行。但是這也意味著在初始化完成之前緩存可以被訪問(wèn)。為了解決這個(gè)問(wèn)題,在上面的實(shí)現(xiàn)中,有一個(gè)鎖對(duì)象(lock object)來(lái)確保在磁盤(pán)緩存完成初始化之前,應(yīng)用無(wú)法對(duì)它進(jìn)行讀取。

內(nèi)存緩存的檢查是可以在 UI 線程中進(jìn)行的,磁盤(pán)緩存的檢查需要在后臺(tái)線程中處理。磁盤(pán)操作永遠(yuǎn)都不應(yīng)該在 UI 線程中發(fā)生。當(dāng)圖片處理完成后,Bitmap 需要添加到內(nèi)存緩存與磁盤(pán)緩存中,方便之后的使用。

06.不同版本對(duì)Bitmap管理

6.1 演變進(jìn)程

Android 2.3.3 (API level 10)以及之前,一個(gè) Bitmap 的像素?cái)?shù)據(jù)是存放在 Native 內(nèi)存空間中的。這些數(shù)據(jù)與 Bitmap 對(duì)象本身是隔離的,Bitmap 本身被存放在 Dalvik 堆中。并且無(wú)法預(yù)測(cè)在 Native 內(nèi)存中的像素級(jí)數(shù)據(jù)何時(shí)會(huì)被釋放,這意味著程序容易超過(guò)它的內(nèi)存限制并且崩潰。

Android 3.0 (API Level 11)開(kāi)始,像素?cái)?shù)據(jù)則是與 Bitmap 本身一起存放在 Dalvik 堆中。

Android 8.0(Android O)及之后的版本中,Bitmap 的像素?cái)?shù)據(jù)的內(nèi)存分配又回到了 Native 層,它是在 Native 堆空間進(jìn)行分配的。

6.2 管理Bitmap內(nèi)存

管理 Android 2.3.3 及以下版本的內(nèi)存使用。在 Android 2.3.3 (API level 10) 以及更低版本上,推薦使用 recycle() 方法。 如果在應(yīng)用中顯示了大量的 Bitmap 數(shù)據(jù),我們很可能會(huì)遇到 OutOfMemoryError 的錯(cuò)誤。 recycle() 方法可以使得程序更快的釋放內(nèi)存。

管理 Android 3.0 及其以上版本的內(nèi)存,從 Android 3.0 (API Level 11)開(kāi)始,引進(jìn)了 BitmapFactory.Options.inBitmap 字段。 如果使用了這個(gè)設(shè)置字段,decode 方法會(huì)在加載 Bitmap 數(shù)據(jù)的時(shí)候去重用已經(jīng)存在的 Bitmap。這意味著 Bitmap 的內(nèi)存是被重新利用的,這樣可以提升性能,并且減少了內(nèi)存的分配與回收。然而,使用 inBitmap 有一些限制,特別是在Android 4.4 (API level 19)之前,只有同等大小的位圖才可以被重用。

管理 Android 8.0 及其以上版本的內(nèi)存。在 Android 8.0 及其以上版本,處理內(nèi)存,也遵循 Android 3.0 以上版本同樣的方式。同時(shí),圖片像素?cái)?shù)據(jù)存儲(chǔ)在 native 層,并且不占用 Java 堆的空間,這也代表著我們擁有更大的圖片存儲(chǔ)空間,可以加載質(zhì)量更高、數(shù)據(jù)更多的圖片到內(nèi)存中。但是,內(nèi)存依然不是無(wú)限的,應(yīng)用還是要受到手機(jī)內(nèi)存的限制,所以一定要注意這一點(diǎn)。

6.3 提高Bitmap復(fù)用

Android3.0之后,并沒(méi)有強(qiáng)調(diào)Bitmap.recycle();而是強(qiáng)調(diào)Bitmap的復(fù)用。

使用LruCache對(duì)Bitmap進(jìn)行緩存,當(dāng)再次使用到這個(gè)Bitmap的時(shí)候直接獲取,而不用重走編碼流程。

Android3.0(API 11之后)引入了BitmapFactory.Options.inBitmap字段,設(shè)置此字段之后解碼方法會(huì)嘗試復(fù)用一張存在的Bitmap。這意味著B(niǎo)itmap的內(nèi)存被復(fù)用,避免了內(nèi)存的回收及申請(qǐng)過(guò)程,顯然性能表現(xiàn)更佳。

使用這個(gè)字段有幾點(diǎn)限制:

  • 聲明可被復(fù)用的Bitmap必須設(shè)置inMutable為true;
  • Android4.4(API 19)之前只有格式為jpg、png,同等寬高(要求苛刻),inSampleSize為1的Bitmap才可以復(fù)用;
  • Android4.4(API 19)之前被復(fù)用的Bitmap的inPreferredConfig會(huì)覆蓋待分配內(nèi)存的Bitmap設(shè)置的inPreferredConfig;
  • Android4.4(API 19)之后被復(fù)用的Bitmap的內(nèi)存必須大于需要申請(qǐng)內(nèi)存的Bitmap的內(nèi)存;
  • Android4.4(API 19)之前待加載Bitmap的Options.inSampleSize必須明確指定為1。

Bitmap復(fù)用的實(shí)驗(yàn),代碼如下所示,然后看打印的日志信息

  • 從內(nèi)存地址的打印可以看出,兩個(gè)對(duì)象其實(shí)是一個(gè)對(duì)象,Bitmap復(fù)用成功;
  • bitmapReuse占用的內(nèi)存(4346880)正好是bitmap占用內(nèi)存(1228800)的四分之一;
  • getByteCount()獲取到的是當(dāng)前圖片應(yīng)當(dāng)所占內(nèi)存大小,getAllocationByteCount()獲取到的是被復(fù)用Bitmap真實(shí)占用內(nèi)存大小。雖然bitmapReuse的內(nèi)存只有4346880,但是因?yàn)槭菑?fù)用的bitmap的內(nèi)存,因而其真實(shí)占用的內(nèi)存大小是被復(fù)用的bitmap的內(nèi)存大?。?228800)。這也是getAllocationByteCount()可能比getByteCount()大的原因。
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
private void initBitmap() {
    BitmapFactory.Options options = new BitmapFactory.Options();
    // 圖片復(fù)用,這個(gè)屬性必須設(shè)置;
    options.inMutable = true;
    // 手動(dòng)設(shè)置縮放比例,使其取整數(shù),方便計(jì)算、觀察數(shù)據(jù);
    options.inDensity = 320;
    options.inTargetDensity = 320;
    Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bg_autumn_tree_min, options);
    // 對(duì)象內(nèi)存地址;
    Log.i("ycBitmap", "bitmap = " + bitmap);
    Log.i("ycBitmap", "ByteCount = " + bitmap.getByteCount() + ":::bitmap:AllocationByteCount = " + bitmap.getAllocationByteCount());
    // 使用inBitmap屬性,這個(gè)屬性必須設(shè)置;
    options.inBitmap = bitmap; options.inDensity = 320;
    // 設(shè)置縮放寬高為原始寬高一半;
    options.inTargetDensity = 160;
    options.inMutable = true;
    Bitmap bitmapReuse = BitmapFactory.decodeResource(getResources(), R.drawable.bg_kites_min, options);
    // 復(fù)用對(duì)象的內(nèi)存地址;
    Log.i("ycBitmap", "bitmapReuse = " + bitmapReuse);
    Log.i("ycBitmap", "bitmap:ByteCount = " + bitmap.getByteCount() + ":::bitmap:AllocationByteCount = " + bitmap.getAllocationByteCount());
    Log.i("ycBitmap", "bitmapReuse:ByteCount = " + bitmapReuse.getByteCount() + ":::bitmapReuse:AllocationByteCount = " + bitmapReuse.getAllocationByteCount());
  
    //11-26 18:24:07.971 15470-15470/com.yc.ycbanner I/ycBitmap: bitmap = android.graphics.Bitmap@9739bff
    //11-26 18:24:07.972 15470-15470/com.yc.ycbanner I/ycBitmap: bitmap:ByteCount = 4346880:::bitmap:AllocationByteCount = 4346880
    //11-26 18:24:07.994 15470-15470/com.yc.ycbanner I/ycBitmap: bitmapReuse = android.graphics.Bitmap@9739bff
    //11-26 18:24:07.994 15470-15470/com.yc.ycbanner I/ycBitmap: bitmap:ByteCount = 1228800:::bitmap:AllocationByteCount = 4346880
    //11-26 18:24:07.994 15470-15470/com.yc.ycbanner I/ycBitmap: bitmapReuse:ByteCount = 1228800:::bitmapReuse:AllocationByteCount = 4346880
}

07.圖片其他方面優(yōu)化

7.1 減少PNG圖片使用

這里要介紹一種新的圖片格式:Webp,它是由 Google 推出的一種既保留 png 格式的優(yōu)點(diǎn),又能夠減少圖片大小的一種新型圖片格式。

在 Android 4.0(API level 14) 中支持有損的 WebP 圖像,在 Android 4.3(API level 18) 和更高版本中支持無(wú)損和透明的 WebP 圖像。

注意一下,Webp格式圖片僅僅只是減少圖片的質(zhì)量大小,并不會(huì)減少加載圖片后的內(nèi)存占用。

Android 為何推薦用WebP格式圖片:WebP 格式的圖片壓縮率通常比 JPEG 和 PNG 更高,可以在保證圖像質(zhì)量的前提下顯著減少文件大小。支持無(wú)損壓縮,支持透明度等。

7.2 切割圓角優(yōu)化

業(yè)務(wù)背景介紹:在顯示圖片是有時(shí)候需要顯示圓角圖片,我們應(yīng)該都知道圓角顯示肯定是更加耗費(fèi)內(nèi)存和性能,會(huì)導(dǎo)致圖片的過(guò)度繪制等問(wèn)題。

給控件設(shè)置圓角,代碼層面一般怎么做?

  1. 第一種:比如給TextView設(shè)置Shape圓角
  2. 第二種:自定義控件實(shí)現(xiàn),大概原理:要實(shí)圓角或者圓形的顯示效果,就是對(duì)圖片顯示的內(nèi)容區(qū)域進(jìn)行“裁剪”,只顯示指定的區(qū)域即可。
  3. 第三種:使用Glide加載圖片設(shè)置圓角

clipPath切割圓角的核心原理

第一步:圖片是被繪制在畫(huà)布上的,所以用 canvas 的 clipPath()方法先將畫(huà)布裁剪成指定形狀。由于clipPath()方法不支持抗鋸齒,圖片邊緣會(huì)有明顯的毛糙感,體驗(yàn)并不理想。

setXfermode切割圓角的核心原理

使用圖像的 Alpha 合成模式。整個(gè)過(guò)程就是先繪制目標(biāo)圖像,也就是圖片;再繪制原圖像,即一個(gè)圓角矩形或者圓形,這樣最終目標(biāo)圖像只顯示和原圖像重合的區(qū)域。如果是圖片,需要通過(guò)src屬性或者對(duì)應(yīng)的方法來(lái)設(shè)置圖片,否則不能達(dá)到預(yù)期效果。

  1. 第一步:核心邏輯是在draw方法中進(jìn)行繪制,首先調(diào)用canvas.saveLayer設(shè)置離屏緩存,新建一個(gè)控件區(qū)域大小的圖層
  2. 第二步:相當(dāng)于使用super.onDraw繪制自己,這一步調(diào)用super方法即可
  3. 第三步:設(shè)置畫(huà)筆Paint屬性,先調(diào)用setXfermode設(shè)置混合模式,然后在調(diào)用canvas.drawPath(path, paint)繪制path,最后再清除Xfermode
  4. 第四步:添加邊框,只需要繪制一個(gè)指定樣式的圓角矩形或者圓形即可。比如繪制圓角,調(diào)用Path.addRoundRect添加path,然后調(diào)用canvas.drawPath繪制path

目前設(shè)置控件圓角有哪些方式

  1. 第一種:比如給TextView設(shè)置Shape圓角,非常常見(jiàn)的使用
  2. 第二種:使用背景圖片
  3. 第三種:自定義控件實(shí)現(xiàn)
  4. 第四種:使用ViewOutlineProvider裁剪View
  5. 第五種:使用CardView
  6. 第六種:使用Glide加載圖片設(shè)置圓角

各種設(shè)置圓角的優(yōu)缺點(diǎn)對(duì)比

  1. 第一種:shape常見(jiàn),簡(jiǎn)單直觀。缺點(diǎn)是項(xiàng)目中xml,越寫(xiě)越多
  2. 第二種:使用切圖沒(méi)什么說(shuō)的,使用起來(lái)不方便
  3. 第三種:自定義控件,彌補(bǔ)shape上不足,采用attr屬性設(shè)置圓角,那樣圓角樣式多,使用起來(lái)方便
  4. 第四種:用于實(shí)現(xiàn)view陰影和輪廓
  5. 第五種:使用CardView,官方支持陰影和圓角控件
  6. 第六種:使用Glide加載圓角,一般用于圖片設(shè)置,比較方便

具體案例可見(jiàn):RoundCorners

7.3 如何給圖片置灰色

大概的操作步驟。具體可以參考:PicCalculateUtils

  • 第一步:獲取原始圖片的寬高,然后創(chuàng)建一個(gè)bitmap可變位圖對(duì)象。
  • 第二步:創(chuàng)建畫(huà)板canvas對(duì)象,然后創(chuàng)建畫(huà)筆paint。然后調(diào)用canvas.drawBitmap方法繪制圖片
  • 第三步:對(duì)畫(huà)筆進(jìn)行修飾,設(shè)置畫(huà)筆顏色屬性,這里使用到了ColorMatrix,核心就是設(shè)置飽和度為0,即可繪制灰色內(nèi)容

7.4 如何處理圖片旋轉(zhuǎn)呢

在Android中使用ImageView顯示圖片的時(shí)候發(fā)現(xiàn)圖片顯示不正,方向偏了或者倒過(guò)來(lái)了。

解決這個(gè)問(wèn)題很自然想到的兩步走,首先是要自動(dòng)識(shí)別圖像方向,計(jì)算旋轉(zhuǎn)角度,然后對(duì)圖像進(jìn)行旋轉(zhuǎn)并顯示。

  1. 第一步:識(shí)別圖像方向

首先在這里提一個(gè)概念EXIF(Exchangeable Image File Format,可交換圖像文件)。簡(jiǎn)而言之,Exif是一個(gè)標(biāo)準(zhǔn),用于電子照相機(jī)(也包括手機(jī)、掃描器等)上,用來(lái)規(guī)范圖片、聲音、視屏以及它們的一些輔助標(biāo)記格式。

Exif支持的格式如下:圖像;壓縮圖像文件:JPEG、DCT;非壓縮圖像文件:TIFF;音頻;RIFF、WAV

Android提供了對(duì)JPEG格式圖像Exif接口支持,可以讀取JPEG文件metadata信息,參見(jiàn)ExifInterface。這些Metadata信息總的來(lái)說(shuō)大致分為三類:日期時(shí)間、空間信息(經(jīng)緯度、高度)、Camera信息(孔徑、焦距、旋轉(zhuǎn)角、曝光量等等)。

  1. 第二步:關(guān)于圖像旋轉(zhuǎn)

獲取了圖片的旋轉(zhuǎn)方向后,然后再設(shè)置圖像旋轉(zhuǎn)。最后Bitmap提供的靜態(tài)createBitmap方法,可以對(duì)圖片設(shè)置旋轉(zhuǎn)角度。具體看:PicCalculateUtils

7.5 保存圖片且刷相冊(cè)

大概的操作步驟如下所示。具體可看:ImageSaveUtils

  • 第一步:創(chuàng)建圖片文件,然后將bitmap對(duì)象寫(xiě)到圖片文件中
  • 第二步:通過(guò)MediaStore將圖片插入到共享目錄相冊(cè)中
  • 第三步:發(fā)送通知,通知相冊(cè)中刷新插入圖片的數(shù)據(jù)。注意,獲取圖片資源uri刷新即可,避免刷新所有數(shù)據(jù)造成等待時(shí)間過(guò)長(zhǎng)。

MediaStore 是 Android 提供的一個(gè)內(nèi)容提供者(Content Provider),用于管理設(shè)備上的媒體文件(如圖片、視頻、音頻等)。通過(guò) MediaStore,可以將圖片文件插入到系統(tǒng)的媒體庫(kù)中,使其在相冊(cè)中可見(jiàn)。

7.6 統(tǒng)一圖片域名優(yōu)化

域名統(tǒng)一,減少了10%+的重復(fù)圖片下載和內(nèi)存消耗。同時(shí)減少之前多域名圖片加載時(shí)重復(fù)創(chuàng)建HTTPS請(qǐng)求的過(guò)程,減少圖片加載時(shí)間。

統(tǒng)一圖片域名優(yōu)化 是一種通過(guò)將圖片資源統(tǒng)一托管在同一個(gè)域名下,并結(jié)合 CDN(內(nèi)容分發(fā)網(wǎng)絡(luò))和圖片處理服務(wù),來(lái)提升圖片加載性能、降低服務(wù)器壓力、優(yōu)化用戶體驗(yàn)的技術(shù)手段。

7.7 優(yōu)化H5圖片加載

通過(guò)攔截WebView圖片加載的方式,讓原生圖片庫(kù)來(lái)下載圖片之后傳遞圖片二進(jìn)制數(shù)據(jù)給WebView顯示。

采用OkHttp攔截資源緩存,下面是大概的思路。緩存的入口從shouldInterceptRequest出發(fā)

  • 第一步,拿到WebResourceRequest對(duì)象中請(qǐng)求資源的url還有header,如果開(kāi)發(fā)者設(shè)置不緩存則返回null
  • 第二步,如果緩存,通過(guò)url判斷攔截資源的條件,過(guò)濾非http,音視頻等資源,這個(gè)是可自由配置緩存內(nèi)容比如css,png,jpg,xml,txt等
  • 第三步,判斷本地是否有OkHttp緩存數(shù)據(jù),如果有則直接讀取本地資源,通過(guò)url找到對(duì)應(yīng)的path路徑,然后讀取文件流,組裝數(shù)據(jù)返回。
  • 第四步,如果沒(méi)有緩存數(shù)據(jù),創(chuàng)建OkHttp的Request請(qǐng)求,將資源網(wǎng)絡(luò)請(qǐng)求交給okHttp來(lái)處理,并且用它自帶的緩存功能,當(dāng)然如果是請(qǐng)求失敗或者異常則返回null,否則返回正常數(shù)據(jù)

關(guān)于webView圖片緩存的方案,可以直接參考:YCWebView

7.8 優(yōu)化圖片陰影效果

陰影效果有哪些實(shí)現(xiàn)方式

  • 第一種:使用CardView,但是不能設(shè)置陰影顏色
  • 第二種:采用shape疊加,存在后期UI效果不便優(yōu)化
  • 第三種:UI切圖
  • 第四種:自定義View
  • 第五種:自定義Drawable

否定上面前兩種方案原因分析?

第一個(gè)方案的CardView漸變色和陰影效果很難控制,只能支持線性或者環(huán)裝形式漸變,這種不滿足需要,因?yàn)殛幱氨旧硎且粋€(gè)四周一層很淡的顏色包圍,在一個(gè)矩形框的層面上顏色大概一致,而且這個(gè)CardView有很多局限性,比如不能修改陰影的顏色,不能修改陰影的深淺。所以這個(gè)思路無(wú)法實(shí)現(xiàn)這個(gè)需求。

第二個(gè)采用shape疊加,可以實(shí)現(xiàn)陰影效果,但是影響UI,且陰影部分是占像素的,而且不靈活。

第三個(gè)方案詢問(wèn)了一下ui。他們給出的結(jié)果是如果使用切圖的話那標(biāo)注的話很難標(biāo),身為一個(gè)優(yōu)秀的設(shè)計(jì)師大多對(duì)像素點(diǎn)都和敏感,界面上的像素點(diǎn)有一點(diǎn)不協(xié)調(diào)那都是無(wú)法容忍的。

網(wǎng)上一些介紹陰影效果方案

所有在深?yuàn)W的技術(shù),也都是為需求做準(zhǔn)備的。也就是需要實(shí)踐并且可以用到實(shí)際開(kāi)發(fā)中,這篇文章不再抽象介紹陰影效果原理,理解三維空間中如何處理偏移光線達(dá)到陰影視差等,網(wǎng)上看了一些文章也沒(méi)看明白或者理解。這篇博客直接通過(guò)調(diào)用api實(shí)現(xiàn)預(yù)期的效果。

多個(gè)drawable疊加,使用layer-list可以將多個(gè)drawable按照順序?qū)盈B在一起顯示,默認(rèn)情況下,所有的item中的drawable都會(huì)自動(dòng)根據(jù)它附上view的大小而進(jìn)行縮放,layer-list中的item是按照順序從下往上疊加的,即先定義的item在下面,后面的依次往上面疊放

陰影是否占位?1.使用CardView陰影不占位,不能設(shè)置陰影顏色和效果。2.使用shape陰影是可以設(shè)置陰影顏色,但是是占位的

幾種方案優(yōu)缺點(diǎn)對(duì)比分析

CardView 優(yōu)點(diǎn):自帶功能實(shí)現(xiàn)簡(jiǎn)單 缺點(diǎn):自帶圓角不一定可適配所有需求

layer(shape疊加) 優(yōu)點(diǎn):實(shí)現(xiàn)形式簡(jiǎn)單 缺點(diǎn):效果一般

自定義實(shí)現(xiàn) 優(yōu)點(diǎn):實(shí)現(xiàn)效果好可配置能力高 缺點(diǎn):需要開(kāi)發(fā)者自行開(kāi)發(fā)

關(guān)于解決陰影效果,具體各種方案的對(duì)比可以參考這個(gè)demo:AppShadowLib

7.9 圖片資源的壓縮

我們應(yīng)用中使用的圖片,設(shè)計(jì)師出的原圖通常都非常大,他們通常會(huì)使用工具,經(jīng)過(guò)一定的壓縮,縮減到比較小一些的大小。

但是,這些圖片通常都有一定的可壓縮空間,我在之前的項(xiàng)目中,對(duì)圖片進(jìn)行了二次壓縮,整體壓縮率達(dá)到了 40%~50% ,效果還是非常不錯(cuò)的。

這里介紹下常用的,圖片壓縮的方法:

  • 使用壓縮工具對(duì)圖片進(jìn)行二次壓縮。
  • 根據(jù)最終圖片是否需要透明度展示,優(yōu)先選擇不透明的圖片格式,例如,我們應(yīng)該避免使用 png 格式的圖片。
  • 對(duì)于色彩簡(jiǎn)單,例如,一些背景之類的圖片,可以選擇使用布局文件來(lái)定義(矢量圖),這樣就會(huì)非常節(jié)省內(nèi)存了。
  • 如果包含透明度,優(yōu)先使用 WebP 等格式圖像。

圖片在上線前進(jìn)行壓縮處理,不但可以減少內(nèi)存的使用,如果圖片是網(wǎng)絡(luò)獲取的,也可以減少網(wǎng)絡(luò)加載的流量和時(shí)間。推薦一個(gè)圖片壓縮網(wǎng)站:tinypng網(wǎng)站

08.筆記匯總一下

模塊 描述 備注
GitHub 多個(gè)YC系列開(kāi)源項(xiàng)目,包含Android組件庫(kù),以及多個(gè)案例 GitHub
博客匯總 匯聚Java,Android,C/C++,網(wǎng)絡(luò)協(xié)議,算法,編程總結(jié)等 YCBlogs
設(shè)計(jì)模式 六大設(shè)計(jì)原則,23種設(shè)計(jì)模式,設(shè)計(jì)模式案例,面向?qū)ο笏枷?/td> 設(shè)計(jì)模式
Java進(jìn)階 數(shù)據(jù)設(shè)計(jì)和原理,面向?qū)ο蠛诵乃枷耄琁O,異常,線程和并發(fā),JVM Java高級(jí)
網(wǎng)絡(luò)協(xié)議 網(wǎng)絡(luò)實(shí)際案例,網(wǎng)絡(luò)原理和分層,Https,網(wǎng)絡(luò)請(qǐng)求,故障排查 網(wǎng)絡(luò)協(xié)議
計(jì)算機(jī)原理 計(jì)算機(jī)組成結(jié)構(gòu),框架,存儲(chǔ)器,CPU設(shè)計(jì),內(nèi)存設(shè)計(jì),指令編程原理,異常處理機(jī)制,IO操作和原理 計(jì)算機(jī)基礎(chǔ)
學(xué)習(xí)C編程 C語(yǔ)言入門(mén)級(jí)別系統(tǒng)全面的學(xué)習(xí)教程,學(xué)習(xí)三到四個(gè)綜合案例 C編程
C++編程 C++語(yǔ)言入門(mén)級(jí)別系統(tǒng)全面的教學(xué)教程,并發(fā)編程,核心原理 C++編程
算法實(shí)踐 專欄,數(shù)組,鏈表,棧,隊(duì)列,樹(shù),哈希,遞歸,查找,排序等 Leetcode
Android 基礎(chǔ)入門(mén),開(kāi)源庫(kù)解讀,性能優(yōu)化,F(xiàn)ramework,方案設(shè)計(jì) Android
  • 1.1 為什么說(shuō)圖片比較占用內(nèi)存:一個(gè)手機(jī)拍攝的 2700 * 1900 像素的照片,需要 5.1M 的存儲(chǔ)空間,但是在圖像解碼配置 ARGB_8888 時(shí),它加載到內(nèi)存需要 19.6M 內(nèi)存空間
  • 1.2 加載網(wǎng)絡(luò)圖片大概流程:1.獲取網(wǎng)絡(luò)資源文件流;2.decodeStream加載到bitmap中;3.然后壓縮圖片;4.然后處理圖片變換;5.圖片緩存策略;
  • 1.3 三方庫(kù)加載圖片邏輯大概是怎樣:從網(wǎng)絡(luò)拉取圖片;然后解碼圖片;然后進(jìn)行壓縮;接著會(huì)有圖片常用圓角裁剪等處理;然后三級(jí)緩存加載的圖片;當(dāng)然加載圖片過(guò)程涉及同步加載和異步加載;最后設(shè)置到具體view控件上。
  • 1.4 BitmapFactory可以加載哪些內(nèi)容:提供了四類方法來(lái)加載Bitmap:decodeFile加載文件、decodeResource加載資源、decodeStream加載流、decodeByteArray加載字節(jié)數(shù)組。
  • 1.5 如何理解圖片大小和占用內(nèi)存:圖片大小是經(jīng)過(guò)相對(duì)應(yīng)的壓縮算法將原圖每個(gè)像素點(diǎn)信息轉(zhuǎn)換用另一種數(shù)據(jù)格式表示;圖片內(nèi)存是這張圖片加載進(jìn)內(nèi)存所占用的大小。
  • 1.6 Bitmap能直接存儲(chǔ)嗎:bitmap是一個(gè)對(duì)象,如果要存儲(chǔ)成本地可以查看的圖片文件,則必須對(duì)bitmap進(jìn)行編碼,然后通過(guò)io流寫(xiě)到本地file文件上。
  • 1.7 Bitmap創(chuàng)建的對(duì)象內(nèi)存分配在哪里:8.0 之前版本為 Bitmap 像素從 Java heap 申請(qǐng)內(nèi)存。 8.0 版本后為 Bitmap 像素從 Native heap 申請(qǐng)內(nèi)存。
  • 1.8 如果你是谷歌開(kāi)發(fā),圖片框架如何設(shè)計(jì):大多數(shù)圖片框架加載流程,概括來(lái)說(shuō),圖片加載包含封裝,解析,下載,解碼,變換,緩存,顯示等操作。
  • 2.1 如何計(jì)算圖片占用內(nèi)存:bitmap內(nèi)存大小 = 圖片長(zhǎng)度 x 圖片寬度 x 單位像素占用的字節(jié)數(shù)(這個(gè)就跟編碼方式有關(guān)了)
  • 2.2 加載本地資源圖片占用內(nèi)存怎么計(jì)算:它占用的內(nèi)存 = width * height * nTargetDensity/inDensity 一個(gè)像素所占的內(nèi)存。
  • 2.3 一個(gè)像素占用多大內(nèi)存:ARGB_8888: 每個(gè)像素4字節(jié). 共32位,默認(rèn)設(shè)置。RGB_565:共16位,2字節(jié),只存儲(chǔ)RGB值。
  • 2.5 影響B(tài)itmap內(nèi)存因素有哪些:圖片加載的分辨率;編碼格式;設(shè)備的屏幕密度等因素。
  • 2.6 加載xhdpi和xxhdpi圖片有何區(qū)別:相同的圖片,顯示在控件上面所占用內(nèi)存是不一樣的。因?yàn)閮蓚€(gè)文件夾對(duì)應(yīng)的dpi是不一樣的。
  • 2.7 圖片放在res不同目錄,加載的內(nèi)存是一樣的嗎:在加載 res 目錄下的資源圖片時(shí),會(huì)根據(jù)圖片存放的不同目錄做一次分辨率的轉(zhuǎn)換,新圖的高度 = 原圖高度 * (設(shè)備的 dpi / 目錄對(duì)應(yīng)的 dpi )
  • 3.1 常見(jiàn)圖片壓縮有哪些:1.質(zhì)量壓縮不會(huì)對(duì)內(nèi)存產(chǎn)生影響;2.內(nèi)存壓縮,按照比例縮小圖片加載減少內(nèi)存占用
  • 3.2.1 如何理解圖片尺寸壓縮:原理是通過(guò)降低圖片的寬度和高度(即減少像素?cái)?shù)量),從而減少圖片的存儲(chǔ)大小。
  • 3.2.2 BitmapFactory.Options.inSampleSize核心思想是什么:是通過(guò)降低圖片的分辨率來(lái)減少內(nèi)存占用。它是在解碼階段生效的,因此它可以在圖片加載到內(nèi)存之前就完成壓縮,避免不必要的內(nèi)存消耗。
  • 3.2.3 inJustDecodeBounds這個(gè)參數(shù)是干什么的:如果設(shè)置為true則表示decode函數(shù)不會(huì)生成bitmap對(duì)象,僅是將圖像相關(guān)的參數(shù)填充到option對(duì)象里,這樣我們就可以在不生成bitmap而獲取到圖像的相關(guān)參數(shù)。
  • 3.3 如何理解圖片質(zhì)量壓縮:通過(guò)減少圖像文件中像素的細(xì)節(jié)和信息來(lái)降低圖像文件大小的技術(shù)。這種壓縮方法通常會(huì)犧牲一定程度的圖像質(zhì)量,以換取更小的文件大小。
  • 3.4 如何理解雙線性采樣壓縮:這種壓縮方法通過(guò)對(duì)圖像進(jìn)行插值計(jì)算,以平滑地減少圖像的像素?cái)?shù)量,同時(shí)盡可能保留圖像的細(xì)節(jié)和質(zhì)量。
  • 3.5 高清圖分片加載的核心思想:將大尺寸的高清圖像分割成多個(gè)小塊(片段),根據(jù)顯示需求動(dòng)態(tài)加載和拼接這些片段,以實(shí)現(xiàn)高清圖像的顯示。幫助減少內(nèi)存占用和提高性能,保持高清圖像的清晰度。
  • 4.1 如何理解RGB色彩模式:一種基于紅、綠、藍(lán)三種原色的彩色表示方式,通過(guò)不同比例的這三種顏色的組合來(lái)表示各種顏色。
  • 4.2 如何理解ARGB色彩模式:在 RGB 色彩模式基礎(chǔ)上增加了透明度通道,用于控制像素的透明度,提供了更多的圖像處理和顯示選項(xiàng)。
  • 4.3 Android中色彩格式如何選擇:1.RGB 格式表示紅藍(lán)綠;2.ARGB 格式表示增加一個(gè)Alpha透明通道;3.十六進(jìn)制格式顏色
  • 5.1 Android圖片內(nèi)存緩存思想是什么:內(nèi)存緩存的核心思想是利用內(nèi)存的高速讀寫(xiě)特性,緩存最近使用的圖片,從而避免重復(fù)加載和減少磁盤(pán) I/O 操作。
  • 5.2 Lru內(nèi)存緩存核心思想是什么:一種基于 最近最少使用(LRU) 策略的緩存算法,廣泛應(yīng)用于內(nèi)存緩存設(shè)計(jì)中。其核心思想是通過(guò)淘汰最近最少使用的緩存項(xiàng),來(lái)高效管理有限的緩存空間。
  • 5.3 Lru緩存注意事項(xiàng)有哪些:
  • 6.1 Bitmap演變進(jìn)程是什么:不同版本對(duì) Bitmap 管理有一些差異,主要涉及到內(nèi)存管理、資源回收和性能優(yōu)化等方面。
  • 7.1 為何推薦用WebP格式圖片:WebP 格式的圖片壓縮率通常比 JPEG 和 PNG 更高,可以在保證圖像質(zhì)量的前提下顯著減少文件大小。支持無(wú)損壓縮,支持透明度等。
  • 7.4 如何處理圖片旋轉(zhuǎn):解決這個(gè)問(wèn)題很自然想到的兩步走,首先是要自動(dòng)識(shí)別圖像方向,計(jì)算旋轉(zhuǎn)角度,然后對(duì)圖像進(jìn)行旋轉(zhuǎn)并顯示。
  • 7.5 如何保存圖片到相冊(cè):MediaStore 是 Android 提供的一個(gè)內(nèi)容提供者(Content Provider),用于管理設(shè)備上的媒體文件(如圖片、視頻、音頻等)。
  • 7.6 如何理解統(tǒng)一圖片域名優(yōu)化:將圖片資源統(tǒng)一托管在同一個(gè)域名下,并結(jié)合 CDN(內(nèi)容分發(fā)網(wǎng)絡(luò))和圖片處理服務(wù),來(lái)提升圖片加載性能、降低服務(wù)器壓力、優(yōu)化用戶體驗(yàn)的技術(shù)手段。
  • 7.7 如何理解優(yōu)化H5圖片加載:通過(guò)攔截WebView圖片加載的方式,讓原生圖片庫(kù)來(lái)下載圖片之后傳遞圖片二進(jìn)制數(shù)據(jù)給WebView顯示。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容