Android面試基礎(chǔ)整理

基礎(chǔ)知識 – 四大組件(生命周期襟雷,使用場景,如何啟動)
java基礎(chǔ) – 數(shù)據(jù)結(jié)構(gòu)鲫咽,線程姨伤,mvc框架
通信 – 網(wǎng)絡(luò)連接(HttpClient劫恒,HttpUrlConnetion)贩幻,Socket
數(shù)據(jù)持久化 – SQLite,SharedPreferences两嘴,ContentProvider
性能優(yōu)化 – 布局優(yōu)化丛楚,內(nèi)存優(yōu)化,電量優(yōu)化
安全 – 數(shù)據(jù)加密憔辫,代碼混淆趣些,WebView/Js調(diào)用,https
UI– 動畫
其他 – JNI贰您,AIDL坏平,Handler拢操,Intent等
開源框架 – Volley,Gilde舶替,RxJava等(簡歷上寫你會的令境,用過的)
拓展 – Android6.0/7.0/8.0/9.0特性,kotlin語言顾瞪,I/O大會
急急忙忙投簡歷展父,趕面試,還不如沉淀一兩天時間玲昧,再過一遍以上內(nèi)容。想穩(wěn)妥拿到一個offer篮绿,最好能理解實現(xiàn)原理孵延,并且知道使用場景了。不要去背亲配!要去理解尘应!面試官聽了一天這些內(nèi)容是很厭倦的,最好能說出一些自己的見解吼虎。

Java中引用類型的區(qū)別犬钢,具體的使用場景
Java中引用類型分為四類:強(qiáng)引用、軟引用思灰、弱引用玷犹、虛引用。

強(qiáng)引用:強(qiáng)引用指的是通過new對象創(chuàng)建的引用洒疚,垃圾回收器即使是內(nèi)存不足也不會回收強(qiáng)引用指向的對象歹颓。

軟引用:軟引用是通過SoftRefrence實現(xiàn)的,它的生命周期比強(qiáng)引用短油湖,在內(nèi)存不足巍扛,拋出OOM之前,垃圾回收器會回收軟引用引用的對象乏德。軟引用常見的使用場景是存儲一些內(nèi)存敏感的緩存撤奸,當(dāng)內(nèi)存不足時會被回收。

弱引用:弱引用是通過WeakRefrence實現(xiàn)的喊括,它的生命周期比軟引用還短胧瓜,GC只要掃描到弱引用的對象就會回收。弱引用常見的使用場景也是存儲一些內(nèi)存敏感的緩存瘾晃。

虛引用:虛引用是通過FanttomRefrence實現(xiàn)的贷痪,它的生命周期最短,隨時可能被回收蹦误。如果一個對象只被虛引用引用劫拢,我們無法通過虛引用來訪問這個對象的任何屬性和方法肉津。它的作用僅僅是保證對象在finalize后,做某些事情舱沧。虛引用常見的使用場景是跟蹤對象被垃圾回收的活動妹沙,當(dāng)一個虛引用關(guān)聯(lián)的對象被垃圾回收器回收之前會收到一條系統(tǒng)通知。

Exception和Error的區(qū)別
Exception和Error都繼承于Throwable熟吏,在Java中距糖,只有Throwable類型的對象才能被throw或者catch,它是異常處理機(jī)制的基本組成類型牵寺。

Exception和Error體現(xiàn)了Java對不同異常情況的分類悍引。Exception是程序正常運(yùn)行中,可以預(yù)料的意外情況帽氓,可能并且應(yīng)該被捕獲趣斤,進(jìn)行相應(yīng)的處理。

Error是指在正常情況下黎休,不大可能出現(xiàn)的情況浓领,絕大部分Error都會使程序處于非正常、不可恢復(fù)的狀態(tài)势腮。既然是非正常联贩,所以不便于也不需要捕獲,常見的OutOfMemoryError就是Error的子類捎拯。

Exception又分為checked Exception和unchecked Exception泪幌。checked Exception在代碼里必須顯式的進(jìn)行捕獲,這是編譯器檢查的一部分署照。unchecked Exception也就是運(yùn)行時異常座菠,類似空指針異常、數(shù)組越界等藤树,通常是可以避免的邏輯錯誤浴滴,具體根據(jù)需求來判斷是否需要捕獲,并不會在編譯器強(qiáng)制要求岁钓。

volatile
一般提到volatile升略,就不得不提到內(nèi)存模型相關(guān)的概念。我們都知道屡限,在程序運(yùn)行中品嚣,每條指令都是由CPU執(zhí)行的,而指令的執(zhí)行過程中钧大,勢必涉及到數(shù)據(jù)的讀取和寫入翰撑。程序運(yùn)行中的數(shù)據(jù)都存放在主存中,這樣會有一個問題啊央,由于CPU的執(zhí)行速度是要遠(yuǎn)高于主存的讀寫速度眶诈,所以直接從主存中讀寫數(shù)據(jù)會降低CPU的效率涨醋。為了解決這個問題,就有了高速緩存的概念逝撬,在每個CPU中都有高速緩存浴骂,它會事先從主存中讀取數(shù)據(jù),在CPU運(yùn)算之后在合適的時候刷新到主存中宪潮。

這樣的運(yùn)行模式在單線程中是沒有任何問題的溯警,但在多線程中,會導(dǎo)致緩存一致性的問題狡相。舉個簡單的例子:i=i+1 ,在兩個線程中執(zhí)行這句代碼梯轻,假設(shè)i的初始值為0。我們期望兩個線程運(yùn)行后得到2尽棕,那么有這樣的一種情況檩淋,兩個線程都從主存中讀取i到各自的高速緩存中,這時候兩個線程中的i都為0萄金。在線程1執(zhí)行完畢得到i=1,將之刷新到主存后媚朦,線程2開始執(zhí)行氧敢,由于線程2中的i是高速緩存中的0,所以在執(zhí)行完線程2之后刷新到主存的i仍舊是1询张。

所以這就導(dǎo)致了對共享變量的緩存一致性的問題孙乖,那么為了解決這個問題,提出了緩存一致性協(xié)議:當(dāng)CPU在寫數(shù)據(jù)時份氧,如果發(fā)現(xiàn)操作的是共享變量唯袄,它會通知其他CPU將它們內(nèi)部的這個共享變量置為無效狀態(tài),當(dāng)其他CPU讀取緩存中的共享變量時蜗帜,發(fā)現(xiàn)這個變量是無效的恋拷,它會從新從主存中讀取最新的值。

在Java的多線程開發(fā)中厅缺,有三個重要概念:原子性蔬顾、可見性、有序性湘捎。
原子性:一個或多個操作要么都不執(zhí)行诀豁,要么都執(zhí)行。
可見性:一個線程中對共享變量(類中的成員變量或靜態(tài)變量)的修改窥妇,在其他線程立即可見舷胜。
有序性:程序執(zhí)行的順序按照代碼的順序執(zhí)行。
把一個變量聲明為volatile活翩,其實就是保證了可見性和有序性烹骨。
可見性我上面已經(jīng)說過了翻伺,在多線程開發(fā)中是很有必要的。這個有序性還是得說一下展氓,為了執(zhí)行的效率穆趴,有時候會發(fā)生指令重排,這在單線程中指令重排之后的輸出與我們的代碼邏輯輸出還是一致的遇汞。但在多線程中就可能發(fā)生問題未妹,volatile在一定程度上可以避免指令重排。

volatile的原理是在生成的匯編代碼中多了一個lock前綴指令空入,這個前綴指令相當(dāng)于一個內(nèi)存屏障络它,這個內(nèi)存屏障有3個作用:

確保指令重排的時候不會把屏障后的指令排在屏障前,確保不會把屏障前的指令排在屏障后歪赢。

修改緩存中的共享變量后立即刷新到主存中化戳。

當(dāng)執(zhí)行寫操作時會導(dǎo)致其他CPU中的緩存無效。

