[MISC]關(guān)于Bitmap在Android平臺的整理

前言

最近看了一個問題忌穿,和 bitmap有關(guān),由此回顧自己的工作經(jīng)歷,發(fā)現(xiàn)和圖片打交道時間其實挺多的结啼,包括曾經(jīng)參與好幾款的圖庫和修圖app的開發(fā)掠剑,也主導(dǎo)過一款圖庫的bug fix,想想其實是可以做個 bitmap在 Android平臺的整理郊愧。

基礎(chǔ)知識

在談 Android平臺的 bitmap之前朴译,我們先來回顧或?qū)W習(xí)下關(guān)于圖片的一些基礎(chǔ)知識。

1属铁、圖片的存儲

回歸到最原始狀態(tài)眠寿,由馮諾伊曼集大成的提出二進(jìn)制和現(xiàn)代電子計算機(jī)架構(gòu)后,聰明的人類利用電子器件的物理特性,讓計算機(jī)世界一直運行在二進(jìn)制世界,且之后一直在蓬勃發(fā)展焦蘑。而在二進(jìn)制世界里,想要表達(dá)任何信息,就需要也只能使用0和1兩個數(shù)字來表達(dá)(當(dāng)然想要完整的構(gòu)建一個二進(jìn)制世界盯拱,邏輯運算必不可少,這里只討論“靜態(tài)”信息例嘱,不做過多拓展)0或1所占的空間為最小單位狡逢,8個bit對應(yīng)的byte則在程度上更像是一個基本單位,當(dāng)然還有KB,MB,GB,依次以1024的倍數(shù)拓展蝶防,正是這樣宏觀數(shù)量級的比特位構(gòu)成了豐富多彩的計算機(jī)世界甚侣。

而計算機(jī)能提供的信息形式其實是可以直接羅列出來的,無外乎:文字,圖片,聲音,視頻。文字的話,對于美國人來說想要表達(dá)其實只要能表達(dá)26個字母再做組合就行了间学,所以最初的 ANSI碼就是用8個bit位能表達(dá)的信息(28)的頭127位就表達(dá)了包括常見的標(biāo)點殷费、數(shù)字、字母還分大小寫低葫。當(dāng)然之后還有中國人的GB2312详羡,用16個bit位,也就是兩個byte來表達(dá)一個漢字嘿悬,再到后來國際通用的utf-8实柠。除了文字之外,聲音和視頻在這里不討論善涨,那圖片呢窒盐,該怎么表達(dá)?

圖片其實可以看作是一個一個點密集的拼湊而成,也就是常稱作的像素钢拧,所以粗暴點來看只要能表達(dá)像素也就可以表達(dá)圖片了蟹漓。事實也就是這么粗暴,圖片就是用bit位表達(dá)的像素堆積而成源内,這應(yīng)該也是 Bitmap名稱的由來吧葡粒。那么表達(dá)一個像素需要多少幾個bit位呢?我們先來看像素表達(dá)的信息有哪些,最直觀的就是顏色,再來可能還有一個透明度嗽交。如果用RGB顏色模型來展現(xiàn)的話,需要一個R(red)卿嘲,一個G( green),一個B(blue)來混合組成一個顏色,如果紅色按照程度分有256種的話夫壁,則需要8個bit位來表達(dá)一個紅色拾枣,綠色和藍(lán)色亦然,再加上256種透明度掌唾,所以想要高精度的表達(dá)一個像素放前,就需要四個字節(jié)。而我們常說的手機(jī)攝像頭有一千萬像素糯彬,兩千萬像素,也就是實實在在的有這么多像素葱她,那么來簡單算一下撩扒,一千萬像素相當(dāng)于10x1024x1024,每個像素所占空間有4個字節(jié)吨些,也就是40x1024x1024個byte搓谆,約等于40MB,注意這里的40MB就是一張圖片加載到內(nèi)存里然后做渲染展示到屏幕上所需要的空間豪墅。

一張圖片40MB內(nèi)存是非常驚人的泉手,我們都知道Android平臺的每個終端出廠都有設(shè)定允許運行app的最大堆內(nèi)存。早期的Android機(jī)最大堆內(nèi)存小的可憐偶器,要是同時加載幾張圖片不就是直接爆了斩萌。至于怎么處理后面再來介紹。既然是圖片的存儲屏轰,除了在內(nèi)存中颊郎,當(dāng)然還有磁盤中的存儲。

關(guān)于圖片在磁盤上的存儲霎苗,我想大家都有了解姆吭。按照前面所說的,表達(dá)一張圖片需要40MB唁盏,就算在磁盤上也是很大的壓力内狸,開玩笑,25張圖片一個G厘擂,存不起啊昆淡。不過確實有這樣的圖片文件,就是windows平臺的bmp格式的位圖文件驴党。但是我們平時使用的圖片文件當(dāng)然不會那么大瘪撇,常見的圖片文件jpeg,png,gif倔既,svg等后綴結(jié)尾的文件恕曲。這些文件格式其實代表了不同的壓縮算法,既然這么大渤涌,只能壓縮啊佩谣。壓縮的中心思想也是圍繞像素展開,如果一張圖片一行像素都一樣实蓬,那顯然不用存儲每個像素茸俭,壓縮算法不做展開,我也不熟安皱,在這里稍微提一下调鬓,壓縮算法分為有損壓縮和無損壓縮,我們常見的jpeg是有損壓縮酌伊,png是無損腾窝。

Bitmap in Android

1、java層的bitmap

前面說了這么多居砖,下面我們來看下Bitmap在Android中的具體實現(xiàn)虹脯。首先看下Bitmap相關(guān)的Java類所在位置(注意,以下代碼以Android O為標(biāo)準(zhǔn)):

~aosp/frameworks/base/graphics/java/android/graphics/Bitmap.java~

與它同級的還有BitmapFactory.java BitmapRegionDecoder.java ColorSpace.java 都分在了/framework/base/graphics包下奏候,注意這里的graphics我個人稱作是狹義上的graphics循集,特指可繪制的素材和繪制相關(guān)的類組成的包,并不指Android平臺或其它操作系統(tǒng)常說的graphics子系統(tǒng)蔗草,關(guān)于graphics subsystem我后面準(zhǔn)備分幾篇wiki嘗試講明白講通透咒彤,敬請期待。

其實關(guān)于Bitmap的類是不多的蕉世,上層開發(fā)常用的其實就是Bitmap 和 BitmapFactory蔼紧,當(dāng)然除此之外少不了的是各個開發(fā)者各種三方框架及各個廠商自己定義的工具類和解決方案。再回到Bitmap.java 首先看它的強關(guān)系狠轻,繼承了誰奸例,實現(xiàn)了誰:

