前言
最近看了一個問題忌穿,和 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中連帶TAG共有154個方法和內(nèi)部變量湖蜕,除常量最靠前的mNativePtr變量看名字就知道是Bitmap連接JNI的橋梁逻卖,其實它就是實際的bitmap,native的bitmap之后會介紹到昭抒。繼續(xù)看其它的變量和方法:
除了一些看名字就知道意思的常用變量评也,有一個內(nèi)部的聯(lián)合體稍微提一下:
這個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:
值得一提的是它一個靜態(tài)內(nèi)部類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ā)访敌。