網(wǎng)絡(luò)相關(guān)面試題
http 狀態(tài)碼
http 與 https 的區(qū)別埋凯?https 是如何工作的点楼?
http是超文本傳輸協(xié)議,而https可以簡單理解為安全的http協(xié)議白对。https通過在http協(xié)議下添加了一層ssl協(xié)議對數(shù)據(jù)進(jìn)行加密從而保證了安全掠廓。https的作用主要有兩點(diǎn):建立安全的信息傳輸通道,保證數(shù)據(jù)傳輸安全甩恼;確認(rèn)網(wǎng)站的真實性蟀瞧。

http與https的區(qū)別主要如下:

https需要到CA申請證書,很少免費(fèi)条摸,因而需要一定的費(fèi)用

http是明文傳輸悦污,安全性低;而https在http的基礎(chǔ)上通過ssl加密钉蒲,安全性高

二者的默認(rèn)端口不一樣切端,http使用的默認(rèn)端口是80;https使用的默認(rèn)端口是443

https的工作流程

提到https的話首先要說到加密算法顷啼,加密算法分為兩類:對稱加密和非對稱加密帆赢。

對稱加密:加密和解密用的都是相同的秘鑰,優(yōu)點(diǎn)是速度快线梗,缺點(diǎn)是安全性低椰于。常見的對稱加密算法有DES、AES等等仪搔。

非對稱加密:非對稱加密有一個秘鑰對瘾婿,分為公鑰和私鑰。一般來說,私鑰自己持有偏陪,公鑰可以公開給對方抢呆,優(yōu)點(diǎn)是安全性比對稱加密高,缺點(diǎn)是數(shù)據(jù)傳輸效率比對稱加密低笛谦。采用公鑰加密的信息只有對應(yīng)的私鑰可以解密抱虐。常見的非對稱加密包括RSA等。

在正式的使用場景中一般都是對稱加密和非對稱加密結(jié)合使用饥脑,使用非對稱加密完成秘鑰的傳遞恳邀,然后使用對稱秘鑰進(jìn)行數(shù)據(jù)加密和解密。二者結(jié)合既保證了安全性灶轰,又提高了數(shù)據(jù)傳輸效率谣沸。

https的具體流程如下:

客戶端(通常是瀏覽器)先向服務(wù)器發(fā)出加密通信的請求
支持的協(xié)議版本,比如TLS 1.0版

一個客戶端生成的隨機(jī)數(shù) random1笋颤,稍后用于生成”對話密鑰”

支持的加密方法乳附,比如RSA公鑰加密

支持的壓縮方法

服務(wù)器收到請求,然后響應(yīng)
確認(rèn)使用的加密通信協(xié)議版本,比如TLS 1.0版本伴澄。如果瀏覽器與服務(wù)器支持的版本不一致赋除,服務(wù)器關(guān)閉加密通信

一個服務(wù)器生成的隨機(jī)數(shù)random2,稍后用于生成”對話密鑰”

確認(rèn)使用的加密方法非凌,比如RSA公鑰加密

服務(wù)器證書

客戶端收到證書之后會首先會進(jìn)行驗證
首先驗證證書的安全性

驗證通過之后举农,客戶端會生成一個隨機(jī)數(shù)pre-master secret,然后使用證書中的公鑰進(jìn)行加密清焕,然后傳遞給服務(wù)器端

服務(wù)器收到使用公鑰加密的內(nèi)容,在服務(wù)器端使用私鑰解密之后獲得隨機(jī)數(shù)pre-master secret祭犯,然后根據(jù)radom1秸妥、radom2、pre-master secret通過一定的算法得出一個對稱加密的秘鑰沃粗,作為后面交互過程中使用對稱秘鑰粥惧。同時客戶端也會使用radom1、radom2最盅、pre-master secret突雪,和同樣的算法生成對稱秘鑰。

然后再后續(xù)的交互中就使用上一步生成的對稱秘鑰對傳輸?shù)膬?nèi)容進(jìn)行加密和解密涡贱。

TCP三次握手流程
Android面試題
進(jìn)程間通信的方式有哪幾種
AIDL 咏删、廣播、文件问词、socket督函、管道

廣播靜態(tài)注冊和動態(tài)注冊的區(qū)別
動態(tài)注冊廣播不是常駐型廣播,也就是說廣播跟隨Activity的生命周期。注意在Activity結(jié)束前辰狡,移除廣播接收器锋叨。 靜態(tài)注冊是常駐型,也就是說當(dāng)應(yīng)用程序關(guān)閉后宛篇,如果有信息廣播來娃磺,程序也會被系統(tǒng)調(diào)用自動運(yùn)行。

當(dāng)廣播為有序廣播時:優(yōu)先級高的先接收(不分靜態(tài)和動態(tài))叫倍。同優(yōu)先級的廣播接收器偷卧,動態(tài)優(yōu)先于靜態(tài)

同優(yōu)先級的同類廣播接收器,靜態(tài):先掃描的優(yōu)先于后掃描的段标,動態(tài):先注冊的優(yōu)先于后注冊的涯冠。

當(dāng)廣播為默認(rèn)廣播時:無視優(yōu)先級,動態(tài)廣播接收器優(yōu)先于靜態(tài)廣播接收器逼庞。同優(yōu)先級的同類廣播接收器蛇更,靜態(tài):先掃描的優(yōu)先于后掃描的,動態(tài):先注冊的優(yōu)先于后冊的赛糟。

Android性能優(yōu)化工具使用(這個問題建議配合Android中的性能優(yōu)化)
Android中常用的性能優(yōu)化工具包括這些:Android Studio自帶的Android Profiler派任、LeakCanary、BlockCanary

Android自帶的Android Profiler其實就很好用璧南,Android Profiler可以檢測三個方面的性能問題:CPU掌逛、MEMORY、NETWORK司倚。

LeakCanary是一個第三方的檢測內(nèi)存泄漏的庫豆混,我們的項目集成之后LeakCanary會自動檢測應(yīng)用運(yùn)行期間的內(nèi)存泄漏,并將之輸出給我們动知。

BlockCanary也是一個第三方檢測UI卡頓的庫皿伺,項目集成后Block也會自動檢測應(yīng)用運(yùn)行期間的UI卡頓,并將之輸出給我們盒粮。

Android中的類加載器
PathClassLoader鸵鸥,只能加載系統(tǒng)中已經(jīng)安裝過的apk
DexClassLoader,可以加載jar/apk/dex丹皱,可以從SD卡中加載未安裝的apk

Android中的動畫有哪幾類妒穴,它們的特點(diǎn)和區(qū)別是什么
Android中動畫大致分為3類:幀動畫、補(bǔ)間動畫(View Animation)摊崭、屬性動畫(Object Animation)讼油。

幀動畫:通過xml配置一組圖片,動態(tài)播放呢簸。很少會使用汁讼。

補(bǔ)間動畫(View Animation):大致分為旋轉(zhuǎn)淆攻、透明、縮放嘿架、位移四類操作瓶珊。很少會使用。

屬性動畫(Object Animation):屬性動畫是現(xiàn)在使用的最多的一種動畫耸彪,它比補(bǔ)間動畫更加強(qiáng)大伞芹。屬性動畫大致分為兩種使用類型,分別是ViewPropertyAnimator和ObjectAnimator蝉娜。前者適合一些通用的動畫唱较,比如旋轉(zhuǎn)、位移召川、縮放和透明南缓,使用方式也很簡單通過View.animate()即可得到ViewPropertyAnimator,之后進(jìn)行相應(yīng)的動畫操作即可荧呐。后者適合用于為我們的自定義控件添加動畫汉形,當(dāng)然首先我們應(yīng)該在自定義View中添加相應(yīng)的getXXX()和setXXX()相應(yīng)屬性的getter和setter方法,這里需要注意的是在setter方法內(nèi)改變了自定義View中的屬性后要調(diào)用invalidate()來刷新View的繪制倍阐。之后調(diào)用ObjectAnimator.of屬性類型()返回一個ObjectAnimator概疆,調(diào)用start()方法啟動動畫即可。

補(bǔ)間動畫與屬性動畫的區(qū)別:

補(bǔ)間動畫是父容器不斷的繪制view峰搪,看起來像移動了效果,其實view沒有變化岔冀,還在原地。

是通過不斷改變view內(nèi)部的屬性值概耻,真正的改變view使套。

Handler機(jī)制
說到Handler,就不得不提與之密切相關(guān)的這幾個類:Message鞠柄、MessageQueue侦高,Looper。

Message春锋。Message中有兩個成員變量值得關(guān)注:target和callback矫膨。target其實就是發(fā)送消息的Handler對象差凹,callback是當(dāng)調(diào)用handler.post(runnable)時傳入的Runnable類型的任務(wù)期奔。post事件的本質(zhì)也是創(chuàng)建了一個Message,將我們傳入的這個runnable賦值給創(chuàng)建的Message的callback這個成員變量危尿。

MessageQueue呐萌。消息隊列很明顯是存放消息的隊列,值得關(guān)注的是MessageQueue中的next()方法谊娇,它會返回下一個待處理的消息肺孤。

Looper。Looper消息輪詢器其實是連接Handler和消息隊列的核心。首先我們都知道赠堵,如果想要在一個線程中創(chuàng)建一個Handler小渊,首先要通過Looper.prepare()創(chuàng)建Looper,之后還得調(diào)用Looper.loop()開啟輪詢茫叭。我們著重看一下這兩個方法酬屉。

prepare()。這個方法做了兩件事:首先通過ThreadLocal.get()獲取當(dāng)前線程中的Looper,如果不為空揍愁,則會拋出一個RunTimeException呐萨,意思是一個線程不能創(chuàng)建2個Looper。如果為null則執(zhí)行下一步莽囤。第二步是創(chuàng)建了一個Looper谬擦,并通過ThreadLocal.set(looper)。將我們創(chuàng)建的Looper與當(dāng)前線程綁定朽缎。這里需要提一下的是消息隊列的創(chuàng)建其實就發(fā)生在Looper的構(gòu)造方法中惨远。

loop()。這個方法開啟了整個事件機(jī)制的輪詢饵沧。它的本質(zhì)是開啟了一個死循環(huán)锨络,不斷的通過MessageQueue的next()方法獲取消息。拿到消息后會調(diào)用msg.target.dispatchMessage()來做處理狼牺。其實我們在說到Message的時候提到過羡儿,msg.target其實就是發(fā)送這個消息的handler。這句代碼的本質(zhì)就是調(diào)用handler的dispatchMessage()是钥。

Handler掠归。上面做了這么多鋪墊,終于到了最重要的部分悄泥。Handler的分析著重在兩個部分:發(fā)送消息和處理消息虏冻。
發(fā)送消息。其實發(fā)送消息除了sendMessage之外還有sendMessageDelayed和post以及postDelayed等等不同的方式弹囚。但它們的本質(zhì)都是調(diào)用了sendMessageAtTime厨相。在sendMessageAtTime這個方法中調(diào)用了enqueueMessage。在enqueueMessage這個方法中做了兩件事:通過msg.target = this實現(xiàn)了消息與當(dāng)前handler的綁定鸥鹉。然后通過queue.enqueueMessage實現(xiàn)了消息入隊蛮穿。

處理消息。消息處理的核心其實就是dispatchMessage()這個方法毁渗。這個方法里面的邏輯很簡單践磅,先判斷msg.callback是否為null,如果不為空則執(zhí)行這個runnable灸异。如果為空則會執(zhí)行我們的handleMessage方法府适。

Android性能優(yōu)化
Android中的性能優(yōu)化在我看來分為以下幾個方面:內(nèi)存優(yōu)化羔飞、布局優(yōu)化、網(wǎng)絡(luò)優(yōu)化檐春、安裝包優(yōu)化逻淌。

內(nèi)存優(yōu)化:下一個問題就是。

布局優(yōu)化:布局優(yōu)化的本質(zhì)就是減少View的層級疟暖。常見的布局優(yōu)化方案如下

在LinearLayout和RelativeLayout都可以完成布局的情況下優(yōu)先選擇RelativeLayout恍风,可以減少View的層級

將常用的布局組件抽取出來使用 < include > 標(biāo)簽

通過 < ViewStub > 標(biāo)簽來加載不常用的布局

使用 < Merge > 標(biāo)簽來減少布局的嵌套層次

網(wǎng)絡(luò)優(yōu)化:常見的網(wǎng)絡(luò)優(yōu)化方案如下

盡量減少網(wǎng)絡(luò)請求,能夠合并的就盡量合并

避免DNS解析誓篱,根據(jù)域名查詢可能會耗費(fèi)上百毫秒的時間朋贬,也可能存在DNS劫持的風(fēng)險〈芙荆可以根據(jù)業(yè)務(wù)需求采用增加動態(tài)更新IP的方式锦募,或者在IP方式訪問失敗時切換到域名訪問方式。

大量數(shù)據(jù)的加載采用分頁的方式

網(wǎng)絡(luò)數(shù)據(jù)傳輸采用GZIP壓縮

加入網(wǎng)絡(luò)數(shù)據(jù)的緩存邻遏,避免頻繁請求網(wǎng)絡(luò)

上傳圖片時糠亩,在必要的時候壓縮圖片

安裝包優(yōu)化:安裝包優(yōu)化的核心就是減少apk的體積,常見的方案如下

使用混淆准验,可以在一定程度上減少apk體積赎线,但實際效果微乎其微

減少應(yīng)用中不必要的資源文件,比如圖片糊饱,在不影響APP效果的情況下盡量壓縮圖片垂寥,有一定的效果

在使用了SO庫的時候優(yōu)先保留v7版本的SO庫,刪掉其他版本的SO庫另锋。原因是在2018年滞项,v7版本的SO庫可以滿足市面上絕大多數(shù)的要求,可能八九年前的手機(jī)滿足不了夭坪,但我們也沒必要去適配老掉牙的手機(jī)文判。實際開發(fā)中減少apk體積的效果是十分顯著的,如果你使用了很多SO庫室梅,比方說一個版本的SO庫一共10M戏仓,那么只保留v7版本,刪掉armeabi和v8版本的SO庫亡鼠,一共可以減少20M的體積赏殃。

Android內(nèi)存優(yōu)化
Android的內(nèi)存優(yōu)化在我看來分為兩點(diǎn):避免內(nèi)存泄漏、擴(kuò)大內(nèi)存拆宛,其實就是開源節(jié)流嗓奢。

其實內(nèi)存泄漏的本質(zhì)就是較長生命周期的對象引用了較短生命周期的對象讼撒。

常見的內(nèi)存泄漏:

單例模式導(dǎo)致的內(nèi)存泄漏浑厚。最常見的例子就是創(chuàng)建這個單例對象需要傳入一個Context股耽,這時候傳入了一個Activity類型的Context,由于單例對象的靜態(tài)屬性钳幅,導(dǎo)致它的生命周期是從單例類加載到應(yīng)用程序結(jié)束為止物蝙,所以即使已經(jīng)finish掉了傳入的Activity,由于我們的單例對象依然持有Activity的引用敢艰,所以導(dǎo)致了內(nèi)存泄漏诬乞。解決辦法也很簡單,不要使用Activity類型的Context钠导,使用Application類型的Context可以避免內(nèi)存泄漏震嫉。

靜態(tài)變量導(dǎo)致的內(nèi)存泄漏。靜態(tài)變量是放在方法區(qū)中的抖格,它的生命周期是從類加載到程序結(jié)束洽沟,可以看到靜態(tài)變量生命周期是非常久的抛计。最常見的因靜態(tài)變量導(dǎo)致內(nèi)存泄漏的例子是我們在Activity中創(chuàng)建了一個靜態(tài)變量,而這個靜態(tài)變量的創(chuàng)建需要傳入Activity的引用this悴势。在這種情況下即使Activity調(diào)用了finish也會導(dǎo)致內(nèi)存泄漏。原因就是因為這個靜態(tài)變量的生命周期幾乎和整個應(yīng)用程序的生命周期一致措伐,它一直持有Activity的引用特纤,從而導(dǎo)致了內(nèi)存泄漏。