public final class Bitmap implements Parcelable {

從實現(xiàn)了Parcelable接口來看,Bitmap已經(jīng)明示了我們它會參與序列化傳遞向楼,換句話說就是大概率會跨進(jìn)程傳遞查吊。再看Bitmap.java中包含的變量和方法:

Bitmap.java的變量和方法

總的來看,Bitmap.java中連帶TAG共有154個方法和內(nèi)部變量湖蜕,除常量最靠前的mNativePtr變量看名字就知道是Bitmap連接JNI的橋梁逻卖,其實它就是實際的bitmap,native的bitmap之后會介紹到昭抒。繼續(xù)看其它的變量和方法:
除了一些看名字就知道意思的常用變量评也,有一個內(nèi)部的聯(lián)合體稍微提一下:


Bitmap.Config

這個Config聯(lián)合體定義了配置圖片解析時針對每個像素的存儲配置炼杖,默認(rèn)的是ARGB_8888也就是我們之前提到的在alpha, R, G, B四個通道用8個bit位來表示一個像素,另一個用的比較多的是RGB_565盗迟,意思是alpha通道不表示即圖片不分透明度坤邪,然后RGB分別用5個6個5個bit位來表達(dá),這樣總共16個bit位占用兩個字節(jié)罚缕,比ARGB_8888的方式節(jié)省一半的空間艇纺。ARGB_4444看起來是個比較好的折中方案,又能顯示透明度又只占一半的空間邮弹,但是它現(xiàn)在已經(jīng)被Deprecated了黔衡,原因是顯示質(zhì)量低。畢竟只用12位(RGB=4*3)在現(xiàn)代追求視網(wǎng)膜屏的大環(huán)境下來展示豐富多彩的顏色腌乡,確實有點太差了盟劫。至于其它的幾種配置方式,不再多做介紹导饲。除了這些捞高,Bitmap.java對上層暴露的接口還包括壓縮、復(fù)制渣锦,獲取基本屬性等。

Bitmap.java更像是對一堆像素集做了一個封裝氢哮,這堆像素集堆在一起自然有了自己的屬性袋毙,例如長,寬冗尤,密度等听盖。那從磁盤中把一個壓縮文件(圖片文件)解析的操作放在這樣的一個抽象里好像有點不合適,所以decode bitmap的幾種重載方法放到了BitmapFactory.java中裂七,我們簡單過一下:

首先就如名字圖片工廠一般皆看,它的幾乎所有方法都是decode 操作,包括從不同資源下decode bitmap:


decode methods

值得一提的是它一個靜態(tài)內(nèi)部類Options:


Options

這個Options設(shè)定了decode bitmap時你能配置的參數(shù)背零,比如inPreferredConfig就是配置我們之前講到的像素存儲方式腰吟,默認(rèn)是ARGB_8888,這些Options的組合使用加上合適的像素存儲格式徙瓶,就能基礎(chǔ)的展現(xiàn)一波小小的操作來應(yīng)對這個難搞的Bitmap了毛雇。至于這里的小操作和全局的大操作該怎么做我們放在下一節(jié)圖片的處理和優(yōu)化來稍作展開,這里不多做解釋侦镇。最后需要注意的是這里的decode方法全是耗時方法灵疮,想想也是,這可是動輒成百上千萬的像素解析壳繁,不耗時才見了鬼了震捣。

介紹到這里荔棉,想必大家對圖片本身和圖片在Android上的封裝有了簡單的認(rèn)識。但是這里有幾個問題:圖片既然這么耗內(nèi)存蒿赢,這些內(nèi)存是怎么分配的润樱?又是分配在哪呢?這樣的大的內(nèi)存該怎么跨進(jìn)程傳遞诉植?既然是關(guān)于Bitmap的所有祥国,那Bitmap中像素點是怎么渲染到屏幕上的?和普通的GUI又有什么不同呢晾腔?來舌稀,我們繼續(xù)看。

2灼擂、Bitmap的內(nèi)存追蹤

2.1 Bitmap內(nèi)存的分配

我們直接來看下壁查,bitmap在關(guān)于內(nèi)存的操作在代碼中的實現(xiàn)。前面講到剔应,BitmapFactory是用來從資源里decode bitmap的方法類睡腿,那就從它下手我們來看看bitmap 被decode后內(nèi)存到底是去了哪里。從最直觀的decodeFile開始峻贮,可以發(fā)現(xiàn)decodeFile開了一個文件流席怪,交給了decodeStream來處理:

public static Bitmap decodeFile(String pathName, Options opts) {
validate(opts);
Bitmap bm = null;
InputStream stream = null;
try {
stream = new FileInputStream(pathName);
bm = decodeStream(stream, null, opts);
} catch (Exception e) {
//...
}
return bm;
}

而decodeStream的實現(xiàn)位decodeStreamInternal,那我們來看下decodeStreamInternal在干什么:

private static Bitmap decodeStreamInternal(InputStream is, Rect outPadding, Options opts) {
byte [] tempStorage = null;
if (opts != null) tempStorage = opts.inTempStorage;
if (tempStorage == null) tempStorage = new byte[DECODE_BUFFER_SIZE];
//放在了native處理纤控,意料之中
return nativeDecodeStream(is, tempStorage, outPadding, opts);
}

nativeDecodeStream方法毫無疑問的位于/frameworks/base/core/jni/android/graphics/BitmapFactory.cpp挂捻,核心方法為doDecode:

static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding, jobject options) {

//...這個方法真的很長,我們只看我們關(guān)心的部分船万。

// 創(chuàng)建一個codec
std::unique_ptr<SkAndroidCodec> codec(SkAndroidCodec::NewFromStream(
streamDeleter.release(), &peeker));

// 定義幾個Allocator刻撒,然后根據(jù)條件選擇對應(yīng)的allocator賦值給decodeAllocator指針變量,值得注意的是BItmapFactory.h 中include的是
"GraphicsJNI.h" 所以這里HeapAllocator位GraphicsJNI中的HeapAllocator
HeapAllocator defaultAllocator;
RecyclingPixelAllocator recyclingAllocator(reuseBitmap, existingBufferSize);
ScaleCheckingAllocator scaleCheckingAllocator(scale, existingBufferSize);
//位于Skbitmap中的HeapAllocator和GraphicsJNI中同名,容易弄混耿导,這個只在需要scale bitmap或是需要硬件支持時才會用到
SkBitmap::HeapAllocator heapAllocator;
SkBitmap::Allocator* decodeAllocator;
if (javaBitmap != nullptr && willScale) {
// 如果從java端傳入了可重用的bitmap声怔,那么使用如下的分配器
decodeAllocator = &scaleCheckingAllocator;
} else if (javaBitmap != nullptr) {
decodeAllocator = &recyclingAllocator;
} else if (willScale || isHardware) {
decodeAllocator = &heapAllocator;
} else {
decodeAllocator = &defaultAllocator;
}

// 定義一個類型位skbitmap的bitmap變量,注意這里有個tryAllocPixels方法舱呻,開始分配內(nèi)存醋火,這一步完成后,allocator中的mStorage變量被賦值了給bitmap分配的內(nèi)存了狮荔。
SkBitmap decodingBitmap;
if (!decodingBitmap.setInfo(bitmapInfo) ||
!decodingBitmap.tryAllocPixels(decodeAllocator)) {
return nullptr;
}

// 使用codec開始解碼圖片胎撇,注意這個skbitmap的getPixels方法,獲取到的是pixel的地址殖氏,稍微留意下晚树。
SkCodec::Result result = codec->getAndroidPixels(decodeInfo, decodingBitmap.getPixels(),
decodingBitmap.rowBytes(), &codecOptions);
// 定義一個outputBitmmap用來存放處理后的bitmap
SkBitmap outputBitmap;
if (willScale) {
//...如果是需要縮放圖片的操作,單獨處理
SkCanvas canvas(outputBitmap, SkCanvas::ColorBehavior::kLegacy);
canvas.scale(sx, sy);
canvas.drawBitmap(decodingBitmap, 0.0f, 0.0f, &paint);
} else {
//如果不是的話直接置換
outputBitmap.swap(decodingBitmap);
}
// 最后創(chuàng)建一個java bitmap返回雅采,這其中會將bitmap包裝成一個long型數(shù)值存在java端爵憎,這long型數(shù)值就是前面的mNativePtr
return bitmap::createBitmap(env, defaultAllocator.getStorageObjAndReset(),
bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1);
}

這個方法從上到下基本涵蓋了decode的所有操作慨亲,而我們最關(guān)心的其實是
tryAllocPixels的具體做了什么:

bool SkBitmap::tryAllocPixels(Allocator* allocator) {
HeapAllocator stdalloc;

if (nullptr == allocator) {
allocator = &stdalloc;
}
// 下面交給了allocator來處理,如果在Android O之前會處理color table變量
#ifdef SK_SUPPORT_LEGACY_COLORTABLE
return allocator->allocPixelRef(this, nullptr);
#else
return allocator->allocPixelRef(this);
#endif
}

注意:這里傳入的allocator不為空宝鼓,傳入的allocator位于GraphicsJNI中刑棵,使用這個allocator來做分配工作

bool HeapAllocator::allocPixelRef(SkBitmap* bitmap) {
mStorage = android::Bitmap::allocateHeapBitmap(bitmap);
return !!mStorage;

allocateHeapBitmap方法位于hwui lib庫中,它的實現(xiàn)位于allocateBitmap方法:

<Bitmap> allocateBitmap(SkBitmap* bitmap, AllocPixelRef alloc) {
const SkImageInfo& info = bitmap->info();
if (info.colorType() == kUnknown_SkColorType) {
return nullptr;
}
size_t size;
const size_t rowBytes = bitmap->rowBytes();
if (!computeAllocationSize(rowBytes, bitmap->height(), &size)) {
return nullptr;
}
// 在這里開始真正的分配內(nèi)存工作愚铡,用的是alloc函數(shù)蛉签,也就是說原來Android
// O是直接在native上為bitmap分配了內(nèi)存
auto wrapper = alloc(size, info, rowBytes);
if (wrapper) {
wrapper->getSkBitmap(bitmap);
}
return wrapper;
}

從上面的代碼分析可以看出,一個bitmap在上層指定各種option后沥寥,傳入native中碍舍,在Android O里直接在native中為bitmap分配了內(nèi)存。也就是說老一套的在java heap中分配內(nèi)存并且不好好處理會導(dǎo)致應(yīng)用程序OOM的情況在O上不存在了邑雅,這個也是我在寫這篇wiki才發(fā)現(xiàn)的片橡。

這里也提一下Android2.3.3 之后,Android O之前淮野,這中間的很長時間里bitmap內(nèi)存的分配情況捧书,也就是目前最主流的Android 版本上bitmap內(nèi)存的分配情況:在這些版本中以上的方法在doDecode之后會有所區(qū)別,默認(rèn)的allocator是JavaPixelAllocator 是在java heap上為bitmap分配內(nèi)存骤星,上層應(yīng)用時刻要關(guān)心bitmap 對象有沒有垃圾回收器處理经瓷,必要時需要主動recycle。Android O上的改動說實話是做了一些圖片三方處理框架如Fresco在做的事情洞难,個人認(rèn)為只會讓不負(fù)責(zé)任的程序員更加不負(fù)責(zé)任了嚎。Android O之前關(guān)于Bitmap 在內(nèi)存中的分配因為篇幅原因此處就不貼了,總體處理流程一致廊营,allocator不同。

2.2 bitmap內(nèi)存的跨進(jìn)程傳遞

關(guān)于bitmap在內(nèi)存這塊的整理萝勤,還需要提到它在跨進(jìn)程傳遞時的情況露筒。前面已經(jīng)提過Bitmap實現(xiàn)了Parcelable接口,那么說明bitmap是可以序列化傳遞的敌卓,我們首先看下中writeToParcel方法的實現(xiàn):

jboolean Bitmap_writeToParcel(JNIEnv* env, jobject,
jlong bitmapHandle,boolean isMutable, jint density,jobject parcel) {
//...省略一些基礎(chǔ)屬性的寫入
// 獲取了一個匿名共享內(nèi)存的FD
android::status_t status;
int fd = bitmapWrapper->bitmap().getAshmemFd();
if (fd >= 0 && !isMutable && p->allowFds()) {
// 將fd寫入parcel中慎式,在這一步會復(fù)制一個fd
status = p->writeDupImmutableBlobFileDescriptor(fd);
if (status) {
return JNI_FALSE;
}
return JNI_TRUE;
}
// 如果bitmap不是寫在匿名共享內(nèi)存中的
// parcel中寫入一個blob data,這個data存放的是bitmap的內(nèi)存數(shù)據(jù)
bool mutableCopy = isMutable;
//...
size_t size = bitmap.getSize();
android::Parcel::WritableBlob blob;
status = p->writeBlob(size, mutableCopy, &blob);
const void* pSrc = bitmap.getPixels();
if (pSrc == NULL) {
memset(blob.data(), 0, size);
} else {
//將bitmap的數(shù)據(jù)復(fù)制到blob data中
memcpy(blob.data(), pSrc, size);
}
blob.release();
return JNI_TRUE;
}

同樣的我們來看下createFromParcel的實現(xiàn)

static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) {
android::Parcel* p = android::parcelForJavaObject(env, parcel);
//...中間省略基礎(chǔ)屬性的獲取
//
std::unique_ptr<SkBitmap> bitmap(new SkBitmap);
// 讀取bitmap blob的數(shù)據(jù)放在blob變量里面
size_t size = bitmap->getSize();
android::Parcel::ReadableBlob blob;
android::status_t status = p->readBlob(size, &blob);

sk_sp<Bitmap> nativeBitmap;
//如果blob存放數(shù)據(jù)成功且大小大于匿名共享內(nèi)存的規(guī)定尺寸
if (blob.fd() >= 0 && (blob.isMutable() || !isMutable) && (size >= ASHMEM_BITMAP_MIN_SIZE)) {
// Dup the file descriptor so we can keep a reference to it after the Parcel
// is disposed.
// 復(fù)制一個文件描述符
int dupFd = dup(blob.fd());
// bitmap映射到匿名共享內(nèi)存然后創(chuàng)建一個native bitmap
nativeBitmap = sk_sp<Bitmap>(GraphicsJNI::mapAshmemBitmap(env, bitmap.get(),
dupFd, const_cast<void*>(blob.data()), size, !isMutable));

// Clear the blob handle, don't release it.
blob.clear();
} else {
// 否則新建一個bitmap 將數(shù)據(jù)復(fù)制到新的bitmap中
nativeBitmap = Bitmap::allocateHeapBitmap(bitmap.get());
memcpy(bitmap->getPixels(), blob.data(), size);

// Release the blob handle.
blob.release();
}

return createBitmap(env, nativeBitmap.release(),
getPremulBitmapCreateFlags(isMutable), NULL, NULL, density);
}

從上面的代碼分析我們可以看出趟径,bitmap在跨進(jìn)程傳遞時瘪吏,如果之前是以匿名共享內(nèi)存的形式存儲,則在parcel中傳遞的是fd蜗巧,如果是其他形式則在parcel中傳遞的就是實實在在的二進(jìn)制數(shù)據(jù)掌眠。

值得一提的是在傳遞fd時,寫入parcel新復(fù)制了一個fd幕屹,讀取parcel的時候同樣復(fù)制了一個fd蓝丙,所以每傳遞一個bitmap至少兩個fd會被創(chuàng)建级遭。之所以提到這個是因為我們有一個因為bitmap fd過多導(dǎo)致system server重啟的問題。在那個問題中system_server充當(dāng)中間人在app和systemUI中傳遞帶有bitmap的notification渺尘,過多的bitmap傳遞直接使system_server的fd數(shù)量超過限制挫鸽,從而導(dǎo)致系統(tǒng)重啟,類似的問題可能會在其他地方復(fù)現(xiàn)鸥跟,值得稍微留意一下丢郊。

3、Bitmap的渲染

說完bitmap的內(nèi)存医咨,我們來說說bitmap的渲染枫匾。view的渲染或是bitmap的渲染準(zhǔn)備放在其他wiki上仔細(xì)分析,這里只簡單提一下腋逆,不做太多拓展婿牍。首先了解一下skia圖形庫,在之前的很長一段時間惩歉,Android使用skia來繪制2d圖形等脂,openGL來繪制3d圖形,但是skia作為一個向量繪圖庫撑蚌,使用cpu來進(jìn)行運算上遥,性能瓶頸尤其明顯。之后hwui逐漸取代skia開始為2d圖形同樣提供硬件支持争涌,直至目前的app默認(rèn)開啟硬件加速粉楚,默認(rèn)使用hwui繪制,skia只在其中做些少量的繪制工作亮垫。

