----------------------JAVA---------------------
類加載過程
Java 中類加載分為 3 個步驟:加載捣鲸、鏈接瑟匆、初始化。
加載栽惶。 加載是將字節(jié)碼數(shù)據(jù)從不同的數(shù)據(jù)源讀取到JVM內(nèi)存愁溜,并映射為 JVM 認可的數(shù)據(jù)結(jié)構(gòu),也就是 Class 對象的過程媒役。數(shù)據(jù)源可以是 Jar 文件祝谚、Class 文件等等。如果數(shù)據(jù)的格式并不是 ClassFile 的結(jié)構(gòu)酣衷,則會報 ClassFormatError交惯。
鏈接。 鏈接是類加載的核心部分穿仪,這一步分為 3 個步驟:驗證席爽、準備、解析啊片。
驗證只锻。 驗證是保證JVM安全的重要步驟。JVM需要校驗字節(jié)信息是否符合規(guī)范紫谷,避免惡意信息和不規(guī)范數(shù)據(jù)危害JVM運行安全齐饮。如果驗證出錯,則會報VerifyError笤昨。
準備祖驱。 這一步會創(chuàng)建靜態(tài)變量,并為靜態(tài)變量開辟內(nèi)存空間瞒窒。
解析捺僻。 這一步會將符號引用替換為直接引用。初始化。 初始化會為靜態(tài)變量賦值匕坯,并執(zhí)行靜態(tài)代碼塊中的邏輯束昵。
雙親委派模型
類加載器大致分為3類:啟動類加載器、擴展類加載器葛峻、應(yīng)用程序類加載器锹雏。
- 啟動類加載器主要加載 jre/lib下的jar文件。
- 擴展類加載器主要加載 jre/lib/ext 下的jar文件术奖。
- 應(yīng)用程序類加載器主要加載 classpath 下的文件逼侦。
所謂的雙親委派模型就是當加載一個類時,會優(yōu)先使用父類加載器加載腰耙,當父類加載器無法加載時才會使用子類加載器去加載。這么做的目的是為了避免類的重復(fù)加載铲球。
Java 中的集合類
HashMap 的原理
HashMap 的內(nèi)部可以看做數(shù)組+鏈表的復(fù)合結(jié)構(gòu)挺庞。數(shù)組被分為一個個的桶(bucket)。哈希值決定了鍵值對在數(shù)組中的尋址稼病。具有相同哈希值的鍵值對會組成鏈表选侨。需要注意的是當鏈表長度超過閾值(默認是8)的時候會觸發(fā)樹化,鏈表會變成樹形結(jié)構(gòu)然走。
把握HashMap的原理需要關(guān)注4個方法:hash援制、put、get芍瑞、resize晨仑。
hash方法。 將 key 的 hashCode 值的高位數(shù)據(jù)移位到低位進行異或運算拆檬。這么做的原因是有些 key 的 hashCode 值的差異集中在高位洪己,而哈希尋址是忽略容量以上高位的,這種做法可以有效避免哈希沖突竟贯。
-
put 方法答捕。 put 方法主要有以下幾個步驟:
- 通過 hash 方法獲取 hash 值,根據(jù) hash 值尋址屑那。
- 如果未發(fā)生碰撞拱镐,直接放到桶中。
- 如果發(fā)生碰撞持际,則以鏈表形式放在桶后沃琅。
- 當鏈表長度大于閾值后會觸發(fā)樹化,將鏈表轉(zhuǎn)換為紅黑樹选酗。
- 如果數(shù)組長度達到閾值阵难,會調(diào)用 resize 方法擴展容量。
-
get方法芒填。 get 方法主要有以下幾個步驟:
- 通過 hash 方法獲取 hash 值呜叫,根據(jù) hash 值尋址空繁。
- 如果與尋址到桶的 key 相等,直接返回對應(yīng)的 value朱庆。
- 如果發(fā)生沖突盛泡,分兩種情況。如果是樹娱颊,則調(diào)用 getTreeNode 獲取 value傲诵;如果是鏈表則通過循環(huán)遍歷查找對應(yīng)的 value。
-
resize 方法箱硕。 resize 做了兩件事:
- 將原數(shù)組擴展為原來的 2 倍
- 重新計算 index 索引值拴竹,將原節(jié)點重新放到新的數(shù)組中。這一步可以將原先沖突的節(jié)點分散到新的桶中剧罩。
sleep 和 wait 的區(qū)別
- sleep 方法是 Thread 類中的靜態(tài)方法栓拜,wait 是 Object 類中的方法
- sleep 并不會釋放同步鎖,而 wait 會釋放同步鎖
- sleep 可以在任何地方使用惠昔,而 wait 只能在同步方法或者同步代碼塊中使用
- sleep 中必須傳入時間幕与,而 wait 可以傳,也可以不傳镇防,不傳時間的話只有 notify 或者 notifyAll - 才能喚醒啦鸣,傳時間的話在時間之后會自動喚醒
volatile和synchronize的區(qū)別
final、finally来氧、finalize區(qū)別
- final 可以修飾類诫给、變量和方法。修飾類代表這個類不可被繼承啦扬。修飾變量代表此變量不可被改變蝙搔。修飾方法表示此方法不可被重寫 (override)。
- finally 是保證重點代碼一定會執(zhí)行的一種機制考传。通常是使用 try-finally 或者 try-catch-finally 來進行文件流的關(guān)閉等操作吃型。
- finalize 是 Object 類中的一個方法,它的設(shè)計目的是保證對象在垃圾收集前完成特定資源的回收僚楞。finalize 機制現(xiàn)在已經(jīng)不推薦使用勤晚,并且在 JDK 9已經(jīng)被標記為 deprecated。
Java中引用類型的區(qū)別泉褐,具體的使用場景
Java中引用類型分為四類:強引用赐写、軟引用厅篓、弱引用葫录、虛引用。
- 強引用: 強引用指的是通過 new 對象創(chuàng)建的引用灯萍,垃圾回收器即使是內(nèi)存不足也不會回收強引用指向的對象。
- 軟引用: 軟引用是通過 SoftRefrence 實現(xiàn)的端铛,它的生命周期比強引用短泣矛,在內(nèi)存不足,拋出 OOM 之前禾蚕,垃圾回收器會回收軟引用引用的對象您朽。軟引用常見的使用場景是存儲一些內(nèi)存敏感的緩存,當內(nèi)存不足時會被回收换淆。
- 弱引用: 弱引用是通過 WeakRefrence 實現(xiàn)的哗总,它的生命周期比軟引用還短,GC 只要掃描到弱引用的對象就會回收倍试。弱引用常見的使用場景也是存儲一些內(nèi)存敏感的緩存讯屈。
- 虛引用: 虛引用是通過 FanttomRefrence 實現(xiàn)的,它的生命周期最短县习,隨時可能被回收耻煤。如果一個對象只被虛引用引用,我們無法通過虛引用來訪問這個對象的任何屬性和方法准颓。它的作用僅僅是保證對象在 finalize 后,做某些事情棺妓。虛引用常見的使用場景是跟蹤對象被垃圾回收的活動攘已,當一個虛引用關(guān)聯(lián)的對象被垃圾回收器回收之前會收到一條系統(tǒng)通知。
Exception 和 Error的區(qū)別
- Exception 和 Error 都繼承于 Throwable怜跑,在 Java 中样勃,只有 Throwable 類型的對象才能被 throw 或者 catch,它是異常處理機制的基本組成類型.
- Exception 和 Error 體現(xiàn)了 Java 對不同異常情況的分類性芬。Exception 是程序正常運行中峡眶,可以預(yù)料的意外情況,可能并且應(yīng)該被捕獲植锉,進行相應(yīng)的處理辫樱。
- Error 是指在正常情況下,不大可能出現(xiàn)的情況俊庇,絕大部分 Error 都會使程序處于非正常狮暑、不可恢復(fù)的狀態(tài)。既然是非正常辉饱,所以不便于也不需要捕獲搬男,常見的 OutOfMemoryError 就是 Error 的子類。
- Exception 又分為 checked Exception 和 unchecked Exception彭沼。
- checked Exception 在代碼里必須顯式的進行捕獲缔逛,這是編譯器檢查的一部分。
- unchecked Exception 也就是運行時異常,類似空指針異常褐奴、數(shù)組越界等按脚,通常是可以避免的邏輯錯誤,具體根據(jù)需求來判斷是否需要捕獲歉糜,并不會在編譯器強制要求乘寒。
--------------------網(wǎng)絡(luò)相關(guān)面試題-------------------
http 與 https 的區(qū)別?https 是如何工作的匪补?
http 是超文本傳輸協(xié)議伞辛,而 https 可以簡單理解為安全的 http 協(xié)議。https 通過在 http 協(xié)議下添加了一層 ssl 協(xié)議對數(shù)據(jù)進行加密從而保證了安全夯缺。https 的作用主要有兩點:建立安全的信息傳輸通道蚤氏,保證數(shù)據(jù)傳輸安全;確認網(wǎng)站的真實性踊兜。
http 與 https 的區(qū)別主要如下:
- https 需要到 CA 申請證書竿滨,很少免費,因而需要一定的費用
- http 是明文傳輸捏境,安全性低于游;而 https 在 http 的基礎(chǔ)上通過 ssl 加密,安全性高
- 二者的默認端口不一樣垫言,http 使用的默認端口是80贰剥;https使用的默認端口是 443
https 的工作流程
提到 https 的話首先要說到加密算法,加密算法分為兩類:對稱加密和非對稱加密筷频。
- 對稱加密: 加密和解密用的都是相同的秘鑰蚌成,優(yōu)點是速度快,缺點是安全性低凛捏。常見的對稱加密算法有 DES担忧、AES 等等。
- 非對稱加密: 非對稱加密有一個秘鑰對坯癣,分為公鑰和私鑰瓶盛。一般來說,私鑰自己持有示罗,公鑰可以公開給對方蓬网,優(yōu)點是安全性比對稱加密高,缺點是數(shù)據(jù)傳輸效率比對稱加密低鹉勒。采用公鑰加密的信息只有對應(yīng)的私鑰可以解密帆锋。常見的非對稱加密包括RSA等。
在正式的使用場景中一般都是對稱加密和非對稱加密結(jié)合使用禽额,使用非對稱加密完成秘鑰的傳遞锯厢,然后使用對稱秘鑰進行數(shù)據(jù)加密和解密皮官。二者結(jié)合既保證了安全性,又提高了數(shù)據(jù)傳輸效率实辑。
https 的具體流程如下:
1.客戶端(通常是瀏覽器)先向服務(wù)器發(fā)出加密通信的請求
- 支持的協(xié)議版本捺氢,比如 TLS 1.0版
- 一個客戶端生成的隨機數(shù) random1,稍后用于生成"對話密鑰"
- 支持的加密方法剪撬,比如 RSA 公鑰加密
- 支持的壓縮方法
2.服務(wù)器收到請求,然后響應(yīng) - 確認使用的加密通信協(xié)議版本摄乒,比如 TLS 1.0版本。如果瀏覽器與服務(wù)器支持的版本不一致残黑,服務(wù)器關(guān)閉加密通信
- 一個服務(wù)器生成的隨機數(shù) random2馍佑,稍后用于生成"對話密鑰"
- 確認使用的加密方法,比如 RSA 公鑰加密
- 服務(wù)器證書
3.客戶端收到證書之后會首先會進行驗證 - 首先驗證證書的安全性
- 驗證通過之后梨水,客戶端會生成一個隨機數(shù) pre-master secret拭荤,然后使用證書中的公鑰進行加密,然后傳遞給服務(wù)器端
4.服務(wù)器收到使用公鑰加密的內(nèi)容疫诽,在服務(wù)器端使用私鑰解密之后獲得隨機數(shù) pre-master secret舅世,然后根據(jù) radom1、radom2奇徒、pre-master secret 通過一定的算法得出一個對稱加密的秘鑰雏亚,作為后面交互過程中使用對稱秘鑰。同時客戶端也會使用 radom1摩钙、radom2罢低、pre-master secret,和同樣的算法生成對稱秘鑰腺律。
5.然后再后續(xù)的交互中就使用上一步生成的對稱秘鑰對傳輸?shù)膬?nèi)容進行加密和解密。
TCP三次握手流程
-----------------Android面試題-------------------
進程間通信的方式有哪幾種
AIDL 宜肉、廣播匀钧、文件、socket谬返、管道
廣播靜態(tài)注冊和動態(tài)注冊的區(qū)別
- 動態(tài)注冊廣播不是常駐型廣播之斯,也就是說廣播跟隨 Activity 的生命周期。注意在 Activity 結(jié)束前遣铝,移除廣播接收器佑刷。 靜態(tài)注冊是常駐型,也就是說當應(yīng)用程序關(guān)閉后酿炸,如果有信息廣播來瘫絮,程序也會被系統(tǒng)調(diào)用自動運行。
- 當廣播為有序廣播時:優(yōu)先級高的先接收(不分靜態(tài)和動態(tài))填硕。同優(yōu)先級的廣播接收器麦萤,動態(tài)優(yōu)先于靜態(tài)
- 同優(yōu)先級的同類廣播接收器鹿鳖,靜態(tài):先掃描的優(yōu)先于后掃描的,動態(tài):先注冊的優(yōu)先于后注冊的壮莹。
- 當廣播為默認廣播時:無視優(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)用運行期間的內(nèi)存泄漏骤素,并將之輸出給我們。
- BlockCanary 也是一個第三方檢測UI卡頓的庫愚屁,項目集成后Block也會自動檢測應(yīng)用運行期間的UI卡頓济竹,并將之輸出給我們。
Android中的類加載器
PathClassLoader霎槐,只能加載系統(tǒng)中已經(jīng)安裝過的 apk
DexClassLoader送浊,可以加載 jar/apk/dex,可以從 SD卡中加載未安裝的 apk
Android中的動畫有哪幾類丘跌,它們的特點和區(qū)別是什么
Android中動畫大致分為3類:幀動畫袭景、補間動畫(View Animation)、屬性動畫(Object Animation)闭树。
- 幀動畫:通過xml配置一組圖片耸棒,動態(tài)播放。很少會使用报辱。
- 補間動畫(View Animation):大致分為旋轉(zhuǎn)与殃、透明、縮放碍现、位移四類操作幅疼。很少會使用。
- 屬性動畫(Object Animation):屬性動畫是現(xiàn)在使用的最多的一種動畫昼接,它比補間動畫更加強大爽篷。屬性動畫大致分為兩種使用類型,分別是 ViewPropertyAnimator 和 ObjectAnimator慢睡。前者適合一些通用的動畫狼忱,比如旋轉(zhuǎn)膨疏、位移、縮放和透明钻弄,使用方式也很簡單通過 View.animate() 即可得到 ViewPropertyAnimator佃却,之后進行相應(yī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() 方法啟動動畫即可灶泵。
補間動畫與屬性動畫的區(qū)別:
- 補間動畫是父容器不斷的繪制 view,看起來像移動了效果,其實 view 沒有變化对途,還在原地赦邻。
- 是通過不斷改變 view 內(nèi)部的屬性值,真正的改變 view实檀。
Handler 機制
說到 Handler惶洲,就不得不提與之密切相關(guān)的這幾個類:Message、MessageQueue膳犹,Looper恬吕。
- Message。 Message 中有兩個成員變量值得關(guān)注:target 和 callback须床。
- target 其實就是發(fā)送消息的 Handler 對象
- callback 是當調(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()獲取當前線程中的Looper,如果不為空,則會拋出一個RunTimeException北启,意思是一個線程不能創(chuàng)建2個Looper卜朗。如果為null則執(zhí)行下一步拔第。第二步是創(chuàng)建了一個Looper,并通過 ThreadLocal.set(looper)场钉。將我們創(chuàng)建的Looper與當前線程綁定蚊俺。這里需要提一下的是消息隊列的創(chuàng)建其實就發(fā)生在Looper的構(gòu)造方法中。
- loop()逛万。 這個方法開啟了整個事件機制的輪詢泳猬。它的本質(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)了消息與當前 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 >標簽
- 通過 < ViewStub >標簽來加載不常用的布局
- 使用 < Merge >標簽來減少布局的嵌套層次
- 網(wǎng)絡(luò)優(yōu)化: 常見的網(wǎng)絡(luò)優(yōu)化方案如下
- 盡量減少網(wǎng)絡(luò)請求唯鸭,能夠合并的就盡量合并
- 避免 DNS 解析,根據(jù)域名查詢可能會耗費上百毫秒的時間硅确,也可能存在DNS劫持的風險目溉∶靼梗可以根據(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ù)的要求忘蟹,可能八九年前的手機滿足不了飒房,但我們也沒必要去適配老掉牙的手機。實際開發(fā)中減少 apk 體積的效果是十分顯著的媚值,如果你使用了很多 SO 庫狠毯,比方說一個版本的SO庫一共 10M,那么只保留 v7 版本褥芒,刪掉 armeabi 和 v8 版本的 SO 庫嚼松,一共可以減少 20M 的體積。
Android 內(nèi)存優(yōu)化
Android的內(nèi)存優(yōu)化在我看來分為兩點:避免內(nèi)存泄漏锰扶、擴大內(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í)行延時操作的時候會一直持有當前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處于另一個進程中综膀,當這個 Activity 結(jié)束時殺死當前 WebView 所處的進程即可,我記得阿里釘釘?shù)?WebView 就是另外開啟的一個進程局齿,應(yīng)該也是采用這種方法避免內(nèi)存泄漏剧劝。
擴大內(nèi)存
為什么要擴大我們的內(nèi)存呢?有時候我們實際開發(fā)中不可避免的要使用很多第三方商業(yè)的 SDK抓歼,這些 SDK 其實有好有壞讥此,大廠的 SDK 可能內(nèi)存泄漏會少一些,但一些小廠的 SDK 質(zhì)量也就不太靠譜一些谣妻。那應(yīng)對這種我們無法改變的情況萄喳,最好的辦法就是擴大內(nèi)存。
擴大內(nèi)存通常有兩種方法:一個是在清單文件中的 Application 下添加largeHeap="true"這個屬性蹋半,另一個就是同一個應(yīng)用開啟多個進程來擴大一個應(yīng)用的總內(nèi)存空間取胎。第二種方法其實就很常見了,比方說我使用過個推的 S DK湃窍,個推的 Service 其實就是處在另外一個單獨的進程中闻蛀。
Android 中的內(nèi)存優(yōu)化總的來說就是開源和節(jié)流,開源就是擴大內(nèi)存您市,節(jié)流就是避免內(nèi)存泄漏觉痛。
Binder 機制
在Linux中,為了避免一個進程對其他進程的干擾茵休,進程之間是相互獨立的薪棒。在一個進程中其實還分為用戶空間和內(nèi)核空間。這里的隔離分為兩個部分榕莺,進程間的隔離和進程內(nèi)的隔離俐芯。
既然進程間存在隔離,那其實也是存在著交互钉鸯。進程間通信就是 IPC吧史,用戶空間和內(nèi)核空間的通信就是系統(tǒng)調(diào)用。
Linux 為了保證獨立性和安全性唠雕,進程之間不能直接相互訪問贸营,Android 是基于 Linux 的,所以也是需要解決進程間通信的問題岩睁。
其實 Linux 進程間通信有很多方式钞脂,比如管道、socket 等等捕儒。為什么 Android 進程間通信采用了Binder而不是 Linux
已有的方式冰啃,主要是有這么兩點考慮:性能和安全
- 性能。 在移動設(shè)備上對性能要求是比較嚴苛的。Linux傳統(tǒng)的進程間通信比如管道阎毅、socket等等進程間通信是需要復(fù)制兩次數(shù)據(jù)焚刚,而Binder則只需要一次。所以Binder在性能上是優(yōu)于傳統(tǒng)進程通信的净薛。
- 安全。 傳統(tǒng)的 Linux 進程通信是不包含通信雙方的身份驗證的蒲拉,這樣會導(dǎo)致一些安全性問題肃拜。而Binder機制自帶身份驗證,從而有效的提高了安全性雌团。
Binder 是基于 CS 架構(gòu)的燃领,有四個主要組成部分。
- Client锦援。 客戶端進程猛蔽。
- Server灵寺。 服務(wù)端進程。
- ServiceManager毁枯。 提供注冊叮称、查詢和返回代理服務(wù)對象的功能。
- Binder 驅(qū)動赂韵。 主要負責建立進程間的 Binder 連接挠蛉,進程間的數(shù)據(jù)交互等等底層操作。
Binder 機制主要的流程是這樣的:
- 服務(wù)端通過Binder驅(qū)動在 ServiceManager 中注冊我們的服務(wù)谴古。
- 客戶端通過Binder驅(qū)動查詢在 ServiceManager 中注冊的服務(wù)。
- ServiceManager 通過 inder 驅(qū)動返回服務(wù)端的代理對象蹂窖。
- 客戶端拿到服務(wù)端的代理對象后即可進行進程間通信恩敌。
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 這個屬性默認是 false,但我們的 LruCache 需要按訪問順序輸出,所以顯式的將 accessOrder 設(shè)置為 true。
- get方法: 本質(zhì)上是調(diào)用 LinkedHashMap 的 get 方法构回,由于我們將 accessOrder 設(shè)置為了 true,所以每調(diào)用一次get方法北专,就會將我們訪問的當前元素放置到這個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 的特性鲁猩,會將每次訪問到的元素放置到尾部廓握。當我們的內(nèi)存達到閾值后隙券,會觸發(fā) trimToSize 方法來刪除 LinkedHashMap 首部的元素殉了,直到當前內(nèi)存小于 maxSize薪铜。為什么刪除首部的元素隔箍,原因很明顯:我們最近經(jīng)常訪問的元素都會放置到尾部滨达,那首部的元素肯定就是 最近最少使用 的元素了捡遍,因此當內(nèi)存不足時應(yīng)當優(yōu)先刪除這些元素画株。
設(shè)計一個圖片的異步加載框架
設(shè)計一個圖片加載框架,肯定要用到圖片加載的三級緩存的思想续挟。三級緩存分為內(nèi)存緩存庸推、本地緩存和網(wǎng)絡(luò)緩存贬媒。
內(nèi)存緩存 :將Bitmap緩存到內(nèi)存中坡倔,運行速度快罪塔,但是內(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前普,當然拭卿,名字隨便起,這個類包含了之前提到的 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 進行綁定噪沙,這是為了避免在列表中加載網(wǎng)絡(luò)圖片時會由于ImageView的復(fù)用導(dǎo)致的圖片錯位的 bug正歼。之后會從 MemeryAndDiskCache 中獲取緩存局义,如果存在萄唇,直接加載穷绵;如果不存在,則調(diào)用從網(wǎng)絡(luò)獲取圖片這個方法揍障。從網(wǎng)絡(luò)獲取圖片方法很多癌蚁,這里我一般都會使用 OkHttp+Retrofit努释。當從網(wǎng)絡(luò)中獲取到圖片之后,首先判斷一下imageView.getTag()與圖片的 url 是否一致逸邦,如果一致則加載圖片缕减,如果不一致則不加載圖片桥狡,通過這樣的方式避免了列表中異步加載圖片的錯位呈宇。同時在獲取到圖片之后會通過 MemeryAndDiskCache 來緩存圖片局雄。
Android中的事件分發(fā)機制
在我們的手指觸摸到屏幕的時候蜈漓,事件其實是通過 Activity -> ViewGroup -> View 這樣的流程到達最后響應(yīng)我們觸摸事件的 View融虽。
說到事件分發(fā)有额,必不可少的是這幾個方法:dispatchTouchEvent()巍佑、onInterceptTouchEvent()、onTouchEvent脆栋。接下來就按照Activity -> ViewGroup -> View 的流程來大致說一下事件分發(fā)機制椿争。
我們的手指觸摸到屏幕的時候秦踪,會觸發(fā)一個 Action_Down 類型的事件洋侨,當前頁面的 Activity 會首先做出響應(yīng),也就是說會走到 Activity 的 dispatchTouchEvent() 方法內(nèi)陵且。在這個方法內(nèi)部簡單來說是這么一個邏輯:
- 調(diào)用 getWindow.superDispatchTouchEvent()聊疲。
- 如果上一步返回 true获洲,直接返回 true;否則就 return 自己的 onTouchEvent()门岔。
這個邏輯很好理解寒随,getWindow().superDispatchTouchEvent() 如果返回 true 代表當前事件已經(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() 判斷當前 ViewGroup 是否攔截事件,默認的 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 的點擊事件溺拱。這個例子中,當我們按下 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苛败,所以也不存在攔截不攔截這一說肃叶。忽略一些細節(jié)低缩,View 的 dispatchTouchEvent()中直接 return 了自己的 onTouchEvent()匀伏。如果 onTouchEvent()返回 true 代表事件被處理减余,否則未處理的事件會向上傳遞赚抡,直到有 View 處理了事件或者一直沒有處理,最終到達了 Activity 的 onTouchEvent() 終止箩做。
這里經(jīng)常有人問 onTouch 和 onTouchEvent 的區(qū)別巍棱。首先讹躯,這兩個方法都在 View 的 dispatchTouchEvent()中帆竹,是這么一個邏輯:
- 如果 touchListener 不為 null沥割,并且這個 View 是 enable 的堵未,而且 onTouch 返回的是 true,滿足這三個條件時會直接 return true,不會走 onTouchEvent()方法忘古。
- 上面只要有一個條件不滿足驶沼,就會走到 onTouchEvent()方法中。所以 onTouch 的順序是在 onTouchEvent 之前的。
View的繪制流程
視圖繪制的起點在 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)化和準備工作后會調(diào)用 onMeasure 方法進行實際的自我測量件炉。onMeasure方法在View和ViewGroup做的事情是不一樣的:
- View勘究。 View 中的 onMeasure 方法會計算自己的尺寸并通過 setMeasureDimension 保存。
- ViewGroup斟冕。 ViewGroup 中的 onMeasure 方法會調(diào)用所有子 iew的measure 方法進行自我測量并保存口糕。然后通過子View的尺寸和位置計算出自己的尺寸并保存。
-
布局階段磕蛇。 layout 方法會被父View調(diào)用景描,layout 方法會保存父 View 傳進來的尺寸和位置十办,并調(diào)用 onLayout 進行實際的內(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 方法進行 View 的自我繪制候引。draw 方法的調(diào)度流程大致是這樣的:
- 繪制背景侯养。 對應(yīng) drawBackground(Canvas)方法。
- 繪制主體澄干。 對應(yīng) onDraw(Canvas)方法逛揩。
- 繪制子View。 對應(yīng) dispatchDraw(Canvas)方法麸俘。
- 繪制滑動相關(guān)和前景辩稽。 對應(yīng) onDrawForeground(Canvas)。
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)點是很簡潔,缺點是沒有返回值紧憾,如果需要拿到j(luò)s方法的返回值則需要js調(diào)用Android中的方法來拿到這個返回值到千。
- WebView.evaluateJavaScript("javascript:js中的方法名",ValueCallback)。 這種方法比 loadUrl 好的是可以通過 ValueCallback 這個回調(diào)拿到 js方法的返回值赴穗。缺點是這個方法 Android4.4 才有憔四,兼容性較差。不過放在 2018 年來說般眉,市面上絕大多數(shù) App 都要求最低版本是 4.4 了加矛,所以我認為這個兼容性問題不大。
-
js 調(diào) Android煤篙。 js 調(diào) Android有三種方法:
- WebView.addJavascriptInterface()。 這是官方解決 js 調(diào)用 Android 方法的方案毁腿,需要注意的是要在供 js 調(diào)用的 Android 方法上加上 @JavascriptInterface 注解辑奈,以避免安全漏洞苛茂。這種方案的缺點是 Android4.2 以前會有安全漏洞,不過在 4.2 以后已經(jīng)修復(fù)了鸠窗。同樣妓羊,在 2018 年來說,兼容性問題不大稍计。
- 重寫 WebViewClient的shouldOverrideUrlLoading()方法來攔截url躁绸, 拿到 url 后進行解析,如果符合雙方的規(guī)定臣嚣,即可調(diào)用 Android 方法净刮。優(yōu)點是避免了 Android4.2 以前的安全漏洞,缺點也很明顯硅则,無法直接拿到調(diào)用 Android 方法的返回值淹父,只能通過 Android 調(diào)用 js 方法來獲取返回值。
- 重寫 WebChromClient 的 onJsPrompt() 方法怎虫,同前一個方式一樣暑认,拿到 url 之后先進行解析,如果符合雙方規(guī)定大审,即可調(diào)用Android方法蘸际。最后如果需要返回值,通過 result.confirm("Android方法返回值") 即可將 Android 的返回值返回給 js徒扶。方法的優(yōu)點是沒有漏洞粮彤,也沒有兼容性限制,同時還可以方便的獲取 Android 方法的返回值酷愧。其實這里需要注意的是在 WebChromeClient 中除 了 onJsPrompt 之外還有 onJsAlert 和 onJsConfirm 方法驾诈。那么為什么不選擇另兩個方法呢?原因在于 onJsAlert 是沒有返回值的溶浴,而 onJsConfirm 只有 true 和 false 兩個返回值乍迄,同時在前端開發(fā)中 prompt 方法基本不會被調(diào)用,所以才會采用 onJsPrompt士败。
Activity 啟動過程
SparseArray 原理
SparseArray闯两,通常來講是 Android 中用來替代 HashMap 的一個數(shù)據(jù)結(jié)構(gòu)。
準確來講谅将,是用來替換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進行升序排列的。
綜合起來來說浸须,SparseArray 所占空間優(yōu)于 HashMap惨寿,而效率低于 HashMap,是典型的時間換空間删窒,適合較小容量的存儲裂垦。
從源碼角度來說,我認為需要注意的是 SparseArray的remove()肌索、put()和 gc()方法蕉拢。
- remove()。 SparseArray 的 remove() 方法并不是直接刪除之后再壓縮數(shù)組诚亚,而是將要刪除的 value 設(shè)置為 DELETE 這個 SparseArray 的靜態(tài)屬性晕换,這個 DELETE 其實就是一個 Object 對象,同時會將 SparseArray 中的 mGarbage 這個屬性設(shè)置為 true站宗,這個屬性是便于在合適的時候調(diào)用自身的 gc()方法壓縮數(shù)組來避免浪費空間闸准。這樣可以提高效率,如果將來要添加的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)存的浪費义屏。
- put()。 put 方法是這么一個邏輯,如果通過二分查找 在 mKeys 數(shù)組中找到了 key湿蛔,那么直接覆蓋 value 即可膀曾。如果沒有找到,會拿到與數(shù)組中與要添加的 key 最接近的 key 索引阳啥,如果這個索引對應(yīng)的 value 為 DELETE,則直接把新的 value 覆蓋 DELET 即可财喳,在這里可以避免數(shù)組元素的移動察迟,從而提高了效率。如果 value 不為 DELETE耳高,會判斷 mGarbage扎瓶,如果為 true,則會調(diào)用 gc()方法壓縮數(shù)組泌枪,之后會找到合適的索引概荷,將索引之后的鍵值對后移,插入新的鍵值對碌燕,這個過程中可能會觸發(fā)數(shù)組的擴容误证。
圖片加載如何避免 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 可以縮小圖片的長寬胖腾,從而減少 BItma p 所占內(nèi)存烟零。需要注意的是這個 inSampleSize 大小需要是 2 的冪次方,如果小于 1咸作,代碼會強制讓inSampleSize為1锨阿。
- 減少像素所占內(nèi)存。Options 中有一個屬性 inPreferredConfig记罚,默認是 ARGB_8888墅诡,代表每個像素所占尺寸。我們可以通過將之修改為 RGB_565 或者 ARGB_4444 來減少一半內(nèi)存。
大圖加載
加載高清大圖末早,比如清明上河圖烟馅,首先屏幕是顯示不下的,而且考慮到內(nèi)存情況然磷,也不可能一次性全部加載到內(nèi)存郑趁。這時候就需要局部加載了,Android中有一個負責局部加載的類:BitmapRegionDecoder姿搜。使用方法很簡單寡润,通過BitmapRegionDecoder.newInstance()創(chuàng)建對象,之后調(diào)用decodeRegion(Rect rect, BitmapFactory.Options options)即可舅柜。第一個參數(shù)rect是要顯示的區(qū)域梭纹,第二個參數(shù)是BitmapFactory中的內(nèi)部類Options。