非靜態(tài)內(nèi)部類導(dǎo)致的內(nèi)存泄漏侥加。非靜態(tài)內(nèi)部類導(dǎo)致內(nèi)存泄漏的原因是非靜態(tài)內(nèi)部類持有外部類的引用捧存,最常見的例子就是在Activity中使用Handler和Thread了。使用非靜態(tài)內(nèi)部類創(chuàng)建的Handler和Thread在執(zhí)行延時操作的時候會一直持有當(dāng)前Activity的引用担败,如果在執(zhí)行延時操作的時候就結(jié)束Activity矗蕊,這樣就會導(dǎo)致內(nèi)存泄漏。解決辦法有兩種:第一種是使用靜態(tài)內(nèi)部類氢架,在靜態(tài)內(nèi)部類中使用弱引用調(diào)用Activity傻咖。第二種方法是在Activity的onDestroy中調(diào)用handler.removeCallbacksAndMessages來取消延時事件。

使用資源未及時關(guān)閉導(dǎo)致的內(nèi)存泄漏岖研。常見的例子有:操作各種數(shù)據(jù)流未及時關(guān)閉卿操,操作Bitmap未及時recycle等等。

使用第三方庫未能及時解綁孙援。有的三方庫提供了注冊和解綁的功能害淤,最常見的就是EventBus了,我們都知道使用EventBus要在onCreate中注冊拓售,在onDestroy中解綁窥摄。如果沒有解綁的話,EventBus其實是一個單例模式础淤,他會一直持有Activity的引用崭放,導(dǎo)致內(nèi)存泄漏哨苛。同樣常見的還有RxJava,在使用Timer操作符做了一些延時操作后也要注意在onDestroy方法中調(diào)用disposable.dispose()來取消操作币砂。

屬性動畫導(dǎo)致的內(nèi)存泄漏建峭。常見的例子就是在屬性動畫執(zhí)行的過程中退出了Activity,這時View對象依然持有Activity的引用從而導(dǎo)致了內(nèi)存泄漏决摧。解決辦法就是在onDestroy中調(diào)用動畫的cancel方法取消屬性動畫亿蒸。

WebView導(dǎo)致的內(nèi)存泄漏。WebView比較特殊掌桩,即使是調(diào)用了它的destroy方法边锁,依然會導(dǎo)致內(nèi)存泄漏。其實避免WebView導(dǎo)致內(nèi)存泄漏的最好方法就是讓W(xué)ebView所在的Activity處于另一個進(jìn)程中波岛,當(dāng)這個Activity結(jié)束時殺死當(dāng)前WebView所處的進(jìn)程即可砚蓬,我記得阿里釘釘?shù)腤ebView就是另外開啟的一個進(jìn)程,應(yīng)該也是采用這種方法避免內(nèi)存泄漏盆色。

擴(kuò)大內(nèi)存灰蛙,為什么要擴(kuò)大我們的內(nèi)存呢?有時候我們實際開發(fā)中不可避免的要使用很多第三方商業(yè)的SDK隔躲,這些SDK其實有好有壞摩梧,大廠的SDK可能內(nèi)存泄漏會少一些,但一些小廠的SDK質(zhì)量也就不太靠譜一些宣旱。那應(yīng)對這種我們無法改變的情況仅父,最好的辦法就是擴(kuò)大內(nèi)存。

擴(kuò)大內(nèi)存通常有兩種方法:一個是在清單文件中的Application下添加largeHeap=”true”這個屬性浑吟,另一個就是同一個應(yīng)用開啟多個進(jìn)程來擴(kuò)大一個應(yīng)用的總內(nèi)存空間笙纤。第二種方法其實就很常見了,比方說我使用過個推的SDK组力,個推的Service其實就是處在另外一個單獨(dú)的進(jìn)程中省容。

Android中的內(nèi)存優(yōu)化總的來說就是開源和節(jié)流,開源就是擴(kuò)大內(nèi)存燎字,節(jié)流就是避免內(nèi)存泄漏腥椒。

Binder機(jī)制
在Linux中,為了避免一個進(jìn)程對其他進(jìn)程的干擾候衍,進(jìn)程之間是相互獨(dú)立的笼蛛。在一個進(jìn)程中其實還分為用戶空間和內(nèi)核空間。這里的隔離分為兩個部分蛉鹿,進(jìn)程間的隔離和進(jìn)程內(nèi)的隔離滨砍。

既然進(jìn)程間存在隔離,那其實也是存在著交互。進(jìn)程間通信就是IPC惋戏,用戶空間和內(nèi)核空間的通信就是系統(tǒng)調(diào)用领追。

Linux為了保證獨(dú)立性和安全性,進(jìn)程之間不能直接相互訪問日川,Android是基于Linux的,所以也是需要解決進(jìn)程間通信的問題矩乐。

其實Linux進(jìn)程間通信有很多方式龄句,比如管道、socket等等散罕。為什么Android進(jìn)程間通信采用了Binder而不是Linux已有的方式分歇,主要是有這么兩點(diǎn)考慮:性能和安全

性能。在移動設(shè)備上對性能要求是比較嚴(yán)苛的欧漱。Linux傳統(tǒng)的進(jìn)程間通信比如管道职抡、socket等等進(jìn)程間通信是需要復(fù)制兩次數(shù)據(jù),而Binder則只需要一次误甚。所以Binder在性能上是優(yōu)于傳統(tǒng)進(jìn)程通信的缚甩。

安全。傳統(tǒng)的Linux進(jìn)程通信是不包含通信雙方的身份驗證的窑邦,這樣會導(dǎo)致一些安全性問題擅威。而Binder機(jī)制自帶身份驗證,從而有效的提高了安全性冈钦。

Binder是基于CS架構(gòu)的郊丛,有四個主要組成部分。

Client瞧筛±魇欤客戶端進(jìn)程。

Server较幌。服務(wù)端進(jìn)程揍瑟。

ServiceManager。提供注冊乍炉、查詢和返回代理服務(wù)對象的功能月培。

Binder驅(qū)動。主要負(fù)責(zé)建立進(jìn)程間的Binder連接恩急,進(jìn)程間的數(shù)據(jù)交互等等底層操作杉畜。

Binder機(jī)制主要的流程是這樣的:

服務(wù)端通過Binder驅(qū)動在ServiceManager中注冊我們的服務(wù)。

客戶端通過Binder驅(qū)動查詢在ServiceManager中注冊的服務(wù)衷恭。

ServiceManager通過Binder驅(qū)動返回服務(wù)端的代理對象此叠。

客戶端拿到服務(wù)端的代理對象后即可進(jìn)行進(jìn)程間通信。

LruCache的原理
LruCache的核心原理就是對LinkedHashMap的有效利用随珠,它的內(nèi)部存在一個LinkedHashMap成員變量灭袁。值得我們關(guān)注的有四個方法:構(gòu)造方法猬错、get、put茸歧、trimToSize倦炒。

構(gòu)造方法:在LruCache的構(gòu)造方法中做了兩件事,設(shè)置了maxSize软瞎、創(chuàng)建了一個LinkedHashMap逢唤。這里值得注意的是LruCache將LinkedHashMap的accessOrder設(shè)置為了true,accessOrder就是遍歷這個LinkedHashMap的輸出順序涤浇。true代表按照訪問順序輸出鳖藕,false代表按添加順序輸出,因為通常都是按照添加順序輸出只锭,所以accessOrder這個屬性默認(rèn)是false著恩,但我們的LruCache需要按訪問順序輸出,所以顯式的將accessOrder設(shè)置為true蜻展。

get方法:本質(zhì)上是調(diào)用LinkedHashMap的get方法喉誊,由于我們將accessOrder設(shè)置為了true,所以每調(diào)用一次get方法纵顾,就會將我們訪問的當(dāng)前元素放置到這個LinkedHashMap的尾部裹驰。

put方法:本質(zhì)上也是調(diào)用了LinkedHashMap的put方法,由于LinkedHashMap的特性片挂,每調(diào)用一次put方法幻林,也會將新加入的元素放置到LinkedHashMap的尾部。添加之后會調(diào)用trimToSize方法來保證添加后的內(nèi)存不超過maxSize音念。

trimToSize方法:trimToSize方法的內(nèi)部其實是開啟了一個while(true)的死循環(huán)沪饺,不斷的從LinkedHashMap的首部刪除元素,直到刪除之后的內(nèi)存小于maxSize之后使用break跳出循環(huán)闷愤。

其實到這里我們可以總結(jié)一下整葡,為什么這個算法叫 最近最少使用 算法呢?原理很簡單讥脐,我們的每次put或者get都可以看做一次訪問遭居,由于LinkedHashMap的特性,會將每次訪問到的元素放置到尾部旬渠。當(dāng)我們的內(nèi)存達(dá)到閾值后俱萍,會觸發(fā)trimToSize方法來刪除LinkedHashMap首部的元素,直到當(dāng)前內(nèi)存小于maxSize告丢。為什么刪除首部的元素枪蘑,原因很明顯:我們最近經(jīng)常訪問的元素都會放置到尾部,那首部的元素肯定就是 最近最少使用 的元素了,因此當(dāng)內(nèi)存不足時應(yīng)當(dāng)優(yōu)先刪除這些元素岳颇。

DiskLruCache原理
設(shè)計一個圖片的異步加載框架
設(shè)計一個圖片加載框架照捡,肯定要用到圖片加載的三級緩存的思想。三級緩存分為內(nèi)存緩存话侧、本地緩存和網(wǎng)絡(luò)緩存栗精。

內(nèi)存緩存:將Bitmap緩存到內(nèi)存中,運(yùn)行速度快瞻鹏,但是內(nèi)存容量小悲立。
本地緩存:將圖片緩存到文件中,速度較慢乙漓,但容量較大级历。
網(wǎng)絡(luò)緩存:從網(wǎng)絡(luò)獲取圖片释移,速度受網(wǎng)絡(luò)影響叭披。

如果我們設(shè)計一個圖片加載框架,流程一定是這樣的:

拿到圖片url后首先從內(nèi)存中查找BItmap玩讳,如果找到直接加載涩蜘。

內(nèi)存中沒有找到,會從本地緩存中查找熏纯,如果本地緩存可以找到同诫,則直接加載。

內(nèi)存和本地都沒有找到樟澜,這時會從網(wǎng)絡(luò)下載圖片误窖,下載到后會加載圖片,并且將下載到的圖片放到內(nèi)存緩存和本地緩存中秩贰。

上面是一些基本的概念霹俺,如果是具體的代碼實現(xiàn)的話,大概需要這么幾個方面的文件:

首先需要確定我們的內(nèi)存緩存毒费,這里一般用的都是LruCache丙唧。

確定本地緩存,通常用的是DiskLruCache觅玻,這里需要注意的是圖片緩存的文件名一般是url被MD5加密后的字符串想际,為了避免文件名直接暴露圖片的url。

內(nèi)存緩存和本地緩存確定之后溪厘,需要我們創(chuàng)建一個新的類MemeryAndDiskCache胡本,當(dāng)然,名字隨便起畸悬,這個類包含了之前提到的LruCache和DiskLruCache打瘪。在MemeryAndDiskCache這個類中我們定義兩個方法,一個是getBitmap,另一個是putBitmap闺骚,對應(yīng)著圖片的獲取和緩存彩扔,內(nèi)部的邏輯也很簡單。getBitmap中按內(nèi)存僻爽、本地的優(yōu)先級去取BItmap虫碉,putBitmap中先緩存內(nèi)存,之后緩存到本地胸梆。

在緩存策略類確定好之后敦捧,我們創(chuàng)建一個ImageLoader類,這個類必須包含兩個方法碰镜,一個是展示圖片displayImage(url,imageView)兢卵,另一個是從網(wǎng)絡(luò)獲取圖片downloadImage(url,imageView)。在展示圖片方法中首先要通過ImageView.setTag(url)绪颖,將url和imageView進(jìn)行綁定秽荤,這是為了避免在列表中加載網(wǎng)絡(luò)圖片時會由于ImageView的復(fù)用導(dǎo)致的圖片錯位的bug。之后會從MemeryAndDiskCache中獲取緩存柠横,如果存在窃款,直接加載;如果不存在牍氛,則調(diào)用從網(wǎng)絡(luò)獲取圖片這個方法晨继。從網(wǎng)絡(luò)獲取圖片方法很多,這里我一般都會使用OkHttp+Retrofit搬俊。當(dāng)從網(wǎng)絡(luò)中獲取到圖片之后紊扬,首先判斷一下imageView.getTag()與圖片的url是否一致,如果一致則加載圖片唉擂,如果不一致則不加載圖片餐屎,通過這樣的方式避免了列表中異步加載圖片的錯位。同時在獲取到圖片之后會通過MemeryAndDiskCache來緩存圖片楔敌。

Android中的事件分發(fā)機(jī)制
在我們的手指觸摸到屏幕的時候啤挎,事件其實是通過 Activity -> ViewGroup -> View 這樣的流程到達(dá)最后響應(yīng)我們觸摸事件的View。

說到事件分發(fā)卵凑,必不可少的是這幾個方法:dispatchTouchEvent()庆聘、onInterceptTouchEvent()、onTouchEvent勺卢。接下來就按照 Activity -> ViewGroup -> View 的流程來大致說一下事件分發(fā)機(jī)制伙判。

我們的手指觸摸到屏幕的時候,會觸發(fā)一個Action_Down類型的事件黑忱,當(dāng)前頁面的Activity會首先做出響應(yīng)宴抚,也就是說會走到Activity的dispatchTouchEvent()方法內(nèi)勒魔。在這個方法內(nèi)部簡單來說是這么一個邏輯:

調(diào)用getWindow.superDispatchTouchEvent()。

如果上一步返回true菇曲,直接返回true冠绢;否則就return自己的onTouchEvent()。
這個邏輯很好理解常潮,getWindow().superDispatchTouchEvent()如果返回true代表當(dāng)前事件已經(jīng)被處理弟胀,無需調(diào)用自己的onTouchEvent;否則代表事件并沒有被處理喊式,需要Activity自己處理孵户,也就是調(diào)用自己的onTouchEvent。

getWindow()方法返回了一個Window類型的對象岔留,這個我們都知道夏哭,在Android中,PhoneWindow是Window的唯一實現(xiàn)類献联。所以這句本質(zhì)上是調(diào)用了PhoneWindow中的superDispatchTouchEvent()竖配。

而在PhoneWindow的這個方法中實際調(diào)用了mDecor.superDispatchTouchEvent(event)。這個mDecor就是DecorView酱固,它是FrameLayout的一個子類械念,在DecorView中的superDispatchTouchEvent()中調(diào)用的是super.dispatchTouchEvent()头朱。到這里就很明顯了运悲,DecorView是一個FrameLayout的子類,F(xiàn)rameLayout是一個ViewGroup的子類项钮,本質(zhì)上調(diào)用的還是ViewGroup的dispatchTouchEvent()班眯。

分析到這里,我們的事件已經(jīng)從Activity傳遞到了ViewGroup烁巫,接下來我們來分析下ViewGroup中的這幾個事件處理方法署隘。

在ViewGroup中的dispatchTouchEvent()中的邏輯大致如下:

通過onInterceptTouchEvent()判斷當(dāng)前ViewGroup是否攔截事件,默認(rèn)的ViewGroup都是不攔截的亚隙;

如果攔截磁餐,則return自己的onTouchEvent();

如果不攔截阿弃,則根據(jù) child.dispatchTouchEvent()的返回值判斷诊霹。如果返回true,則return true渣淳;否則return自己的onTouchEvent()脾还,在這里實現(xiàn)了未處理事件的向上傳遞。

通常情況下ViewGroup的onInterceptTouchEvent()都返回false入愧,也就是不攔截鄙漏。這里需要注意的是事件序列嗤谚,比如Down事件、Move事件……Up事件怔蚌,從Down到Up是一個完整的事件序列巩步,對應(yīng)著手指從按下到抬起這一系列的事件,如果ViewGroup攔截了Down事件桦踊,那么后續(xù)事件都會交給這個ViewGroup的onTouchEvent渗钉。如果ViewGroup攔截的不是Down事件,那么會給之前處理這個Down事件的View發(fā)送一個Action_Cancel類型的事件钞钙,通知子View這個后續(xù)的事件序列已經(jīng)被ViewGroup接管了鳄橘,子View恢復(fù)之前的狀態(tài)即可。

這里舉一個常見的例子:在一個Recyclerview鐘有很多的Button芒炼,我們首先按下了一個button瘫怜,然后滑動一段距離再松開,這時候Recyclerview會跟著滑動本刽,并不會觸發(fā)這個button的點(diǎn)擊事件鲸湃。這個例子中,當(dāng)我們按下button時子寓,這個button接收到了Action_Down事件暗挑,正常情況下后續(xù)的事件序列應(yīng)該由這個button處理。但我們滑動了一段距離斜友,這時Recyclerview察覺到這是一個滑動操作炸裆,攔截了這個事件序列,走了自身的onTouchEvent()方法鲜屏,反映在屏幕上就是列表的滑動烹看。而這時button仍然處于按下的狀態(tài),所以在攔截的時候需要發(fā)送一個Action_Cancel來通知button恢復(fù)之前狀態(tài)洛史。

事件分發(fā)最終會走到View的dispatchTouchEvent()中惯殊。在View的dispatchTouchEvent()中沒有onInterceptTouchEvent(),這也很容易理解也殖,View不是ViewGroup土思,不會包含其他子View,所以也不存在攔截不攔截這一說忆嗜。忽略一些細(xì)節(jié)己儒,View的dispatchTouchEvent()中直接return了自己的onTouchEvent()。如果onTouchEvent()返回true代表事件被處理霎褐,否則未處理的事件會向上傳遞址愿,直到有View處理了事件或者一直沒有處理,最終到達(dá)了Activity的onTouchEvent()終止冻璃。

這里經(jīng)常有人問onTouch和onTouchEvent的區(qū)別响谓。首先损合,這兩個方法都在View的dispatchTouchEvent()中,是這么一個邏輯:

如果touchListener不為null娘纷,并且這個View是enable的嫁审,而且onTouch返回的是true,滿足這三個條件時會直接return true赖晶,不會走onTouchEvent()方法律适。

上面只要有一個條件不滿足,就會走到onTouchEvent()方法中遏插。所以onTouch的順序是在onTouchEvent之前的捂贿。

View的繪制流程
視圖繪制的起點(diǎn)在ViewRootImpl類的performTraversals()方法,在這個方法內(nèi)其實是按照順序依次調(diào)用了mView.measure()胳嘲、mView.layout()厂僧、mView.draw()

View的繪制流程分為3步:測量、布局了牛、繪制颜屠,分別對應(yīng)3個方法measure、layout鹰祸、draw甫窟。

測量階段。measure方法會被父View調(diào)用蛙婴,在measure方法中做一些優(yōu)化和準(zhǔn)備工作后會調(diào)用onMeasure方法進(jìn)行實際的自我測量粗井。onMeasure方法在View和ViewGroup做的事情是不一樣的:

View。View中的onMeasure方法會計算自己的尺寸并通過setMeasureDimension保存敬锐。

ViewGroup背传。ViewGroup中的onMeasure方法會調(diào)用所有子View的measure方法進(jìn)行自我測量并保存呆瞻。然后通過子View的尺寸和位置計算出自己的尺寸并保存台夺。

布局階段。layout方法會被父View調(diào)用痴脾,layout方法會保存父View傳進(jìn)來的尺寸和位置颤介,并調(diào)用onLayout進(jìn)行實際的內(nèi)部布局。onLayout在View和ViewGroup中做的事情也是不一樣的:

View赞赖。因為View是沒有子View的滚朵,所以View的onLayout里面什么都不做。

ViewGroup前域。ViewGroup中的onLayout方法會調(diào)用所有子View的layout方法辕近,把尺寸和位置傳給他們,讓他們完成自我的內(nèi)部布局匿垄。

繪制階段移宅。draw方法會做一些調(diào)度工作归粉,然后會調(diào)用onDraw方法進(jìn)行View的自我繪制。draw方法的調(diào)度流程大致是這樣的:

繪制背景漏峰。對應(yīng)drawBackground(Canvas)方法糠悼。

繪制主體。對應(yīng)onDraw(Canvas)方法浅乔。

繪制子View倔喂。對應(yīng)dispatchDraw(Canvas)方法。

繪制滑動相關(guān)和前景靖苇。對應(yīng)onDrawForeground(Canvas)席噩。

Android源碼中常見的設(shè)計模式以及自己在開發(fā)中常用的設(shè)計模式
Android與js是如何交互的
在Android中,Android與js的交互分為兩個方面:Android調(diào)用js里的方法贤壁、js調(diào)用Android中的方法班挖。

Android調(diào)js。Android調(diào)js有兩種方法:

WebView.loadUrl(“javascript:js中的方法名”)芯砸。這種方法的優(yōu)點(diǎn)是很簡潔萧芙,缺點(diǎn)是沒有返回值,如果需要拿到j(luò)s方法的返回值則需要js調(diào)用Android中的方法來拿到這個返回值假丧。

WebView.evaluateJavaScript(“javascript:js中的方法名”,ValueCallback)双揪。這種方法比loadUrl好的是可以通過ValueCallback這個回調(diào)拿到j(luò)s方法的返回值。缺點(diǎn)是這個方法Android4.4才有包帚,兼容性較差渔期。不過放在2018年來說,市面上絕大多數(shù)App都要求最低版本是4.4了渴邦,所以我認(rèn)為這個兼容性問題不大疯趟。

js調(diào)Android。js調(diào)Android有三種方法:

WebView.addJavascriptInterface()谋梭。這是官方解決js調(diào)用Android方法的方案信峻,需要注意的是要在供js調(diào)用的Android方法上加上 @JavascriptInterface 注解,以避免安全漏洞瓮床。這種方案的缺點(diǎn)是Android4.2以前會有安全漏洞盹舞,不過在4.2以后已經(jīng)修復(fù)了。同樣隘庄,在2018年來說踢步,兼容性問題不大。

重寫WebViewClient的shouldOverrideUrlLoading()方法來攔截url丑掺,拿到url后進(jìn)行解析获印,如果符合雙方的規(guī)定,即可調(diào)用Android方法街州。優(yōu)點(diǎn)是避免了Android4.2以前的安全漏洞兼丰,缺點(diǎn)也很明顯绰咽,無法直接拿到調(diào)用Android方法的返回值,只能通過Android調(diào)用js方法來獲取返回值地粪。

重寫WebChromClient的onJsPrompt()方法取募,同前一個方式一樣,拿到url之后先進(jìn)行解析蟆技,如果符合雙方規(guī)定玩敏,即可調(diào)用Android方法。最后如果需要返回值质礼,通過result.confirm(“Android方法返回值”)即可將Android的返回值返回給js旺聚。方法的優(yōu)點(diǎn)是沒有漏洞,也沒有兼容性限制眶蕉,同時還可以方便的獲取Android方法的返回值砰粹。其實這里需要注意的是在WebChromeClient中除了onJsPrompt之外還有onJsAlert和onJsConfirm方法。那么為什么不選擇另兩個方法呢造挽?原因在于onJsAlert是沒有返回值的碱璃,而onJsConfirm只有true和false兩個返回值,同時在前端開發(fā)中prompt方法基本不會被調(diào)用饭入,所以才會采用onJsPrompt嵌器。

熱修復(fù)原理
Activity啟動過程
SparseArray原理
SparseArray,通常來講是Android中用來替代HashMap的一個數(shù)據(jù)結(jié)構(gòu)谐丢。
準(zhǔn)確來講爽航,是用來替換key為Integer類型,value為Object類型的HashMap乾忱。需要注意的是SparseArray僅僅實現(xiàn)了Cloneable接口讥珍,所以不能用Map來聲明。
從內(nèi)部結(jié)構(gòu)來講窄瘟,SparseArray內(nèi)部由兩個數(shù)組組成衷佃,一個是int[]類型的mKeys,用來存放所有的鍵寞肖;另一個是Object[]類型的mValues纲酗,用來存放所有的值。
最常見的是拿SparseArray跟HashMap來做對比新蟆,由于SparseArray內(nèi)部組成是兩個數(shù)組,所以占用內(nèi)存比HashMap要小右蕊。我們都知道琼稻,增刪改查等操作都首先需要找到相應(yīng)的鍵值對,而SparseArray內(nèi)部是通過二分查找來尋址的饶囚,效率很明顯要低于HashMap的常數(shù)級別的時間復(fù)雜度帕翻。提到二分查找鸠补,這里還需要提一下的是二分查找的前提是數(shù)組已經(jīng)是排好序的,沒錯嘀掸,SparseArray中就是按照key進(jìn)行升序排列的紫岩。
綜合起來來說,SparseArray所占空間優(yōu)于HashMap睬塌,而效率低于HashMap泉蝌,是典型的時間換空間,適合較小容量的存儲揩晴。
從源碼角度來說勋陪,我認(rèn)為需要注意的是SparseArray的remove()、put()和gc()方法硫兰。

remove()诅愚。SparseArray的remove()方法并不是直接刪除之后再壓縮數(shù)組,而是將要刪除的value設(shè)置為DELETE這個SparseArray的靜態(tài)屬性劫映,這個DELETE其實就是一個Object對象违孝,同時會將SparseArray中的mGarbage這個屬性設(shè)置為true,這個屬性是便于在合適的時候調(diào)用自身的gc()方法壓縮數(shù)組來避免浪費(fèi)空間泳赋。這樣可以提高效率等浊,如果將來要添加的key等于刪除的key,那么會將要添加的value覆蓋DELETE摹蘑。

gc()筹燕。SparseArray中的gc()方法跟JVM的GC其實完全沒有任何關(guān)系。gc()方法的內(nèi)部實際上就是一個for循環(huán)衅鹿,將value不為DELETE的鍵值對往前移動覆蓋value為DELETE的鍵值對來實現(xiàn)數(shù)組的壓縮撒踪,同時將mGarbage置為false,避免內(nèi)存的浪費(fèi)大渤。

put()制妄。put方法是這么一個邏輯,如果通過二分查找在mKeys數(shù)組中找到了key泵三,那么直接覆蓋value即可耕捞。如果沒有找到,會拿到與數(shù)組中與要添加的key最接近的key索引烫幕,如果這個索引對應(yīng)的value為DELETE俺抽,則直接把新的value覆蓋DELETE即可,在這里可以避免數(shù)組元素的移動较曼,從而提高了效率磷斧。如果value不為DELETE,會判斷mGarbage,如果為true弛饭,則會調(diào)用gc()方法壓縮數(shù)組冕末,之后會找到合適的索引,將索引之后的鍵值對后移侣颂,插入新的鍵值對档桃,這個過程中可能會觸發(fā)數(shù)組的擴(kuò)容。

圖片加載如何避免OOM
我們知道內(nèi)存中的Bitmap大小的計算公式是:長所占像素 *寬所占像素 *每個像素所占內(nèi)存。想避免OOM有兩種方法:等比例縮小長寬、減少每個像素所占的內(nèi)存半哟。

等比縮小長寬。我們知道Bitmap的創(chuàng)建是通過BitmapFactory的工廠方法仅炊,decodeFile()、decodeStream()澎蛛、decodeByteArray()抚垄、decodeResource()。這些方法中都有一個Options類型的參數(shù)谋逻,這個Options是BitmapFactory的內(nèi)部類呆馁,存儲著BItmap的一些信息。Options中有一個屬性:inSampleSize毁兆。我們通過修改inSampleSize可以縮小圖片的長寬浙滤,從而減少BItmap所占內(nèi)存。需要注意的是這個inSampleSize大小需要是2的冪次方气堕,如果小于1纺腊,代碼會強(qiáng)制讓inSampleSize為1。

減少像素所占內(nèi)存茎芭。Options中有一個屬性inPreferredConfig揖膜,默認(rèn)是ARGB_8888,代表每個像素所占尺寸梅桩。我們可以通過將之修改為RGB_565或者ARGB_4444來減少一半內(nèi)存壹粟。

大圖加載
加載高清大圖,比如清明上河圖宿百,首先屏幕是顯示不下的趁仙,而且考慮到內(nèi)存情況,也不可能一次性全部加載到內(nèi)存垦页。這時候就需要局部加載了雀费,Android中有一個負(fù)責(zé)局部加載的類:BitmapRegionDecoder。使用方法很簡單外臂,通過BitmapRegionDecoder.newInstance()創(chuàng)建對象坐儿,之后調(diào)用decodeRegion(Rect rect, BitmapFactory.Options options)即可律胀。第一個參數(shù)rect是要顯示的區(qū)域宋光,第二個參數(shù)是BitmapFactory中的內(nèi)部類Options貌矿。

Android三方庫的源碼分析
由于源碼分析篇幅太大,所以這里之貼出我的源碼分析的鏈接(掘金)罪佳。

OkHttp
OkHttp源碼分析

Retrofit
Retrofit源碼分析1
Retrofit源碼分析2
Retrofit源碼分析3

RxJava
RxJava源碼分析

Glide
Glide源碼分析

EventBus
EventBus源碼分析

大致是這么一個流程:
register:

獲取訂閱者的Class對象

使用反射查找訂閱者中的事件處理方法集合

遍歷事件處理方法集合逛漫,調(diào)用subscribe(subscriber,subscriberMethod)方法赘艳,在subscribe方法內(nèi):

如果事件繼承性為true酌毡,遍歷這個Map類型的stickEvents,通過isAssignableFrom方法判斷當(dāng)前事件是否是遍歷事件的父類蕾管,如果是則發(fā)送事件

如果事件繼承性為false枷踏,通過stickyEvents.get(eventType)獲取事件并發(fā)送

如果事件類型集合為空則創(chuàng)建一個新的集合,這一步目的是延遲集合的初始化

拿到事件類型集合后將新的事件類型加入到集合中

如果Subscription集合為空則創(chuàng)建一個新的集合掰曾,這一步目的是延遲集合的初始化

拿到Subscription集合后遍歷這個集合旭蠕,通過比較事件處理的優(yōu)先級,將新的Subscription對象加入合適的位置

通過subscriberMethod獲取處理的事件類型eventType

將訂閱者subscriber和方法subscriberMethod綁在一起形成一個Subscription對象

通過subscriptionsByEventType.get(eventType)獲取Subscription集合

通過typesBySubscriber.get(subscriber)獲取事件類型集合

判斷當(dāng)前事件類型是否是sticky

如果當(dāng)前事件類型不是sticky(粘性事件)旷坦,subscribe(subscriber掏熬,subscriberMethod)到此終結(jié)

如果是sticky,判斷EventBus中的一個事件繼承性的屬性秒梅,默認(rèn)是true

post:

postSticky

將事件加入到stickyEvents這個Map類型的集合中

