本文出自 Eddy Wiki ,轉(zhuǎn)載請(qǐng)注明出處:http://eddy.wiki/interview-opensource.html
本文收集整理了 Android 中常用的一些開(kāi)源庫(kù)。
圖片加載開(kāi)源庫(kù)
參考:
網(wǎng)絡(luò)請(qǐng)求開(kāi)源庫(kù)
參考:
Volley 源碼解析
參考:
http://a.codekk.com/detail/Android/grumoon/Volley%20%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90
Volley的磁盤(pán)緩存
在面試的時(shí)候,聊到 Volley 請(qǐng)求到網(wǎng)絡(luò)的數(shù)據(jù)緩存。當(dāng)時(shí)說(shuō)到是 Volley 會(huì)將每次通過(guò)網(wǎng)絡(luò)請(qǐng)求到的數(shù)據(jù),采用FileOutputStream,寫(xiě)入到本地的文件中。
那么問(wèn)題來(lái)了:這個(gè)緩存文件,是聲明在一個(gè)SD卡文件夾中的(也可以是getCacheFile())。如果不停的請(qǐng)求網(wǎng)絡(luò)數(shù)據(jù),這個(gè)緩存文件夾將無(wú)限制的增大,最終達(dá)到SD卡容量時(shí),會(huì)發(fā)生無(wú)法寫(xiě)入的異常(因?yàn)榇鎯?chǔ)空間滿(mǎn)了)。
這個(gè)問(wèn)題的確以前沒(méi)有想到,當(dāng)時(shí)也沒(méi)說(shuō)出怎么回事。回家了趕緊又看了看代碼才知道,原來(lái) Volley 考慮過(guò)這個(gè)問(wèn)題(汗!想想也是)
翻看代碼DiskBasedCache#pruneIfNeeded()
private void pruneIfNeeded(int neededSpace) {
if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes) {
return;
}
long before = mTotalSize;
int prunedFiles = 0;
long startTime = SystemClock.elapsedRealtime();
Iterator<Map.Entry<String, CacheHeader>> iterator = mEntries.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, CacheHeader> entry = iterator.next();
CacheHeader e = entry.getValue();
boolean deleted = getFileForKey(e.key).delete();
if (deleted) {
mTotalSize -= e.size;
} else {
//print log
}
iterator.remove();
prunedFiles++;
if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) {
break;
}
}
}
其中mMaxCacheSizeInBytes是構(gòu)造方法傳入的一個(gè)緩存文件夾的大小,如果不傳默認(rèn)是5M的大小。
通過(guò)這個(gè)方法可以發(fā)現(xiàn),每當(dāng)被調(diào)用時(shí)會(huì)傳入一個(gè)neededSpace,也就是需要申請(qǐng)的磁盤(pán)大小(即要新緩存的那個(gè)文件所需大小)。首先會(huì)判斷如果這個(gè)neededSpace申請(qǐng)成功以后是否會(huì)超過(guò)最大可用容量,如果會(huì)超過(guò),則通過(guò)遍歷本地已經(jīng)保存的緩存文件的header(header中包含了緩存文件的緩存有效期、占用大小等信息)去刪除文件,直到可用容量不大于聲明的緩存文件夾的大小。
其中HYSTERESIS_FACTOR是一個(gè)值為0.9的常量,應(yīng)該是為了防止誤差的存在吧(我猜的)。
Volley緩存命中率的優(yōu)化
如果讓你去設(shè)計(jì)Volley的緩存功能,你要如何增大它的命中率。
可惜了,如果上面的緩存功能是昨天看的,今天的面試這個(gè)問(wèn)題就能說(shuō)出來(lái)了。
還是上面的代碼,在緩存內(nèi)容可能超過(guò)緩存文件夾的大小時(shí),刪除的邏輯是直接遍歷header刪除。這個(gè)時(shí)候刪除的文件有可能是我們上一次請(qǐng)求時(shí)剛剛保存下來(lái)的,屁股都還沒(méi)坐穩(wěn)呢,現(xiàn)在立即刪掉,有點(diǎn)舍不得啊。
如果遍歷的時(shí)候,判斷一下,首先刪除超過(guò)緩存有效期的(過(guò)期緩存),其次按照LRU算法,刪除最久未使用的,豈不是更合適?
Volley緩存文件名的計(jì)算
這個(gè)是我一直沒(méi)弄懂的問(wèn)題。
如下代碼:
private String getFilenameForKey(String key) {
int firstHalfLength = key.length() / 2;
String localFilename = String.valueOf(key.substring(0, firstHalfLength).hashCode());
localFilename += String.valueOf(key.substring(firstHalfLength).hashCode());
return localFilename;
}
為什么會(huì)要把一個(gè)key分成兩部分,分別求hashCode,最后又做拼接。
這個(gè)問(wèn)題之前在stackoverflow上問(wèn)過(guò) #鏈接
原諒我,別人的回答我最初并沒(méi)有看懂。直到最近被問(wèn)到,如果讓你設(shè)計(jì)一個(gè)HashMap,如何避免value被覆蓋,我才想到原因。
先來(lái)看一下 String#hashCode() 的實(shí)現(xiàn):
@Override public int hashCode() {
int hash = hashCode;
if (hash == 0) {
if (count == 0) {
return 0;
}
final int end = count + offset;
final char[] chars = value;
for (int i = offset; i < end; ++i) {
hash = 31*hash + chars[i];
}
hashCode = hash;
}
return hash;
}
從上面的實(shí)現(xiàn)可以看到,String的hashcode是根據(jù)字符數(shù)組中每個(gè)位置的字母的int值再加上上次hash值乘以31,這種算法求出來(lái)的,至于為什么是31,我也不清楚。
但是可以肯定一點(diǎn),hashcode并不是唯一的。不信你運(yùn)行下面這兩個(gè)輸出:
System.out.print("======" + "vFrKiaNHfF7t[9::E[XsX?L7xPp3DZSteIZvdRT8CX:w6d;v<_KZnhsM_^dqoppe".hashCode());
System.out.print("======" + "hI4pFxGOfS@suhVUd:mTo_begImJPB@Fl[6WJ?ai=RXfIx^=Aix@9M;;?Vdj_Zsi".hashCode());
這兩個(gè)字符串是根據(jù)hashcode的算法逆向出來(lái)的,他們的hashcode都是12345。逆向算法請(qǐng)見(jiàn)這里
再回到我們的問(wèn)題,為什么會(huì)要把一個(gè)key分成兩部分?,F(xiàn)在可以肯定的答出,目的是為了盡可能避免hashcode重復(fù)造成的文件名重復(fù)(求兩次hash兩次都與另一個(gè)url重復(fù)的概率總要比一次重復(fù)的概率小吧)。
順帶再提一點(diǎn),就像上面說(shuō)的,概率小并不代表不存在。但是Java計(jì)算hashcode的速度是很快的,應(yīng)該是在效率和安全性上取舍的結(jié)果吧。
Glide源碼解析
參考:
http://www.lightskystreet.com/2015/10/12/glide_source_analysis/
http://frodoking.github.io/2015/10/10/android-glide/
Retrofit源碼分析
參考:
EventBus源碼分析
參考:
greenDAO
參考:
RxJava
參考: