摘要:需要校驗(yàn)字節(jié)信息是否符合規(guī)范,避免惡意信息和不規(guī)范數(shù)據(jù)危害運(yùn)行安全。具有相同哈希值的鍵值對(duì)會(huì)組成鏈表。通過(guò)在協(xié)議下添加了一層協(xié)議對(duì)數(shù)據(jù)進(jìn)行加密從而保證了安全。常見的非對(duì)稱加密包括等。
類加載過(guò)程
Java 中類加載分為 3 個(gè)步驟:加載、鏈接、初始化。
加載。 加載是將字節(jié)碼數(shù)據(jù)從不同的數(shù)據(jù)源讀取到JVM內(nèi)存,并映射為 JVM 認(rèn)可的數(shù)據(jù)結(jié)構(gòu),也就是 Class 對(duì)象的過(guò)程。數(shù)據(jù)源可以是 Jar 文件、Class 文件等等。如果數(shù)據(jù)的格式并不是 ClassFile 的結(jié)構(gòu),則會(huì)報(bào) ClassFormatError。
鏈接。 鏈接是類加載的核心部分,這一步分為 3 個(gè)步驟:驗(yàn)證、準(zhǔn)備、解析。
驗(yàn)證。 驗(yàn)證是保證JVM安全的重要步驟。JVM需要校驗(yàn)字節(jié)信息是否符合規(guī)范,避免惡意信息和不規(guī)范數(shù)據(jù)危害JVM運(yùn)行安全。如果驗(yàn)證出錯(cuò),則會(huì)報(bào)VerifyError。
準(zhǔn)備。 這一步會(huì)創(chuàng)建靜態(tài)變量,并為靜態(tài)變量開辟內(nèi)存空間。
解析。 這一步會(huì)將符號(hào)引用替換為直接引用。
初始化。 初始化會(huì)為靜態(tài)變量賦值,并執(zhí)行靜態(tài)代碼塊中的邏輯。
雙親委派模型類加載器大致分為3類:?jiǎn)?dòng)類加載器、擴(kuò)展類加載器、應(yīng)用程序類加載器。
啟動(dòng)類加載器主要加載 jre/lib下的jar文件。
擴(kuò)展類加載器主要加載 jre/lib/ext 下的jar文件。
應(yīng)用程序類加載器主要加載 classpath 下的文件。
所謂的雙親委派模型就是當(dāng)加載一個(gè)類時(shí),會(huì)優(yōu)先使用父類加載器加載,當(dāng)父類加載器無(wú)法加載時(shí)才會(huì)使用子類加載器去加載。這么做的目的是為了避免類的重復(fù)加載。
Java 中的集合類 HashMap 的原理HashMap 的內(nèi)部可以看做數(shù)組+鏈表的復(fù)合結(jié)構(gòu)。數(shù)組被分為一個(gè)個(gè)的桶(bucket)。哈希值決定了鍵值對(duì)在數(shù)組中的尋址。具有相同哈希值的鍵值對(duì)會(huì)組成鏈表。需要注意的是當(dāng)鏈表長(zhǎng)度超過(guò)閾值(默認(rèn)是8)的時(shí)候會(huì)觸發(fā)樹化,鏈表會(huì)變成樹形結(jié)構(gòu)。
把握HashMap的原理需要關(guān)注4個(gè)方法:hash、put、get、resize。hash方法。 將 key 的 hashCode 值的高位數(shù)據(jù)移位到低位進(jìn)行異或運(yùn)算。這么做的原因是有些 key 的 hashCode 值的差異集中在高位,而哈希尋址是忽略容量以上高位的,這種做法可以有效避免哈希沖突。
put 方法。 put 方法主要有以下幾個(gè)步驟:
通過(guò) hash 方法獲取 hash 值,根據(jù) hash 值尋址。
如果未發(fā)生碰撞,直接放到桶中。
如果發(fā)生碰撞,則以鏈表形式放在桶后。
當(dāng)鏈表長(zhǎng)度大于閾值后會(huì)觸發(fā)樹化,將鏈表轉(zhuǎn)換為紅黑樹。
如果數(shù)組長(zhǎng)度達(dá)到閾值,會(huì)調(diào)用 resize 方法擴(kuò)展容量。
get方法。 get 方法主要有以下幾個(gè)步驟:
通過(guò) hash 方法獲取 hash 值,根據(jù) hash 值尋址。
如果與尋址到桶的 key 相等,直接返回對(duì)應(yīng)的 value。
如果發(fā)生沖突,分兩種情況。如果是樹,則調(diào)用 getTreeNode 獲取 value;如果是鏈表則通過(guò)循環(huán)遍歷查找對(duì)應(yīng)的 value。
resize 方法。 resize 做了兩件事:
將原數(shù)組擴(kuò)展為原來(lái)的 2 倍
重新計(jì)算 index 索引值,將原節(jié)點(diǎn)重新放到新的數(shù)組中。這一步可以將原先沖突的節(jié)點(diǎn)分散到新的桶中。
sleep 和 wait 的區(qū)別sleep 方法是 Thread 類中的靜態(tài)方法,wait 是 Object 類中的方法
sleep 并不會(huì)釋放同步鎖,而 wait 會(huì)釋放同步鎖
sleep 可以在任何地方使用,而 wait 只能在同步方法或者同步代碼塊中使用
sleep 中必須傳入時(shí)間,而 wait 可以傳,也可以不傳,不傳時(shí)間的話只有 notify 或者 notifyAll - 才能喚醒,傳時(shí)間的話在時(shí)間之后會(huì)自動(dòng)喚醒
volatile和synchronize的區(qū)別 final、finally、finalize區(qū)別final 可以修飾類、變量和方法。修飾類代表這個(gè)類不可被繼承。修飾變量代表此變量不可被改變。修飾方法表示此方法不可被重寫 (override)。
finally 是保證重點(diǎn)代碼一定會(huì)執(zhí)行的一種機(jī)制。通常是使用 try-finally 或者 try-catch-finally 來(lái)進(jìn)行文件流的關(guān)閉等操作。
finalize 是 Object 類中的一個(gè)方法,它的設(shè)計(jì)目的是保證對(duì)象在垃圾收集前完成特定資源的回收。finalize 機(jī)制現(xiàn)在已經(jīng)不推薦使用,并且在 JDK 9已經(jīng)被標(biāo)記為 deprecated。
Java中引用類型的區(qū)別,具體的使用場(chǎng)景Java中引用類型分為四類:強(qiáng)引用、軟引用、弱引用、虛引用。
強(qiáng)引用: 強(qiáng)引用指的是通過(guò) new 對(duì)象創(chuàng)建的引用,垃圾回收器即使是內(nèi)存不足也不會(huì)回收強(qiáng)引用指向的對(duì)象。
軟引用: 軟引用是通過(guò) SoftRefrence 實(shí)現(xiàn)的,它的生命周期比強(qiáng)引用短,在內(nèi)存不足,拋出 OOM 之前,垃圾回收器會(huì)回收軟引用引用的對(duì)象。軟引用常見的使用場(chǎng)景是存儲(chǔ)一些內(nèi)存敏感的緩存,當(dāng)內(nèi)存不足時(shí)會(huì)被回收。
弱引用: 弱引用是通過(guò) WeakRefrence 實(shí)現(xiàn)的,它的生命周期比軟引用還短,GC 只要掃描到弱引用的對(duì)象就會(huì)回收。弱引用常見的使用場(chǎng)景也是存儲(chǔ)一些內(nèi)存敏感的緩存。
虛引用: 虛引用是通過(guò) FanttomRefrence 實(shí)現(xiàn)的,它的生命周期最短,隨時(shí)可能被回收。如果一個(gè)對(duì)象只被虛引用引用,我們無(wú)法通過(guò)虛引用來(lái)訪問(wèn)這個(gè)對(duì)象的任何屬性和方法。它的作用僅僅是保證對(duì)象在 finalize 后,做某些事情。虛引用常見的使用場(chǎng)景是跟蹤對(duì)象被垃圾回收的活動(dòng),當(dāng)一個(gè)虛引用關(guān)聯(lián)的對(duì)象被垃圾回收器回收之前會(huì)收到一條系統(tǒng)通知。
Exception 和 Error的區(qū)別Exception 和 Error 都繼承于 Throwable,在 Java 中,只有 Throwable 類型的對(duì)象才能被 throw 或者 catch,它是異常處理機(jī)制的基本組成類型.
Exception 和 Error 體現(xiàn)了 Java 對(duì)不同異常情況的分類。Exception 是程序正常運(yùn)行中,可以預(yù)料的意外情況,可能并且應(yīng)該被捕獲,進(jìn)行相應(yīng)的處理。
Error 是指在正常情況下,不大可能出現(xiàn)的情況,絕大部分 Error 都會(huì)使程序處于非正常、不可恢復(fù)的狀態(tài)。既然是非正常,所以不便于也不需要捕獲,常見的 OutOfMemoryError 就是 Error 的子類。
Exception 又分為 checked Exception 和 unchecked Exception。
checked Exception 在代碼里必須顯式的進(jìn)行捕獲,這是編譯器檢查的一部分。
unchecked Exception 也就是運(yùn)行時(shí)異常,類似空指針異常、數(shù)組越界等,通常是可以避免的邏輯錯(cuò)誤,具體根據(jù)需求來(lái)判斷是否需要捕獲,并不會(huì)在編譯器強(qiáng)制要求。
--------------------網(wǎng)絡(luò)相關(guān)面試題------------------- http 與 https 的區(qū)別?https 是如何工作的?http 是超文本傳輸協(xié)議,而 https 可以簡(jiǎn)單理解為安全的 http 協(xié)議。https 通過(guò)在 http 協(xié)議下添加了一層 ssl 協(xié)議對(duì)數(shù)據(jù)進(jìn)行加密從而保證了安全。https 的作用主要有兩點(diǎn):建立安全的信息傳輸通道,保證數(shù)據(jù)傳輸安全;確認(rèn)網(wǎng)站的真實(shí)性。
http 與 https 的區(qū)別主要如下:https 需要到 CA 申請(qǐng)證書,很少免費(fèi),因而需要一定的費(fèi)用
http 是明文傳輸,安全性低;而 https 在 http 的基礎(chǔ)上通過(guò) ssl 加密,安全性高
二者的默認(rèn)端口不一樣,http 使用的默認(rèn)端口是80;https使用的默認(rèn)端口是 443
https 的工作流程提到 https 的話首先要說(shuō)到加密算法,加密算法分為兩類:對(duì)稱加密和非對(duì)稱加密。
對(duì)稱加密: 加密和解密用的都是相同的秘鑰,優(yōu)點(diǎn)是速度快,缺點(diǎn)是安全性低。常見的對(duì)稱加密算法有 DES、AES 等等。
非對(duì)稱加密: 非對(duì)稱加密有一個(gè)秘鑰對(duì),分為公鑰和私鑰。一般來(lái)說(shuō),私鑰自己持有,公鑰可以公開給對(duì)方,優(yōu)點(diǎn)是安全性比對(duì)稱加密高,缺點(diǎn)是數(shù)據(jù)傳輸效率比對(duì)稱加密低。采用公鑰加密的信息只有對(duì)應(yīng)的私鑰可以解密。常見的非對(duì)稱加密包括RSA等。
在正式的使用場(chǎng)景中一般都是對(duì)稱加密和非對(duì)稱加密結(jié)合使用,使用非對(duì)稱加密完成秘鑰的傳遞,然后使用對(duì)稱秘鑰進(jìn)行數(shù)據(jù)加密和解密。二者結(jié)合既保證了安全性,又提高了數(shù)據(jù)傳輸效率。
1.客戶端(通常是瀏覽器)先向服務(wù)器發(fā)出加密通信的請(qǐng)求
支持的協(xié)議版本,比如 TLS 1.0版
一個(gè)客戶端生成的隨機(jī)數(shù) random1,稍后用于生成"對(duì)話密鑰"
支持的加密方法,比如 RSA 公鑰加密
支持的壓縮方法
2.服務(wù)器收到請(qǐng)求,然后響應(yīng)
確認(rèn)使用的加密通信協(xié)議版本,比如 TLS 1.0版本。如果瀏覽器與服務(wù)器支持的版本不一致,服務(wù)器關(guān)閉加密通信
一個(gè)服務(wù)器生成的隨機(jī)數(shù) random2,稍后用于生成"對(duì)話密鑰"
確認(rèn)使用的加密方法,比如 RSA 公鑰加密
服務(wù)器證書
3.客戶端收到證書之后會(huì)首先會(huì)進(jìn)行驗(yàn)證
首先驗(yàn)證證書的安全性
驗(yàn)證通過(guò)之后,客戶端會(huì)生成一個(gè)隨機(jī)數(shù) pre-master secret,然后使用證書中的公鑰進(jìn)行加密,然后傳遞給服務(wù)器端
4.服務(wù)器收到使用公鑰加密的內(nèi)容,在服務(wù)器端使用私鑰解密之后獲得隨機(jī)數(shù) pre-master secret,然后根據(jù) radom1、radom2、pre-master secret 通過(guò)一定的算法得出一個(gè)對(duì)稱加密的秘鑰,作為后面交互過(guò)程中使用對(duì)稱秘鑰。同時(shí)客戶端也會(huì)使用 radom1、radom2、pre-master secret,和同樣的算法生成對(duì)稱秘鑰。
5.然后再后續(xù)的交互中就使用上一步生成的對(duì)稱秘鑰對(duì)傳輸?shù)膬?nèi)容進(jìn)行加密和解密。
AIDL 、廣播、文件、socket、管道
廣播靜態(tài)注冊(cè)和動(dòng)態(tài)注冊(cè)的區(qū)別動(dòng)態(tài)注冊(cè)廣播不是常駐型廣播,也就是說(shuō)廣播跟隨 Activity 的生命周期。注意在 Activity 結(jié)束前,移除廣播接收器。 靜態(tài)注冊(cè)是常駐型,也就是說(shuō)當(dāng)應(yīng)用程序關(guān)閉后,如果有信息廣播來(lái),程序也會(huì)被系統(tǒng)調(diào)用自動(dòng)運(yùn)行。
當(dāng)廣播為有序廣播時(shí):優(yōu)先級(jí)高的先接收(不分靜態(tài)和動(dòng)態(tài))。同優(yōu)先級(jí)的廣播接收器,動(dòng)態(tài)優(yōu)先于靜態(tài)
同優(yōu)先級(jí)的同類廣播接收器,靜態(tài):先掃描的優(yōu)先于后掃描的,動(dòng)態(tài):先注冊(cè)的優(yōu)先于后注冊(cè)的。
當(dāng)廣播為默認(rèn)廣播時(shí):無(wú)視優(yōu)先級(jí),動(dòng)態(tài)廣播接收器優(yōu)先于靜態(tài)廣播接收器。同優(yōu)先級(jí)的同類廣播接收器,靜態(tài):先掃描的優(yōu)先于后掃描的,動(dòng)態(tài):先注冊(cè)的優(yōu)先于后冊(cè)的。
Android 性能優(yōu)化工具使用(這個(gè)問(wèn)題建議配合Android中的性能優(yōu)化)Android 中常用的性能優(yōu)化工具包括這些:Android Studio 自帶的 Android Profiler、LeakCanary、BlockCanary
Android 自帶的 Android Profiler 其實(shí)就很好用,Android Profiler 可以檢測(cè)三個(gè)方面的性能問(wèn)題:CPU、MEMORY、NETWORK。
LeakCanary 是一個(gè)第三方的檢測(cè)內(nèi)存泄漏的庫(kù),我們的項(xiàng)目集成之后 LeakCanary 會(huì)自動(dòng)檢測(cè)應(yīng)用運(yùn)行期間的內(nèi)存泄漏,并將之輸出給我們。
BlockCanary 也是一個(gè)第三方檢測(cè)UI卡頓的庫(kù),項(xiàng)目集成后Block也會(huì)自動(dòng)檢測(cè)應(yīng)用運(yùn)行期間的UI卡頓,并將之輸出給我們。
Android中的類加載器PathClassLoader,只能加載系統(tǒng)中已經(jīng)安裝過(guò)的 apk
DexClassLoader,可以加載 jar/apk/dex,可以從 SD卡中加載未安裝的 apk
Android中動(dòng)畫大致分為3類:幀動(dòng)畫、補(bǔ)間動(dòng)畫(View Animation)、屬性動(dòng)畫(Object Animation)。
幀動(dòng)畫:通過(guò)xml配置一組圖片,動(dòng)態(tài)播放。很少會(huì)使用。
補(bǔ)間動(dòng)畫(View Animation):大致分為旋轉(zhuǎn)、透明、縮放、位移四類操作。很少會(huì)使用。
屬性動(dòng)畫(Object Animation):屬性動(dòng)畫是現(xiàn)在使用的最多的一種動(dòng)畫,它比補(bǔ)間動(dòng)畫更加強(qiáng)大。屬性動(dòng)畫大致分為兩種使用類型,分別是 ViewPropertyAnimator 和 ObjectAnimator。前者適合一些通用的動(dòng)畫,比如旋轉(zhuǎn)、位移、縮放和透明,使用方式也很簡(jiǎn)單通過(guò) View.animate() 即可得到 ViewPropertyAnimator,之后進(jìn)行相應(yīng)的動(dòng)畫操作即可。后者適合用于為我們的自定義控件添加動(dòng)畫,當(dāng)然首先我們應(yīng)該在自定義 View 中添加相應(yīng)的 getXXX() 和 setXXX() 相應(yīng)屬性的 getter 和 setter 方法,這里需要注意的是在 setter 方法內(nèi)改變了自定義 View 中的屬性后要調(diào)用 invalidate() 來(lái)刷新View的繪制。之后調(diào)用 ObjectAnimator.of 屬性類型()返回一個(gè) ObjectAnimator,調(diào)用 start() 方法啟動(dòng)動(dòng)畫即可。
補(bǔ)間動(dòng)畫與屬性動(dòng)畫的區(qū)別:
補(bǔ)間動(dòng)畫是父容器不斷的繪制 view,看起來(lái)像移動(dòng)了效果,其實(shí) view 沒(méi)有變化,還在原地。
是通過(guò)不斷改變 view 內(nèi)部的屬性值,真正的改變 view。
Handler 機(jī)制說(shuō)到 Handler,就不得不提與之密切相關(guān)的這幾個(gè)類:Message、MessageQueue,Looper。
Message。 Message 中有兩個(gè)成員變量值得關(guān)注:target 和 callback。
target 其實(shí)就是發(fā)送消息的 Handler 對(duì)象
callback 是當(dāng)調(diào)用 handler.post(runnable) 時(shí)傳入的 Runnable 類型的任務(wù)。post 事件的本質(zhì)也是創(chuàng)建了一個(gè) Message,將我們傳入的這個(gè) runnable 賦值給創(chuàng)建的Message的 callback 這個(gè)成員變量。
MessageQueue。 消息隊(duì)列很明顯是存放消息的隊(duì)列,值得關(guān)注的是 MessageQueue 中的 next() 方法,它會(huì)返回下一個(gè)待處理的消息。
Looper。 Looper 消息輪詢器其實(shí)是連接 Handler 和消息隊(duì)列的核心。首先我們都知道,如果想要在一個(gè)線程中創(chuàng)建一個(gè) Handler,首先要通過(guò) Looper.prepare() 創(chuàng)建 Looper,之后還得調(diào)用 Looper.loop()開啟輪詢。我們著重看一下這兩個(gè)方法。
prepare()。 這個(gè)方法做了兩件事:首先通過(guò)ThreadLocal.get()獲取當(dāng)前線程中的Looper,如果不為空,則會(huì)拋出一個(gè)RunTimeException,意思是一個(gè)線程不能創(chuàng)建2個(gè)Looper。如果為null則執(zhí)行下一步。第二步是創(chuàng)建了一個(gè)Looper,并通過(guò) ThreadLocal.set(looper)。將我們創(chuàng)建的Looper與當(dāng)前線程綁定。這里需要提一下的是消息隊(duì)列的創(chuàng)建其實(shí)就發(fā)生在Looper的構(gòu)造方法中。
loop()。 這個(gè)方法開啟了整個(gè)事件機(jī)制的輪詢。它的本質(zhì)是開啟了一個(gè)死循環(huán),不斷的通過(guò) MessageQueue的next()方法獲取消息。拿到消息后會(huì)調(diào)用 msg.target.dispatchMessage()來(lái)做處理。其實(shí)我們?cè)谡f(shuō)到 Message 的時(shí)候提到過(guò),msg.target 其實(shí)就是發(fā)送這個(gè)消息的 handler。這句代碼的本質(zhì)就是調(diào)用 handler的dispatchMessage()。
Handler。 上面做了這么多鋪墊,終于到了最重要的部分。Handler 的分析著重在兩個(gè)部分:發(fā)送消息和處理消息。
發(fā)送消息。其實(shí)發(fā)送消息除了 sendMessage 之外還有 sendMessageDelayed 和 post 以及 postDelayed 等等不同的方式。但它們的本質(zhì)都是調(diào)用了 sendMessageAtTime。在 sendMessageAtTime 這個(gè)方法中調(diào)用了 enqueueMessage。在 enqueueMessage 這個(gè)方法中做了兩件事:通過(guò) msg.target = this 實(shí)現(xiàn)了消息與當(dāng)前 handler 的綁定。然后通過(guò) queue.enqueueMessage 實(shí)現(xiàn)了消息入隊(duì)。
處理消息。 消息處理的核心其實(shí)就是dispatchMessage()這個(gè)方法。這個(gè)方法里面的邏輯很簡(jiǎn)單,先判斷 msg.callback 是否為 null,如果不為空則執(zhí)行這個(gè) runnable。如果為空則會(huì)執(zhí)行我們的handleMessage方法。
Android 性能優(yōu)化Android 中的性能優(yōu)化在我看來(lái)分為以下幾個(gè)方面:內(nèi)存優(yōu)化、布局優(yōu)化、網(wǎng)絡(luò)優(yōu)化、安裝包優(yōu)化。
內(nèi)存優(yōu)化: 下一個(gè)問(wèn)題就是。
布局優(yōu)化: 布局優(yōu)化的本質(zhì)就是減少 View 的層級(jí)。常見的布局優(yōu)化方案如下
在 LinearLayout 和 RelativeLayout 都可以完成布局的情況下優(yōu)先選擇 RelativeLayout,可以減少 View 的層級(jí)
將常用的布局組件抽取出來(lái)使用 < include >標(biāo)簽
通過(guò) < ViewStub >標(biāo)簽來(lái)加載不常用的布局
使用 < Merge >標(biāo)簽來(lái)減少布局的嵌套層次
網(wǎng)絡(luò)優(yōu)化: 常見的網(wǎng)絡(luò)優(yōu)化方案如下
盡量減少網(wǎng)絡(luò)請(qǐng)求,能夠合并的就盡量合并
避免 DNS 解析,根據(jù)域名查詢可能會(huì)耗費(fèi)上百毫秒的時(shí)間,也可能存在DNS劫持的風(fēng)險(xiǎn)。可以根據(jù)業(yè)務(wù)需求采用增加動(dòng)態(tài)更新 IP 的方式,或者在 IP 方式訪問(wèn)失敗時(shí)切換到域名訪問(wèn)方式。
大量數(shù)據(jù)的加載采用分頁(yè)的方式
網(wǎng)絡(luò)數(shù)據(jù)傳輸采用 GZIP 壓縮
加入網(wǎng)絡(luò)數(shù)據(jù)的緩存,避免頻繁請(qǐng)求網(wǎng)絡(luò)
上傳圖片時(shí),在必要的時(shí)候壓縮圖片
安裝包優(yōu)化: 安裝包優(yōu)化的核心就是減少 apk 的體積,常見的方案如
使用混淆,可以在一定程度上減少 apk 體積,但實(shí)際效果微乎其微
減少應(yīng)用中不必要的資源文件,比如圖片,在不影響 APP 效果的情況下盡量壓縮圖片,有一定的效果
在使用了 SO 庫(kù)的時(shí)候優(yōu)先保留 v7 版本的 SO 庫(kù),刪掉其他版本的SO庫(kù)。原因是在 2018 年,v7 版本的 SO 庫(kù)可以滿足市面上絕大多數(shù)的要求,可能八九年前的手機(jī)滿足不了,但我們也沒(méi)必要去適配老掉牙的手機(jī)。實(shí)際開發(fā)中減少 apk 體積的效果是十分顯著的,如果你使用了很多 SO 庫(kù),比方說(shuō)一個(gè)版本的SO庫(kù)一共 10M,那么只保留 v7 版本,刪掉 armeabi 和 v8 版本的 SO 庫(kù),一共可以減少 20M 的體積。
Android 內(nèi)存優(yōu)化Android的內(nèi)存優(yōu)化在我看來(lái)分為兩點(diǎn):避免內(nèi)存泄漏、擴(kuò)大內(nèi)存,其實(shí)就是開源節(jié)流。
其實(shí)內(nèi)存泄漏的本質(zhì)就是較長(zhǎng)生命周期的對(duì)象引用了較短生命周期的對(duì)象。
單例模式導(dǎo)致的內(nèi)存泄漏。 最常見的例子就是創(chuàng)建這個(gè)單例對(duì)象需要傳入一個(gè) Context,這時(shí)候傳入了一個(gè) Activity 類型的 Context,由于單例對(duì)象的靜態(tài)屬性,導(dǎo)致它的生命周期是從單例類加載到應(yīng)用程序結(jié)束為止,所以即使已經(jīng) finish 掉了傳入的 Activity,由于我們的單例對(duì)象依然持有 Activity 的引用,所以導(dǎo)致了內(nèi)存泄漏。解決辦法也很簡(jiǎn)單,不要使用 Activity 類型的 Context,使用 Application 類型的 Context 可以避免內(nèi)存泄漏。
靜態(tài)變量導(dǎo)致的內(nèi)存泄漏。 靜態(tài)變量是放在方法區(qū)中的,它的生命周期是從類加載到程序結(jié)束,可以看到靜態(tài)變量生命周期是非常久的。最常見的因靜態(tài)變量導(dǎo)致內(nèi)存泄漏的例子是我們?cè)?Activity 中創(chuàng)建了一個(gè)靜態(tài)變量,而這個(gè)靜態(tài)變量的創(chuàng)建需要傳入 Activity 的引用 this。在這種情況下即使 Activity 調(diào)用了 finish 也會(huì)導(dǎo)致內(nèi)存泄漏。原因就是因?yàn)檫@個(gè)靜態(tài)變量的生命周期幾乎和整個(gè)應(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í)行延時(shí)操作的時(shí)候會(huì)一直持有當(dāng)前Activity的引用,如果在執(zhí)行延時(shí)操作的時(shí)候就結(jié)束 Activity,這樣就會(huì)導(dǎo)致內(nèi)存泄漏。解決辦法有兩種:第一種是使用靜態(tài)內(nèi)部類,在靜態(tài)內(nèi)部類中使用弱引用調(diào)用Activity。第二種方法是在 Activity 的 onDestroy 中調(diào)用 handler.removeCallbacksAndMessages 來(lái)取消延時(shí)事件。
使用資源未及時(shí)關(guān)閉導(dǎo)致的內(nèi)存泄漏。 常見的例子有:操作各種數(shù)據(jù)流未及時(shí)關(guān)閉,操作 Bitmap 未及時(shí) recycle 等等。
使用第三方庫(kù)未能及時(shí)解綁。 有的三方庫(kù)提供了注冊(cè)和解綁的功能,最常見的就 EventBus 了,我們都知道使用 EventBus 要在 onCreate 中注冊(cè),在 onDestroy 中解綁。如果沒(méi)有解綁的話,EventBus 其實(shí)是一個(gè)單例模式,他會(huì)一直持有 Activity 的引用,導(dǎo)致內(nèi)存泄漏。同樣常見的還有 RxJava,在使用 Timer 操作符做了一些延時(shí)操作后也要注意在 onDestroy 方法中調(diào)用 disposable.dispose()來(lái)取消操作。
屬性動(dòng)畫導(dǎo)致的內(nèi)存泄漏。 常見的例子就是在屬性動(dòng)畫執(zhí)行的過(guò)程中退出了 Activity,這時(shí) View 對(duì)象依然持有 Activity 的引用從而導(dǎo)致了內(nèi)存泄漏。解決辦法就是在 onDestroy 中調(diào)用動(dòng)畫的 cancel 方法取消屬性動(dòng)畫。
WebView 導(dǎo)致的內(nèi)存泄漏。WebView 比較特殊,即使是調(diào)用了它的 destroy 方法,依然會(huì)導(dǎo)致內(nèi)存泄漏。其實(shí)避免WebView導(dǎo)致內(nèi)存泄漏的最好方法就是讓W(xué)ebView所在的Activity處于另一個(gè)進(jìn)程中,當(dāng)這個(gè) Activity 結(jié)束時(shí)殺死當(dāng)前 WebView 所處的進(jìn)程即可,我記得阿里釘釘?shù)?WebView 就是另外開啟的一個(gè)進(jìn)程,應(yīng)該也是采用這種方法避免內(nèi)存泄漏。
為什么要擴(kuò)大我們的內(nèi)存呢?有時(shí)候我們實(shí)際開發(fā)中不可避免的要使用很多第三方商業(yè)的 SDK,這些 SDK 其實(shí)有好有壞,大廠的 SDK 可能內(nèi)存泄漏會(huì)少一些,但一些小廠的 SDK 質(zhì)量也就不太靠譜一些。那應(yīng)對(duì)這種我們無(wú)法改變的情況,最好的辦法就是擴(kuò)大內(nèi)存。
擴(kuò)大內(nèi)存通常有兩種方法:一個(gè)是在清單文件中的 Application 下添加largeHeap="true"這個(gè)屬性,另一個(gè)就是同一個(gè)應(yīng)用開啟多個(gè)進(jìn)程來(lái)擴(kuò)大一個(gè)應(yīng)用的總內(nèi)存空間。第二種方法其實(shí)就很常見了,比方說(shuō)我使用過(guò)個(gè)推的 S DK,個(gè)推的 Service 其實(shí)就是處在另外一個(gè)多帶帶的進(jìn)程中。
Android 中的內(nèi)存優(yōu)化總的來(lái)說(shuō)就是開源和節(jié)流,開源就是擴(kuò)大內(nèi)存,節(jié)流就是避免內(nèi)存泄漏。
在Linux中,為了避免一個(gè)進(jìn)程對(duì)其他進(jìn)程的干擾,進(jìn)程之間是相互獨(dú)立的。在一個(gè)進(jìn)程中其實(shí)還分為用戶空間和內(nèi)核空間。這里的隔離分為兩個(gè)部分,進(jìn)程間的隔離和進(jìn)程內(nèi)的隔離。
既然進(jìn)程間存在隔離,那其實(shí)也是存在著交互。進(jìn)程間通信就是 IPC,用戶空間和內(nèi)核空間的通信就是系統(tǒng)調(diào)用。
Linux 為了保證獨(dú)立性和安全性,進(jìn)程之間不能直接相互訪問(wèn),Android 是基于 Linux 的,所以也是需要解決進(jìn)程間通信的問(wèn)題。
其實(shí) Linux 進(jìn)程間通信有很多方式,比如管道、socket 等等。為什么 Android 進(jìn)程間通信采用了Binder而不是 Linux
已有的方式,主要是有這么兩點(diǎn)考慮:性能和安全
性能。 在移動(dòng)設(shè)備上對(duì)性能要求是比較嚴(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)程通信是不包含通信雙方的身份驗(yàn)證的,這樣會(huì)導(dǎo)致一些安全性問(wèn)題。而Binder機(jī)制自帶身份驗(yàn)證,從而有效的提高了安全性。
Binder 是基于 CS 架構(gòu)的,有四個(gè)主要組成部分。
Client。 客戶端進(jìn)程。
Server。 服務(wù)端進(jìn)程。
ServiceManager。 提供注冊(cè)、查詢和返回代理服務(wù)對(duì)象的功能。
Binder 驅(qū)動(dòng)。 主要負(fù)責(zé)建立進(jìn)程間的 Binder 連接,進(jìn)程間的數(shù)據(jù)交互等等底層操作。
Binder 機(jī)制主要的流程是這樣的:
服務(wù)端通過(guò)Binder驅(qū)動(dòng)在 ServiceManager 中注冊(cè)我們的服務(wù)。
客戶端通過(guò)Binder驅(qū)動(dòng)查詢?cè)?ServiceManager 中注冊(cè)的服務(wù)。
ServiceManager 通過(guò) inder 驅(qū)動(dòng)返回服務(wù)端的代理對(duì)象。
客戶端拿到服務(wù)端的代理對(duì)象后即可進(jìn)行進(jìn)程間通信。
LruCache的原理LruCache 的核心原理就是對(duì) LinkedHashMap 的有效利用,它的內(nèi)部存在一個(gè) LinkedHashMap 成員變量。值得我們關(guān)注的有四個(gè)方法:構(gòu)造方法、get、put、trimToSize。
構(gòu)造方法: 在 LruCache 的構(gòu)造方法中做了兩件事,設(shè)置了 maxSize、創(chuàng)建了一個(gè) LinkedHashMap。這里值得注意的是 LruCache 將 LinkedHashMap的accessOrder 設(shè)置為了 true,accessOrder 就是遍歷這個(gè)LinkedHashMap 的輸出順序。true 代表按照訪問(wèn)順序輸出,false代表按添加順序輸出,因?yàn)橥ǔ6际前凑仗砑禹樞蜉敵觯?accessOrder 這個(gè)屬性默認(rèn)是 false,但我們的 LruCache 需要按訪問(wèn)順序輸出,所以顯式的將 accessOrder 設(shè)置為 true。
get方法: 本質(zhì)上是調(diào)用 LinkedHashMap 的 get 方法,由于我們將 accessOrder 設(shè)置為了 true,所以每調(diào)用一次get方法,就會(huì)將我們?cè)L問(wèn)的當(dāng)前元素放置到這個(gè)LinkedHashMap的尾部。
put方法: 本質(zhì)上也是調(diào)用了 LinkedHashMap 的 put 方法,由于 LinkedHashMap 的特性,每調(diào)用一次 put 方法,也會(huì)將新加入的元素放置到 LinkedHashMap 的尾部。添加之后會(huì)調(diào)用 trimToSize 方法來(lái)保證添加后的內(nèi)存不超過(guò) maxSize。
trimToSize方法: trimToSize 方法的內(nèi)部其實(shí)是開啟了一個(gè) while(true)的死循環(huán),不斷的從 LinkedHashMap 的首部刪除元素,直到刪除之后的內(nèi)存小于 maxSize 之后使用 break 跳出循環(huán)。
其實(shí)到這里我們可以總結(jié)一下,為什么這個(gè)算法叫 最近最少使用 算法呢?原理很簡(jiǎn)單,我們的每次 put 或者get都可以看做一次訪問(wèn),由于 LinkedHashMap 的特性,會(huì)將每次訪問(wèn)到的元素放置到尾部。當(dāng)我們的內(nèi)存達(dá)到閾值后,會(huì)觸發(fā) trimToSize 方法來(lái)刪除 LinkedHashMap 首部的元素,直到當(dāng)前內(nèi)存小于 maxSize。為什么刪除首部的元素,原因很明顯:我們最近經(jīng)常訪問(wèn)的元素都會(huì)放置到尾部,那首部的元素肯定就是 最近最少使用 的元素了,因此當(dāng)內(nèi)存不足時(shí)應(yīng)當(dāng)優(yōu)先刪除這些元素。
設(shè)計(jì)一個(gè)圖片的異步加載框架設(shè)計(jì)一個(gè)圖片加載框架,肯定要用到圖片加載的三級(jí)緩存的思想。三級(jí)緩存分為內(nèi)存緩存、本地緩存和網(wǎng)絡(luò)緩存。
內(nèi)存緩存 :將Bitmap緩存到內(nèi)存中,運(yùn)行速度快,但是內(nèi)存容量小。
本地緩存 :將圖片緩存到文件中,速度較慢,但容量較大。
網(wǎng)絡(luò)緩存 :從網(wǎng)絡(luò)獲取圖片,速度受網(wǎng)絡(luò)影響。
如果我們?cè)O(shè)計(jì)一個(gè)圖片加載框架,流程一定是這樣的:
拿到圖片url后首先從內(nèi)存中查找BItmap,如果找到直接加載。
內(nèi)存中沒(méi)有找到,會(huì)從本地緩存中查找,如果本地緩存可以找到,則直接加載。
內(nèi)存和本地都沒(méi)有找到,這時(shí)會(huì)從網(wǎng)絡(luò)下載圖片,下載到后會(huì)加載圖片,并且將下載到的圖片放到內(nèi)存緩存和本地緩存中。
上面是一些基本的概念,如果是具體的代碼實(shí)現(xiàn)的話,大概需要這么幾個(gè)方面的文件:
首先需要確定我們的內(nèi)存緩存,這里一般用的都是 LruCache。
確定本地緩存,通常用的是 DiskLruCache,這里需要注意的是圖片緩存的文件名一般是 url 被 MD5 加密后的字符串,為了避免文件名直接暴露圖片的 url。
內(nèi)存緩存和本地緩存確定之后,需要我們創(chuàng)建一個(gè)新的類 MemeryAndDiskCache,當(dāng)然,名字隨便起,這個(gè)類包含了之前提到的 LruCache 和 DiskLruCache。在 MemeryAndDiskCache 這個(gè)類中我們定義兩個(gè)方法,一個(gè)是 getBitmap,另一個(gè)是 putBitmap,對(duì)應(yīng)著圖片的獲取和緩存,內(nèi)部的邏輯也很簡(jiǎn)單。getBitmap中按內(nèi)存、本地的優(yōu)先級(jí)去取 BItmap,putBitmap 中先緩存內(nèi)存,之后緩存到本地。
在緩存策略類確定好之后,我們創(chuàng)建一個(gè) ImageLoader 類,這個(gè)類必須包含兩個(gè)方法,一個(gè)是展示圖片 displayImage(url,imageView),另一個(gè)是從網(wǎng)絡(luò)獲取圖片downloadImage(url,imageView)。在展示圖片方法中首先要通過(guò) ImageView.setTag(url),將 url 和 imageView 進(jìn)行綁定,這是為了避免在列表中加載網(wǎng)絡(luò)圖片時(shí)會(huì)由于ImageView的復(fù)用導(dǎo)致的圖片錯(cuò)位的 bug。之后會(huì)從 MemeryAndDiskCache 中獲取緩存,如果存在,直接加載;如果不存在,則調(diào)用從網(wǎng)絡(luò)獲取圖片這個(gè)方法。從網(wǎng)絡(luò)獲取圖片方法很多,這里我一般都會(huì)使用 OkHttp+Retrofit。當(dāng)從網(wǎng)絡(luò)中獲取到圖片之后,首先判斷一下imageView.getTag()與圖片的 url 是否一致,如果一致則加載圖片,如果不一致則不加載圖片,通過(guò)這樣的方式避免了列表中異步加載圖片的錯(cuò)位。同時(shí)在獲取到圖片之后會(huì)通過(guò) MemeryAndDiskCache 來(lái)緩存圖片。
Android中的事件分發(fā)機(jī)制在我們的手指觸摸到屏幕的時(shí)候,事件其實(shí)是通過(guò) Activity -> ViewGroup -> View 這樣的流程到達(dá)最后響應(yīng)我們觸摸事件的 View。
說(shuō)到事件分發(fā),必不可少的是這幾個(gè)方法:dispatchTouchEvent()、onInterceptTouchEvent()、onTouchEvent。接下來(lái)就按照Activity -> ViewGroup -> View 的流程來(lái)大致說(shuō)一下事件分發(fā)機(jī)制。
我們的手指觸摸到屏幕的時(shí)候,會(huì)觸發(fā)一個(gè) Action_Down 類型的事件,當(dāng)前頁(yè)面的 Activity 會(huì)首先做出響應(yīng),也就是說(shuō)會(huì)走到 Activity 的 dispatchTouchEvent() 方法內(nèi)。在這個(gè)方法內(nèi)部簡(jiǎn)單來(lái)說(shuō)是這么一個(gè)邏輯:
調(diào)用 getWindow.superDispatchTouchEvent()。
如果上一步返回 true,直接返回 true;否則就 return 自己的 onTouchEvent()。
這個(gè)邏輯很好理解,getWindow().superDispatchTouchEvent() 如果返回 true 代表當(dāng)前事件已經(jīng)被處理,無(wú)需調(diào)用自己的 onTouchEvent;否則代表事件并沒(méi)有被處理,需要 Activity 自己處理,也就是調(diào)用自己的 onTouchEvent。
getWindow()方法返回了一個(gè) Window 類型的對(duì)象,這個(gè)我們都知道,在 Android 中,PhoneWindow 是Window 的唯一實(shí)現(xiàn)類。所以這句本質(zhì)上是調(diào)用了`PhoneWindow中的superDispatchTouchEvent()。
而在 PhoneWindow 的這個(gè)方法中實(shí)際調(diào)用了mDecor.superDispatchTouchEvent(event)。這個(gè) mDecor 就是 DecorView,它是 FrameLayout 的一個(gè)子類,在 DecorView 中的 superDispatchTouchEvent() 中調(diào)用的是 super.dispatchTouchEvent()。到這里就很明顯了,DecorView 是一個(gè) FrameLayout 的子類,F(xiàn)rameLayout 是一個(gè) ViewGroup 的子類,本質(zhì)上調(diào)用的還是 ViewGroup的dispatchTouchEvent()。
分析到這里,我們的事件已經(jīng)從 Activity 傳遞到了 ViewGroup,接下來(lái)我們來(lái)分析下 ViewGroup 中的這幾個(gè)事件處理方法。
在 ViewGroup 中的 dispatchTouchEvent()中的邏輯大致如下:
通過(guò) onInterceptTouchEvent() 判斷當(dāng)前 ViewGroup 是否攔截事件,默認(rèn)的 ViewGroup 都是不攔截的;
如果攔截,則 return 自己的 onTouchEvent();
如果不攔截,則根據(jù) child.dispatchTouchEvent()的返回值判斷。如果返回 true,則 return true;否則 return 自己的 onTouchEvent(),在這里實(shí)現(xiàn)了未處理事件的向上傳遞。
通常情況下 ViewGroup 的 onInterceptTouchEvent()都返回 false,也就是不攔截。這里需要注意的是事件序列,比如 Down 事件、Move 事件......Up事件,從 Down 到 Up 是一個(gè)完整的事件序列,對(duì)應(yīng)著手指從按下到抬起這一系列的事件,如果 ViewGroup 攔截了 Down 事件,那么后續(xù)事件都會(huì)交給這個(gè) ViewGroup的onTouchEvent。如果 ViewGroup 攔截的不是 Down 事件,那么會(huì)給之前處理這個(gè) Down 事件的 View 發(fā)送一個(gè) Action_Cancel 類型的事件,通知子 View 這個(gè)后續(xù)的事件序列已經(jīng)被 ViewGroup 接管了,子 View 恢復(fù)之前的狀態(tài)即可。
這里舉一個(gè)常見的例子:在一個(gè) Recyclerview 鐘有很多的 Button,我們首先按下了一個(gè) button,然后滑動(dòng)一段距離再松開,這時(shí)候 Recyclerview 會(huì)跟著滑動(dòng),并不會(huì)觸發(fā)這個(gè) button 的點(diǎn)擊事件。這個(gè)例子中,當(dāng)我們按下 button 時(shí),這個(gè) button 接收到了 Action_Down 事件,正常情況下后續(xù)的事件序列應(yīng)該由這個(gè) button處理。但我們滑動(dòng)了一段距離,這時(shí) Recyclerview 察覺(jué)到這是一個(gè)滑動(dòng)操作,攔截了這個(gè)事件序列,走了自身的 onTouchEvent()方法,反映在屏幕上就是列表的滑動(dòng)。而這時(shí) button 仍然處于按下的狀態(tài),所以在攔截的時(shí)候需要發(fā)送一個(gè) Action_Cancel 來(lái)通知 button 恢復(fù)之前狀態(tài)。
事件分發(fā)最終會(huì)走到 View 的 dispatchTouchEvent()中。在 View 的 dispatchTouchEvent() 中沒(méi)有 onInterceptTouchEvent(),這也很容易理解,View 不是 ViewGroup,不會(huì)包含其他子 View,所以也不存在攔截不攔截這一說(shuō)。忽略一些細(xì)節(jié),View 的 dispatchTouchEvent()中直接 return 了自己的 onTouchEvent()。如果 onTouchEvent()返回 true 代表事件被處理,否則未處理的事件會(huì)向上傳遞,直到有 View 處理了事件或者一直沒(méi)有處理,最終到達(dá)了 Activity 的 onTouchEvent() 終止。
這里經(jīng)常有人問(wèn) onTouch 和 onTouchEvent 的區(qū)別。首先,這兩個(gè)方法都在 View 的 dispatchTouchEvent()中,是這么一個(gè)邏輯:
如果 touchListener 不為 null,并且這個(gè) View 是 enable 的,而且 onTouch 返回的是 true,滿足這三個(gè)條件時(shí)會(huì)直接 return true,不會(huì)走 onTouchEvent()方法。
上面只要有一個(gè)條件不滿足,就會(huì)走到 onTouchEvent()方法中。所以 onTouch 的順序是在 onTouchEvent 之前的。
View的繪制流程視圖繪制的起點(diǎn)在 ViewRootImpl 類的 performTraversals()方法,在這個(gè)方法內(nèi)其實(shí)是按照順序依次調(diào)用了 mView.measure()、mView.layout()、mView.draw()
View的繪制流程分為3步:測(cè)量、布局、繪制,分別對(duì)應(yīng)3個(gè)方法 measure、layout、draw。
測(cè)量階段。 measure 方法會(huì)被父 View 調(diào)用,在measure 方法中做一些優(yōu)化和準(zhǔn)備工作后會(huì)調(diào)用 onMeasure 方法進(jìn)行實(shí)際的自我測(cè)量。onMeasure方法在View和ViewGroup做的事情是不一樣的:
View。 View 中的 onMeasure 方法會(huì)計(jì)算自己的尺寸并通過(guò) setMeasureDimension 保存。
ViewGroup。 ViewGroup 中的 onMeasure 方法會(huì)調(diào)用所有子 iew的measure 方法進(jìn)行自我測(cè)量并保存。然后通過(guò)子View的尺寸和位置計(jì)算出自己的尺寸并保存。
布局階段。 layout 方法會(huì)被父View調(diào)用,layout 方法會(huì)保存父 View 傳進(jìn)來(lái)的尺寸和位置,并調(diào)用 onLayout 進(jìn)行實(shí)際的內(nèi)部布局。onLayout 在 View 和 ViewGroup 中做的事情也是不一樣的:
View。 因?yàn)?View 是沒(méi)有子 View 的,所以View的onLayout里面什么都不做。
ViewGroup。 ViewGroup 中的 onLayout 方法會(huì)調(diào)用所有子 View 的 layout 方法,把尺寸和位置傳給他們,讓他們完成自我的內(nèi)部布局。
繪制階段。 draw 方法會(huì)做一些調(diào)度工作,然后會(huì)調(diào)用 onDraw 方法進(jìn)行 View 的自我繪制。draw 方法的調(diào)度流程大致是這樣的:
繪制背景。 對(duì)應(yīng) drawBackground(Canvas)方法。
繪制主體。 對(duì)應(yīng) onDraw(Canvas)方法。
繪制子View。 對(duì)應(yīng) dispatchDraw(Canvas)方法。
繪制滑動(dòng)相關(guān)和前景。 對(duì)應(yīng) onDrawForeground(Canvas)。
Android與 js 是如何交互的在 Android 中,Android 與js 的交互分為兩個(gè)方面:Android 調(diào)用 js 里的方法、js 調(diào)用 Android 中的方法。
Android調(diào)js。 Android 調(diào) js 有兩種方法:
WebView.loadUrl("javascript:js中的方法名")。 這種方法的優(yōu)點(diǎn)是很簡(jiǎn)潔,缺點(diǎn)是沒(méi)有返回值,如果需要拿到j(luò)s方法的返回值則需要js調(diào)用Android中的方法來(lái)拿到這個(gè)返回值。
WebView.evaluateJavaScript("javascript:js中的方法名",ValueCallback)。 這種方法比 loadUrl 好的是可以通過(guò) ValueCallback 這個(gè)回調(diào)拿到 js方法的返回值。缺點(diǎn)是這個(gè)方法 Android4.4 才有,兼容性較差。不過(guò)放在 2018 年來(lái)說(shuō),市面上絕大多數(shù) App 都要求最低版本是 4.4 了,所以我認(rèn)為這個(gè)兼容性問(wèn)題不大。
js 調(diào) Android。 js 調(diào) Android有三種方法:
WebView.addJavascriptInterface()。 這是官方解決 js 調(diào)用 Android 方法的方案,需要注意的是要在供 js 調(diào)用的 Android 方法上加上 @JavascriptInterface 注解,以避免安全漏洞。這種方案的缺點(diǎn)是 Android4.2 以前會(huì)有安全漏洞,不過(guò)在 4.2 以后已經(jīng)修復(fù)了。同樣,在 2018 年來(lái)說(shuō),兼容性問(wèn)題不大。
重寫 WebViewClient的shouldOverrideUrlLoading()方法來(lái)攔截url, 拿到 url 后進(jìn)行解析,如果符合雙方的規(guī)定,即可調(diào)用 Android 方法。優(yōu)點(diǎn)是避免了 Android4.2 以前的安全漏洞,缺點(diǎn)也很明顯,無(wú)法直接拿到調(diào)用 Android 方法的返回值,只能通過(guò) Android 調(diào)用 js 方法來(lái)獲取返回值。
重寫 WebChromClient 的 onJsPrompt() 方法,同前一個(gè)方式一樣,拿到 url 之后先進(jìn)行解析,如果符合雙方規(guī)定,即可調(diào)用Android方法。最后如果需要返回值,通過(guò) result.confirm("Android方法返回值") 即可將 Android 的返回值返回給 js。方法的優(yōu)點(diǎn)是沒(méi)有漏洞,也沒(méi)有兼容性限制,同時(shí)還可以方便的獲取 Android 方法的返回值。其實(shí)這里需要注意的是在 WebChromeClient 中除 了 onJsPrompt 之外還有 onJsAlert 和 onJsConfirm 方法。那么為什么不選擇另兩個(gè)方法呢?原因在于 onJsAlert 是沒(méi)有返回值的,而 onJsConfirm 只有 true 和 false 兩個(gè)返回值,同時(shí)在前端開發(fā)中 prompt 方法基本不會(huì)被調(diào)用,所以才會(huì)采用 onJsPrompt。
Activity 啟動(dòng)過(guò)程 SparseArray 原理SparseArray,通常來(lái)講是 Android 中用來(lái)替代 HashMap 的一個(gè)數(shù)據(jù)結(jié)構(gòu)。
準(zhǔn)確來(lái)講,是用來(lái)替換key為 Integer 類型,value為Object 類型的HashMap。需要注意的是 SparseArray 僅僅實(shí)現(xiàn)了 Cloneable 接口,所以不能用Map來(lái)聲明。
從內(nèi)部結(jié)構(gòu)來(lái)講,SparseArray 內(nèi)部由兩個(gè)數(shù)組組成,一個(gè)是 int[]類型的 mKeys,用來(lái)存放所有的鍵;另一個(gè)是 Object[]類型的 mValues,用來(lái)存放所有的值。
最常見的是拿 SparseArray 跟HashMap 來(lái)做對(duì)比,由于 SparseArray 內(nèi)部組成是兩個(gè)數(shù)組,所以占用內(nèi)存比 HashMap 要小。我們都知道,增刪改查等操作都首先需要找到相應(yīng)的鍵值對(duì),而 SparseArray 內(nèi)部是通過(guò)二分查找來(lái)尋址的,效率很明顯要低于 HashMap 的常數(shù)級(jí)別的時(shí)間復(fù)雜度。提到二分查找,這里還需要提一下的是二分查找的前提是數(shù)組已經(jīng)是排好序的,沒(méi)錯(cuò),SparseArray 中就是按照key進(jìn)行升序排列的。
綜合起來(lái)來(lái)說(shuō),SparseArray 所占空間優(yōu)于 HashMap,而效率低于 HashMap,是典型的時(shí)間換空間,適合較小容量的存儲(chǔ)。
從源碼角度來(lái)說(shuō),我認(rèn)為需要注意的是 SparseArray的remove()、put()和 gc()方法。
remove()。 SparseArray 的 remove() 方法并不是直接刪除之后再壓縮數(shù)組,而是將要?jiǎng)h除的 value 設(shè)置為 DELETE 這個(gè) SparseArray 的靜態(tài)屬性,這個(gè) DELETE 其實(shí)就是一個(gè) Object 對(duì)象,同時(shí)會(huì)將 SparseArray 中的 mGarbage 這個(gè)屬性設(shè)置為 true,這個(gè)屬性是便于在合適的時(shí)候調(diào)用自身的 gc()方法壓縮數(shù)組來(lái)避免浪費(fèi)空間。這樣可以提高效率,如果將來(lái)要添加的key等于刪除的key,那么會(huì)將要添加的 value 覆蓋 DELETE。
gc()。 SparseArray 中的 gc() 方法跟 JVM 的 GC 其實(shí)完全沒(méi)有任何關(guān)系。`gc() 方法的內(nèi)部實(shí)際上就是一個(gè)for循環(huán),將 value 不為 DELETE 的鍵值對(duì)往前移動(dòng)覆蓋value 為DELETE的鍵值對(duì)來(lái)實(shí)現(xiàn)數(shù)組的壓縮,同時(shí)將 mGarbage 置為 false,避免內(nèi)存的浪費(fèi)。
put()。 put 方法是這么一個(gè)邏輯,如果通過(guò)二分查找 在 mKeys 數(shù)組中找到了 key,那么直接覆蓋 value 即可。如果沒(méi)有找到,會(huì)拿到與數(shù)組中與要添加的 key 最接近的 key 索引,如果這個(gè)索引對(duì)應(yīng)的 value 為 DELETE,則直接把新的 value 覆蓋 DELET 即可,在這里可以避免數(shù)組元素的移動(dòng),從而提高了效率。如果 value 不為 DELETE,會(huì)判斷 mGarbage,如果為 true,則會(huì)調(diào)用 gc()方法壓縮數(shù)組,之后會(huì)找到合適的索引,將索引之后的鍵值對(duì)后移,插入新的鍵值對(duì),這個(gè)過(guò)程中可能會(huì)觸發(fā)數(shù)組的擴(kuò)容。
圖片加載如何避免 OOM我們知道內(nèi)存中的 Bitmap 大小的計(jì)算公式是:長(zhǎng)所占像素 寬所占像素 每個(gè)像素所占內(nèi)存。想避免 OOM 有兩種方法:等比例縮小長(zhǎng)寬、減少每個(gè)像素所占的內(nèi)存。
等比縮小長(zhǎng)寬。我們知道 Bitmap 的創(chuàng)建是通過(guò) BitmapFactory 的工廠方法,decodeFile()、decodeStream()、decodeByteArray()、decodeResource()。這些方法中都有一個(gè) Options 類型的參數(shù),這個(gè) Options 是 BitmapFactory 的內(nèi)部類,存儲(chǔ)著 BItmap 的一些信息。Options 中有一個(gè)屬性:inSampleSize。我們通過(guò)修改 inSampleSize 可以縮小圖片的長(zhǎng)寬,從而減少 BItma p 所占內(nèi)存。需要注意的是這個(gè) inSampleSize 大小需要是 2 的冪次方,如果小于 1,代碼會(huì)強(qiáng)制讓inSampleSize為1。
減少像素所占內(nèi)存。Options 中有一個(gè)屬性 inPreferredConfig,默認(rèn)是 ARGB_8888,代表每個(gè)像素所占尺寸。我們可以通過(guò)將之修改為 RGB_565 或者 ARGB_4444 來(lái)減少一半內(nèi)存。
大圖加載加載高清大圖,比如清明上河圖,首先屏幕是顯示不下的,而且考慮到內(nèi)存情況,也不可能一次性全部加載到內(nèi)存。這時(shí)候就需要局部加載了,Android中有一個(gè)負(fù)責(zé)局部加載的類:BitmapRegionDecoder。使用方法很簡(jiǎn)單,通過(guò)BitmapRegionDecoder.newInstance()創(chuàng)建對(duì)象,之后調(diào)用decodeRegion(Rect rect, BitmapFactory.Options options)即可。第一個(gè)參數(shù)rect是要顯示的區(qū)域,第二個(gè)參數(shù)是BitmapFactory中的內(nèi)部類Options。
OkHttpOkHttp源碼分析
RetrofitRetrofit源碼分析
RxJavaRxJava源碼分析
GlideGlide源碼分析
EventBusEventBus源碼分析
#### 閱讀更多
程序員如何寫一份更好的簡(jiǎn)歷?
你這樣介紹項(xiàng)目,輕松搞定面試官|(zhì)offer加分必備
NDK項(xiàng)目實(shí)戰(zhàn)—高仿360手機(jī)助手之卸載監(jiān)聽
(Android)面試題級(jí)答案(精選版)
裸辭后,從Android轉(zhuǎn)戰(zhàn)Web前端的學(xué)習(xí)以及求職之路
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/71789.html
摘要:九安卓中如何取出日志信息把安卓系統(tǒng)日志信息實(shí)時(shí)導(dǎo)入到本地運(yùn)行使用某個(gè),實(shí)時(shí)獲取該的日志信息里面的返回信息接口自動(dòng)化面試題一按你的理解,軟件接口是什么答就是指程序中具體負(fù)責(zé)在不同模塊之間傳輸或接受數(shù)據(jù)的并做處理的類或者函數(shù)。 ...
摘要:文章內(nèi)容包括數(shù)據(jù)結(jié)構(gòu)與算法。因?yàn)槲恼聝?nèi)容太多,加上思否平臺(tái)對(duì)文章大小的限制,所以發(fā)不了該文章,只能保存在文件里面,已經(jīng)提交到上了。文章地址數(shù)據(jù)結(jié)構(gòu)與算法 文章內(nèi)容包括:HTML + CSS + JS + ES6 + Webpack + Vue + React + Node + HTTPS + 數(shù)據(jù)結(jié)構(gòu)與算法 + Git。因?yàn)槲恼聝?nèi)容太多,加上思否平臺(tái)對(duì)文章大小的限制,所以發(fā)不了該文章,...
閱讀 999·2019-08-30 15:55
閱讀 3440·2019-08-30 13:10
閱讀 1268·2019-08-29 18:45
閱讀 2347·2019-08-29 16:25
閱讀 2107·2019-08-29 15:13
閱讀 2422·2019-08-29 11:29
閱讀 552·2019-08-26 17:34
閱讀 1486·2019-08-26 13:57