目錄介紹
- 7.0.0.1 加載bitmap圖片的時(shí)候需要注意什么?為何bitmap容易造成OOM?如何計(jì)算Bitmap占用內(nèi)存?
- 7.0.0.2 如何理解recycle釋放內(nèi)存問題?圖片加載到內(nèi)存其實(shí)有兩部分?jǐn)?shù)據(jù),這是為何?
- 7.0.0.3 如何在不壓縮圖片的情況下加載高清大圖?加載圖的機(jī)制是什么,為何不會(huì)內(nèi)存泄漏?
- 7.0.0.7 LRU算法的原理?核心思想是什么?如果緩存滿了的話,什么方法來管理移除最近最少使用的item和添加新的item?
好消息
- 博客筆記大匯總【15年10月到至今】,包括Java基礎(chǔ)及深入知識(shí)點(diǎn),Android技術(shù)博客,Python學(xué)習(xí)筆記等等,還包括平時(shí)開發(fā)中遇到的bug匯總,當(dāng)然也在工作之余收集了大量的面試題,長(zhǎng)期更新維護(hù)并且修正,持續(xù)完善……開源的文件是markdown格式的!同時(shí)也開源了生活博客,從12年起,積累共計(jì)500篇[近100萬字],將會(huì)陸續(xù)發(fā)表到網(wǎng)上,轉(zhuǎn)載請(qǐng)注明出處,謝謝!
- 鏈接地址:https://github.com/yangchong211/YCBlogs
- 如果覺得好,可以star一下,謝謝!當(dāng)然也歡迎提出建議,萬事起于忽微,量變引起質(zhì)變!所有的筆記將會(huì)更新到GitHub上,同時(shí)保持更新,歡迎同行提出或者push不同的看法或者筆記!
7.0.0.1 加載bitmap圖片的時(shí)候需要注意什么?為何bitmap容易造成OOM?如何計(jì)算Bitmap占用內(nèi)存?
- 注意問題
- 直接加載大容量的高清Bitmap很容易出現(xiàn)顯示不完整、內(nèi)存溢出OOM的問題,所以最好按一定的采樣率將圖片縮小后再加載進(jìn)來
- 為減少流量消耗,可對(duì)圖片采用內(nèi)存緩存策略,又為了避免圖片占用過多內(nèi)存導(dǎo)致內(nèi)存溢出,最好以軟引用方式持有圖片
- 如果還需要網(wǎng)上下載圖片,注意要開子線程去做下載的耗時(shí)操作技術(shù)博客大總結(jié)
- 為何bitmap容易造成OOM
- 如何計(jì)算Bitmap占用內(nèi)存
- 1.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'常見有2種編碼方式:ARGB_8888和RGB_565,ARGB_8888每個(gè)像素點(diǎn)4個(gè)byte,RGB_565是2個(gè)byte,一般都采用ARGB_8888這種。那么常見的1080*1920的圖片內(nèi)存占用就是:1920 x 1080 x 4 = 7.9M
- 1.2 上面方法計(jì)算內(nèi)存對(duì)嗎
- 我看到好多博客都是這樣計(jì)算的,但是這樣算對(duì)嗎?有沒有哥們?cè)囼?yàn)過這種方法正確性?我覺得看博客要對(duì)博主表示懷疑,論證別人寫的是否正確。更多詳細(xì)可以看我的GitHub:https://github.com/yangchong211
- 說出我的結(jié)論:上面1.1這種說法也對(duì),但是不全對(duì),沒有說明場(chǎng)景,同時(shí)也忽略了一個(gè)影響項(xiàng):Density。接下來看看源代碼。
- inDensity默認(rèn)為圖片所在文件夾對(duì)應(yīng)的密度;inTargetDensity為當(dāng)前系統(tǒng)密度。
- 加載一張本地資源圖片,那么它占用的內(nèi)存 = width * height * nTargetDensity/inDensity * 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);
}
- 正確說法,這個(gè)注意呢?計(jì)算公式如下所示
- 對(duì)資源文件:width * height * nTargetDensity/inDensity * nTargetDensity/inDensity * 一個(gè)像素所占的內(nèi)存;
- 別的:width * height * 一個(gè)像素所占的內(nèi)存;
- 1.3 一個(gè)像素占用多大內(nèi)存技術(shù)博客大總結(jié)
- Bitmap.Config用來描述圖片的像素是怎么被存儲(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值。
7.0.0.2 如何理解recycle釋放內(nèi)存問題?圖片加載到內(nèi)存其實(shí)有兩部分?jǐn)?shù)據(jù),這是為何?
- 如何理解recycle釋放內(nèi)存問題?
- 在Android2.3.3(API 10)及之前的版本中,Bitmap對(duì)象與其像素?cái)?shù)據(jù)是分開存儲(chǔ)的,Bitmap對(duì)象存儲(chǔ)在Dalvik heap中,而Bitmap對(duì)象的像素?cái)?shù)據(jù)則存儲(chǔ)在Native Memory(本地內(nèi)存)中或者說Derict Memory(直接內(nèi)存)中,這使得存儲(chǔ)在Native Memory中的像素?cái)?shù)據(jù)的釋放是不可預(yù)知的,我們可以調(diào)用recycle()方法來對(duì)Native Memory中的像素?cái)?shù)據(jù)進(jìn)行釋放,前提是你可以清楚的確定Bitmap已不再使用了,如果你調(diào)用了Bitmap對(duì)象recycle()之后再將Bitmap繪制出來,就會(huì)出現(xiàn)”Canvas: trying to use a recycled bitmap”錯(cuò)誤,而在Android3.0(API 11)之后,Bitmap的像素?cái)?shù)據(jù)和Bitmap對(duì)象一起存儲(chǔ)在Dalvik heap中。
- 圖片加載到內(nèi)存其實(shí)有兩部分?jǐn)?shù)據(jù),這是為何?技術(shù)博客大總結(jié)
- 一個(gè)圖片加載到內(nèi)存里,其實(shí)是有兩部分?jǐn)?shù)據(jù)組成,一部分是圖片的相關(guān)描述信息,另一部分就是最重要的像素信息(這部分是有byte數(shù)組組成的),android系統(tǒng)為了提高對(duì)圖片的處理效率,對(duì)于圖片的處理都是調(diào)用了底層的功能(由C語言實(shí)現(xiàn)的),也就是說一個(gè)圖片加載到內(nèi)存里后是使用兩部分的內(nèi)存區(qū)域,簡(jiǎn)單的說:一部分是java可用的內(nèi)存區(qū),一部分是c可用的內(nèi)存區(qū),這兩個(gè)內(nèi)存區(qū)域是不能相互直接使用的,這個(gè)bitmap對(duì)象是由java分配的,當(dāng)然不用的時(shí)候系統(tǒng)會(huì)自動(dòng)回收了,可是那個(gè)對(duì)應(yīng)的C可用的內(nèi)存區(qū)域jvm是不能直接回收的,這個(gè)只能調(diào)用底層的功能釋放。所以你要調(diào)用recycle方法來釋放那一部分內(nèi)存。
- 查看源碼如下所示

