大綱
Java 基礎(chǔ)
1. ==是趴、equals 和 hashCode 的區(qū)別
== 用于基礎(chǔ)數(shù)據(jù)類型的判斷時滞详,比較的是值,用于引用類型的判斷時拂共,比較的是對象在內(nèi)存中的存放地址牺弄。這是因?yàn)閷ο笫欠旁诙阎械模瑮V写娣诺氖菍ο蟮牡刂芬撕?== 是對棧中的值進(jìn)行比較的势告。
如果要比較堆中對象的內(nèi)容是否相同,就要重寫 equals抚恒。Object 中的 equals 方法同 ==咱台,通過覆蓋該方法來實(shí)現(xiàn)具體功能。
集合查找對象時俭驮,需要使用 equals 遍歷比較回溺,數(shù)據(jù)較多時效率很低,為了提高查找對象的效率混萝,誕生了 hashCode遗遵。對象的 hashCode 有如下特性:
對象相同,hashCode 一定相同譬圣;
hashCode 相同瓮恭,對象不一定相同雄坪。
依據(jù)此特性厘熟,在存對象時屯蹦,可以直接根據(jù) hashCode 值映射出對象應(yīng)該存儲的地址,如果該地址沒對象绳姨,則直接存儲登澜,如果已經(jīng)有 hashCode 值相同的對象存在,則調(diào)用 equals 函數(shù)比較飘庄,相同則丟棄脑蠕,不同則生成鏈表存儲起來。
這樣存儲跪削,相當(dāng)于把 hashCode 值相同的對象放在了一個桶里谴仙,每次查找對象時,直接從目標(biāo)桶里挨個查找想要的對象碾盐,大大提高了查找效率晃跺。
因此,在重寫對象的 equals 函數(shù)時毫玖,必須重寫其 hashCode 函數(shù)掀虎,如果不重寫,在 HashSet 等散列集合存儲時付枫,會因存放桶的不同烹玉,而存儲了 equals 相同的多個對象,這并不是我們想要的結(jié)果阐滩。
在重寫 hashCode 時二打,遵循倆個原則:
倆個 equals 相同的對象,hashCode 返回值一定相同(定位到正確的鏈表掂榔,保證正確去重)址儒;
倆個 equals 不同的對象,hashCode 可能相同衅疙,但是要盡可能保證桶的均勻莲趣,這樣才能達(dá)到最大查找效率。
2. 基礎(chǔ)數(shù)據(jù)類型各占多少字節(jié)
基礎(chǔ)類型 | 字節(jié)數(shù) |
---|---|
byte | 1 |
boolean | 1 |
char | 2 |
short | 2 |
int | 4 |
float | 4 |
long | 8 |
double | 8 |
3. 對多態(tài)的理解
多態(tài)指允許不同類的對象對同一消息作出響應(yīng)饱溢。即同一消息可以根據(jù)發(fā)送對象的不同而采用多種不同的行為方式喧伞。
實(shí)現(xiàn)多態(tài)的技術(shù)稱為動態(tài)綁定,是指在執(zhí)行期間判斷所引用對象的實(shí)際類型绩郎,根據(jù)實(shí)際類型調(diào)用相應(yīng)方法潘鲫。
Java 的倆種多態(tài)是指:運(yùn)行時多態(tài)和編譯時多態(tài)。多態(tài)是面向?qū)ο蟮暮诵奶卣髦焕哒龋惖亩鄳B(tài)性提供類中成員設(shè)計的靈活性和方法執(zhí)行的多樣性溉仑。
4. String、StringBuffer 與 StringBuilder 的區(qū)別
String 的值是不可變的状植,每次對 String 的操作都會生成新的 String 對象浊竟,效率低且浪費(fèi)內(nèi)存空間怨喘。當(dāng)對字符串進(jìn)行修改時,可以使用 StringBuffer 和 StringBuilder振定。StringBuffer 多線程安全必怜,而 StringBuilder 適用于單線程且效率更高。
引申:String 為什么要設(shè)計成不可變的后频?
- Java 中的字符串常量池梳庆,保證了當(dāng)創(chuàng)建一個 String 對象時,假如值已存在于常量池中卑惜,則不會創(chuàng)建新的對象膏执,而是引用已經(jīng)存在的。這是一種常見的優(yōu)化方案露久“幕基于此风范,假如字符串對象允許改變,則改變一個對象會影響到另一個獨(dú)立對象。
- 安全性上講仑性,String 不可變更有利于方法參數(shù)傳遞裙戏。
- 字符串不變性保證了 hash 碼的唯一性孵构,可以放心進(jìn)行緩存而不必每次都去計算新的哈希碼恍飘。
5. 父類的靜態(tài)方法能否被子類重寫
不能
重寫指根據(jù)運(yùn)行時對象的類型來決定調(diào)用哪個方法(動態(tài)綁定),而靜態(tài)變量和靜態(tài)方法是在編譯時與類綁定的梯啤。
6. 線程和進(jìn)程的區(qū)別
- 進(jìn)程是資源分配的最小單位竖伯,線程是程序執(zhí)行的最小單位(資源調(diào)度的最小單位)。
- 進(jìn)程有自己的獨(dú)立地址空間因宇,每啟動一個進(jìn)程七婴,系統(tǒng)就會為它分配地址空間,建立數(shù)據(jù)表來維護(hù)代碼段察滑、堆棧段和數(shù)據(jù)段,這種操作非常昂貴贺辰。
而線程是共享進(jìn)程中的數(shù)據(jù)的户盯,使用相同的地址空間,因此 CPU 切換一個線程的花費(fèi)遠(yuǎn)比進(jìn)程要小很多饲化,同時創(chuàng)建一個線程的開銷也比進(jìn)程要小很多莽鸭。 - 線程之間的通信更方便,同一進(jìn)程下的線程共享全局變量吃靠、靜態(tài)變量等數(shù)據(jù)硫眨,而進(jìn)程之間的通信需要以通信的方式(IPC)進(jìn)行。
- 多進(jìn)程程序更健壯巢块,多線程程序只要有一個線程死掉礁阁,整個進(jìn)程也死掉了巧号,而一個進(jìn)程死掉并不會對另外一個進(jìn)程造成影響,因?yàn)檫M(jìn)程有自己獨(dú)立的地址空間氮兵。
引申:線程間和進(jìn)程間同步的方式裂逐。
線程間同步:
- 同步代碼塊歹鱼;
- 同步方法泣栈;
- 互斥鎖 ReetrantLock;
進(jìn)程間同步:
同步時會阻塞當(dāng)前線程弥姻,直到喚醒為止南片。異步則可以繼續(xù)干其它事情,其它線程完成任務(wù)時通知即可庭敦。要分清楚倆者的概念疼进。
引申:線程間和進(jìn)程間通信的方式。
線程間通信:
- Handler
- Activity.runOnUiThread
- View.post
- AsyncTask
進(jìn)程間通信:
- 共享內(nèi)存
- 管道
- UDS
- RPC
Android 進(jìn)程間通信:
- Intent
- 文件共享
- Message
- AIDL
- ContentProvider
- Broadcast
- Socket
- Binder
完整RPC通信過程如下:
a. 客戶端調(diào)用 Stub 接口秧廉;
b. Stub 根據(jù)操作系統(tǒng)的要求進(jìn)行打包伞广,并執(zhí)行相應(yīng)的系統(tǒng)調(diào)用;
c. 服務(wù)器端 Stub 解包并調(diào)用與數(shù)據(jù)包匹配的進(jìn)程疼电;
d. 進(jìn)程執(zhí)行操作嚼锄;
e. 服務(wù)器以上述步驟的逆向過程將結(jié)果返回給客戶端。
7. final蔽豺、finally区丑、finalize 的區(qū)別
final:
java 中的關(guān)鍵字、修飾符修陡。被 final 修飾的:
- 類不能再派生子類沧侥,不能作為父類被繼承。因此魄鸦,一個類不能同時被聲明為抽象類和 final 類宴杀。
- 變量必須在聲明時給定初值,而在以后的引用中只能讀取拾因,不可修改婴氮。
- 方法只能使用,不能重載盾致。
finally:
Java 的一種異常處理機(jī)制主经,是對 Java 異常處理模型的補(bǔ)充。finally 結(jié)構(gòu)使代碼總會執(zhí)行庭惜,而不管無異常發(fā)生罩驻。使用 finally 可以維護(hù)對象的內(nèi)部狀態(tài),并可以清理非內(nèi)存資源护赊。特別是在關(guān)閉數(shù)據(jù)庫連接這方面惠遏,如果程序員把數(shù)據(jù)庫連接的 close() 方法放到 finally 中砾跃,就會大大降低程序出錯的幾率。
finalize:
Java 使用 finalize 方法在垃圾收集器將對象從內(nèi)存中清除出去前节吮,做必要的清理工作抽高。這個方法是由垃圾收集器在確定這個對象沒被引用時對這個對象調(diào)用的。它是在 Object 類中定義的透绩,因此所有類都繼承了它翘骂。子類覆蓋 finalize() 方法以整理系統(tǒng)資源或者執(zhí)行其他清理工作。
8. Serializable 和 Parcelable 的區(qū)別
使用序列化一般有以下三種用途:
- 本地文件保存對象帚豪;
- 網(wǎng)絡(luò)傳遞對象碳竟;
- 進(jìn)程間傳遞對象。
Serializable 是 Java 的序列化技術(shù)狸臣。
使用簡單莹桅,對序列化的 class 增加繼承,并添加一個序列化 id 即可烛亦。手動設(shè)置序列化 id 的好處是诈泼,當(dāng)前 class 的成員變量發(fā)生改變時(比如增刪),會影響自動生成的 id煤禽,從而導(dǎo)致反序列化失敗铐达,通常情況下手動或自動設(shè)置均可。
Serializable 的原理是通過本地讀寫文件實(shí)現(xiàn)呜师,會產(chǎn)生大量的臨時變量,占用內(nèi)存高且可能引發(fā) GC知牌。
當(dāng)一個父類實(shí)現(xiàn)序列化扁藕,子類自動實(shí)現(xiàn)序列化,不需要再顯式實(shí)現(xiàn) Serializable 接口。
Parcelable 是 Android 特有的序列化 API,雖然使用較為繁瑣板熊,但是效率高容劳、占用內(nèi)存少留量。
原理是在內(nèi)存中建立一塊共享數(shù)據(jù)塊浩峡,序列化和反序列化均是操作這塊的數(shù)據(jù)纸淮。
當(dāng)內(nèi)存使用場景為主時(如通過共享內(nèi)存實(shí)現(xiàn) IPC 通信),推薦使用 Parcelable塘辅,因?yàn)樾阅芨呱胩琛5?Parcelable 不適合持久化濒持,原因是不安全(增刪變量會導(dǎo)致無法升級)酒奶,且有版本兼容問題,至于效率問題則有爭議。
9. 內(nèi)部類的設(shè)計意圖及種類
10. 鎖機(jī)制相關(guān)知識:synchronized臼朗、volatile 和 Lock
Java 鎖機(jī)制詳解(一)synchronized
Java 鎖機(jī)制詳解(二)volatile
Java 鎖機(jī)制詳解(三)Lock
Lock 手寫實(shí)現(xiàn)倆個線程交替、多個線程順序執(zhí)行
進(jìn)入 synchronized 代碼塊前如果拿不到鎖耗绿,則進(jìn)入鎖池等待下次競爭堕绩;
進(jìn)入 synchronized 代碼塊后如果調(diào)用對象的 wait浅浮,則線程釋放鎖本股,并進(jìn)入等待池。直到 notify 喚醒承边,從等待池進(jìn)入鎖池城瞎,重新獲得競爭鎖的權(quán)利。
11. 常見編碼和字節(jié)占用數(shù)
ASCII編碼字節(jié)數(shù):
英文字符:1
Unicode編碼字節(jié)數(shù):
英文字符:4
中文簡體:4
中文繁體:4
UTF-8編碼字節(jié)數(shù):
英文字符:1
中文簡體:3
中文繁體:3
12. 深拷貝和淺拷貝的區(qū)別
- 淺拷貝:將原對象或原數(shù)組的引用直接賦給新對象,新數(shù)組,新對象/數(shù)組只是原對象的一個引用
- 深拷貝:創(chuàng)建一個新的對象和數(shù)組追城,將原對象的各項(xiàng)屬性的“值”(數(shù)組的所有元素)拷貝過來,是“值”而不是“引用”
13. 靜態(tài)代理和動態(tài)代理的區(qū)別馋吗,什么場景使用
靜態(tài)代理類:由程序員創(chuàng)建或由特定工具自動生成源代碼,再對其編譯蒋困。在程序運(yùn)行前龄糊,代理類的 .class 文件就已經(jīng)存在了退敦。
動態(tài)代理類:在程序運(yùn)行時战虏,運(yùn)用反射機(jī)制動態(tài)創(chuàng)建而成中符。
使用場景上,靜態(tài)代理通常只代理一個類汛蝙,并且事先知道要代理什么,比較適合專一化場景谱煤;而動態(tài)代理是代理一個接口下的多個實(shí)現(xiàn)類徽级,并且事先并不知道要代理的東西,只有運(yùn)行時才知道,更加泛用。
14. 如何將一個 Java 對象序列化到文件里
- 對象需要實(shí)現(xiàn)
Serializable
接口; - 通過
ObjectOutputStream.writeObject()
寫入喊衫,
ObjectInputStream.readObject()
讀取世剖。
15. 談?wù)剬ψ⒔獾睦斫?/h4>
以下是總結(jié):
注解本質(zhì)是繼承了 Annotation 的接口埠胖,是一種特殊的標(biāo)注谋竖,本身沒有意義驱敲,依賴解析器來實(shí)現(xiàn)它的價值娩梨。
注解利用幾個元注解來分別決定它的作用范圍纽什、生命周期让蕾、是否允許繼承等萌抵。其中生命周期可分為 Source(編譯期注解可見,Class 不可見)萝快、Class(Class 注解可見冰更,類加載后丟棄)和 Runntime(永久保存,運(yùn)行時可反射)官册。
注解在不同階段有不同的價值击敌,這里舉一些例子牲证。Source 階段缝其,通過 APT 掃描拴驮,可以獲取注解信息。諸如通過 @Override 注解來檢測父類是否有同名方法绪氛,EventBus3 也在該時期通過掃描 @Subscribe 來生成 java 類以保存訂閱信息唆鸡,另外還有 ARouter 生成路由、ButterKnife 生成綁定 id 等枣察。Class 階段實(shí)例不多争占,但是可以應(yīng)用于字節(jié)碼插樁燃逻。Runntime 階段,可以利用反射獲取注解信息臂痕,如 EventBus2 事件總線關(guān)系就是這么生成的伯襟,EventBus3也會在運(yùn)行時未找到指定訂閱時,嘗試通過反射去獲取刻蟹。
除 Java 提供的注解外逗旁,Android 還提供了自己的注解庫 support-annotations,包括如 @Null舆瘪、@StringRes、@StringDef 等注解红伦。
16. 談?wù)剬σ蕾囎⑷氲睦斫?/h4>
依賴注入英古,即由容器動態(tài)的將某個依賴關(guān)系注入到組件之中。
依賴注入的目的并非為軟件系統(tǒng)帶來更多功能昙读,而是為了提升組件重用的頻率召调,并為系統(tǒng)搭建一個靈活、可擴(kuò)展的平臺蛮浑。通過依賴注入機(jī)制唠叛,我們只需要通過簡單的配置,而無需任何代碼就可指定目標(biāo)需要的資源沮稚,完成自身的業(yè)務(wù)邏輯艺沼,而不需要關(guān)心具體的資源來自何處,由誰實(shí)現(xiàn)蕴掏。
17. 談?wù)剬Ψ盒偷睦斫?/h4>
泛型是 Java 1.5 的新特性障般,泛型的本質(zhì)是參數(shù)化類型,也就是說所操作的數(shù)據(jù)類型被指定為一個參數(shù)盛杰。這種參數(shù)類型可以用在類挽荡、接口和方法的創(chuàng)建中,分別稱為泛型類即供、泛型接口定拟、泛型方法。
在 Java SE 1.5 之前逗嫡,沒有泛型的情況的下青自,通過對類型 Object 的引用來實(shí)現(xiàn)參數(shù)的“任意化”,“任意化”帶來的缺點(diǎn)是要做顯式的強(qiáng)制類型轉(zhuǎn)換祸穷,而這種轉(zhuǎn)換是要求開發(fā)者對實(shí)際參數(shù)類型可以預(yù)知的情況下進(jìn)行的性穿。對于強(qiáng)制類型轉(zhuǎn)換錯誤的情況,編譯器可能不提示錯誤雷滚,在運(yùn)行的時候才出現(xiàn)異常需曾,這是一個安全隱患。
泛型的好處有三個:分別是安全性,泛型提供了編譯期類型檢測的機(jī)制呆万;然后是擴(kuò)展性商源,相比較 Object,泛型使數(shù)據(jù)的類別可以像參數(shù)一樣由外傳遞進(jìn)來谋减;最后是可讀性牡彻,不必等到運(yùn)行時才去強(qiáng)制轉(zhuǎn)換,定義或?qū)嵗A段出爹,就能看到要操作的數(shù)據(jù)類型庄吼。
Java 的泛型是偽泛型,在生成的 Java 字節(jié)碼中是不包含泛型中的類型信息的严就,虛擬機(jī)層面是不存在所謂『泛型』的概念的总寻。在編譯期間,所有的泛型信息都會被擦除掉梢为。這個過程就稱為類型擦除渐行。
在泛型類被類型擦除的時候,之前泛型類中的類型參數(shù)部分如果沒有指定上限铸董,如 <T> 則會被轉(zhuǎn)譯成普通的 Object 類型祟印,如果指定了上限如 <T extends String> 則類型參數(shù)就被替換成類型上限。
類型擦除雖然有局限性粟害,但也可以利用類型擦除來繞過編譯器不允許的操作限制蕴忆。如利用反射往 List 中添加其它類型的參數(shù)。
18. ArrayList 和 LinkedList 的區(qū)別我磁,以及應(yīng)用場景
- ArrayList 基于數(shù)組孽文,LinkedList 基于雙鏈表。
- 因?yàn)?ArrayList 基于數(shù)組夺艰,所以搜索和讀取數(shù)據(jù)很快芋哭,可以直接返回指定 index 位置的元素。但是插入郁副、刪除數(shù)據(jù)開銷很大减牺,需要移動數(shù)組插入位置后的所有元素。LinkList 則相反存谎。
- LinkedList 會占用更多的內(nèi)存拔疚,因?yàn)槊總€節(jié)點(diǎn)還存儲了前后節(jié)點(diǎn)的位置信息。
應(yīng)用場景:
- 隨機(jī)訪問較多既荚,使用 ArrayList稚失。插入刪除頻繁,使用 LinkedList恰聘。
- ArrayList 的插入句各、刪除也不一定比 LinkedList 慢吸占,如果在 List 末尾插入,LinkedList 需要一直查找到列表尾部凿宾,而 ArrayList 直接插入即可矾屯,這時 ArrayList 比 LinkedList 要快。所以要視情況而定初厚。
Java 虛擬機(jī)
包括:
- APK 的生成件蚕;
- APK 的安裝方式(JIT、AOT)产禾、Dalvik 和 ART 的異同等排作;
- Java 內(nèi)存結(jié)構(gòu);
- Java 內(nèi)存模型下愈;
- Java 引用類型纽绍;
- 垃圾識別策略;
- 垃圾回收算法势似;
- 類的加載過程(如何從 class 轉(zhuǎn)變?yōu)楸惶摂M機(jī)識別的類);
虛引用必須與 ReferenceQueue 一起使用僧著,當(dāng) GC 準(zhǔn)備回收一個對象履因,如果發(fā)現(xiàn)它有虛引用,就會在回收之前盹愚,把這個 虛引用 加入到與之關(guān)聯(lián)的 ReferenceQueue 中栅迄。
僅通過 ReferenceQueue 就能拿到內(nèi)存泄漏的對象嗎?
不能皆怕,因?yàn)?GC 回收之后毅舆,ReferenceQueue 中的虛引用都是正常回收對象的虛引用愈腾,無法通過 ReferenceQueue 拿到內(nèi)存泄漏的對象憋活。
一般做法是,將虛引用和包含對象的弱引用相關(guān)聯(lián)(通過 map)虱黄,每次 ReferenceQueue 獲得新的虛引用時(queue.poll)悦即,就移除與之關(guān)聯(lián)的弱引用,這樣 GC 完畢之后橱乱,通過剩余的弱引用就能拿到內(nèi)存泄漏的對象辜梳。
Java 多線程
1. 開啟線程的幾種方式
繼承 Thread 類
實(shí)現(xiàn) Runnable 接口
通過 Callable 和 Future 創(chuàng)建對象
2. 線程的幾種狀態(tài)
Java 線程狀態(tài)在 Thread 中定義农渊,源碼中能看到有個枚舉 State乏沸,總共定義了六種狀態(tài):
NEW:新建狀態(tài)。線程對象已經(jīng)創(chuàng)建桐愉,但尚未啟動危纫;
RUNNABLE:就緒狀態(tài)宗挥,可運(yùn)行狀態(tài)乌庶。調(diào)用了線程的 start 方法,已經(jīng)在 java 虛擬機(jī)中執(zhí)行属韧,等待獲取操作系統(tǒng)資源如 CPU安拟,操作系統(tǒng)調(diào)度運(yùn)行;
BLOCKED:阻塞狀態(tài)宵喂。線程等待鎖的狀態(tài)糠赦,等待獲取鎖進(jìn)入同步塊/方法或調(diào)用 wait 后重新進(jìn)入需要競爭鎖;
WAITING:等待狀態(tài)锅棕。等待另一個線程以執(zhí)行特定的操作拙泽。調(diào)用以下方法進(jìn)入等待狀態(tài)。 Object.wait裸燎、Thread.join顾瞻、LockSupport.park;
TIMED_WAITING:線程等待一段時間德绿。調(diào)用方法 Thread.sleep荷荤、Object.wait、Thread.join移稳、LockSupport.parkNanos蕴纳、LockSupport.parkUntil;
TERMINATED:進(jìn)程結(jié)束狀態(tài)个粱。
處于 NEW古毛、TERMINATED 這兩個狀態(tài)的線程不會出現(xiàn)在堆棧信息里面。
3. 如何控制某個方法允許并發(fā)訪問線程的個數(shù)
利用 Semaphore 類都许。
4. wait稻薇、sleep 的區(qū)別
- wait 來自 Object 類,sleep 則是 Thread 的方法胶征;
- 雖然都會讓出 cpu 資源塞椎,但是 sleep 不會釋放鎖,而 wait 方法釋放了鎖弧烤,使得其他線程可以使用同步代碼塊或者同步方法忱屑;
- wait、notify 和 notifyAll 只能在同步方法或者同步代碼塊里面使用暇昂,而 sleep 可以在任何地方使用莺戒。
5. 什么可以導(dǎo)致線程阻塞/為什么會出現(xiàn)線程阻塞
一個線程需要依賴并等待另一個線程的執(zhí)行結(jié)果,這時就會產(chǎn)生阻塞急波。
- 線程睡眠从铲。Thread.sleep;
- 線程等待澄暮。Object.wait名段;
- 線程禮讓阱扬。Thread.yield;放棄執(zhí)行機(jī)會伸辟,重新進(jìn)入鎖池麻惶。
- 線程自閉。Thread.join信夫;在當(dāng)前線程中調(diào)用另一個線程的 join() 方法窃蹋,則當(dāng)前線程轉(zhuǎn)入阻塞狀態(tài),直到另一個進(jìn)程運(yùn)行結(jié)束静稻,當(dāng)前線程再由阻塞轉(zhuǎn)為就緒狀態(tài)警没。
- suspend 和 resume。因可能導(dǎo)致死鎖而廢棄振湾。
6. 線程如何關(guān)閉
- 運(yùn)行完畢后自動終止杀迹;
- Thread.stop,但是該方法是非常不安全的押搪。
- Thread.interrupt树酪,interrupt 依賴中斷線程對中斷狀態(tài)的響應(yīng)。
為什么棄用 stop大州?
- 調(diào)用 stop 方法會立刻停止 run() 方法中剩余的全部工作嗅回,包括在 catch 或 finally 語句中的,因此可能會導(dǎo)致一些清理性的工作的得不到完成摧茴,如文件,數(shù)據(jù)庫等的關(guān)閉埂陆。
- 調(diào)用 stop 方法會立即釋放該線程所持有的所有的鎖苛白,導(dǎo)致數(shù)據(jù)得不到同步,出現(xiàn)數(shù)據(jù)不一致的問題焚虱。
7. Java 中實(shí)現(xiàn)同步的幾種方法(如何實(shí)現(xiàn)線程同步)
- 使用 synchronized 關(guān)鍵字购裙。可以用來修飾同步方法或同步代碼塊鹃栽。
- 使用 wait + notify躏率。
- 使用可重入鎖 ReentrantLock。ReentrantLock 是可重入民鼓、互斥薇芝、實(shí)現(xiàn)了 Lock 接口的鎖,它和 synchronized 具有相同的基本行為和語義丰嘉,并擴(kuò)展了其它能力夯到。
- 使用 ThreadLocal。使用 ThreadLocal 管理變量饮亏,則每一個使用該變量的線程都獲得該變量的副本耍贾,副本之間相互獨(dú)立阅爽,這樣每一個線程都可以隨意修改自己的變量副本,而不會對其他線程產(chǎn)生影響杂伟。
- 使用上層封裝好的 concurrent 包频蛔。比如阻塞隊列三圆。
8. ThreadLocal 原理,實(shí)現(xiàn)及如何保證 Local 屬性
每個線程都創(chuàng)建有一個 map围苫,其中 key 為 ThreadLocal强法,value 為要保存的變量副本库倘。這樣:
ThreadLocal.set
實(shí)際為Thread.currentThread.map.put(this, value)
ThreadLocal.get
實(shí)際為Thread.currentThread.map.get(this)
。
所以當(dāng)調(diào)用 get/set 方法時蚂且,實(shí)際是在操縱當(dāng)前線程的變量副本凡伊。
9. 什么是線程安全,如何保證線程安全(如何實(shí)現(xiàn)線程同步)
定義:在擁有共享數(shù)據(jù)的多條線程并行執(zhí)行的程序中饮寞,線程安全的代碼會通過同步機(jī)制保證各個線程都可以正常且正確的執(zhí)行咨油,不會出現(xiàn)數(shù)據(jù)污染等意外情況赚爵。
如何保證:
- synchronized 關(guān)鍵字;
- Lock 接口宴霸;
- volatile + CAS囱晴;(volatile 保證了可見性以及防止了指令重排,但是不具備原子性瓢谢,所以要配合 CAS)
- atomic 原子類畸写。
深入點(diǎn)說,線程安全可以體現(xiàn)在三個方面氓扛,分別是原子性枯芬、可見性和有序性(Java 內(nèi)存模型的三個特性)。針對這三點(diǎn)采郎,Java 提供了底層封裝的 API 來保證線程安全:
首先是 synchronized千所,它能保證原子性、可見性和有序性蒜埋,相對重量級淫痰。
需要注意的是,synchronized 雖然能保證原子性整份,但其實(shí)是每一個 synchronized 塊可以看成是一個原子操作待错,它保證每個時刻只有一個線程執(zhí)行同步代碼,非原子操作仍然可能發(fā)生指令重排烈评,如 new 操作并不是原子操作火俄,所以單例 double check 仍然需要使用 volatile 來防止指令重排。再比如自增不是原子操作讲冠,可能發(fā)生指令重排瓜客,但重排對結(jié)果沒有影響,因?yàn)?synchronized 保證了塊之間的有序執(zhí)行。
volatile 能保證可見性和有序性谱仪,并能真正防止指令重排玻熙。
需要注意的是,volatile 的有序性指的是指令與指令之間有序芽卿,它本身不具有原子性揭芍,如自增操作(可分解為 1.讀取原始值;2.加一卸例;3.寫入工作內(nèi)存)称杨,多線程下,雖然 volatile 保證了指令不重排筷转,但并不能保證自增過程中沒有其它線程插入執(zhí)行姑原,導(dǎo)致臟數(shù)據(jù)。
synchronized 有序性保證了塊與塊之間的有序
volatile 有序性保證了指令與指令之間的有序
根本原因是
synchronized 靠系統(tǒng)內(nèi)核互斥鎖實(shí)現(xiàn)
volatile 靠內(nèi)存屏障
其它保證線程安全的還有 Lock呜舒、 Atomic 類以及 concurrent 包锭汛。
synchronized 能防止指令重排序嗎-鏈接
cas 原理-鏈接
10. 如何保證 List 線程安全(線程間操作 List)
- Vector硝烂。效率低圃郊,已過時瘸恼。
- java.util.Collections.SynchronizedList捐腿。它能把所有 List 接口的實(shí)現(xiàn)類轉(zhuǎn)換成線程安全的 List,由于它所有方法都是帶同步對象鎖的邢滑,即使是讀操作淳蔼,所以性能也一般坷襟。
- java.util.concurrent.CopyOnWriteArrayList乡范。并發(fā)包里的并發(fā)集合類配名。即復(fù)制再寫入,就是在添加元素的時候晋辆,先把原 List 列表復(fù)制一份渠脉,再添加新的元素。添加元素時瓶佳,先加鎖芋膘,再進(jìn)行復(fù)制替換操作,最后再釋放鎖霸饲。獲取元素則沒有加鎖索赏,提升了讀取性能。
11. Java 對象的生命周期
創(chuàng)建階段(Creation)贴彼、應(yīng)用階段(Using)、不可視階段(Invisible)埃儿、不可達(dá)階段(Unreachable)器仗、可收集階段(Collected)、終結(jié)階段(Finalized)與釋放階段(Free)。
12. 談?wù)?synchronized 中的類鎖精钮、方法鎖和可重入鎖
類鎖包括:
- synchronized 修飾靜態(tài)方法
- synchronized 鎖代碼塊威鹿,鎖為類
對象鎖包括:
- synchronized 修飾成員方法(即方法鎖,屬于對象鎖的一種)
- synchronized 鎖代碼塊轨香,鎖為對象
可重入鎖:
可重入鎖就是說某個線程已經(jīng)獲得某個鎖忽你,可以再次獲取鎖而不會出現(xiàn)死鎖。
可重入原理:
每一個鎖關(guān)聯(lián)一個線程持有者和計數(shù)器臂容,當(dāng)計數(shù)器為 0 時表示該鎖沒有被任何線程持有科雳,那么任何線程都可能獲得該鎖而調(diào)用相應(yīng)的方法;當(dāng)某一線程請求成功后脓杉,JVM 會記下鎖的持有線程糟秘,并且將計數(shù)器置為 1;此時其它線程請求該鎖球散,則必須等待尿赚;而該持有鎖的線程如果再次請求這個鎖,就可以再次拿到這個鎖蕉堰,同時計數(shù)器會遞增凌净;當(dāng)線程退出同步代碼塊時,計數(shù)器會遞減屋讶,如果計數(shù)器為 0冰寻,則釋放鎖。
13. synchronized 的原理
synchronized 底層是通過一個 monitor 的對象來完成丑婿,其實(shí) wait/notify 等方法也依賴于 monitor 對象性雄,這就是為什么只有在同步的塊或者方法中才能調(diào)用 wait/notify 等方法,否則會拋出 java.lang.IllegalMonitorStateException 異常的原因羹奉。
14. synchronized 與 Lock 的區(qū)別
- synchronized 是 Java 的關(guān)鍵字秒旋,來自 JVM 層面,而 Lock 是一個類诀拭。
- synchronized 是可重入迁筛、不可中斷、非公平的鎖耕挨,而 Lock 可重入细卧、可中斷、可公平筒占。
- synchronized 無法判斷鎖狀態(tài)贪庙,Lock 可以。
- synchronized 發(fā)生異常會自動釋放鎖翰苫,因此不會死鎖止邮;Lock 發(fā)生異常这橙,不會主動釋放鎖,必須手動 unlock 來釋放鎖导披,可能引起死鎖屈扎。(所以最好將同步代碼塊用 try-catch 包起來,finally 中寫入 unlock撩匕,避免死鎖的發(fā)生)
至于性能鹰晨,1.6 版本優(yōu)化后 synchronized 要比 Lock 快很多,參考 synchronized 實(shí)現(xiàn)原理和鎖的優(yōu)化止毕。
15. 什么是死鎖模蜡,如何避免死鎖
多個線程因競爭多個資源而造成的一種相互等待的情況,若無外力作用滓技,這些進(jìn)程都將無法向前推進(jìn)哩牍。
避免死鎖的技術(shù):
- 加鎖順序。線程按照一定順序加鎖令漂;
- 加鎖時限膝昆。給嘗試獲取鎖加上一定的時限,超時則放棄獲取鎖叠必,并釋放自身占有的鎖荚孵,然后等待一段隨機(jī)的時間再重試;
- 死鎖檢測纬朝。主要針對不可能實(shí)現(xiàn)按序加鎖并且鎖超時也不可行的場景收叶。例如,線程 A 請求某鎖共苛,但是該鎖被線程 B 持有判没,這時就可以檢查下線程 B 是否在請求線程 A 當(dāng)前所持有的鎖,如果有則死鎖隅茎。檢測后有倆種方式解決死鎖問題:一種是釋放所有鎖澄峰,等待一段隨機(jī)時間后重試。另一種是設(shè)置優(yōu)先級辟犀,并讓一部分線程回退重試俏竞。
16. ReentrantLock 的內(nèi)部實(shí)現(xiàn)
ReentrantLock 的內(nèi)部實(shí)現(xiàn)
17. 死鎖的四個必要條件
- 互斥條件。即在一段時間內(nèi)某資源僅為一個進(jìn)程所占有堂竟。
- 不可剝奪條件魂毁。進(jìn)程所獲得的資源在未使用完畢之前,不能被其他進(jìn)程強(qiáng)行奪走出嘹。
- 請求與保持條件席楚。進(jìn)程已經(jīng)保持了至少一個資源,但又提出了新的資源請求税稼,而該資源已被其他進(jìn)程占有烦秩,此時請求進(jìn)程被阻塞刁赦,但對自己已獲得的資源保持不放。
- 循環(huán)等待條件闻镶。存在一種進(jìn)程資源的循環(huán)等待鏈,鏈中每一個進(jìn)程已獲得的資源同時被鏈中下一個進(jìn)程所請求丸升。
只要系統(tǒng)發(fā)生死鎖铆农,這些條件必然成立,而只要上述條件之一不滿足狡耻,就不會發(fā)生死鎖墩剖。
18. 線程池常見問題
1. 為什么使用線程池,線程池的優(yōu)勢或好處
- 節(jié)省頻繁創(chuàng)建線程的開銷夷狰;
- 避免線程棧溢出導(dǎo)致進(jìn)程崩潰岭皂;
2. 線程池類的繼承關(guān)系
線程池的真正實(shí)現(xiàn):ThreadPoolExecutor implements ExecutorService
創(chuàng)建固定類型線程池的類:Executors
3. ThreadPoolExecutor 有哪些參數(shù),作用分別是什么
核心線程數(shù)量:默認(rèn)情況下沼头,核心線程一直在線程池中存活爷绘,即使處于閑置狀態(tài)。
最大線程數(shù):達(dá)到該線程數(shù)进倍,后續(xù)新任務(wù)會被阻塞土至。
最大存活時間:非核心線程閑置時的超時時間,超過該時間非核心線程會被回收猾昆。
時間單位:最大存活時間的時間單位陶因。
BlockingQueue:任務(wù)隊列;
線程工廠:創(chuàng)建線程的線程工廠垂蜗;
4. 線程池的任務(wù)執(zhí)行規(guī)則
- 線程數(shù)量未達(dá)核心線程數(shù)量楷扬,則直接啟動一個核心線程執(zhí)行任務(wù);
- 線程池中的線程數(shù)量已經(jīng)達(dá)到或超過核心贴见,則進(jìn)入任務(wù)隊列等待烘苹;
- 如果無法插入到任務(wù)隊列,則隊列已滿蝇刀;此時如果線程數(shù)量未達(dá)到最大線程數(shù)螟加,則啟動一個非核心線程來執(zhí)行任務(wù);
- 如果隊列已滿且線程數(shù)量大于等于最大線程數(shù)吞琐,則拒絕任務(wù)捆探,并拋出 RejectedExecution 異常。
5. 有哪些固定類型的線程池
FixedThreadPool:只有核心線程站粟,用于更快響應(yīng)外界請求黍图。
CacheThreadPool:沒有核心線程,線程空閑時會被復(fù)用奴烙,閑置超過 60s 則會被回收助被。因?yàn)槿蝿?wù)會被立即執(zhí)行剖张,適合執(zhí)行大量耗時少的任務(wù)。
SingleThreadPool:只有一個核心線程,且任務(wù)隊列無上限矾端,主要用來順序執(zhí)行耗時任務(wù)陈辱。
ScheduledThreadPool:核心線程數(shù)量固定,非核心線程可以無限大顾犹,回收時間為 10s,用來執(zhí)行定時任務(wù)或周期性重復(fù)任務(wù)褒墨。
19. 談?wù)剬Χ嗑€程(并發(fā)編程)的理解
1. 為什么使用多線程(使用多線程的好處炫刷,使用多線程解決了哪些問題)
- 充分利用計算機(jī)處理器性能,提高程序運(yùn)行的效率郁妈;
- 任務(wù)處理異步化浑玛,不阻塞主任務(wù)。
2. 如何實(shí)現(xiàn)多線程
- 通過 new Thread噩咪;
- 通過線程池顾彰。
3. 多線程有哪些問題,如何解決多線程問題
- 線程安全問題剧腻。關(guān)于線程安全問題,參考【多線程-9-什么是線程安全书在,如何保證線程安全】
- 死鎖問題灰伟。關(guān)于死鎖問題,參考【多線程-15-什么是死鎖儒旬,如何避免死鎖】
- 資源消耗問題栏账。無限制的創(chuàng)建線程不僅導(dǎo)致資源消耗,還可能因線程棧溢出導(dǎo)致進(jìn)程崩潰栈源。
20. 談?wù)勀銓Χ嗑€程同步機(jī)制的理解
說白了就是:什么是線程同步(安全)挡爵?如何做到線程同步(安全)?
參考【多線程-9-什么是線程安全甚垦,如何保證線程安全】
21. 多線程斷點(diǎn)續(xù)傳原理
大致原理茶鹃,理解即可..
斷點(diǎn)續(xù)傳:
- 客戶端從臨時文件讀取斷點(diǎn)值并發(fā)送給服務(wù)端。
- 服務(wù)端與客戶端將文件指針移至斷點(diǎn)處(RandomAccessFile)
- 從斷點(diǎn)處傳輸文件艰亮。
多線程下載:
將源文件按長度為分為 M 塊文件闭翩,然后開辟 N 個線程,每個線程傳輸一部分迄埃,最后合并所有線程文件疗韵。
Http 相關(guān)
1. https 和 http 的區(qū)別
https 協(xié)議需要到 ca 申請證書,一般免費(fèi)證書很少侄非,需要交費(fèi)蕉汪。
http 是超文本傳輸協(xié)議流译,信息是明文傳輸,https 則是具有安全性的 ssl 加密傳輸協(xié)議者疤。
http 和 https 使用的是完全不同的連接方式用的端口也不一樣福澡,前者是 80,后者是 443驹马。
2. https 的請求過程
3. 描述下網(wǎng)絡(luò)的幾層結(jié)構(gòu)
層級 | 作用 | 協(xié)議 | 關(guān)鍵字 |
---|---|---|---|
實(shí)體層 | 把電腦通過物理方式連接起來竞漾,負(fù)責(zé)傳送 0 1 信號 | ||
鏈路層 | 對 0 1 信號分組,數(shù)據(jù)最長 1500 字節(jié) | 以太網(wǎng)協(xié)議 | MAC 地址(每塊網(wǎng)卡的唯一地址)窥翩,電腦到電腦 |
網(wǎng)絡(luò)層 | 引進(jìn)新的地址,區(qū)分不同計算機(jī)是否屬于同一子網(wǎng)絡(luò) | IP 協(xié)議 | IP 地址鳞仙,電腦到電腦 |
傳輸層 | 表示數(shù)據(jù)包供哪個進(jìn)程使用寇蚊。 | UDP TCP 協(xié)議 | 端口到端口 |
應(yīng)用層 | 區(qū)分不同數(shù)據(jù)格式 | HTTP FTP |
其中 http 數(shù)據(jù)放在 TCP 數(shù)據(jù)包的數(shù)據(jù)部分,也就是圖中的【應(yīng)用層數(shù)據(jù)包】棍好。
4. http 訪問網(wǎng)頁的完整流程
- 訪問 DNS 服務(wù)器仗岸,根據(jù)域名獲取 IP 地址;
- 根據(jù)子網(wǎng)掩碼判斷是否屬于同一子網(wǎng)絡(luò)借笙,不屬于則通過網(wǎng)關(guān)轉(zhuǎn)發(fā)扒怖;
- 先后封裝為 http 數(shù)據(jù)包、TCP 數(shù)據(jù)包业稼、IP 數(shù)據(jù)包盗痒、以太數(shù)據(jù)包,經(jīng)過網(wǎng)關(guān)轉(zhuǎn)發(fā)低散,到服務(wù)器俯邓。
- 服務(wù)器分別解析上述數(shù)據(jù)包,獲取 http 請求熔号,作出 http 響應(yīng)稽鞭,再按上述流程發(fā)回。
步驟 3 封裝圖:
5. 描述下 http 的三次握手
- 客戶端向服務(wù)器發(fā)送 SYN (同步)包引镊;
- 服務(wù)器向客戶端發(fā)送 ACK (確認(rèn))包朦蕴,同時也向客戶端發(fā)送一條 SYN 包,倆者放到同一請求里弟头;
- 客戶端向服務(wù)器發(fā)送 ACK 包吩抓。
6. 網(wǎng)絡(luò)連接中使用到哪些設(shè)備
設(shè)備名稱 | 對應(yīng)層、協(xié)議 | 作用 |
---|---|---|
集線器 | 實(shí)體層 | 連接設(shè)備 |
交換機(jī) | 鏈路層 | 解決設(shè)備沖突亮瓷,實(shí)現(xiàn)任意倆臺設(shè)備的互聯(lián)琴拧,即 MAC 地址 |
路由器 | 網(wǎng)絡(luò)層 | 通過 IP 地址區(qū)分子網(wǎng)絡(luò),實(shí)現(xiàn)互聯(lián)網(wǎng)連接嘱支。 |
7. http2 和 http1.1 的區(qū)別
8. 描述下網(wǎng)絡(luò)通信協(xié)議
一般提問方式有:對網(wǎng)絡(luò)通信協(xié)議熟悉嗎沛膳?對 socket 和 http 了解嗎扔枫?能解釋下嗎?
網(wǎng)絡(luò)通信協(xié)議可以簡單分為五層锹安,分別是應(yīng)用層(http)短荐、傳輸層(TCP、UDP)叹哭、網(wǎng)絡(luò)層(IP)忍宋、鏈路層(MAC)、物理層风罩。
套接字(Socket)是支持 TCP/IP 協(xié)議的網(wǎng)絡(luò)通信的基本操作單元糠排。它是網(wǎng)絡(luò)通信過程中端點(diǎn)的抽象表示,包含進(jìn)行網(wǎng)絡(luò)通信必須的五種信息:連接使用的協(xié)議超升,本地主機(jī)的 IP 地址入宦,本地進(jìn)程的協(xié)議端口,遠(yuǎn)地主機(jī)的 IP 地址室琢,遠(yuǎn)地進(jìn)程的協(xié)議端口乾闰。
應(yīng)用層通過傳輸層進(jìn)行數(shù)據(jù)通信時,TCP 會遇到同時為多個應(yīng)用程序進(jìn)程提供并發(fā)服務(wù)的問題盈滴。多個 TCP 連接或多個應(yīng)用程序進(jìn)程可能需要通過同一個 TCP 協(xié)議端口傳輸數(shù)據(jù)涯肩。為了區(qū)別不同的應(yīng)用程序進(jìn)程和連接,許多計算機(jī)操作系統(tǒng)為應(yīng)用程序與 TCP/IP 協(xié)議交互提供了套接字(Socket)接口巢钓。應(yīng)用層可以和傳輸層通過 Socket 接口宽菜,區(qū)分來自不同應(yīng)用程序進(jìn)程或網(wǎng)絡(luò)連接的通信,實(shí)現(xiàn)數(shù)據(jù)傳輸?shù)牟l(fā)服務(wù)竿报。
建立 Socket 連接至少需要一對套接字铅乡,其中一個運(yùn)行于客戶端,稱為 ClientSocket 烈菌,另一個運(yùn)行于服務(wù)器端阵幸,稱為 ServerSocket。
套接字之間的連接過程分為三個步驟:服務(wù)器監(jiān)聽芽世,客戶端請求挚赊,連接確認(rèn)。
服務(wù)器監(jiān)聽:服務(wù)器端套接字并不定位具體的客戶端套接字济瓢,而是處于等待連接的狀態(tài)荠割,實(shí)時監(jiān)控網(wǎng)絡(luò)狀態(tài),等待客戶端的連接請求。
客戶端請求:指客戶端的套接字提出連接請求蔑鹦,要連接的目標(biāo)是服務(wù)器端的套接字夺克。為此,客戶端的套接字必須首先描述它要連接的服務(wù)器的套接字嚎朽,指出服務(wù)器端套接字的地址和端口號铺纽,然后就向服務(wù)器端套接字提出連接請求。
連接確認(rèn):當(dāng)服務(wù)器端套接字監(jiān)聽到或者說接收到客戶端套接字的連接請求時哟忍,就響應(yīng)客戶端套接字的請求狡门,建立一個新的線程,把服務(wù)器端套接字的描述發(fā)給客戶端锅很,一旦客戶端確認(rèn)了此描述其馏,雙方就正式建立連接。而服務(wù)器端套接字繼續(xù)處于監(jiān)聽狀態(tài)爆安,繼續(xù)接收其他客戶端套接字的連接請求尝偎。
http 是超文本傳輸協(xié)議,是應(yīng)用層協(xié)議鹏控,依賴于 TCP/IP 協(xié)議,是一種請求相應(yīng)式肤寝、無連接当辐、無狀態(tài)的協(xié)議,請求結(jié)束后鲤看,會自動釋放連接缘揪。
http1.0 中,客戶端的每次請求都要求建立一次單獨(dú)的連接义桂,在處理完本次請求后找筝,就自動釋放連接。
http1.1 中慷吊,則可以在一次連接中處理多個請求袖裕,并且多個請求可以重疊進(jìn)行,不需要等待一個請求結(jié)束后再發(fā)送下一個請求溉瓶。
http2.0 中急鳄,新增了多路復(fù)用,支持壓縮算法堰酿。
https 在 http 的基礎(chǔ)上新增 ssl 層疾宏,使用加密傳輸(會進(jìn)行加解密驗(yàn)證),端口號是 443触创。
9. 描述下 http 的消息結(jié)構(gòu)
請求
請求行:包括請求方法(Get坎藐、Post...)、URL(/hello.txt)哼绑、協(xié)議版本(HTTP/1.1)
請求頭:大量鍵值對,如 User-Agent:xxx座菠、Host:xxx 等
空行
請求數(shù)據(jù):post 請求數(shù)據(jù)在此
示例:
GET /hello.txt HTTP/1.1
User-Agent: curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3
Host: www.example.com
Accept-Language: en, mi
響應(yīng)
狀態(tài)行:包括協(xié)議和狀態(tài)碼经备,如 HTTP/1.1 200 OK
消息報頭:大量鍵值對,如 Content-Type:text/html吟逝、Content-Length:122 等等
空行
響應(yīng)正文:響應(yīng)結(jié)果數(shù)據(jù)
示例:
HTTP/1.1 200 OK
Date: Mon, 27 Jul 2009 12:28:53 GMT
Server: Apache
Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT
ETag: "34aa387-d-1568eb00"
Accept-Ranges: bytes
Content-Length: 51
Vary: Accept-Encoding
Content-Type: text/plain
10. http 的請求類型有哪些
類型 | 功能 |
---|---|
OPTIONS | 返回服務(wù)器針對特定資源所支持的 http 請求方法。也可以利用向 Web 服務(wù)器發(fā)送'*'的請求來測試服務(wù)器的功能性赦肋。 |
HEAD | 向服務(wù)器索要與 GET 請求相一致的響應(yīng)块攒,只不過響應(yīng)體將不會被返回。這一方法可以在不必傳輸整個響應(yīng)內(nèi)容的情況下佃乘,就可以獲取包含在響應(yīng)消息頭中的元信息囱井。 |
GET | 向特定的資源發(fā)出請求。 |
POST | 向指定資源提交數(shù)據(jù)進(jìn)行處理請求(例如提交表單或者上傳文件)程帕。數(shù)據(jù)被包含在請求體中住练。POST 請求可能會導(dǎo)致新的資源的創(chuàng)建或已有資源的修改。 |
PUT | 向指定資源位置上傳其最新內(nèi)容愁拭。 |
DELETE | 請求服務(wù)器刪除 Request-URI 所標(biāo)識的資源盏混。 |
TRACE | 回顯服務(wù)器收到的請求惜论,主要用于測試或診斷混聊。 |
CONNECT | http1.1 協(xié)議中預(yù)留給能夠?qū)⑦B接改為管道方式的代理服務(wù)器。 |
雖然 http 的請求方式有 8 種军浆,但是我們在實(shí)際應(yīng)用中常用的也就是 get 和 post挡闰,其他請求方式也都可以通過這兩種方式間接的來實(shí)現(xiàn)乒融。
Android 基礎(chǔ)
1. 四大組件及其簡單介紹
Activity、Service摄悯、BroadcastReceiver赞季、ContentProvider
Activity
活動,可以簡單的理解為頁面奢驯。
生命周期:onCreate申钩、onStart(可見不可交互)、onResume(可見可交互)叨橱、onPause(可見不可交互)、onStop(不可見)断盛、onDestroy罗洗、onRestart。
四種狀態(tài):Running(激活)钢猛、Paused(可見不可交互)伙菜、Stopped(不可見不可交互)、Killed(被殺掉或啟動以前)
Service
服務(wù)命迈,可以后臺運(yùn)行贩绕,不需要呈現(xiàn)頁面。Service 是運(yùn)行在主線程的壶愤,如果要耗時操作淑倾,可以在 Service 中創(chuàng)建一個子線程來執(zhí)行任務(wù),或者使用 IntentService征椒。
倆種啟動方式:
- start娇哆。生命周期為:onCreate → onStartCommand → onDestroy
- bind。生命周期為:onCreate → onBind → onUnbind → onDestroy
onStart 方法已被廢棄
使用 start 方式多次啟動時,onCreate 只執(zhí)行一次碍讨,onStartCommand 執(zhí)行多次治力,但僅存在一個 Service 實(shí)例。該 Service 將會一直在后臺運(yùn)行勃黍,而不管 Activity 是否還存在宵统,直到被調(diào)用 stopService,或自身的 stopSelf覆获。當(dāng)然如果系統(tǒng)資源不足马澈,android 系統(tǒng)也可能結(jié)束服務(wù)。
使用 bind 方式多次啟動時锻梳,onCreate箭券、onBind 均只執(zhí)行一次,onStartCommand 始終不會被調(diào)用疑枯。當(dāng)連接建立之后辩块,Service 將會一直運(yùn)行,除非調(diào)用 Context.unbindService 斷開連接或者之前調(diào)用 bindService 的 Context 不存在了荆永,系統(tǒng)將會自動停止 Service废亭,對應(yīng) onDestroy 將被調(diào)用。
既 start具钥,又 bind 的服務(wù)豆村,則該 Service 將會一直在后臺運(yùn)行。僅調(diào)用 unbindService 或 stopService 將不會停止 Service骂删,而必須同時調(diào)用來停止服務(wù)(測試結(jié)果是這樣掌动,網(wǎng)上有各種爭論..)。
IntentService 特性:
會創(chuàng)建獨(dú)立的工作線程來處理所有的 Intent 請求宁玫;
會創(chuàng)建獨(dú)立的工作線程來處理 onHandleIntent 方法實(shí)現(xiàn)的代碼粗恢,無需處理多線程問題;
所有請求處理完成后欧瘪,IntentService 會自動停止眷射;
為 Service 的 onBind 提供默認(rèn)實(shí)現(xiàn),返回 null佛掖;
為 Service 的 onStartCommand 提供默認(rèn)實(shí)現(xiàn)妖碉,將請求 Intent 添加到隊列中;
BroadcastReceiver
廣播接收器芥被,用于接收廣播欧宜。
倆種類型:
- 無序廣播。能被所有接收者接收拴魄,傳遞效率高鱼鸠。
- 有序廣播猛拴。傳遞有序,接收者可以將處理結(jié)果傳給下個接收者蚀狰,并且可以終止廣播的傳播愉昆。
倆種注冊方式:
- 靜態(tài)注冊。Androidmanifest 中注冊麻蹋,常駐跛溉,即使應(yīng)用程序關(guān)閉,程序也會被系統(tǒng)調(diào)用扮授。
- 動態(tài)注冊芳室。跟隨 context 的生命周期。如 activity 關(guān)閉時刹勃,其注冊的廣播也會被移除堪侯。
廣播接收順序:
- 有序廣播時,優(yōu)先級高的先接收荔仁。
- 無序廣播或同優(yōu)先級廣播接收器伍宦,動態(tài)高于靜態(tài)。
- 同優(yōu)先級的同類廣播接收器乏梁,先注冊的優(yōu)先于后注冊的次洼。
ContentProvider
內(nèi)容提供者。支持在多個應(yīng)用間存儲和讀取數(shù)據(jù)遇骑,相當(dāng)于數(shù)據(jù)庫卖毁。
它的作用就是將程序的內(nèi)部的數(shù)據(jù)和外部進(jìn)行共享,為數(shù)據(jù)提供外部訪問接口落萎,被訪問的數(shù)據(jù)主要以數(shù)據(jù)庫的形式存在亥啦,而且還可以選擇共享哪一部分的數(shù)據(jù)。這樣一來练链,對于程序當(dāng)中的隱私數(shù)據(jù)可以不共享翔脱,從而更加安全。ContentProvider 是 Android 中一種跨程序共享數(shù)據(jù)的重要組件兑宇。
2. Activity 在各種場景下的生命周期變化
1.正常情況
啟動:create → start → resume
返回鍵退出: pause → stop → destroy
2.Home 鍵
按 Home 鍵:pause → stop
再次啟動:restart → start → resume
3.覆蓋:
被半透明或非全屏 Activity 覆蓋:A.pause → B.create → B.start → B.resume
從覆蓋 Activity 回退:B.pause → A.resume → B.stop → B.destroy
4.跳轉(zhuǎn)
跳轉(zhuǎn)到新 Activity:A.pause → B.create → B.start → B.resume → A.stop
從新 Activity 回退:B.pause → A.restart → A.start → A.resume → B.stop → B.destroy
5.彈出 dialog
當(dāng) Activity 彈出 dialog 對話框的時候粱坤,Activity 不會調(diào)用 onPause();只有當(dāng) Activity 啟動了 dialog 風(fēng)格的 Activity 時才會調(diào)用梯嗽。
6.異常終止
onStop 前調(diào)用 onSaveInstance
3. Activity 的四種啟動模式
standard
默認(rèn)啟動模式崩掘,順序壓入棧
singleTop
如果新 activity 位于任務(wù)棧棧頂?shù)臅r候饵骨,activity 不會被重新創(chuàng)建竭翠,同時回調(diào) onNewIntent 方法。
實(shí)際生命周期函數(shù)為 a.pause → a.onNewIntent → a.resume
應(yīng)用場景:推送通知頁薇搁,避免啟動多個推送頁斋扰。
singleTask
如果 activity 實(shí)例在棧中存在,那么會將該 activity 彈至棧頂啃洋,其上 activity 全部彈出传货。生命周期同 singleTop。
應(yīng)用場景:退出到主界面宏娄。
singleInstance
如果實(shí)例不存在问裕,則創(chuàng)建實(shí)例并放置到獨(dú)立的棧中,里面有且僅有它自己一個實(shí)例孵坚,以后如果繼續(xù)啟動該 activity粮宛,則直接將棧移動到前臺。
singleInstance 由于其特殊性卖宠,下面根據(jù)具體場景說明 activity 的切換過程巍杈。
a 為 standard,b 為 singleInstance逗堵。
Q1:a 啟動 b秉氧,b 再啟動 a眷昆,此時棧信息是怎樣的蜒秤?
A1:初始 a 所在棧中有倆個 a 實(shí)例汁咏,另一個棧中有一個 b 實(shí)例。
Q2:在新啟動的 a 頁面點(diǎn)擊返回作媚,返回到初始 a 還是 b 頁面恩急?
A2:初始 a瞎领。因?yàn)樵谔D(zhuǎn)到新的 a 頁面時,已經(jīng)切換了棧。
Q3:在 Q2 的基礎(chǔ)上再次點(diǎn)擊返回等舔,返回到哪個頁面?
A3:返回到 b 頁面肋杖,因?yàn)榇藭r a 所在棧已經(jīng)空了趴腋,所以切換下一個棧。
4. Activity 之間的通信方式
- Intent吧兔。
- 類的靜態(tài)變量磷仰。
- Application。
- EventBus 事件總線境蔼。
- 借助 SharedPreference灶平、SQLite、File箍土、Service 等逢享。
5. App 的啟動流程,以及 Activity 的啟動過程
Android App 啟動流程源碼分析
源碼分析 Activity 可見性真實(shí)時機(jī)
Activity 啟動與 App 的啟動原理機(jī)制上大同小異吴藻,熟悉 App 的啟動過程即可瞒爬。
6. 橫豎屏切換的時候,Activity 的生命周期
1.未配置 configChanges:
onPause → onStop → onDestroy → onCreate → onStart → onResume
即重新調(diào)用各個生命周期沟堡,切橫屏執(zhí)行一次疮鲫,切豎屏執(zhí)行倆次。
2.配置 configChanges = "orientation"
重新調(diào)用各個生命周期弦叶,無論橫豎屏都只執(zhí)行一次俊犯。
3.配置 configChanges = "orientation|keyboardHidden|screenSize"
不會調(diào)用各個生命周期,只執(zhí)行 onConfigurationChanged 方法伤哺。
自從 Android 3.2(API 13)燕侠,在設(shè)置 Activity 的 android:configChanges="orientation|keyboardHidden" 后,還是會重新調(diào)用各個生命周期函數(shù)立莉。因?yàn)?screen size 也開始跟著設(shè)備的橫豎切換而改變绢彤。所以,在 AndroidManifest.xml 里設(shè)置的 MinSdkVersion 和 TargetSdkVersion 屬性大于等于 13 的情況下蜓耻,如果想阻止程序在運(yùn)行時重新加載 Activity茫舶,除了設(shè)置 "orientation", 你還必須設(shè)置 "ScreenSize"刹淌。
7. Activity 與 Fragment 之間生命周期比較
Fragment 的生命周期:
onAttach → onCreate → onCreateView → onActivityCreated → onStart → onResume → onPause → onStop → onDestroyView → onDestroy → onDetach
onAttach:Fragment 和 Activity 建立關(guān)聯(lián)時調(diào)用饶氏;
onCreateView:Fragment 創(chuàng)建視圖時調(diào)用讥耗;
onActivityCreated:相關(guān)聯(lián)的 Activity 的 onCreate 方法已返回時調(diào)用;
onDestroyView:Fragment 中的視圖被移除時調(diào)用疹启;
onDetach:Fragment 和 Activity 取消關(guān)聯(lián)時調(diào)用古程。
下面是當(dāng) Activity 包含一個 Fragment 的時候,Activity 和 Fragment 生命周期的變化喊崖,其中靜態(tài)加載和動態(tài)加載有所區(qū)別挣磨。
下面 f 指代 fragment,a 指代 activity荤懂,以下為親測數(shù)據(jù)茁裙。
靜態(tài)加載:
f.onAttach → f.onCreate → f.onCreateView → a.onCreate → f.onActivityCreated → f.onStart → a.onStart → a.onResume → f.onResume → f.onPause → a.onPause → f.onStop → a.stop → f.onDestroyView → f.destroy → f.detach → a.destroy
動態(tài)加載
a.onCreate → f.onAttach → f.onCreate → f.onCreateView → f.onActivityCreated → f.onStart → a.onStart → a.onResume → f.onResume → f.onPause → a.onPause → f.onStop → a.stop → f.onDestroyView → f.destroy → f.detach → a.destroy
有幾個需要注意的點(diǎn):
- 靜態(tài)注冊和動態(tài)注冊的區(qū)別在于,f.onAttach节仿、f.onCreate呜达、f.onCreateView 和 a.onCreate 的先后順序不同。
- 后續(xù)同類生命周期方法粟耻,均是 fragment 先于 activity 執(zhí)行查近,onResume 方法除外。
- 這里的順序是以方法執(zhí)行完畢為準(zhǔn)的挤忙,比如 a.super.onStart 中調(diào)用了 f.onStart霜威,可以說 a.onStart 先執(zhí)行(a.onStart 先開始執(zhí)行,以方法開始執(zhí)行為準(zhǔn))册烈,也可以說 a.onStart 后于 f.onStart 執(zhí)行(a.onStart 后執(zhí)行完畢戈泼,以方法執(zhí)行完畢為準(zhǔn))。因此網(wǎng)上可能有不同的順序赏僧,個人覺得沒必要太較真大猛,了解下圖的對應(yīng)關(guān)系即可,如果想知道詳細(xì)的順序淀零,可以參考再后一張圖挽绩。
簡化示意圖:
完整流程圖:
8. Fragment 在各場景下的生命周期變化
1.Home 鍵或跳轉(zhuǎn)至其它 Activity
按下 Home 鍵或跳轉(zhuǎn)到其他 Activity:onPause → onStop
重新打開或回退:onStart → onResume
2.使用 replace 替換 fragment
b.onAttach → b.onCreate → a.onPause → a.onStop → a.onDestroyView → a.destroy → a.detach → b.onCreateView → b.onActivityCreated → b.onStart → b.onResume
3.使用 show/hide 切換 fragment
生命周期無調(diào)用
9. Fragment 之間以及和 Activity 的通信
1. Activity 將數(shù)據(jù)傳給 Fragment
- 使用 bundle
- 使用廣播
- EventBus
2. Fragment 將數(shù)據(jù)傳給 Activity
- 實(shí)現(xiàn)接口回調(diào)形式(onAttach 中將 Context 強(qiáng)轉(zhuǎn)為接口類型)
- 使用廣播
- EventBus
3. Fragment 之間通信
- 借助 Activity 傳 bunndle
- 定義接口
- EventBus
10. 本地廣播和全局廣播有什么差別?
1驾中、本地廣播:發(fā)送的廣播事件不被其他應(yīng)用程序獲取唉堪,也不能響應(yīng)其他應(yīng)用程序發(fā)送的廣播事件。本地廣播只能被動態(tài)注冊肩民,不能靜態(tài)注冊唠亚。動態(tài)注冊需要用到 LocalBroadcastManager。
2持痰、全局廣播:發(fā)送的廣播事件可被其他應(yīng)用程序獲取灶搜,也能響應(yīng)其他應(yīng)用程序發(fā)送的廣播事件。全局廣播既可以動態(tài)注冊,也可以靜態(tài)注冊割卖。
本地廣播實(shí)際還是通過 Handler 實(shí)現(xiàn)前酿,只不過利用了 HashMap 和 List 來維護(hù)廣播接收者、IntentFilter究珊、Intent 三者之間的關(guān)系。
11. ContentProvider纵苛、ContentResolver剿涮、ContentObserver 之間的關(guān)系
- ContentProvider:內(nèi)容提供者,對外共享數(shù)據(jù)攻人,可以通過 ContentProvider 把應(yīng)用中的數(shù)據(jù)共享給其他應(yīng)用訪問取试。
- ContentResolver:內(nèi)容解析者,其作用是按照一定規(guī)則訪問內(nèi)容提供者的數(shù)據(jù)腿椎。
- ContentObserver:內(nèi)容觀察者筝家,目的是觀察特定 URI 引起的數(shù)據(jù)庫的變化舀武,繼而做一些相應(yīng)的處理,類似于數(shù)據(jù)庫技術(shù)中的觸發(fā)器猿棉,當(dāng) ContentObserver 所觀察的 URI 發(fā)生變化時,便會觸發(fā)屑咳。
12. 廣播使用的方式和場景
方式:靜態(tài)注冊萨赁、動態(tài)注冊。詳見【Android 基礎(chǔ)-1-四大組件及其簡單介紹】
在 Android 系統(tǒng)中兆龙,為什么需要廣播機(jī)制呢杖爽?廣播機(jī)制,本質(zhì)上它就是一種組件間的通信方式紫皇,如果是兩個組件位于不同的進(jìn)程當(dāng)中慰安,那么可以用 Binder 機(jī)制來實(shí)現(xiàn),如果兩個組件是在同一個進(jìn)程中聪铺,那么它們之間可以用來通信的方式就更多了化焕,這樣看來,廣播機(jī)制似乎是多余的铃剔。然而锣杂,廣播機(jī)制卻是不可替代的,它和 Binder 機(jī)制不一樣的地方在于番宁,廣播的發(fā)送者和接收者事先是不需要知道對方的存在的元莫,這樣帶來的好處便是,系統(tǒng)的各個組件可以松耦合地組織在一起蝶押,這樣系統(tǒng)就具有高度的可擴(kuò)展性踱蠢,容易與其它系統(tǒng)進(jìn)行集成。
在軟件工程中,是非常強(qiáng)調(diào)模塊之間的高內(nèi)聚低耦合性的茎截,不然的話苇侵,隨著系統(tǒng)越來越龐大,就會面臨著越來越難維護(hù)的風(fēng)險企锌,最后導(dǎo)致整個項(xiàng)目的失敗榆浓。Android 應(yīng)用程序的組織方式,可以說是把這種高內(nèi)聚低耦合性的思想貫徹得非常透徹撕攒,在任何一個 Activity 中陡鹃,都可以使用一個簡單的 Intent,通過 startActivity 或者 startService抖坪,就可以把另外一個 Activity 或者 Service 啟動起來為它服務(wù)萍鲸,而且它根本上不依賴這個 Activity 或者 Service 的實(shí)現(xiàn),只需要知道它的字符串形式的名字即可擦俐,而廣播機(jī)制更絕脊阴,它連接收者的名字都不需要知道。
引自 Android 系統(tǒng)中的廣播機(jī)制簡要介紹
- 需要低耦合通信時使用蚯瞧;
- 由于性能消耗大嘿期,同類型的觀察者模式或者 EventBus 也能解決。故一般當(dāng)需要進(jìn)程間通信或者監(jiān)聽系統(tǒng)廣播事件時埋合,選擇 BroadcastReceiver秽五。比如監(jiān)聽網(wǎng)絡(luò)情況,有網(wǎng)重試饥悴。
- 全局監(jiān)聽場景坦喘。比如推送;
13. 廣播里可以直接啟動 Activity 嗎
onReceive 中 context 參數(shù)西设,如果是靜態(tài)注冊的廣播瓣铣,context 為 ReceiverRestrictedContext,所以在這里啟動一個 Activity贷揽,需要在 intent 中添加 Intent.FLAG_ACTIVITY_NEW_TASK棠笑;如果是動態(tài)注冊的廣播,context 為當(dāng)前注冊時所在的組件禽绪,比如 Activity 或者 Service(Service 里啟動 Activity 的話蓖救,也需要添加 FLAG_ACTIVITY_NEW_TASK)。
14. AlertDialog印屁、PopupWindow 的區(qū)別
AlertDialog 是 Dialog 的子類循捺,需要通過 builder 來創(chuàng)建對話框。通常用于提示用戶做一些額外的行為雄人,要求用戶做出行動才能繼續(xù)進(jìn)行从橘。
適用場景:輸入賬號密碼、請求權(quán)限、警告等恰力,總之是需要用戶明確知道一些信息叉谜,用戶做進(jìn)一步操作前,需要確定或者填入信息踩萎。
PopupWindow 是 android.widget 包里的類停局,因此是懸浮窗的一種,只不過這個懸浮窗是懸浮在當(dāng)前 activity 之上的香府。PopupWindow 可以用來顯示任意視圖董栽,是出現(xiàn)在當(dāng)前 activity 上的一個浮動容器。
適用場景:輸入的補(bǔ)全信息回还、下拉選擇的菜單裆泳,可以是一些提示的信息叹洲。
倆者對 activity 的生命周期都沒影響
15. getApplication 和 getApplicationContext 的區(qū)別
沒區(qū)別柠硕,來源歸根結(jié)底都是來自 LoadedApk.makeApplication()
創(chuàng)建/直接返回的 Application媚创,因此是同一個 Application着饥,只不過返回途徑不一樣而已规脸。
源碼分析:getApplication 和 getApplicationContext 的區(qū)別
16. Application 和 Activity 的 Context 對象的區(qū)別
- Application 的 Context 直接繼承自 ContextWrapper向抢,而 Activity 的 Context 直接繼承自 ContextThemeWrapper理朋。
- Application 的 Context 生命周期貫穿整個 App 存活期間跷乐,而使用 Activity 的 Context 則可能導(dǎo)致內(nèi)存泄漏扣墩。
- Application 相較 Activity骤视,不能 showDialog栈妆、startActivity(需要 Flag) 以及 LayoutInflate(直接使用默認(rèn)主題)胁编。
- 一個應(yīng)用只能有一個 Application Context。
17. 描述下 Android 的幾種動畫
- 幀動畫鳞尔。GIF
- 補(bǔ)間動畫嬉橙。有透明、大小寥假、位置市框、旋轉(zhuǎn)等,補(bǔ)間動畫的 View 不是真正的移動糕韧,只是視覺意義枫振。
- 屬性動畫。真正改變屬性的動畫萤彩。
- 觸摸反饋動畫粪滤。如水波紋效果。
- 揭露動畫雀扶。背景色擴(kuò)散效果额衙。
- 轉(zhuǎn)場動畫。
- Vector 矢量動畫。
18. 屬性動畫的特征
- 不局限于 View窍侧。
- 真實(shí)改變 View 屬性县踢。
- 動畫效果豐富,支持自定義組合屬性伟件。
19. 如何導(dǎo)入已有的外部數(shù)據(jù)庫
android 系統(tǒng)的數(shù)據(jù)庫應(yīng)該存放在 /data/data/(package name)/ 目錄下硼啤,所以我們需要用 FileInputStream 讀取原數(shù)據(jù)庫,再用 FileOutputStream 把讀取到的數(shù)據(jù)寫入到該目錄斧账。
20. Android 中三種播放視頻的方式
- 使用自帶播放器谴返。指定 Action 為 ACTION_VIEW,Data 為 uri咧织,Type 為其 MIME 類型嗓袱。
- 使用 MediaController + VideoView。
- 使用 MediaPlayer + SurfaceView习绢。
21. 介紹下 Android 中數(shù)據(jù)存儲的方式
- SharedPreference渠抹;用于保存鍵值對數(shù)據(jù)。
- 文件存儲闪萄;
- SQLite 數(shù)據(jù)庫梧却;
- ContentProvider。為存儲和獲取數(shù)據(jù)提供了統(tǒng)一的接口败去,可以在不同應(yīng)用程序之間共享數(shù)據(jù)放航。
- 網(wǎng)絡(luò)存儲。
22. SharedPreference 中 commit 和 apply 的區(qū)別
- apply 沒有返回值而 commit 返回 boolean 表明修改是否提交成功圆裕;
- apply 是將修改數(shù)據(jù)提交到內(nèi)存, 而后異步真正提交到硬件磁盤, 而 commit 是同步的提交到硬件磁盤广鳍。
效率上,apply > commit
安全上吓妆,commit > apply
23. 什么是混淆赊时,Android 中哪些類不能混淆
混淆,將類名耿战、方法名蛋叼、成員變量等重命名為無意義的簡短名稱,以增加逆向工程的難度剂陡,同時通過移除無用的類減少包的大小狈涮。
以下為不能混淆的類:
- 反射使用的類或變量;
- bean 對象鸭栖;
- 四大組件歌馍;
- 注解;
- 枚舉晕鹊;
- JNI 調(diào)用的 Java 方法松却;
- Java 調(diào)用的 Native 方法暴浦;
- JS 調(diào)用的 Java 方法;
- JavaScript 方法晓锻;
- Parcelable 序列化相關(guān)類歌焦;
- Gson、EventBus 等砚哆。
24. 什么是依賴倒置
依賴倒置原則:程序要依賴于抽象接口独撇,不依賴于具體實(shí)現(xiàn)。核心是面向接口編程躁锁。
高層不依賴底層纷铣,應(yīng)該依賴抽象,抽象不應(yīng)該依賴于細(xì)節(jié)战转,細(xì)節(jié)應(yīng)該依賴于抽象搜立。
25. Android 權(quán)限
Android權(quán)限適配(一)
Android權(quán)限適配(二)
1. 權(quán)限的分類
- 正常權(quán)限。AndroidManifest 配置即可獲得;
- 危險權(quán)限。6.0 之后定義的權(quán)限昂灵,有權(quán)限組的概念。不僅需要需要在 AndroidManifest 中配置社痛,還需要在使用前 check 是否真正擁有權(quán)限见转,以動態(tài)申請命雀。
- 特殊權(quán)限。如通知欄權(quán)限斩箫、自啟動吏砂、懸浮窗和無障礙輔助等。嚴(yán)格的來講乘客,這部分內(nèi)容不屬于 Android 權(quán)限部分狐血。因?yàn)樗鼈儾恍枰?app 中配置,而是需要用戶到系統(tǒng)對應(yīng)的設(shè)置頁面打開開關(guān)易核。但是實(shí)際開發(fā)中匈织,確實(shí)有這樣的需求:檢測能不能彈出通知,不能則提示用戶牡直,或直接跳轉(zhuǎn)到對應(yīng)頁面缀匕,引導(dǎo)用戶打開開關(guān)。故把這部分納入權(quán)限的范疇碰逸。
2. Android 中讀寫 SD 卡需要權(quán)限嗎
讀寫權(quán)限雖然是危險權(quán)限乡小,但并不是只要讀寫就要配置這倆個權(quán)限:
內(nèi)置存儲:
Context.getFileDir():/data/data/應(yīng)用包名/files/
Context.getCacheDir():/data/data/應(yīng)用包名/cache/
內(nèi)置存儲讀寫不需要配置任何權(quán)限。
外置sd卡:
Context.getExternalFilesDir():SDCard/Android/data/應(yīng)用包名/files/
Context.getExternalCacheDir():SDCard/Android/data/應(yīng)用包名/cache/
API<19 需要配置權(quán)限饵史,API>=19 不需要配置權(quán)限满钟。
即對于配置了讀寫權(quán)限的 app胜榔,使用"SDCard/Android/data/應(yīng)用包名/"讀寫操作時,4.4 系統(tǒng)以下因?yàn)榕渲昧藱?quán)限而能正常讀寫湃番,4.4 及以上系統(tǒng)因?yàn)椴恍枰獧?quán)限亦能正常讀寫夭织。但是為了不配置多余的權(quán)限,建議如下寫:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="18"/>
以上文件夾吠撮,當(dāng) app 卸載時會被系統(tǒng)自動刪除摔癣。
其余 sd 卡位置,6.0 以上需要動態(tài)申請讀寫權(quán)限纬向。
讀寫權(quán)限只是為了限制 app 污染用戶 sd 卡择浊,保護(hù)手機(jī)安全,對于 app 而言逾条,讀寫操作是正常而必要的琢岩,故劃分出以上幾個文件,使 app 在無需權(quán)限的情況下能正常存儲必要的文件师脂,且能在被卸載時自動刪除這些文件担孔,以達(dá)到保護(hù)用戶的目的。
26. Android Profiler
Android Profiler 提供了四種工具吃警,分別對 CPU糕篇、內(nèi)存、流量和電量作監(jiān)測酌心,它們分別是 CPU Profiler拌消、Memory Profiler、Network Profiler 和 Energy Profiler安券。
Android Profiler(一)CPU Profiler
Android Profiler(二)Memory Profiler
CPU Profiler 主要用來檢測這些卡頓:方法耗時過長墩崩、啟動耗時過長、幀渲染過長等侯勉。
Memory Profiler 主要用來檢測這些卡頓:內(nèi)存占用過高鹦筹、內(nèi)存泄漏、內(nèi)存抖動導(dǎo)致的頻繁 GC 卡頓址貌。
使用 Memory Profiler 檢測以下幾種內(nèi)存泄漏:
- 累積性內(nèi)存泄漏铐拐。泄漏內(nèi)存不斷累積,最危險练对、最容易導(dǎo)致 OOM 的內(nèi)存泄漏遍蟋,容易檢測。
- 覆蓋性內(nèi)存泄露锹淌。泄漏對象會被新泄漏的對象覆蓋匿值,同樣相對容易檢測。
- 短期內(nèi)存泄漏赂摆。只會在短時間內(nèi)泄漏挟憔,如子線程耗時任務(wù)钟些、網(wǎng)絡(luò)調(diào)度等,不容易檢測绊谭,如果是重型頁面政恍、又恰好用戶手機(jī)不佳,還可能導(dǎo)致 OOM达传,這種情況推薦使用 LeakCanary 去檢測篙耗。
27. Android 項(xiàng)目圖片文件夾對應(yīng)的分辨率分別是
尺寸 | 分辨率 | dpi |
---|---|---|
ldpi | 240 x 320 | 120 |
mdpi | 320 x 480 | 160 |
hdpi | 480 x 800 | 240 |
xhdpi | 720 x 1280 | 320 |
xxhdpi | 1080 x 1920 | 480 |
Android 源碼機(jī)制
1. LruCache 和 DiskLruCache 原理
源碼分析 LruCache
源碼分析 DiskLruCache
LruCache
- LruCache 會創(chuàng)建一個固定大小的緩存池,并維持一個 LinkHashMap 來有序的緩存數(shù)據(jù)宪赶。
- 在往緩存池 put 或 get 數(shù)據(jù)的時候宗弯,LinkHashMap 會將最近使用的數(shù)據(jù)移動到隊尾。
- 在往緩存池 put 數(shù)據(jù)的時候搂妻,LruCache 會計算當(dāng)前已緩存數(shù)據(jù)的大小蒙保。如果當(dāng)前緩存數(shù)據(jù)超過了限定值,LruCache 會將 LinkHashMap 隊首的數(shù)據(jù)刪除欲主,直到緩存數(shù)據(jù)大小滿足限定值邓厕。
DiskLruCache 和 LruCache 區(qū)別不大,都是利用 LinkHashMap 實(shí)現(xiàn)緩存算法扁瓢。只不過 DiskLruCache 是硬盤緩存宰掉,故需要持久化 LinkHashMap 中維持的 Lru 順序關(guān)系鞠值。
Journal 文件詳細(xì)的記錄了緩存的操作記錄甥郑,以便于 app 啟動時嘱丢,可以根據(jù)之前的操作記錄,恢復(fù) LinkHashMap 的數(shù)據(jù)她紫。這份數(shù)據(jù)包括:有哪些緩存硅堆,以及這些緩存的 Lru 順序屿储。
2. Android 插件化和熱修復(fù)
1. 為什么使用插件化
Android 開發(fā)中經(jīng)常有這樣的情況:模塊化不清晰贿讹、方法數(shù)超限、想在不重新安裝 App 的情況下增加新的模塊够掠,基于這樣的需求民褂,催生出了 Android 插件化技術(shù)。
2. 什么是插件化/插件化的基本原理是什么
利用 DexClassLoader 可以加載外部 dex 文件以及包含 dex 的壓縮文件(apk 和 jar)的特點(diǎn)疯潭,通過 Hook 技術(shù)來啟動外部 dex 文件中的 Activity赊堪。
3. 詳細(xì)的描述下插件化實(shí)現(xiàn)/如何利用插件化啟動外部 apk
Android 啟動 Activity,大致可以分為以下幾步:
- 當(dāng)前 Activity 通知 AMS 檢測 AndroidManifest 中是否有目標(biāo) Activity竖哩;
- AMS 檢測到有目標(biāo) Activity哭廉,則通知當(dāng)前 Activity 休眠;
- AMS 檢測目標(biāo) Activity 進(jìn)程是否存在相叁,不存在通知 Zygote fork 進(jìn)程遵绰;
- 啟動目標(biāo) Activity辽幌。創(chuàng)建 Context,并通過 ClassLoader 創(chuàng)建 Activity椿访。
因?yàn)橐獙?shí)現(xiàn)插件化乌企,啟動外部 apk,所以可以依賴第 1成玫、4 步驟實(shí)現(xiàn)加酵。
針對第一步,在 AndroidManifest 中創(chuàng)建占位的 Activity哭当,然后通過 Hook猪腕,將本進(jìn)程里,把要送檢的 Activity 替換為占位 Activity 以繞過檢測钦勘。同時码撰,將真正要啟動的 Activity 暫存在 Intent 中。
針對第四步个盆,會進(jìn)入 ActivityThread 的 performLaunchActivity 方法脖岛,也就是要啟動 Activity 之前,再次利用 Hook 技術(shù)將真正要啟動的 Activity 還原出來颊亮。在啟動目標(biāo) Activity 時柴梆,利用 DexClassLoader 將外部 dex 文件加載進(jìn)來,創(chuàng)建目標(biāo) Activity 的 Class 文件终惑,同時將外部資源等也一并加載傳入绍在,然后啟動目標(biāo) Activity 即可。
實(shí)際過程是復(fù)雜的雹有,插件化的難點(diǎn)在于需要對系統(tǒng)源碼非常熟悉偿渡,以選擇合適的 Hook 點(diǎn),另外就是系統(tǒng)版本的兼容性霸奕。
4. 熱修復(fù)了解嗎溜宽,原理是什么
熱修復(fù)和插件化的原理大同小異,都是利用 DexClassLoader 來加載外部 dex 文件质帅。
Android 加載 Class 文件使用的是 ClassLoader 及其雙親委托原理适揉,即由下至上委托,由上至下查找煤惩,查找本應(yīng)用的類則是在 PathClassLoader 中嫉嘀,系統(tǒng)創(chuàng)建了一個 Dex 文件數(shù)組,會依次遍歷數(shù)組魄揉,直到找到目標(biāo) Class 為止剪侮。
利用這個原理,可以使用 DexClassLoader 將包含修復(fù) Class 的 Dex 文件加載進(jìn)來洛退,然后利用 Hook 技術(shù)瓣俯,將目標(biāo) Dex 插入到數(shù)組前列红淡,這樣加載類時由于先找到,實(shí)現(xiàn)了替換降铸。
當(dāng)然這只是熱修復(fù)方案的一種在旱。其他方法還有:
- 底層替換方案,原理是替換 C 中對 Java 方法的調(diào)用推掸,如替換入口桶蝎、替換 ArtMethod 結(jié)構(gòu)體(該結(jié)構(gòu)體包含了 Java 方法的所有信息,包括執(zhí)行入口谅畅、訪問權(quán)限登渣、所屬類和代碼執(zhí)行地址)。優(yōu)點(diǎn)是立即生效不用重啟毡泻。
- Instant Run 方案胜茧。利用 ASM 字節(jié)碼操作庫,在方法中注入替換代碼仇味。該代碼會判斷函數(shù)是否有修改呻顽,以決定是否執(zhí)行修改后的代碼。
5. 插件化丹墨、熱修復(fù)系列博客
1. ClassLoader 詳解 (Class 的加載)
2. HookJava 基礎(chǔ) (如何 Hook)
3. Android App 啟動流程源碼分析 (Activity 的啟動源碼)
4. 從零開始的 Android 插件化:宿主 app 加載 sd 卡 apk (如何一步一步實(shí)現(xiàn)基礎(chǔ)插件化)
5. 熱修復(fù)原理 (熱修復(fù)的種類廊遍、原理等)
6. 熱修復(fù)原理與基礎(chǔ)范例
3. Android 組件化
1. 為什么組件化
隨著 app 版本迭代,業(yè)務(wù)越來越復(fù)雜贩挣,單一模塊的功能越來越多喉前,每個工程師需要熟悉大量的代碼旭蠕,而且很難多人協(xié)作员咽,無論是維護(hù)還是編譯都耗時耗力闽铐。而且單一功能耦合嚴(yán)重寨辩,根本無法做單元測試,所以必須要有更加靈活的架構(gòu)贡耽。
安全性现使、可讀性叼屠、可維護(hù)性疯溺、可擴(kuò)展性都差
2. 如何實(shí)現(xiàn)組件化/描述下組件化方案
關(guān)于組件化论颅,可以從以下幾個方面描述:
- 組件化基礎(chǔ)架構(gòu)。包括架構(gòu)圖囱嫩,以及參考組件化框架 ComponentLight 源碼,說明如何利用輔助框架更好的實(shí)現(xiàn)架構(gòu)應(yīng)用漏设。(解決切換成本高墨闲、開發(fā)運(yùn)行不同依賴狀態(tài)干擾的問題)
- 組件化通信。EventBus 解耦郑口。
- 組件化跳轉(zhuǎn)鸳碧。隱式啟動盾鳞、ARouter 或 SimpleRouter。
- 組件化存儲瞻离。GreenDao 對象關(guān)系型數(shù)據(jù)庫腾仅。
- 組件化權(quán)限。一種方式是僅將危險權(quán)限放入到具體子 module套利,好處是移除模塊危險權(quán)限也跟著移除推励;還有一種是將所有權(quán)限都放入到具體子 module,好處是最大程度解耦肉迫,但是增加了編譯合并檢測的消耗验辞。另外權(quán)限申請框架有 AndPermission。
- 組件化混淆喊衫。這里推薦是僅在主 module 混淆跌造、子 module 不混淆。因?yàn)檫@樣可以避免重復(fù)混淆而導(dǎo)致資源找不到族购,但缺點(diǎn)是需要子 module 開發(fā)人員與主 module 同步壳贪,存在溝通成本。
- 組件化分發(fā)朝墩。參考 Activity 組件化分發(fā)結(jié)構(gòu)收苏。主要優(yōu)勢是:不是通過 Activity 直接控制 Manager 來執(zhí)行相關(guān)模塊的業(yè)務(wù)懦鼠,而是讓模塊通過關(guān)注分發(fā)的生命周期睦袖,獨(dú)立的完成自身業(yè)務(wù)烈和,最大化解耦忘渔。
- 組件化流通。打包成 aar对蒲、上傳 maven 等蝠咆。參考 Android 倉庫解析。
- Gradle 優(yōu)化联喘。也可以參考 Gradle 核心 個人筆記捂蕴。
- 其他優(yōu)化。如 findViewById玫荣,可以使用 ButterKnife(不推薦)、DataBinding(不推薦)官边、ViewBinding捐晶。再如編譯流程優(yōu)化,可以使用 gradle-task-tree池凄、InstantRun、并行編譯荔泳、重復(fù)任務(wù)過濾等。
組件化中需要注意的問題:
- AndroidManifest 是會合并的昨登,因此要注意包名胚想、Application 沖突等問題鸿吆。
- 子 module 編譯的靜態(tài)變量不能是 final 的,因此 switch 場景不能使用乘瓤,需要轉(zhuǎn)換為 if-else 形式危队。
- 同名資源叔锐,會保留主 module 資源禀苦,所以布局起名最好以 module 名開頭作區(qū)分。
Android 組件化架構(gòu) 個人筆記
Android 組件化框架 ComponentLight
Activity 組件化分發(fā)結(jié)構(gòu)
4. Handler 機(jī)制
1. 描述下 Handler 的原理
Handler 主要用于跨線程通信裆装。涉及 MessageQueue桅滋、Message、Looper/Handler 這四個類贞远。
首先是 Message擎勘,它是消息且蓬,是線程間通信的數(shù)據(jù)媒介昵仅;然后是 MessageQueue命辖,它是消息隊列,主要功能是向消息池投遞信息(MessageQueue.enqueueMessage)和取走消息池的信息(MessageQueue.next)登颓。它是個阻塞隊列者铜,因此當(dāng)消息為空時藻烤,它會阻塞取消息的線程绷雏。最后是 Looper怖亭,它會不斷循環(huán)的從 MessageQueue 中取出消息并執(zhí)行涎显。
以主線程為例說明,主線程在啟動時(ActivityThread.main)兴猩,會調(diào)用 Looper.prepare余佃、Looper.loop充包,來創(chuàng)建 MessageQueue退客,并開啟死循環(huán)從 MessageQueue 中取出消息,執(zhí)行 Message.target.handleMessage 方法箭跳。當(dāng)消息隊列為空時,此循環(huán)會被阻塞潭千。子線程通過調(diào)用 Handler.sendMessage 方法谱姓,將消息發(fā)送到消息隊列中,此時隊列不為空刨晴,循環(huán)被喚醒屉来,主線程獲取消息并執(zhí)行。
整個過程類似生產(chǎn)-消費(fèi)者模型狈癞。
2. 一個線程可以有幾個 Looper茄靠、幾個 MessageQueue 和幾個 Handler
Looper 利用了 ThreadLocal,因此每個線程只有一個 Looper蝶桶。
Looper 構(gòu)造函數(shù)中創(chuàng)建了 MessageQueue 對象慨绳,因此一個線程只有一個 MessageQueue。
Handler 在創(chuàng)建時與 Looper 和 MessageQueue 產(chǎn)生關(guān)聯(lián)真竖,因此可以有多個脐雪。
3. 可以在子線程直接創(chuàng)建一個 Handler 嗎,會出現(xiàn)什么問題疼邀,那該怎么做
不能喂江,Handler 依賴于 Looper,而 Looper 是消費(fèi)者線程(這里是子線程)消費(fèi) Message 的關(guān)鍵旁振,因此必須在 Looper.prepare 之后。
class MyThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare(); // 為線程創(chuàng)建 Looper 對象
mHandler = new Handler() {
public void handleMessage(Message msg) {
}
};
Looper.loop(); // 啟動消息循環(huán)
}
}
4. Looper 死循環(huán)為什么不會導(dǎo)致應(yīng)用卡死涨岁,會消耗大量資源嗎
可以簡單的將 MessageQueue 理解為阻塞隊列拐袜,當(dāng)沒有消息時,主線程會進(jìn)入阻塞梢薪,釋放 CPU 資源蹬铺。binder 線程會通過 Handler 來喚醒主線程,而 binder 線程則會與系統(tǒng) AMS 進(jìn)行通信秉撇。實(shí)際上 Android 四大組件的生命周期也是這么運(yùn)行的甜攀。
深入點(diǎn)講,MessageQueue 實(shí)際上是管道琐馆。涉及到 Linux pipe/epoll 機(jī)制规阀,簡單說就是在主線程的 MessageQueue 沒有消息時,便阻塞在 Loop 的 queue.next() 中的 nativePollOnce() 方法里谁撼,此時主線程會釋放 CPU 資源進(jìn)入休眠狀態(tài),直到下個消息到達(dá)或者有事務(wù)發(fā)生滋饲,通過往 pipe 管道寫端寫入數(shù)據(jù)來喚醒主線程工作厉碟。這里采用的 epoll 機(jī)制喊巍,是一種 IO 多路復(fù)用機(jī)制,可以同時監(jiān)控多個描述符箍鼓,當(dāng)某個描述符就緒(讀或?qū)懢途w)崭参,則立刻通知相應(yīng)程序進(jìn)行讀或?qū)懖僮鳎举|(zhì)同步 I/O款咖,即讀寫是阻塞的何暮。 所以說,主線程大多數(shù)時候都是處于休眠狀態(tài)之剧,并不會消耗大量 CPU 資源郭卫。
真正會卡死主線程的操作是在回調(diào)方法 onCreate/onStart/onResume 等操作時間過長,會導(dǎo)致掉幀背稼,甚至發(fā)生 ANR贰军,looper.loop 本身不會導(dǎo)致應(yīng)用卡死。
5. ANR 的原理
產(chǎn)生 ANR 的條件:
- 輸入事件 5s 內(nèi)沒有處理完畢蟹肘;
- BroadcastReceiver 的 onReceive 函數(shù)時 10 秒內(nèi)沒有處理完畢词疼;
- Service 的各個生命周期函數(shù) 20 秒內(nèi)沒有處理完畢。
導(dǎo)致 ANR 的原因:
- 主線程執(zhí)行了耗時操作帘腹;如網(wǎng)絡(luò)請求贰盗、數(shù)據(jù)庫操作、文件操作阳欲。
- 其他【進(jìn)程】占用 CPU舵盈,導(dǎo)致本進(jìn)程得不到 CPU 時間片,比如其他進(jìn)程的頻繁讀寫操作可能會導(dǎo)致這個問題球化。
- UI 線程等待子線程釋放鎖秽晚,從而無法處理用戶請求;
- 耗時動畫導(dǎo)致 CPU 負(fù)載過重筒愚。
ANR 的實(shí)現(xiàn)原理:
在應(yīng)用收到輸入事件赴蝇、廣播或運(yùn)行服務(wù)時,AMS 的 Handler 會收到消息巢掺,并開啟一個定時任務(wù)(收到什么消息句伶、定多長時間,視場景而定)陆淀,如果未超時考余,則會取消掉該任務(wù),否則拋出 ANR 異常倔约。
ANR 的分析:
- 本地結(jié)合 logcat 日志分析秃殉。
- 遠(yuǎn)程使用保存在手機(jī)本地的 /data/anr/traces.txt 日志文件分析。
6. MessageQueue 是隊列嗎,它是什么數(shù)據(jù)結(jié)構(gòu)
MessageQueue 不是隊列钾军,它內(nèi)部使用一個 Message 鏈表實(shí)現(xiàn)消息的存和取鳄袍。 鏈表的排列依據(jù)是 Message.when,表示 Message 期望被分發(fā)的時間吏恭,該值是 SystemClock. uptimeMillis 與 delayMillis 之和拗小。
7. Handler.postDelayed 函數(shù)延時執(zhí)行計時是否準(zhǔn)確
當(dāng)上一個消息存在耗時任務(wù)的時候,會占用延時任務(wù)執(zhí)行的時機(jī)樱哼,實(shí)際延遲時間可能會超過預(yù)設(shè)延時時間哀九,這時候就不準(zhǔn)確了。
8. Handler.sendMessageDelayed 是延遲的把消息放入到 MessageQueue 中的嗎
不是搅幅。
Handler.sendMessageDelayed 實(shí)際調(diào)用了 sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
阅束,最終是在 queue.next 方法中處理的。
queue.next 在取消息時茄唐,如果頭部 Message 是有延遲且時間未到的息裸,不返回 Message 而且會計算下時間胧奔,然后利用 nativePollOnce 進(jìn)行阻塞馋缅。期間如果有新的消息進(jìn)來,并且這個消息不是延遲盾剩、或是比當(dāng)前延遲時間短蚁廓,這個消息就插入頭部并喚醒線程访圃。
這么設(shè)計的原因是,如果通過延遲將消息放入到 MessageQueue 中相嵌,那么多個延遲消息就需要多個定時器腿时。
參考源碼
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
SystemClock.uptimeMillis 是系統(tǒng)從開機(jī)啟動到現(xiàn)在的時間,期間不包括休眠的時間饭宾,獲得到的時間是一個相對的時間圈匆。之所以使用這種方式來計算時間,而不是獲得當(dāng)前 CurrentTime 來計算捏雌,在于 handler 會阻塞、掛起笆搓、睡眠等性湿,這些時候是不應(yīng)該執(zhí)行的。如果使用絕對時間的話满败,就會搶占資源來執(zhí)行當(dāng)前 handler 的內(nèi)容肤频,顯然這是不應(yīng)該出現(xiàn)的情況,所以要避免算墨。
MessageQueen.enqueueMessage 插入消息宵荒,MessageQueue.next 取消息。由此也可知:消息在加入消息隊列時就進(jìn)行好排序,鏈表頭的延遲時間小报咳,尾部延遲時間最大侠讯。
9. 你了解 HandlerThread 嗎
HandlerThread 可以作為消費(fèi)者,在子線程里處理 Message暑刃,其原理實(shí)際就是實(shí)現(xiàn)了 Looper.prepare厢漩、Looper.loop。
Handler 小節(jié)參考資料 談?wù)?Handler 機(jī)制和原理
Handler 小節(jié)參考資料 Handler postDelayed 的原理
5. Bundle 機(jī)制
1. 介紹下 Bundle
Bundle 是 Android 中傳遞數(shù)據(jù)的容器岩臣,通常用在 Intent溜嗜、Message.setData、Fragment.setArguments 等相互通信的方法中架谎。
Bundle 使用 final 修飾炸宵,意味著不可繼承。它實(shí)現(xiàn)了 Parcelable 接口谷扣。內(nèi)部則是通過 ArrayMap 去存儲數(shù)據(jù)土全。
2. ArrayMap 原理
ArrayMap 主要是針對內(nèi)存優(yōu)化的數(shù)據(jù)結(jié)構(gòu)。
內(nèi)部有倆個數(shù)組存儲數(shù)據(jù)抑钟,一個數(shù)組負(fù)責(zé)存儲 key 的 hash 值涯曲,另一個數(shù)組負(fù)責(zé)存儲 key 和 value。數(shù)組按照從小到大的順序排序在塔,添加幻件、刪除、查找數(shù)據(jù)的時候都是先使用二分查找法得到相應(yīng)的 index蛔溃,然后通過 index 來進(jìn)行添加绰沥、查找、刪除等操作贺待。
原理圖參考 此鏈接
6. HashMap 源碼徽曲,SparseArray 原理
1. HashMap 的工作原理
HashMap 基于哈希原理,hashCode 最大的特點(diǎn)是:不同的對象麸塞,hashCode 可能相同秃臣;同一個對象,hashCode 一定相同哪工“麓耍基于此,利用數(shù)組 + 鏈表的形式來實(shí)現(xiàn) HashMap雁比。
HashMap 通過 put 和 get 方法儲存和獲取對象稚虎。當(dāng)將鍵值對傳遞給 put 方法時,它調(diào)用鍵對象的 hashCode 方法來計算哈希值偎捎,然后找到 bucket 位置來儲存值對象蠢终。如果當(dāng)前位置已有對象了序攘,對象將會儲存在鏈表的下一個節(jié)點(diǎn)中。 當(dāng)獲取對象時寻拂,同樣是先用鍵對象的 hashCode 定位 bucket 位置程奠,然后利用 equals 找到目標(biāo)對象。
因此重寫 hashCode兜喻,一是要保證同一對象的 hashCode 一定相同梦染,二是要保證不同對象的 hashCode 可以相同、可以不同朴皆,但分布要均勻帕识。
2. HashMap 和 HashTable 的區(qū)別
- HashMap 不是線程安全的,HashTable 則相反遂铡;(ConcurrentHashMap 同樣是線程安全的肮疗,且擴(kuò)展性更好)
- HashMap 可以接受為 null 的鍵值。
- 父類不同扒接。HashMap 是 AbstractMap伪货,而 HashTable 繼承自 Dictionary。
讓 HashMap 轉(zhuǎn)為同步的方法:Map concurrentMap = Collections.synchronizeMap(hashMap);
3. HashMap 和 HashSet 的區(qū)別
- 實(shí)現(xiàn)接口不同钾怔。HashMap 實(shí)現(xiàn)了 Map 接口碱呼,HashSet 實(shí)現(xiàn)了 Set 接口;
- HashMap 存儲鍵值對宗侦,HashMap 僅存儲對象愚臀;
- HashMap 使用 put 添加對象,HashSet 使用 add矾利。
沒什么特別的區(qū)別姑裂,照著 map 和 set 的區(qū)別說即可。
4. HashMap 的大小超過了負(fù)載因子(load factor)定義的容量會怎么辦
HashMap 默認(rèn)的負(fù)載因子大小為 0.75男旗,也就是說舶斧,當(dāng)一個 map 填滿了 75% 的 bucket 時候,和其它集合類(如 ArrayList)一樣察皇,將會創(chuàng)建原來 HashMap 大小的兩倍的 bucket 數(shù)組茴厉,來重新調(diào)整 map 的大小,并將原來的對象放入新的 bucket 數(shù)組中什荣。
5. 為什么 String呀忧、Interger 這樣的類適合作為鍵
- 它們是 final、不可變的溃睹,因?yàn)殒I值改變可能導(dǎo)致 hashCode 改變,如果放入時和獲取時返回不同的 hashCode胰坟,那就不能正確找到對象因篇。
- 它們已經(jīng)重寫了 hashCode 和 equals 方法泞辐。
引申:什么樣的對象可以作為鍵?
6. 介紹下 SparseArray
稀疏數(shù)組竞滓。
SparseArray 是 android 里為 <Interger, Object> 這樣的 Hashmap 而專門寫的類咐吼,目的是提高內(nèi)存效率,其核心是折半查找函數(shù)(binarySearch)商佑。
SparseArray 有兩個優(yōu)點(diǎn):1.避免了自動裝箱(auto-boxing)2.數(shù)據(jù)結(jié)構(gòu)不依賴于外部對象映射锯茄。(SparseArray 內(nèi)部使用兩個一維數(shù)組來保存數(shù)據(jù),一個用來存 key茶没,一個用來存 value)
7. 介紹下 SurfaceView
如果繪制過程很復(fù)雜肌幽,并且界面更新還非常頻繁,這時候就會造成界面的卡頓抓半,影響用戶體驗(yàn)喂急,為此 Android 提供了 SurfaceView 來解決這一問題。
View 和 SurfaceView 的區(qū)別:
- View 適用于主動更新笛求,SurfaceView 適用于被動更新廊移,比如頻繁刷新界面;
- View 在主線程中刷新探入,而 SurfaceView 則開啟一個子線程來進(jìn)行刷新狡孔。
- SurfaceVie 在底層機(jī)制中實(shí)現(xiàn)了雙緩沖機(jī)制。
雙緩沖主要是為了解決反復(fù)局部刷屏帶來的閃爍蜂嗽,把要畫的東西先畫到一個內(nèi)存區(qū)域里苗膝,然后整體的一次性畫出來。
8. Android 里有哪些內(nèi)存泄露
長生命周期的對象持有短生命周期對象的引用徒爹,盡管短生命周期的對象不再使用荚醒,但是因?yàn)殚L生命周期對象持有它的引用而導(dǎo)致不能被回收。
- 靜態(tài)變量持有 Context隆嗅;
- 匿名內(nèi)部類導(dǎo)致內(nèi)存泄漏界阁。如子線程耗時任務(wù)、Handler胖喳、TimerTask 等泡躯。解決辦法:使用靜態(tài)內(nèi)部類代替內(nèi)部類,使用弱引用持有 Context丽焊,或在 onDestroy 中移除 Message较剃;
- 監(jiān)聽沒有取消注冊;
- 資源對象未關(guān)閉技健。如 Cursor写穴、File、Stream雌贱、Bitmap 等啊送。因?yàn)橘Y源性對象往往都用了一些緩沖偿短,緩沖不僅存在于 java 虛擬機(jī)內(nèi),還存在于 java 虛擬機(jī)外馋没。如果僅僅是把它的引用置 null昔逗,而不關(guān)閉它們,也會造成內(nèi)存泄漏篷朵。
- 容器類持有對象勾怒;
- WebView。解決方案是單開一個進(jìn)程声旺。
檢測內(nèi)存泄漏的工具:
- Memory Profiler笔链;
- LeakCanary;
- MAT艾少;輸入 HRPOF 文件卡乾,輸出分析結(jié)果。
9. Android app 里有哪些性能優(yōu)化方案
優(yōu)化種類缚够、方案很多幔妨,視情況而定,以下是一些類型及其舉例谍椅。
內(nèi)存優(yōu)化:
- 內(nèi)存泄漏的優(yōu)化误堡。靜態(tài)內(nèi)部類 + 弱引用、onDestroy 及時移除 Message雏吭,并把相關(guān)資源置空锁施,取消注冊、及時關(guān)閉 Cursor杖们、Bitmap 記得調(diào)用 recycle 等悉抵。
- 內(nèi)存占用的優(yōu)化。Bitmap 壓縮和復(fù)用摘完,枚舉使用注解替代等姥饰。
- 內(nèi)存抖動的優(yōu)化。避免在 onDraw 等頻繁調(diào)度的方法或循環(huán)中創(chuàng)建對象孝治。
包大小優(yōu)化:
- 刪除重復(fù)的庫炮温。
- 刪除無用的資源霎槐。
- 使用混淆。(混淆可以刪除未用到的庫)
- 圖片使用 SVG笤受、WebP 替代犯戏。
穩(wěn)定性優(yōu)化:
- 日志埋點(diǎn)監(jiān)控椒涯。如 Firebase垂蜗、無痕埋點(diǎn)等伟墙。crash 監(jiān)控 + 報警。
流暢性優(yōu)化:
- 使用線程池解決耗時問題手素;
- 避免過度繪制吕喘,避免繪制層級過高赘那;
- 使用 merge、ViewStub氯质、include 等元素。
- 列表優(yōu)化祠斧。如將過重的 item 拆分為多個 item闻察,因?yàn)榛瑒又量梢姇r才加載,所以變相優(yōu)化了重量級的 item琢锋。
include:將布局中的公共部分提取出來供其他 layout 復(fù)用辕漂;
merge:用于消除視圖層次結(jié)構(gòu)中的冗余視圖。例如根布局是 LinearLayout吴超,如果又 include 一個 LinerLayout 布局钉嘹,不僅無意義,還會減慢 UI 加載速度鲸阻。
ViewStub:占位 View跋涣。優(yōu)點(diǎn)是需要時才會加載,否則不會占用內(nèi)存鸟悴。通常用于如新手引導(dǎo)陈辱、消息提醒等。
省電性優(yōu)化:
- 加鎖等待细诸,讓出 CPU 資源沛贪,避免無意義的循環(huán)等待。
- 加緩存震贵,如使用享元模式等利赋。
- 對網(wǎng)絡(luò)數(shù)據(jù)進(jìn)行壓縮。
安全性優(yōu)化:
- 使用 so 庫存儲密鑰猩系。
- 使用 local.properties 存儲密鑰媚送,密鑰本地流通,不上傳 Git蝙眶。
- 對本地數(shù)據(jù)季希、網(wǎng)絡(luò)數(shù)據(jù)進(jìn)行加密等。
- 額外加固幽纷。如梆梆加固式塌。
構(gòu)建優(yōu)化:
- Gradle 優(yōu)化、使用 Gradle 插件友浸、進(jìn)行分包(Gradle3.2.x 分包填坑)等峰尝。
10. Android 的系統(tǒng)架構(gòu)
- 應(yīng)用程序?qū)印m攲铀械膽?yīng)用程序收恢,如通訊錄武学、瀏覽器等等祭往。
- 應(yīng)用程序框架層。為應(yīng)用程序提供的高級服務(wù)火窒。如 ActivityManager硼补、WindowManager、PackageManager熏矿、ResourceManager已骇、LocationManager 等等。
- 系統(tǒng)運(yùn)行庫層票编。一系列 C/C++ 程序庫的集合褪储,包括用于存儲的 SQLite、用于網(wǎng)絡(luò)安全的 SSL慧域、用于播放音視頻的庫鲤竹、瀏覽器引擎等,Android 的一些核心庫(如 android.app)以及虛擬機(jī)也在此層昔榴。
- Linux 內(nèi)核層辛藻。Linux 提供了系統(tǒng)最基本的功能:進(jìn)程管理、內(nèi)存管理论泛、設(shè)備管理以及網(wǎng)絡(luò)和大量設(shè)備驅(qū)動揩尸。
11. Android 啟動如何優(yōu)化
App 啟動我們可以優(yōu)化的過程包括【主 Application 啟動】和【主 Activity 啟動】,因此針對這倆個點(diǎn)做優(yōu)化屁奏。
Application:
- 延遲加載岩榆。一些耗時的 sdk 可以延遲、或需要時坟瓢、或使用 IdleHandler 在系統(tǒng)空閑時再加載勇边。
- 異步加載。把不需要同步等待的業(yè)務(wù)全部放到異步加載折联。
- 閃屏優(yōu)化粒褒。因?yàn)槔鋯訒葐涌瞻状翱冢賵?zhí)行 Application 初始化诚镰,所以可以先加背景圖奕坟,給用戶視覺已啟動的感覺。
- 緩存數(shù)據(jù)清笨。對于一些更新頻率比較低的配置信息月杉,或者資源等,可以采用緩存的方式避免每次啟動都去下載抠艾,從而節(jié)省啟動時間和 CPU 資源苛萎。
- dex 優(yōu)化。通過分包、精簡主包的方式去優(yōu)化啟動讀取 class 的效率腌歉。
MainActivity:
- 布局優(yōu)化蛙酪。簡化層級、減少過度繪制翘盖、暫時不需要顯示的 View 可以使用 ViewStub 占位桂塞。
- 延遲加載。為了讓用戶盡快看到主界面馍驯,在首頁繪制完畢后再做部分必須要在 UI 線程中執(zhí)行的邏輯藐俺,實(shí)現(xiàn)方式是通過監(jiān)聽 onFirstDrawFinish。
- 異步泥彤。
12. 一個應(yīng)用程序安裝到手機(jī)上時發(fā)生了什么
此問題等同于應(yīng)用程序的安裝過程。
- 將 apk 復(fù)制到 data/app 目錄下卿啡;
- 解壓 apk吟吝,將 dex 拷貝至 data/dalvik-cache,命名為 apk 路徑 + classes.dex颈娜;
- 在 data/data 目錄下創(chuàng)建對應(yīng)的包名目錄剑逃,并在此目錄下創(chuàng)建存儲應(yīng)用數(shù)據(jù)的相關(guān)目錄。
13. App 是如何沙箱化官辽,為什么要這么做蛹磺;
如何沙箱化:即每一個 Android 應(yīng)用程序都在自己的進(jìn)程中運(yùn)行,都擁有一個獨(dú)立的虛擬機(jī)實(shí)例同仆。應(yīng)用之間相互隔離萤捆、獨(dú)自運(yùn)行并禁止相互訪問。
至于權(quán)限隔離俗批,基礎(chǔ)是建立在 Linux 已有的 uid俗或、gid 上的 。Android 每安裝一個應(yīng)用程序岁忘,就會為它分配一個 uid辛慰。(其中普通 Android 應(yīng)用程序的 uid 是從 10000 開始分配,10000 以下是系統(tǒng)進(jìn)程的 uid)而對于普通應(yīng)用程序來說干像,gid 等于 uid帅腌。
為什么要沙箱:
主要是出于安全性考慮。
- 數(shù)據(jù)安全麻汰。應(yīng)用之間隔離數(shù)據(jù)不可胡亂訪問速客。
- 系統(tǒng)安全。一個進(jìn)程什乙、虛擬機(jī)的崩潰不會導(dǎo)致其他應(yīng)用出現(xiàn)問題挽封。
- 權(quán)限管理。可以為不同進(jìn)程劃分不同權(quán)限辅愿,方便系統(tǒng)管理智亮。
14. 圖片優(yōu)化,如何壓縮点待、如何緩存
優(yōu)化方案
- 壓縮圖片阔蛉;有質(zhì)量壓縮(像素不變,改變位深和透明度)癞埠、尺寸壓縮(降低像素)状原、采樣率壓縮(好處是不會將大圖讀入內(nèi)存,缺點(diǎn)是不能很好的保證圖片質(zhì)量)
- 內(nèi)存緩存苗踪;使用 LruCache 最近最少使用緩存颠区。
- 使用軟引用;內(nèi)存吃緊則回收對象通铲。
15. 如何降低程序崩潰率
- 首先要保證代碼質(zhì)量毕莱。開發(fā)代碼要有統(tǒng)一的約定;開發(fā)時 Lint 全局掃描颅夺,解決報警問題朋截;提交代碼有完善的 review 檢測機(jī)制。
- 發(fā)版前測試吧黄。包括 QA 全量回歸測試部服、云平臺各種機(jī)型穩(wěn)定性測試、崩潰分析等等拗慨。
- 上線后的系統(tǒng)監(jiān)控廓八。如 Firebase 線上監(jiān)控、Google 統(tǒng)計胆描、無痕埋點(diǎn)等瘫想。
- 上線后的解決方案:熱修復(fù)(小 bug)或重發(fā)穩(wěn)定版本(提前準(zhǔn)備穩(wěn)定版本,重大 bug 發(fā)版覆蓋)昌讲。
16. OOM 原因及如何定位
OOM 的原因:
- 內(nèi)存泄漏導(dǎo)致国夜。
- 加載的文件或圖片過大;
定位方法:
- 使用 Memory Profiler短绸,動態(tài)跟蹤內(nèi)存使用信息车吹;
- 使用 LeakCanary 定位內(nèi)存泄漏;
- 使用 MAT醋闭,分析 hprof 文件窄驹,檢測內(nèi)存泄漏。
17. 描述下 Glide
Android 的圖片加載和緩存庫证逻。
特色優(yōu)化:
1.滑動監(jiān)聽乐埠,滑動時暫停加載,停止時開始加載,對應(yīng)方法:resumeRequests丈咐,pauseRequests瑞眼。
2.默認(rèn)基于 HttpUrlConnection,可以替換為 OkHttp棵逊。
細(xì)節(jié):
1.基于三級緩存伤疙,內(nèi)存、硬盤辆影、網(wǎng)絡(luò)徒像。
2.弱引用 + LruCache 共同完成內(nèi)存緩存功能。
3.內(nèi)存緩存最大為:每個進(jìn)程最大可用內(nèi)存 * 0.4蛙讥,低配手機(jī)會替換為 0.33锯蛀。
4.磁盤緩存為 250m膨疏。
18. AsyncTask 機(jī)制及缺點(diǎn)(了解)
AsyncTask 內(nèi)部其實(shí)就是使用子線程(更準(zhǔn)確的是 FutureTask)+ Handler 實(shí)現(xiàn)的胚鸯。
缺點(diǎn)如下:
- AsyncTask 不會隨 Activity 銷毀而銷毀,而一直要等 doInBackground 執(zhí)行完畢硫嘶,可能導(dǎo)致內(nèi)存泄漏经备;
- 內(nèi)部線程池上限為 128 個線程,當(dāng)隊列已滿部默,將會拋出 RejectedExecutionException侵蒙,而且還存在大量消耗系統(tǒng)資源導(dǎo)致系統(tǒng)崩潰的風(fēng)險。
在 Android 1.6 之前的版本傅蹂,AsyncTask 是串行的纷闺,在 1.6 至 2.3 的版本,改成了并行的份蝴。在 2.3 之后的版本又做了修改犁功,可以支持并行和串行,當(dāng)想要串行執(zhí)行時婚夫,直接執(zhí)行 execute 方法浸卦,如果需要并行執(zhí)行,則要執(zhí)行 executeOnExecutor(Executor)案糙。
19. 列舉幾種常見的 RuntimeException限嫌,并說明 RuntimeException 和 Exception 區(qū)別
常見的RuntimeException:
- NullPointerException 空指針異常
- ClassCastException 類型轉(zhuǎn)換異常
- IllegalArgumentException 傳遞非法參數(shù)
- IndexOutOfBoundsException 數(shù)組下標(biāo)越界
- NumberFormatException 數(shù)組格式異常
- SecurityException 安全異常
Error 和 Exception 區(qū)別:
Error 是編譯時錯誤和系統(tǒng)錯誤,而 Exception 是可以被拋出的基本類型时捌。
RuntimeException 和 Exception 區(qū)別:
Exception 必須被開發(fā)者解決后才能編譯通過怒医,解決方式可以 throw 到上層,也可以 try-catch 處理奢讨。如 IOException稚叹,而 RuntimeException 是運(yùn)行時異常,不受異常檢查。
20. OOM 是否可以 try-catch
只在特殊情況下可行:即 try 中聲明了很大的對象扒袖,導(dǎo)致 OOM塞茅。
但是通常情況下,OOM 可能是由內(nèi)存泄漏等其他原因引起的僚稿,很難捕獲凡桥。針對大對象,Java 也提供了 SoftReference蚀同、LruCache 解決內(nèi)存問題缅刽。
21. try-catch 實(shí)現(xiàn)原理是什么,對性能有影響嗎
實(shí)現(xiàn)原理:
當(dāng)把 Java 源碼編譯成相應(yīng)的字節(jié)碼蠢络,如果方法內(nèi)有 try-catch 異常處理衰猛,就會產(chǎn)生與該方法相關(guān)聯(lián)的異常表,異常表記錄的是 try 的起點(diǎn)和終點(diǎn)刹孔、catch 方法體所在的位置啡省,以及聲明捕獲的異常種類。通過這些信息髓霞,當(dāng)程序出現(xiàn)異常時卦睹,Java 虛擬機(jī)就會查找方法對應(yīng)的異常表,如果發(fā)現(xiàn)有聲明的異常與拋出的異常類型匹配就會跳轉(zhuǎn)到 catch 處執(zhí)行相應(yīng)的邏輯方库,如果沒有匹配成功结序,就會回到上層調(diào)用方法中繼續(xù)查找,如此反復(fù)纵潦,一直到異常被處理為止徐鹤,或者停止進(jìn)程。所以邀层,try 在反映到字節(jié)碼上的就是產(chǎn)生一張異常表返敬,只有發(fā)生異常時才會被使用。
對性能的影響:
try-catch 與未使用 try-catch 代碼區(qū)別在于寥院,前者阻止 Java 對 try 塊內(nèi)代碼的一些優(yōu)化劲赠,例如重排序。try-catch 里面的代碼是不會被編譯器優(yōu)化重排的秸谢。所以在實(shí)際編程中经磅,提倡 try 代碼塊的范圍盡量小,這樣才可以充分發(fā)揮 Java 對代碼的優(yōu)化钮追。
Android View
1. View 基礎(chǔ)
2. View 的生命周期如何變化预厌,以及生命周期和 Activity 的對應(yīng)關(guān)系
View 生命周期調(diào)用順序
View 與 Activity 生命周期關(guān)聯(lián)關(guān)系
3. 描述下 Android View 的加載、繪制流程
加載流程:
- Activity.setContentView 間接調(diào)用了 PhoneWindow.setContentView元媚;
- 在此方法中通過 LayoutInflate 去加載對象轧叽,原理是利用 xml 的 pull 解析去解析 xml 布局文件苗沧,最終通過反射創(chuàng)建 view。
繪制流程:
Activity 啟動時炭晒,會將 DecorView 與 ViewRoot 建立關(guān)聯(lián)待逞,之后 ViewRoot 的 requestLayout 會被調(diào)用,最終將調(diào)用 ViewRootImpl.performTraversals网严,它是整個繪制的起點(diǎn)识樱。
performTraversals 會依次調(diào)用 measure、layout震束、draw 三大繪制方法怜庸。其中 measure 用于計算視圖大小,layout 用于擺放視圖位置垢村,draw 用于繪制視圖割疾。
draw 的繪制過程:
- 繪制背景;
- 通過 onDraw 繪制自身內(nèi)容;
- 通過 dispatchDraw 繪制子 View;
- 繪制滾動條;
4. onTouchListener嘉栓、onTouchEvent宏榕、onClickListener 的執(zhí)行順序
優(yōu)先級上,onTouchListener > onTouchEvent > onClickListener侵佃,onTouchListener 返回 true 則攔截事件麻昼。
5. 描述下 View 事件傳遞分發(fā)機(jī)制
Activity → Window(PhoneWindow)→ DecorView → ViewGroup → View
ViewGroup.dispatchTouchEvent → ViewGroup.onInterceptTouchEvent → View.dispatchTouchEvent → View.onTouchEvent → ViewGroup.onTouchEvent
各種場景下的各生命周期方法最好親測熟記。
Android 第三方庫源碼解析
1. Volley
2. OkHttp
3. RxJava
4. Retrofit
5. EventBus
6. Jetpack
持續(xù)更新中...
目錄大綱
Java 基礎(chǔ)
- ==馋辈、equals 和 hashCode 的區(qū)別
- 基礎(chǔ)數(shù)據(jù)類型各占多少字節(jié)
- 對多態(tài)的理解
- String涌献、StringBuffer 與 StringBuilder 的區(qū)別
- 父類的靜態(tài)方法能否被子類重寫
- 進(jìn)程和線程的區(qū)別
- final、finally 和 finalize 的區(qū)別
- Serializable 和 Parcelable 的區(qū)別
- 內(nèi)部類的設(shè)計意圖及種類
- 鎖機(jī)制 synchronized首有、volatile、Lock
- 常見編碼和字節(jié)占用數(shù)
- 深拷貝和淺拷貝的區(qū)別
- 靜態(tài)代理和動態(tài)代理的區(qū)別枢劝,什么場景使用
- 如何將一個 Java 對象序列化到文件里
- 談?wù)剬ψ⒔獾睦斫?/li>
- 談?wù)剬σ蕾囎⑷氲睦斫?/li>
- 談?wù)剬Ψ盒偷睦斫?/li>
Java 虛擬機(jī)
- APK 的生成
- APK 的安裝方式(JIT井联、AOT)、Dalvik 和 ART 的異同等
- Java 內(nèi)存結(jié)構(gòu)
- Java 內(nèi)存模型
- Java 引用類型
- 垃圾識別策略
- 垃圾回收算法
- 類的加載過程(如何從 class 轉(zhuǎn)變?yōu)楸惶摂M機(jī)識別的類)
- 虛引用的功能及原理
Java 多線程
- 開啟線程的幾種方式
- 線程的幾種狀態(tài)
- 如何控制某個方法允許并發(fā)訪問線程的個數(shù)
- wait您旁、sleep 的區(qū)別
- 什么可以導(dǎo)致線程阻塞/為什么會出現(xiàn)線程阻塞
- 線程如何關(guān)閉
- Java 中實(shí)現(xiàn)同步的幾種方法(如何實(shí)現(xiàn)線程同步)
- ThreadLocal 原理烙常,實(shí)現(xiàn)及如何保證 Local 屬性?
- 什么是線程安全鹤盒?如何保證線程安全蚕脏?(如何實(shí)現(xiàn)線程同步)
- 如何保證 List 線程安全?(線程間操作 List)
- Java 對象的生命周期
- 談?wù)?synchronized 中的類鎖侦锯、方法鎖和可重入鎖
- synchronized 的原理
- synchronized 與 Lock 的區(qū)別
- 什么是死鎖驼鞭,如何避免死鎖
- ReentrantLock 的內(nèi)部實(shí)現(xiàn)
- 死鎖的四個必要條件
- 線程池常見問題
- 為什么使用線程池,線程池的優(yōu)勢或好處
- 線程池類的繼承關(guān)系
- ThreadPoolExecutor 有哪些參數(shù)尺碰,作用分別是什么
- 線程池的任務(wù)執(zhí)行規(guī)則
- 有哪些固定類型的線程池
- 談?wù)剬Χ嗑€程(并發(fā)編程)的理解
- 為什么使用多線程(使用多線程的好處挣棕,使用多線程解決了哪些問題)
- 如何實(shí)現(xiàn)多線程
- 多線程有哪些問題译隘,如何解決多線程問題
- 談?wù)勀銓Χ嗑€程同步機(jī)制的理解
- 多線程斷點(diǎn)續(xù)傳原理
Android 基礎(chǔ)
- 四大組件及其簡單介紹
- Activity 在各種場景下的生命周期變化
- Activity 的四種啟動模式
- Activity 之間的通信方式
- App 的啟動流程,以及 Activity 的啟動過程
- 橫豎屏切換的時候洛心,Activity 的生命周期
- Activity 與 Fragment 之間生命周期比較
- Fragment 在各場景下的生命周期變化
- Fragment 之間以及和 Activity 的通信
- 本地廣播和全局廣播有什么差別固耘?
- ContentProvider、ContentResolver词身、ContentObserver 之間的關(guān)系
- 廣播使用的方式和場景
- 廣播里可以直接啟動 Activity 嗎
- AlertDialog厅目、PopupWindow 的區(qū)別
- getApplication 和 getApplicationContext 的區(qū)別
- Application 和 Activity 的 Context 對象的區(qū)別
- 描述下 Android 的幾種動畫
- 屬性動畫的特征
- 如何導(dǎo)入已有的外部數(shù)據(jù)庫
- Android 中三種播放視頻的方式
- 介紹下 Android 中數(shù)據(jù)存儲的方式
- SharedPreference 中 commit 和 apply 的區(qū)別
- 什么是混淆,Android 中哪些類不能混淆
- 什么是依賴倒置
- Android 權(quán)限
- 權(quán)限的分類
- Android 中讀寫 SD 卡需要權(quán)限嗎
- Android Profiler
- Android 項(xiàng)目圖片文件夾對應(yīng)的分辨率
Http 相關(guān)
- https 和 http 的區(qū)別
- https 的請求過程
- 描述下網(wǎng)絡(luò)的幾層結(jié)構(gòu)
- http 訪問網(wǎng)頁的完整流程
- 描述下 http 的三次握手
- 網(wǎng)絡(luò)連接中使用到哪些設(shè)備
- http2 和 http1.1 的區(qū)別
- 描述下網(wǎng)絡(luò)通信協(xié)議
- 描述下 http 的消息結(jié)構(gòu)
- http 的請求類型有哪些
Android View
- View 基礎(chǔ)
- View 的生命周期如何變化法严,以及生命周期和 Activity 的對應(yīng)關(guān)系
- 描述下 Android View 的加載损敷、繪制流程
- onTouchListener、onTouchEvent渐夸、onClickListener 的執(zhí)行順序
- 描述下 View 事件傳遞分發(fā)機(jī)制
Android 源碼機(jī)制
- LruCache 和 DiskLruCache 原理
- Android 插件化和熱修復(fù)
- 為什么使用插件化
- 什么是插件化/插件化的基本原理是什么
- 詳細(xì)的描述下插件化實(shí)現(xiàn)/如何利用插件化啟動外部 apk
- 熱修復(fù)了解嗎嗤锉,原理是什么
- 插件化、熱修復(fù)系列博客
- Android 組件化
- 為什么組件化
- 如何實(shí)現(xiàn)組件化/描述下組件化方案
- Handler 機(jī)制
- 描述下 Handler 的原理
- 一個線程可以有幾個 Looper墓塌、幾個 MessageQueue 和幾個 Handler
- 可以在子線程直接創(chuàng)建一個 Handler 嗎瘟忱,會出現(xiàn)什么問題,那該怎么做
- Looper 死循環(huán)為什么不會導(dǎo)致應(yīng)用卡死苫幢,會消耗大量資源嗎
- ANR 的原理
- MessageQueue 是隊列嗎访诱,它是什么數(shù)據(jù)結(jié)構(gòu)
- Handler.postDelayed 函數(shù)延時執(zhí)行計時是否準(zhǔn)確
- Handler.sendMessageDelayed 是延遲的把消息放入到 MessageQueue 中的嗎
- 你了解 HandlerThread 嗎
- Bundle 機(jī)制
- 介紹下 Bundle
- ArrayMap 原理
- HashMap 源碼,SparseArray 原理
- HashMap 的工作原理
- HashMap 和 HashTable 的區(qū)別
- HashMap 和 HashSet 的區(qū)別
- HashMap 的大小超過了負(fù)載因子(load factor)定義的容量會怎么辦
- 為什么 String韩肝、Interger 適合作為鍵
- 介紹下 SparseArray
- 介紹下 SurfaceView
- Android 里有哪些內(nèi)存泄露
- Android app 里有哪些性能優(yōu)化方案
- Android 的系統(tǒng)架構(gòu)
- Android 啟動如何優(yōu)化
- 一個應(yīng)用程序安裝到手機(jī)上時發(fā)生了什么
- App 是如何沙箱化触菜,為什么要這么做;
- 圖片優(yōu)化哀峻,如何壓縮涡相、如何緩存
- 如何降低程序崩潰率
- OOM 原因及如何定位
- 描述下 Glide
- AsyncTask 機(jī)制及缺點(diǎn)(了解)
- 列舉幾種常見的 RuntimeException,并說明 RuntimeException 和 Exception 區(qū)別
- OOM 是否可以 try-catch
- try-catch 實(shí)現(xiàn)原理是什么剩蟀,對性能有影響嗎
Android 第三方庫源碼解析
- Volley
- OkHttp
- RxJava
- Retrofit
- EventBus
- Jetpack
[TOC]