這樣的話就變成上層app準(zhǔn)備好的數(shù)據(jù)基本全交給hwui來處理模软,而hwui和openGL強相關(guān),換句話說饮潦,bitmap的數(shù)據(jù)是交到openGL中做處理燃异,所以bitmap和普通的GUI在渲染上的區(qū)別主要就是它們在openGL中的區(qū)別。

事實上bitmap對應(yīng)openGL中pixel data的概念继蜡,而普通的GUI則對應(yīng)vertex的概念回俐,當(dāng)在做繪制時通過display list做緩存,最終通過surfaceflinger 和 hardware composer做合成顯示到display上稀并。這一塊的東西非常多仅颇,在此點到為止,我們在這里只需要知道bitmap在渲染上也是和普通GUI有所區(qū)別即可碘举。

圖片在應(yīng)用程序中的處理和優(yōu)化

前面提到圖片首先它吃內(nèi)存忘瓦,其次decode它本身就是耗時操作,再加上操作圖片的應(yīng)用程序不會有例外的都需要有很高的用戶體驗和順滑度殴俱,最好來些酷炫的手勢操作政冻,批量處理枚抵,幻燈片什么的那哪能少,所以不好處理明场。如果說穩(wěn)定性延伸到單個app汽摹,那么圖片造成的問題可能是大頭。

但是前面在分析bitmap內(nèi)存時發(fā)現(xiàn)如今的bitmap內(nèi)存時分配到native中苦锨,java heap的大小限制對與bitmap來說忽然說沒就沒了逼泣,但是,我們該怎么優(yōu)化仍是怎么優(yōu)化舟舒。

在這之前拉庶,仍然把一些前提講清楚。就是圖片雖然是有千萬級像素氏仗,但是我們的手機(jī)屏幕以1080p=1920*1080舉例來說,只有大概兩百萬的像素夺鲜。也就是說你把一張千萬級別的像素全部解析出來做整張圖的預(yù)覽皆尔,千萬只能展示百萬,本身就沒意義币励。所以我們對圖片的decode慷蠕,通常都是兩步,第一步在option中設(shè)置屬性只獲取bounds食呻,獲取到尺寸后計算一個合理的縮放倍數(shù)流炕,再傳入option 來decode一張合理大小的圖片。

在通用的圖庫開發(fā)中仅胞,我把圖片按照尺寸分為微型圖每辟、小圖、中圖干旧、大圖影兽、超大圖五種。以屏幕寬度為標(biāo)準(zhǔn)莱革,微型圖占寬度的五分之一以下,小圖五分之一到三分之一左右讹开,中圖三分之一到二分之一左右盅视,大圖通常是千萬級像素的圖片,超大圖為超過五千萬像素以上的圖片且在手機(jī)上展示需要還原細(xì)節(jié)的圖片旦万。

再對這些不同尺寸的圖片的使用頻率做量化闹击,小圖主要的預(yù)覽圖最常用到,中圖作為相冊封面成艘、連拍預(yù)覽等視圖可以少量緩存赏半,大圖通常是在大圖預(yù)覽中才會遇到且消耗內(nèi)存贺归,最多緩存1-3張,超大圖的緩存不做考慮断箫。與此同時拂酣,不同尺寸的圖片質(zhì)量要求也不一樣,小圖使用RGB_565的格式即可仲义,這樣可以多緩存一倍的小圖婶熬,大圖通常需要ARGB_8888來還原圖片細(xì)節(jié),畢竟是在預(yù)覽界面埃撵。

這樣的話我們就對圖片大概分好了類赵颅,下面就是做緩存,通常緩存是兩級緩存暂刘,也就是內(nèi)存緩存和磁盤緩存饺谬。主要的做法是在內(nèi)存中開辟一塊限制大小的區(qū)域緩存一些使用頻率高的小圖,當(dāng)然這些小圖很多時候是通過裁剪獲取到的谣拣,得來不易募寨,所以肯定要在磁盤中緩存一份。應(yīng)用需要展示圖片的時候先在內(nèi)存緩存中找芝发,找不到再從磁盤中找解析后放在內(nèi)存緩存中绪商。耗時解析的操作可以自己封裝線程池或是用AsyncTask,就不多說辅鲸。

然后就是一些小技巧格郁,就是小圖可以在快速切換展示大圖的時候做大圖的過度,超大圖可以分塊解析独悴,實時回收內(nèi)存達(dá)到手機(jī)也可以看世界地圖的效果例书。有興趣的同學(xué)請私聊。

總結(jié)

圖片作為重要的應(yīng)用廣泛的多媒體介質(zhì)刻炒,談及它的方方面面均可以做延伸拓展决采,作為一個了解性的wiki餐茵,在這里很多方面沒有多講告私,其實有些地方連提都沒提唐础,比如顏色一些的基礎(chǔ)知識册赛,圖片的編碼流程分析等等椭岩。講到的一些绕娘,一部分可以作為了解性閱讀委可,一部分也確實會和穩(wěn)定性工作有關(guān)聯(lián)映皆,希望能對看的人有些啟發(fā)访敌。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末凉敲,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌爷抓,老刑警劉巖势决,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蓝撇,居然都是意外死亡果复,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進(jìn)店門唉地,熙熙樓的掌柜王于貴愁眉苦臉地迎上來据悔,“玉大人,你說我怎么就攤上這事耘沼〖牵” “怎么了?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵群嗤,是天一觀的道長菠隆。 經(jīng)常有香客問我,道長狂秘,這世上最難降的妖魔是什么骇径? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮者春,結(jié)果婚禮上破衔,老公的妹妹穿的比我還像新娘。我一直安慰自己钱烟,他們只是感情好晰筛,可當(dāng)我...
    茶點故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著拴袭,像睡著了一般读第。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上拥刻,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天怜瞒,我揣著相機(jī)與錄音,去河邊找鬼般哼。 笑死吴汪,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蒸眠。 我是一名探鬼主播浇坐,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼黔宛!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤臀晃,失蹤者是張志新(化名)和其女友劉穎觉渴,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體徽惋,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡案淋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了险绘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片踢京。...
    茶點故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖宦棺,靈堂內(nèi)的尸體忽然破棺而出瓣距,到底是詐尸還是另有隱情,我是刑警寧澤代咸,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布蹈丸,位于F島的核電站,受9級特大地震影響呐芥,放射性物質(zhì)發(fā)生泄漏逻杖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一思瘟、第九天 我趴在偏房一處隱蔽的房頂上張望荸百。 院中可真熱鬧,春花似錦滨攻、人聲如沸够话。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽更鲁。三九已至,卻和暖如春奇钞,著一層夾襖步出監(jiān)牢的瞬間澡为,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工景埃, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留媒至,地道東北人。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓谷徙,卻偏偏與公主長得像拒啰,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子完慧,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,933評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 2021期待與你一起共事谋旦,點擊查看崗位[http://www.reibang.com/p/6f4d67fa406...
    閑庭閱讀 16,636評論 0 75
  • 7.1 壓縮圖片 一、基礎(chǔ)知識 1、圖片的格式 jpg:最常見的圖片格式册着。色彩還原度比較好拴孤,可以支持適當(dāng)壓縮后保持...
    AndroidMaster閱讀 2,516評論 0 13
  • 我希望通過這篇文章能夠把Android內(nèi)存相關(guān)的基礎(chǔ)和大部分內(nèi)存相關(guān)問題如:溢出、泄漏甲捏、圖片等等產(chǎn)生的都講解清楚演熟,...
    Cactus_b245閱讀 7,157評論 6 82
  • 總想寫點什么,但不知道如何下筆司顿,也就不曾執(zhí)筆芒粹。前幾天,一個偶然的機(jī)會在朋友圈看到還有簡書這種平臺大溜,下來試試水化漆。 春...
    肉丸_Amy閱讀 119評論 0 0
  • 感賞自已,真的沒想起問兒子今天結(jié)業(yè)考試咋樣猎提。也感賞兒子有了點自信获三,他主動告訴我應(yīng)該只是及格吧,但他很坦然锨苏,不再發(fā)脾...
    高金偉閱讀 250評論 2 3