- 翻譯這段解釋:釋放bitmap內(nèi)存的時(shí)候,它會(huì)釋放和這個(gè)bitmap有關(guān)的native內(nèi)存,同時(shí)它會(huì)清理有關(guān)數(shù)據(jù)對(duì)象的引用,但是這里處理數(shù)據(jù)對(duì)象的引用,并不是立即清理數(shù)據(jù)(他并不是調(diào)用玩recycle()方法,就直接清理這個(gè)內(nèi)存,他只是給垃圾回收機(jī)制發(fā)送一個(gè)指令,讓它在bitmap沒有對(duì)象引用的時(shí)候,來進(jìn)行垃圾回收)。當(dāng)調(diào)用recycle()方法之后,這個(gè)bitmap就會(huì)被表明為“死亡狀態(tài)”。這個(gè)時(shí)候你在調(diào)用bitmap其他相關(guān)的方法,例如果get像素()或set像素()就會(huì)拋出一個(gè)異常。同時(shí)這個(gè)操作是不可逆的,所以一定百分之百確定這個(gè)bitmap在以后的場(chǎng)景下,不會(huì)被你的程序在使用到,再去調(diào)用recycle()方法。所以谷歌源碼中建議我們,可以不用去主動(dòng)調(diào)用recycle()方法,因?yàn)樵跊]有引用的情況下,我們的垃圾回收機(jī)制會(huì)主動(dòng)的清理內(nèi)存。
- 通過看源碼,我們會(huì)發(fā)現(xiàn),這個(gè)方法首先將這個(gè)Bitmap的引用置為null,然后調(diào)用了nativeRecycle(mNativeBitMap)方法,這個(gè)方法很明顯是個(gè)JNI調(diào)用,會(huì)調(diào)用底層的c或者c 代碼就可以做到對(duì)該內(nèi)存的立即回收,而不需要等待那不確定啥時(shí)候會(huì)執(zhí)行的GC來回收了。
7.0.0.3 如何在不壓縮圖片的情況下加載高清大圖?加載圖的機(jī)制是什么,為何不會(huì)內(nèi)存泄漏?
- 如何在不壓縮圖片的情況下加載高清大圖?
- 使用BitmapRegionDecoder,主要用于顯示圖片的某一塊矩形區(qū)域,如果你需要顯示某個(gè)圖片的指定區(qū)域,那么這個(gè)類非常合適。
- 加載圖的機(jī)制是什么,為何不會(huì)內(nèi)存泄漏?
- 自定義可拖動(dòng)的顯示高清大圖的View技術(shù)博客大總結(jié)
- 提供一個(gè)設(shè)置圖片的入口,setInputStream里面去獲得圖片的真實(shí)的寬度和高度,以及初始化我們的mDecoder
- 重寫onTouchEvent,在里面根據(jù)用戶移動(dòng)的手勢(shì),去更新顯示區(qū)域的參數(shù)。在onMeasure里面為我們的顯示區(qū)域的rect賦值,大小為view的尺寸
- 每次更新區(qū)域參數(shù)后,調(diào)用invalidate,onDraw里面去regionDecoder.decodeRegion拿到bitmap,然后draw。
7.0.0.7 LRU算法的原理?核心思想是什么,談?wù)勀愕乃悸罚?/h3>
- 為減少流量消耗,可采用緩存策略。常用的緩存算法是LRU(Least Recently Used):
- 核心思想:當(dāng)緩存滿時(shí), 會(huì)優(yōu)先淘汰那些近期最少使用的緩存對(duì)象。主要是兩種方式:
- LruCache(內(nèi)存緩存):LruCache類是一個(gè)線程安全的泛型類:內(nèi)部采用一個(gè)LinkedHashMap以強(qiáng)引用的方式存儲(chǔ)外界的緩存對(duì)象,并提供get和put方法來完成緩存的獲取和添加操作,當(dāng)緩存滿時(shí)會(huì)移除較早使用的緩存對(duì)象,再添加新的緩存對(duì)象。
- DiskLruCache(磁盤緩存): 通過將緩存對(duì)象寫入文件系統(tǒng)從而實(shí)現(xiàn)緩存效果
- 大概過程如下?技術(shù)博客大總結(jié)
- LruCache是android提供的一個(gè)緩存工具類,其算法是最近最少使用算法。它把最近使用的對(duì)象用“強(qiáng)引用”存儲(chǔ)在LinkedHashMap中,并且把最近最少使用的對(duì)象在緩存值達(dá)到預(yù)設(shè)定值之前就從內(nèi)存中移除。
- 如果緩存滿了的話,什么方法來管理移除最近最少使用的item和添加新的item?
- trimToSize()方法,刪除最年長(zhǎng)的條目,直到剩余條目的總數(shù)達(dá)到或低于請(qǐng)求的大小
public void trimToSize(int maxSize) {
while(true) {
Object key;
Object value;
synchronized(this) {
if (this.size < 0 || this.map.isEmpty() && this.size != 0) {
throw new IllegalStateException(this.getClass().getName() ".sizeOf() is reporting inconsistent results!");
}
if (this.size <= maxSize || this.map.isEmpty()) {
return;
}
Entry<K, V> toEvict = (Entry)this.map.entrySet().iterator().next();
key = toEvict.getKey();
value = toEvict.getValue();
this.map.remove(key);
//計(jì)算現(xiàn)在緩存的大小,然后減掉多余的,內(nèi)部調(diào)用的是sizeOf()方法
this.size -= this.safeSizeOf(key, value);
this.evictionCount;
}
//如果你想在在我們的緩存中實(shí)現(xiàn)二級(jí)緩存,可以實(shí)現(xiàn)此方法,源碼中是空方法。
this.entryRemoved(true, key, value, (Object)null);
}
}
|