調(diào)用post方法

post

事件繼承性為true旗芬,找到當(dāng)前事件所有的父類型并調(diào)用postSingleEventForEventType方法發(fā)送事件

事件繼承性為false,只發(fā)送當(dāng)前事件類型的事件

在postToSubscription中分為四種情況

POSTING捆蜀,調(diào)用invokeSubscriber(subscription, event)處理事件疮丛,本質(zhì)是method.invoke()反射

MAIN,如果在主線程直接invokeSubscriber處理辆它;反之通過handler切換到主線程調(diào)用invokeSubscriber處理事件

BACKGROUND誊薄,如果不在主線程直接invokeSubscriber處理事件;反之開啟一條線程娩井,在線程中調(diào)用invokeSubscriber處理事件

ASYNC暇屋,開啟一條線程,在線程中調(diào)用invokeSubscriber處理事件

在postSingleEventForEventType中洞辣,通過subscriptionsByEventType.get(eventClass)獲取Subscription類型集合

遍歷這個集合咐刨,調(diào)用postToSubscription發(fā)送事件

將事件加入當(dāng)前線程的事件隊列中

通過while循環(huán)不斷從事件隊列中取出事件并調(diào)用postSingleEvent方法發(fā)送事件

在postSingleEvent中,判斷事件繼承性扬霜,默認(rèn)為true

unregister:

刪除subscriptionsByEventType中與訂閱者相關(guān)的所有subscription

刪除typesBySubscriber中與訂閱者相關(guān)的所有類型

數(shù)據(jù)結(jié)構(gòu)與算法
手寫快排
手寫歸并排序
手寫堆以及堆排序
說一下排序算法的區(qū)別(時間復(fù)雜度和空間復(fù)雜度)

<meta charset="utf-8">

1.Activity的啟動過程(不要回答生命周期)

http://blog.csdn.net/luoshengyang/article/details/6689748

1.2.Activity的啟動模式以及使用場景

(1)manifest設(shè)置定鸟,(2)startActivity flag
http://blog.csdn.net/CodeEmperor/article/details/50481726
此處延伸:棧(First In Last Out)與隊列(First In First Out)的區(qū)別

3.Service的兩種啟動方式

(1)startService(),(2)bindService()
http://www.reibang.com/p/2fb6eb14fdec

4.Broadcast注冊方式與區(qū)別

(1)靜態(tài)注冊(minifest)著瓶,(2)動態(tài)注冊
http://www.reibang.com/p/ea5e233d9f43
此處延伸:什么情況下用動態(tài)注冊

5.HttpClient與HttpUrlConnection的區(qū)別

http://blog.csdn.net/guolin_blog/article/details/12452307
此處延伸:Volley里用的哪種請求方式(2.3前HttpClient联予,2.3后HttpUrlConnection)

6.http與https的區(qū)別

http://blog.csdn.net/whatday/article/details/38147103
此處延伸:https的實現(xiàn)原理

7.手寫算法(選擇冒泡必須要會)

http://www.reibang.com/p/ae97c3ceea8d

8.進(jìn)程保活(不死進(jìn)程)

http://www.reibang.com/p/63aafe3c12af
此處延伸:進(jìn)程的優(yōu)先級是什么(下面這篇文章,都有說)
https://segmentfault.com/a/1190000006251859

9.進(jìn)程間通信的方式

(1)AIDL沸久,(2)廣播季眷,(3)Messenger
AIDL : http://www.reibang.com/p/a8e43ad5d7d2
http://www.reibang.com/p/0cca211df63c
Messenger :
http://blog.csdn.net/lmj623565791/article/details/47017485
此處延伸:簡述Binder ,
http://blog.csdn.net/luoshengyang/article/details/6618363/

10.加載大圖

PS:有家小公司(規(guī)模寫假的卷胯,給騙過去了)子刮,直接把項目給我看,讓我說實現(xiàn)原理窑睁。挺峡。
最讓我無語的一次面試,就一個點(diǎn)問的我底褲都快穿了担钮,就差幫他們寫代碼了橱赠。。
http://blog.csdn.net/lmj623565791/article/details/49300989

11.三級緩存(各大圖片框架都可以扯到這上面來)

(1)內(nèi)存緩存箫津,(2)本地緩存狭姨,(3)網(wǎng)絡(luò)
內(nèi)存:http://blog.csdn.net/guolin_blog/article/details/9526203
本地:http://blog.csdn.net/guolin_blog/article/details/28863651

12.MVP框架(必問)

http://blog.csdn.net/lmj623565791/article/details/46596109
此處延伸:手寫mvp例子,與mvc之間的區(qū)別鲤嫡,mvp的優(yōu)勢

13.講解一下Context

http://blog.csdn.net/lmj623565791/article/details/40481055

14.JNI

http://www.reibang.com/p/aba734d5b5cd
此處延伸:項目中使用JNI的地方送挑,如:核心邏輯,密鑰暖眼,加密邏輯

15.java虛擬機(jī)和Dalvik虛擬機(jī)的區(qū)別

http://www.reibang.com/p/923aebd31b65

16.線程sleep和wait有什么區(qū)別

http://blog.csdn.net/liuzhenwen/article/details/4202967

17.View惕耕,ViewGroup事件分發(fā)

http://blog.csdn.net/guolin_blog/article/details/9097463
http://blog.csdn.net/guolin_blog/article/details/9153747

18.保存Activity狀態(tài)

onSaveInstanceState()
http://blog.csdn.net/yuzhiboyi/article/details/7677026

19.WebView與js交互(調(diào)用哪些API)

http://blog.csdn.net/cappuccinolau/article/details/8262821/

20.內(nèi)存泄露檢測,內(nèi)存性能優(yōu)化

http://blog.csdn.net/guolin_blog/article/details/42238627

21.布局優(yōu)化

http://blog.csdn.net/guolin_blog/article/details/43376527

22.自定義view和動畫

以下兩個講解都講得很透徹诫肠,這部分面試官多數(shù)不會問很深司澎,要么就給你一個效果讓你講原理。
(1)http://www.gcssloop.com/customview/CustomViewIndex
(2)http://blog.csdn.net/yanbober/article/details/50577855

轉(zhuǎn)自:http://www.reibang.com/p/5d785c0d4277
http://www.reibang.com/p/564b3920697a

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末栋豫,一起剝皮案震驚了整個濱河市挤安,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌丧鸯,老刑警劉巖蛤铜,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異丛肢,居然都是意外死亡围肥,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進(jìn)店門蜂怎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來穆刻,“玉大人,你說我怎么就攤上這事杠步∏馕埃” “怎么了榜轿?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長朵锣。 經(jīng)常有香客問我谬盐,道長,這世上最難降的妖魔是什么猪勇? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任设褐,我火速辦了婚禮颠蕴,結(jié)果婚禮上泣刹,老公的妹妹穿的比我還像新娘。我一直安慰自己犀被,他們只是感情好椅您,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著寡键,像睡著了一般掀泳。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上西轩,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天员舵,我揣著相機(jī)與錄音,去河邊找鬼藕畔。 笑死马僻,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的注服。 我是一名探鬼主播韭邓,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼溶弟!你這毒婦竟也來了女淑?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤辜御,失蹤者是張志新(化名)和其女友劉穎鸭你,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體擒权,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡袱巨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了菜拓。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瓣窄。...
    茶點(diǎn)故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖纳鼎,靈堂內(nèi)的尸體忽然破棺而出俺夕,到底是詐尸還是另有隱情裳凸,我是刑警寧澤,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布劝贸,位于F島的核電站姨谷,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏映九。R本人自食惡果不足惜梦湘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望件甥。 院中可真熱鬧捌议,春花似錦、人聲如沸引有。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽譬正。三九已至宫补,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間曾我,已是汗流浹背粉怕。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留抒巢,地道東北人贫贝。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像虐秦,于是被迫代替她去往敵國和親平酿。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評論 2 348