聲明本文由做者:Man不經心受權轉載,轉載請聯繫原文做者 原文連接:www.jianshu.com/p/375ad1409…javascript
類加載過程
Java 中類加載分爲 3 個步驟:加載、連接、初始化。前端
-
加載。 加載是將字節碼數據從不一樣的數據源讀取到JVM內存,並映射爲 JVM 承認的數據結構,也就是 Class 對象的過程。數據源能夠是 Jar 文件、Class 文件等等。若是數據的格式並非 ClassFile 的結構,則會報 ClassFormatError。java
-
連接。 連接是類加載的核心部分,這一步分爲 3 個步驟:驗證、準備、解析。 驗證。 驗證是保證JVM安全的重要步驟。JVM須要校驗字節信息是否符合規範,避免惡意信息和不規範數據危害JVM運行安全。若是驗證出錯,則會報VerifyError。 準備。 這一步會建立靜態變量,併爲靜態變量開闢內存空間。 解析。 這一步會將符號引用替換爲直接引用。程序員
-
初始化。 初始化會爲靜態變量賦值,並執行靜態代碼塊中的邏輯。面試
雙親委派模型
類加載器大體分爲3類:啓動類加載器、擴展類加載器、應用程序類加載器。算法
- 啓動類加載器主要加載 jre/lib下的jar文件。
- 擴展類加載器主要加載 jre/lib/ext 下的jar文件。
- 應用程序類加載器主要加載 classpath 下的文件。
所謂的雙親委派模型就是當加載一個類時,會優先使用父類加載器加載,當父類加載器沒法加載時纔會使用子類加載器去加載。這麼作的目的是爲了不類的重複加載。數組
Java 中的集合類
HashMap 的原理
HashMap 的內部能夠看作數組+鏈表的複合結構。數組被分爲一個個的桶(bucket)。哈希值決定了鍵值對在數組中的尋址。具備相同哈希值的鍵值對會組成鏈表。須要注意的是當鏈表長度超過閾值(默認是8)的時候會觸發樹化,鏈表會變成樹形結構。瀏覽器
把握HashMap的原理須要關注4個方法:hash、put、get、resize。
sleep 和 wait 的區別
- sleep 方法是 Thread 類中的靜態方法,wait 是 Object 類中的方法
- sleep 並不會釋放同步鎖,而 wait 會釋放同步鎖
- sleep 能夠在任何地方使用,而 wait 只能在同步方法或者同步代碼塊中使用
- sleep 中必須傳入時間,而 wait 能夠傳,也能夠不傳,不傳時間的話只有 notify 或者 notifyAll - 才能喚醒,傳時間的話在時間以後會自動喚醒
volatile和synchronize的區別
final、finally、finalize區別
- final 能夠修飾類、變量和方法。修飾類表明這個類不可被繼承。修飾變量表明此變量不可被改變。修飾方法表示此方法不可被重寫 (override)。
- finally 是保證重點代碼必定會執行的一種機制。一般是使用 try-finally 或者 try-catch-finally 來進行文件流的關閉等操做。
- finalize 是 Object 類中的一個方法,它的設計目的是保證對象在垃圾收集前完成特定資源的回收。finalize 機制如今已經不推薦使用,而且在 JDK 9已經被標記爲 deprecated。
Java中引用類型的區別,具體的使用場景
Java中引用類型分爲四類:強引用、軟引用、弱引用、虛引用。
- 強引用: 強引用指的是經過 new 對象建立的引用,垃圾回收器即便是內存不足也不會回收強引用指向的對象。
- 軟引用: 軟引用是經過 SoftRefrence 實現的,它的生命週期比強引用短,在內存不足,拋出 OOM 以前,垃圾回收器會回收軟引用引用的對象。軟引用常見的使用場景是存儲一些內存敏感的緩存,當內存不足時會被回收。
- 弱引用: 弱引用是經過 WeakRefrence 實現的,它的生命週期比軟引用還短,GC 只要掃描到弱引用的對象就會回收。弱引用常見的使用場景也是存儲一些內存敏感的緩存。
- 虛引用: 虛引用是經過 FanttomRefrence 實現的,它的生命週期最短,隨時可能被回收。若是一個對象只被虛引用引用,咱們沒法經過虛引用來訪問這個對象的任何屬性和方法。它的做用僅僅是保證對象在 finalize 後,作某些事情。虛引用常見的使用場景是跟蹤對象被垃圾回收的活動,當一個虛引用關聯的對象被垃圾回收器回收以前會收到一條系統通知。
Exception 和 Error的區別
- Exception 和 Error 都繼承於 Throwable,在 Java 中,只有 Throwable 類型的對象才能被 throw 或者 catch,它是異常處理機制的基本組成類型.
- Exception 和 Error 體現了 Java 對不一樣異常狀況的分類。Exception 是程序正常運行中,能夠預料的意外狀況,可能而且應該被捕獲,進行相應的處理。
- Error 是指在正常狀況下,不大可能出現的狀況,絕大部分 Error 都會使程序處於非正常、不可恢復的狀態。既然是非正常,因此不便於也不須要捕獲,常見的 OutOfMemoryError 就是 Error 的子類。
- Exception 又分爲 checked Exception 和 unchecked Exception。
- checked Exception 在代碼裏必須顯式的進行捕獲,這是編譯器檢查的一部分。
- unchecked Exception 也就是運行時異常,相似空指針異常、數組越界等,一般是能夠避免的邏輯錯誤,具體根據需求來判斷是否須要捕獲,並不會在編譯器強制要求。
--------------------網絡相關面試題-------------------
http 與 https 的區別?https 是如何工做的?
http 是超文本傳輸協議,而 https 能夠簡單理解爲安全的 http 協議。https 經過在 http 協議下添加了一層 ssl 協議對數據進行加密從而保證了安全。https 的做用主要有兩點:創建安全的信息傳輸通道,保證數據傳輸安全;確認網站的真實性。
http 與 https 的區別主要以下:
- https 須要到 CA 申請證書,不多免費,於是須要必定的費用
- http 是明文傳輸,安全性低;而 https 在 http 的基礎上經過 ssl 加密,安全性高
- 兩者的默認端口不同,http 使用的默認端口是80;https使用的默認端口是 443
https 的工做流程
提到 https 的話首先要說到加密算法,加密算法分爲兩類:對稱加密和非對稱加密。
- 對稱加密: 加密和解密用的都是相同的祕鑰,優勢是速度快,缺點是安全性低。常見的對稱加密算法有 DES、AES 等等。
- 非對稱加密: 非對稱加密有一個祕鑰對,分爲公鑰和私鑰。通常來講,私鑰本身持有,公鑰能夠公開給對方,優勢是安全性比對稱加密高,缺點是數據傳輸效率比對稱加密低。採用公鑰加密的信息只有對應的私鑰能夠解密。常見的非對稱加密包括RSA等。 在正式的使用場景中通常都是對稱加密和非對稱加密結合使用,使用非對稱加密完成祕鑰的傳遞,而後使用對稱祕鑰進行數據加密和解密。兩者結合既保證了安全性,又提升了數據傳輸效率。
https 的具體流程以下:
1.客戶端(一般是瀏覽器)先向服務器發出加密通訊的請求
- 支持的協議版本,好比 TLS 1.0版
- 一個客戶端生成的隨機數 random1,稍後用於生成"對話密鑰"
- 支持的加密方法,好比 RSA 公鑰加密
- 支持的壓縮方法 2.服務器收到請求,而後響應
- 確認使用的加密通訊協議版本,好比 TLS 1.0版本。若是瀏覽器與服務器支持的版本不一致,服務器關閉加密通訊
- 一個服務器生成的隨機數 random2,稍後用於生成"對話密鑰"
- 確認使用的加密方法,好比 RSA 公鑰加密
- 服務器證書 3.客戶端收到證書以後會首先會進行驗證
- 首先驗證證書的安全性
- 驗證經過以後,客戶端會生成一個隨機數 pre-master secret,而後使用證書中的公鑰進行加密,而後傳遞給服務器端 4.服務器收到使用公鑰加密的內容,在服務器端使用私鑰解密以後得到隨機數 pre-master secret,而後根據 radom一、radom二、pre-master secret 經過必定的算法得出一個對稱加密的祕鑰,做爲後面交互過程當中使用對稱祕鑰。同時客戶端也會使用 radom一、radom二、pre-master secret,和一樣的算法生成對稱祕鑰。 5.而後再後續的交互中就使用上一步生成的對稱祕鑰對傳輸的內容進行加密和解密。
TCP三次握手流程
-----------------Android面試題-------------------
進程間通訊的方式有哪幾種
AIDL 、廣播、文件、socket、管道
廣播靜態註冊和動態註冊的區別
- 動態註冊廣播不是常駐型廣播,也就是說廣播跟隨 Activity 的生命週期。注意在 Activity 結束前,移除廣播接收器。 靜態註冊是常駐型,也就是說當應用程序關閉後,若是有信息廣播來,程序也會被系統調用自動運行。
- 當廣播爲有序廣播時:優先級高的先接收(不分靜態和動態)。同優先級的廣播接收器,動態優先於靜態
- 同優先級的同類廣播接收器,靜態:先掃描的優先於後掃描的,動態:先註冊的優先於後註冊的。
- 當廣播爲默認廣播時:無視優先級,動態廣播接收器優先於靜態廣播接收器。同優先級的同類廣播接收器,靜態:先掃描的優先於後掃描的,動態:先註冊的優先於後冊的。
Android 性能優化工具使用(這個問題建議配合Android中的性能優化)
- Android 中經常使用的性能優化工具包括這些:Android Studio 自帶的 Android Profiler、LeakCanary、BlockCanary
- Android 自帶的 Android Profiler 其實就很好用,Android Profiler 能夠檢測三個方面的性能問題:CPU、MEMORY、NETWORK。
- LeakCanary 是一個第三方的檢測內存泄漏的庫,咱們的項目集成以後 LeakCanary 會自動檢測應用運行期間的內存泄漏,並將之輸出給咱們。
- BlockCanary 也是一個第三方檢測UI卡頓的庫,項目集成後Block也會自動檢測應用運行期間的UI卡頓,並將之輸出給咱們。
Android中的類加載器
PathClassLoader,只能加載系統中已經安裝過的 apk DexClassLoader,能夠加載 jar/apk/dex,能夠從 SD卡中加載未安裝的 apk
Android中的動畫有哪幾類,它們的特色和區別是什麼
Android中動畫大體分爲3類:幀動畫、補間動畫(View Animation)、屬性動畫(Object Animation)。
- 幀動畫:經過xml配置一組圖片,動態播放。不多會使用。
- 補間動畫(View Animation):大體分爲旋轉、透明、縮放、位移四類操做。不多會使用。
- 屬性動畫(Object Animation):屬性動畫是如今使用的最多的一種動畫,它比補間動畫更增強大。屬性動畫大體分爲兩種使用類型,分別是 ViewPropertyAnimator 和 ObjectAnimator。前者適合一些通用的動畫,好比旋轉、位移、縮放和透明,使用方式也很簡單經過 View.animate() 便可獲得 ViewPropertyAnimator,以後進行相應的動畫操做便可。後者適合用於爲咱們的自定義控件添加動畫,固然首先咱們應該在自定義 View 中添加相應的 getXXX() 和 setXXX() 相應屬性的 getter 和 setter 方法,這裏須要注意的是在 setter 方法內改變了自定義 View 中的屬性後要調用 invalidate() 來刷新View的繪製。以後調用 ObjectAnimator.of 屬性類型()返回一個 ObjectAnimator,調用 start() 方法啓動動畫便可。
補間動畫與屬性動畫的區別:
- 補間動畫是父容器不斷的繪製 view,看起來像移動了效果,其實 view 沒有變化,還在原地。
- 是經過不斷改變 view 內部的屬性值,真正的改變 view。
Handler 機制
說到 Handler,就不得不提與之密切相關的這幾個類:Message、MessageQueue,Looper。
- Message。 Message 中有兩個成員變量值得關注:target 和 callback。
- target 其實就是發送消息的 Handler 對象
- callback 是當調用 handler.post(runnable) 時傳入的 Runnable 類型的任務。post 事件的本質也是建立了一個 Message,將咱們傳入的這個 runnable 賦值給建立的Message的 callback 這個成員變量。
- MessageQueue。 消息隊列很明顯是存放消息的隊列,值得關注的是 MessageQueue 中的 next() 方法,它會返回下一個待處理的消息。
- Looper。 Looper 消息輪詢器實際上是鏈接 Handler 和消息隊列的核心。首先咱們都知道,若是想要在一個線程中建立一個 Handler,首先要經過 Looper.prepare() 建立 Looper,以後還得調用 Looper.loop()開啓輪詢。咱們着重看一下這兩個方法。
- prepare()。 這個方法作了兩件事:首先經過ThreadLocal.get()獲取當前線程中的Looper,若是不爲空,則會拋出一個RunTimeException,意思是一個線程不能建立2個Looper。若是爲null則執行下一步。第二步是建立了一個Looper,並經過 ThreadLocal.set(looper)。將咱們建立的Looper與當前線程綁定。這裏須要提一下的是消息隊列的建立其實就發生在Looper的構造方法中。
- loop()。 這個方法開啓了整個事件機制的輪詢。它的本質是開啓了一個死循環,不斷的經過 MessageQueue的next()方法獲取消息。拿到消息後會調用 msg.target.dispatchMessage()來作處理。其實咱們在說到 Message 的時候提到過,msg.target 其實就是發送這個消息的 handler。這句代碼的本質就是調用 handler的dispatchMessage()。
- Handler。 上面作了這麼多鋪墊,終於到了最重要的部分。Handler 的分析着重在兩個部分:發送消息和處理消息。
- 發送消息。其實發送消息除了 sendMessage 以外還有 sendMessageDelayed 和 post 以及 postDelayed 等等不一樣的方式。但它們的本質都是調用了 sendMessageAtTime。在 sendMessageAtTime 這個方法中調用了 enqueueMessage。在 enqueueMessage 這個方法中作了兩件事:經過 msg.target = this 實現了消息與當前 handler 的綁定。而後經過 queue.enqueueMessage 實現了消息入隊。
- 處理消息。 消息處理的核心其實就是dispatchMessage()這個方法。這個方法裏面的邏輯很簡單,先判斷 msg.callback 是否爲 null,若是不爲空則執行這個 runnable。若是爲空則會執行咱們的handleMessage方法。
Android 性能優化
Android 中的性能優化在我看來分爲如下幾個方面:內存優化、佈局優化、網絡優化、安裝包優化。
- 內存優化: 下一個問題就是。
- 佈局優化: 佈局優化的本質就是減小 View 的層級。常見的佈局優化方案以下
- 在 LinearLayout 和 RelativeLayout 均可以完成佈局的狀況下優先選擇 RelativeLayout,能夠減小 View 的層級
- 將經常使用的佈局組件抽取出來使用 < include >標籤
- 經過 < ViewStub >標籤來加載不經常使用的佈局
- 使用 < Merge >標籤來減小布局的嵌套層次
- 網絡優化: 常見的網絡優化方案以下
- 儘可能減小網絡請求,可以合併的就儘可能合併
- 避免 DNS 解析,根據域名查詢可能會耗費上百毫秒的時間,也可能存在DNS劫持的風險。能夠根據業務需求採用增長動態更新 IP 的方式,或者在 IP 方式訪問失敗時切換到域名訪問方式。
- 大量數據的加載採用分頁的方式
- 網絡數據傳輸採用 GZIP 壓縮
- 加入網絡數據的緩存,避免頻繁請求網絡
- 上傳圖片時,在必要的時候壓縮圖片
- 安裝包優化: 安裝包優化的核心就是減小 apk 的體積,常見的方案如
- 使用混淆,能夠在必定程度上減小 apk 體積,但實際效果微乎其微
- 減小應用中沒必要要的資源文件,好比圖片,在不影響 APP 效果的狀況下儘可能壓縮圖片,有必定的效果
- 在使用了 SO 庫的時候優先保留 v7 版本的 SO 庫,刪掉其餘版本的SO庫。緣由是在 2018 年,v7 版本的 SO 庫能夠知足市面上絕大多數的要求,可能八九年前的手機知足不了,但咱們也不必去適配老掉牙的手機。實際開發中減小 apk 體積的效果是十分顯著的,若是你使用了不少 SO 庫,比方說一個版本的SO庫一共 10M,那麼只保留 v7 版本,刪掉 armeabi 和 v8 版本的 SO 庫,一共能夠減小 20M 的體積。
Android 內存優化
Android的內存優化在我看來分爲兩點:避免內存泄漏、擴大內存,其實就是開源節流。 其實內存泄漏的本質就是較長生命週期的對象引用了較短生命週期的對象。
常見的內存泄漏
- 單例模式致使的內存泄漏。 最多見的例子就是建立這個單例對象須要傳入一個 Context,這時候傳入了一個 Activity 類型的 Context,因爲單例對象的靜態屬性,致使它的生命週期是從單例類加載到應用程序結束爲止,因此即便已經 finish 掉了傳入的 Activity,因爲咱們的單例對象依然持有 Activity 的引用,因此致使了內存泄漏。解決辦法也很簡單,不要使用 Activity 類型的 Context,使用 Application 類型的 Context 能夠避免內存泄漏。
- 靜態變量致使的內存泄漏。 靜態變量是放在方法區中的,它的生命週期是從類加載到程序結束,能夠看到靜態變量生命週期是很是久的。最多見的因靜態變量致使內存泄漏的例子是咱們在 Activity 中建立了一個靜態變量,而這個靜態變量的建立須要傳入 Activity 的引用 this。在這種狀況下即便 Activity 調用了 finish 也會致使內存泄漏。緣由就是由於這個靜態變量的生命週期幾乎和整個應用程序的生命週期一致,它一直持有 Activity 的引用,從而致使了內存泄漏。
- 非靜態內部類致使的內存泄漏。 非靜態內部類致使內存泄漏的緣由是非靜態內部類持有外部類的引用,最多見的例子就是在 Activity 中使用 Handler 和 Thread 了。使用非靜態內部類建立的 Handler 和 Thread 在執行延時操做的時候會一直持有當前Activity的引用,若是在執行延時操做的時候就結束 Activity,這樣就會致使內存泄漏。解決辦法有兩種:第一種是使用靜態內部類,在靜態內部類中使用弱引用調用Activity。第二種方法是在 Activity 的 onDestroy 中調用 handler.removeCallbacksAndMessages 來取消延時事件。
- 使用資源未及時關閉致使的內存泄漏。 常見的例子有:操做各類數據流未及時關閉,操做 Bitmap 未及時 recycle 等等。
- 使用第三方庫未能及時解綁。 有的三方庫提供了註冊和解綁的功能,最多見的就 EventBus 了,咱們都知道使用 EventBus 要在 onCreate 中註冊,在 onDestroy 中解綁。若是沒有解綁的話,EventBus 實際上是一個單例模式,他會一直持有 Activity 的引用,致使內存泄漏。一樣常見的還有 RxJava,在使用 Timer 操做符作了一些延時操做後也要注意在 onDestroy 方法中調用 disposable.dispose()來取消操做。
- 屬性動畫致使的內存泄漏。 常見的例子就是在屬性動畫執行的過程當中退出了 Activity,這時 View 對象依然持有 Activity 的引用從而致使了內存泄漏。解決辦法就是在 onDestroy 中調用動畫的 cancel 方法取消屬性動畫。 WebView 致使的內存泄漏。WebView 比較特殊,即便是調用了它的 destroy 方法,依然會致使內存泄漏。其實避免WebView致使內存泄漏的最好方法就是讓WebView所在的Activity處於另外一個進程中,當這個 Activity 結束時殺死當前 WebView 所處的進程便可,我記得阿里釘釘的 WebView 就是另外開啓的一個進程,應該也是採用這種方法避免內存泄漏。
擴大內存
爲何要擴大咱們的內存呢?有時候咱們實際開發中不可避免的要使用不少第三方商業的 SDK,這些 SDK 其實有好有壞,大廠的 SDK 可能內存泄漏會少一些,但一些小廠的 SDK 質量也就不太靠譜一些。那應對這種咱們沒法改變的狀況,最好的辦法就是擴大內存。 擴大內存一般有兩種方法:一個是在清單文件中的 Application 下添加largeHeap="true"這個屬性,另外一個就是同一個應用開啓多個進程來擴大一個應用的總內存空間。第二種方法其實就很常見了,比方說我使用過個推的 S DK,個推的 Service 其實就是處在另一個單獨的進程中。 Android 中的內存優化總的來講就是開源和節流,開源就是擴大內存,節流就是避免內存泄漏。
Binder 機制
在Linux中,爲了不一個進程對其餘進程的干擾,進程之間是相互獨立的。在一個進程中其實還分爲用戶空間和內核空間。這裏的隔離分爲兩個部分,進程間的隔離和進程內的隔離。 既然進程間存在隔離,那其實也是存在着交互。進程間通訊就是 IPC,用戶空間和內核空間的通訊就是系統調用。 Linux 爲了保證獨立性和安全性,進程之間不能直接相互訪問,Android 是基於 Linux 的,因此也是須要解決進程間通訊的問題。 其實 Linux 進程間通訊有不少方式,好比管道、socket 等等。爲何 Android 進程間通訊採用了Binder而不是 Linux 已有的方式,主要是有這麼兩點考慮:性能和安全
- 性能。 在移動設備上對性能要求是比較嚴苛的。Linux傳統的進程間通訊好比管道、socket等等進程間通訊是須要複製兩次數據,而Binder則只須要一次。因此Binder在性能上是優於傳統進程通訊的。
- 安全。 傳統的 Linux 進程通訊是不包含通訊雙方的身份驗證的,這樣會致使一些安全性問題。而Binder機制自帶身份驗證,從而有效的提升了安全性。
Binder 是基於 CS 架構的,有四個主要組成部分。
- Client。 客戶端進程。
- Server。 服務端進程。
- ServiceManager。 提供註冊、查詢和返回代理服務對象的功能。
- Binder 驅動。 主要負責創建進程間的 Binder 鏈接,進程間的數據交互等等底層操做。
Binder 機制主要的流程是這樣的:
- 服務端經過Binder驅動在 ServiceManager 中註冊咱們的服務。
- 客戶端經過Binder驅動查詢在 ServiceManager 中註冊的服務。
- ServiceManager 經過 inder 驅動返回服務端的代理對象。
- 客戶端拿到服務端的代理對象後便可進行進程間通訊。
LruCache的原理
LruCache 的核心原理就是對 LinkedHashMap 的有效利用,它的內部存在一個 LinkedHashMap 成員變量。值得咱們關注的有四個方法:構造方法、get、put、trimToSize。
- 構造方法: 在 LruCache 的構造方法中作了兩件事,設置了 maxSize、建立了一個 LinkedHashMap。這裏值得注意的是 LruCache 將 LinkedHashMap的accessOrder 設置爲了 true,accessOrder 就是遍歷這個LinkedHashMap 的輸出順序。true 表明按照訪問順序輸出,false表明按添加順序輸出,由於一般都是按照添加順序輸出,因此 accessOrder 這個屬性默認是 false,但咱們的 LruCache 須要按訪問順序輸出,因此顯式的將 accessOrder 設置爲 true。
- get方法: 本質上是調用 LinkedHashMap 的 get 方法,因爲咱們將 accessOrder 設置爲了 true,因此每調用一次get方法,就會將咱們訪問的當前元素放置到這個LinkedHashMap的尾部。
- put方法: 本質上也是調用了 LinkedHashMap 的 put 方法,因爲 LinkedHashMap 的特性,每調用一次 put 方法,也會將新加入的元素放置到 LinkedHashMap 的尾部。添加以後會調用 trimToSize 方法來保證添加後的內存不超過 maxSize。
- trimToSize方法: trimToSize 方法的內部實際上是開啓了一個 while(true)的死循環,不斷的從 LinkedHashMap 的首部刪除元素,直到刪除以後的內存小於 maxSize 以後使用 break 跳出循環。
其實到這裏咱們能夠總結一下,爲何這個算法叫 最近最少使用 算法呢?原理很簡單,咱們的每次 put 或者get均可以看作一次訪問,因爲 LinkedHashMap 的特性,會將每次訪問到的元素放置到尾部。當咱們的內存達到閾值後,會觸發 trimToSize 方法來刪除 LinkedHashMap 首部的元素,直到當前內存小於 maxSize。爲何刪除首部的元素,緣由很明顯:咱們最近常常訪問的元素都會放置到尾部,那首部的元素確定就是 最近最少使用 的元素了,所以當內存不足時應當優先刪除這些元素。
設計一個圖片的異步加載框架
設計一個圖片加載框架,確定要用到圖片加載的三級緩存的思想。三級緩存分爲內存緩存、本地緩存和網絡緩存。 內存緩存 :將Bitmap緩存到內存中,運行速度快,可是內存容量小。 本地緩存 :將圖片緩存到文件中,速度較慢,但容量較大。 網絡緩存 :從網絡獲取圖片,速度受網絡影響。 若是咱們設計一個圖片加載框架,流程必定是這樣的:
- 拿到圖片url後首先從內存中查找BItmap,若是找到直接加載。
- 內存中沒有找到,會從本地緩存中查找,若是本地緩存能夠找到,則直接加載。
- 內存和本地都沒有找到,這時會從網絡下載圖片,下載到後會加載圖片,而且將下載到的圖片放到內存緩存和本地緩存中。
上面是一些基本的概念,若是是具體的代碼實現的話,大概須要這麼幾個方面的文件:
- 首先須要肯定咱們的內存緩存,這裏通常用的都是 LruCache。
- 肯定本地緩存,一般用的是 DiskLruCache,這裏須要注意的是圖片緩存的文件名通常是 url 被 MD5 加密後的字符串,爲了不文件名直接暴露圖片的 url。
- 內存緩存和本地緩存肯定以後,須要咱們建立一個新的類 MemeryAndDiskCache,固然,名字隨便起,這個類包含了以前提到的 LruCache 和 DiskLruCache。在 MemeryAndDiskCache 這個類中咱們定義兩個方法,一個是 getBitmap,另外一個是 putBitmap,對應着圖片的獲取和緩存,內部的邏輯也很簡單。getBitmap中按內存、本地的優先級去取 BItmap,putBitmap 中先緩存內存,以後緩存到本地。
- 在緩存策略類肯定好以後,咱們建立一個 ImageLoader 類,這個類必須包含兩個方法,一個是展現圖片 displayImage(url,imageView),另外一個是從網絡獲取圖片downloadImage(url,imageView)。在展現圖片方法中首先要經過 ImageView.setTag(url),將 url 和 imageView 進行綁定,這是爲了不在列表中加載網絡圖片時會因爲ImageView的複用致使的圖片錯位的 bug。以後會從 MemeryAndDiskCache 中獲取緩存,若是存在,直接加載;若是不存在,則調用從網絡獲取圖片這個方法。從網絡獲取圖片方法不少,這裏我通常都會使用 OkHttp+Retrofit。當從網絡中獲取到圖片以後,首先判斷一下imageView.getTag()與圖片的 url 是否一致,若是一致則加載圖片,若是不一致則不加載圖片,經過這樣的方式避免了列表中異步加載圖片的錯位。同時在獲取到圖片以後會經過 MemeryAndDiskCache 來緩存圖片。
Android中的事件分發機制
在咱們的手指觸摸到屏幕的時候,事件實際上是經過 Activity -> ViewGroup -> View 這樣的流程到達最後響應咱們觸摸事件的 View。 說到事件分發,必不可少的是這幾個方法:dispatchTouchEvent()、onInterceptTouchEvent()、onTouchEvent。接下來就按照Activity -> ViewGroup -> View 的流程來大體說一下事件分發機制。 咱們的手指觸摸到屏幕的時候,會觸發一個 Action_Down 類型的事件,當前頁面的 Activity 會首先作出響應,也就是說會走到 Activity 的 dispatchTouchEvent() 方法內。在這個方法內部簡單來講是這麼一個邏輯:
- 調用 getWindow.superDispatchTouchEvent()。
- 若是上一步返回 true,直接返回 true;不然就 return 本身的 onTouchEvent()。 這個邏輯很好理解,getWindow().superDispatchTouchEvent() 若是返回 true 表明當前事件已經被處理,無需調用本身的 onTouchEvent;不然表明事件並無被處理,須要 Activity 本身處理,也就是調用本身的 onTouchEvent。
getWindow()方法返回了一個 Window 類型的對象,這個咱們都知道,在 Android 中,PhoneWindow 是Window 的惟一實現類。因此這句本質上是調用了``PhoneWindow中的superDispatchTouchEvent()。` 而在 PhoneWindow 的這個方法中實際調用了mDecor.superDispatchTouchEvent(event)。這個 mDecor 就是 DecorView,它是 FrameLayout 的一個子類,在 DecorView 中的 superDispatchTouchEvent() 中調用的是 super.dispatchTouchEvent()。到這裏就很明顯了,DecorView 是一個 FrameLayout 的子類,FrameLayout 是一個 ViewGroup 的子類,本質上調用的仍是 ViewGroup的dispatchTouchEvent()。 分析到這裏,咱們的事件已經從 Activity 傳遞到了 ViewGroup,接下來咱們來分析下 ViewGroup 中的這幾個事件處理方法。 在 ViewGroup 中的 dispatchTouchEvent()中的邏輯大體以下:
- 經過 onInterceptTouchEvent() 判斷當前 ViewGroup 是否攔截事件,默認的 ViewGroup 都是不攔截的;
- 若是攔截,則 return 本身的 onTouchEvent();
- 若是不攔截,則根據 child.dispatchTouchEvent()的返回值判斷。若是返回 true,則 return true;不然 return 本身的 onTouchEvent(),在這裏實現了未處理事件的向上傳遞。
一般狀況下 ViewGroup 的 onInterceptTouchEvent()都返回 false,也就是不攔截。這裏須要注意的是事件序列,好比 Down 事件、Move 事件......Up事件,從 Down 到 Up 是一個完整的事件序列,對應着手指從按下到擡起這一系列的事件,若是 ViewGroup 攔截了 Down 事件,那麼後續事件都會交給這個 ViewGroup的onTouchEvent。若是 ViewGroup 攔截的不是 Down 事件,那麼會給以前處理這個 Down 事件的 View 發送一個 Action_Cancel 類型的事件,通知子 View 這個後續的事件序列已經被 ViewGroup 接管了,子 View 恢復以前的狀態便可。 這裏舉一個常見的例子:在一個 Recyclerview 鐘有不少的 Button,咱們首先按下了一個 button,而後滑動一段距離再鬆開,這時候 Recyclerview 會跟着滑動,並不會觸發這個 button 的點擊事件。這個例子中,當咱們按下 button 時,這個 button 接收到了 Action_Down 事件,正常狀況下後續的事件序列應該由這個 button處理。但咱們滑動了一段距離,這時 Recyclerview 察覺到這是一個滑動操做,攔截了這個事件序列,走了自身的 onTouchEvent()方法,反映在屏幕上就是列表的滑動。而這時 button 仍然處於按下的狀態,因此在攔截的時候須要發送一個 Action_Cancel 來通知 button 恢復以前狀態。 事件分發最終會走到 View 的 dispatchTouchEvent()中。在 View 的 dispatchTouchEvent() 中沒有 onInterceptTouchEvent(),這也很容易理解,View 不是 ViewGroup,不會包含其餘子 View,因此也不存在攔截不攔截這一說。忽略一些細節,View 的 dispatchTouchEvent()中直接 return 了本身的 onTouchEvent()。若是 onTouchEvent()返回 true 表明事件被處理,不然未處理的事件會向上傳遞,直到有 View 處理了事件或者一直沒有處理,最終到達了 Activity 的 onTouchEvent() 終止。 這裏常常有人問 onTouch 和 onTouchEvent 的區別。首先,這兩個方法都在 View 的 dispatchTouchEvent()中,是這麼一個邏輯:
- 若是 touchListener 不爲 null,而且這個 View 是 enable 的,並且 onTouch 返回的是 true,知足這三個條件時會直接 return true,不會走 onTouchEvent()方法。
- 上面只要有一個條件不知足,就會走到 onTouchEvent()方法中。因此 onTouch 的順序是在 onTouchEvent 以前的。
View的繪製流程
視圖繪製的起點在 ViewRootImpl 類的 performTraversals()方法,在這個方法內實際上是按照順序依次調用了 mView.measure()、mView.layout()、mView.draw() View的繪製流程分爲3步:測量、佈局、繪製,分別對應3個方法 measure、layout、draw。
-
測量階段。 measure 方法會被父 View 調用,在measure 方法中作一些優化和準備工做後會調用 onMeasure 方法進行實際的自我測量。onMeasure方法在View和ViewGroup作的事情是不同的:
- View。 View 中的 onMeasure 方法會計算本身的尺寸並經過 setMeasureDimension 保存。
- ViewGroup。 ViewGroup 中的 onMeasure 方法會調用全部子 iew的measure 方法進行自我測量並保存。而後經過子View的尺寸和位置計算出本身的尺寸並保存。
-
佈局階段。 layout 方法會被父View調用,layout 方法會保存父 View 傳進來的尺寸和位置,並調用 onLayout 進行實際的內部佈局。onLayout 在 View 和 ViewGroup 中作的事情也是不同的:
- View。 由於 View 是沒有子 View 的,因此View的onLayout裏面什麼都不作。
- ViewGroup。 ViewGroup 中的 onLayout 方法會調用全部子 View 的 layout 方法,把尺寸和位置傳給他們,讓他們完成自個人內部佈局。
-
繪製階段。 draw 方法會作一些調度工做,而後會調用 onDraw 方法進行 View 的自我繪製。draw 方法的調度流程大體是這樣的:
- 繪製背景。 對應 drawBackground(Canvas)方法。
- 繪製主體。 對應 onDraw(Canvas)方法。
- 繪製子View。 對應 dispatchDraw(Canvas)方法。
- 繪製滑動相關和前景。 對應 onDrawForeground(Canvas)。
Android與 js 是如何交互的
在 Android 中,Android 與js 的交互分爲兩個方面:Android 調用 js 裏的方法、js 調用 Android 中的方法。
Activity 啓動過程
SparseArray 原理
SparseArray,一般來說是 Android 中用來替代 HashMap 的一個數據結構。 準確來說,是用來替換key爲 Integer 類型,value爲Object 類型的HashMap。須要注意的是 SparseArray 僅僅實現了 Cloneable 接口,因此不能用Map來聲明。 從內部結構來說,SparseArray 內部由兩個數組組成,一個是 int[]類型的 mKeys,用來存放全部的鍵;另外一個是 Object[]類型的 mValues,用來存放全部的值。 最多見的是拿 SparseArray 跟HashMap 來作對比,因爲 SparseArray 內部組成是兩個數組,因此佔用內存比 HashMap 要小。咱們都知道,增刪改查等操做都首先須要找到相應的鍵值對,而 SparseArray 內部是經過二分查找來尋址的,效率很明顯要低於 HashMap 的常數級別的時間複雜度。提到二分查找,這裏還須要提一下的是二分查找的前提是數組已是排好序的,沒錯,SparseArray 中就是按照key進行升序排列的。 綜合起來來講,SparseArray 所佔空間優於 HashMap,而效率低於 HashMap,是典型的時間換空間,適合較小容量的存儲。 從源碼角度來講,我認爲須要注意的是 SparseArray的remove()、put()和 gc()方法。
- remove()。 SparseArray 的 remove() 方法並非直接刪除以後再壓縮數組,而是將要刪除的 value 設置爲 DELETE 這個 SparseArray 的靜態屬性,這個 DELETE 其實就是一個 Object 對象,同時會將 SparseArray 中的 mGarbage 這個屬性設置爲 true,這個屬性是便於在合適的時候調用自身的 gc()方法壓縮數組來避免浪費空間。這樣能夠提升效率,若是未來要添加的key等於刪除的key,那麼會將要添加的 value 覆蓋 DELETE。
- gc()。 SparseArray 中的 gc() 方法跟 JVM 的 GC 其實徹底沒有任何關係。``gc()` 方法的內部實際上就是一個for循環,將 value 不爲 DELETE 的鍵值對往前移動覆蓋value 爲DELETE的鍵值對來實現數組的壓縮,同時將 mGarbage 置爲 false,避免內存的浪費。
- put()。 put 方法是這麼一個邏輯,若是經過二分查找 在 mKeys 數組中找到了 key,那麼直接覆蓋 value 便可。若是沒有找到,會拿到與數組中與要添加的 key 最接近的 key 索引,若是這個索引對應的 value 爲 DELETE,則直接把新的 value 覆蓋 DELET 便可,在這裏能夠避免數組元素的移動,從而提升了效率。若是 value 不爲 DELETE,會判斷 mGarbage,若是爲 true,則會調用 gc()方法壓縮數組,以後會找到合適的索引,將索引以後的鍵值對後移,插入新的鍵值對,這個過程當中可能會觸發數組的擴容。
圖片加載如何避免 OOM
咱們知道內存中的 Bitmap 大小的計算公式是:長所佔像素 * 寬所佔像素 * 每一個像素所佔內存。想避免 OOM 有兩種方法:等比例縮小長寬、減小每一個像素所佔的內存。
- 等比縮小長寬。咱們知道 Bitmap 的建立是經過 BitmapFactory 的工廠方法,decodeFile()、decodeStream()、decodeByteArray()、decodeResource()。這些方法中都有一個 Options 類型的參數,這個 Options 是 BitmapFactory 的內部類,存儲着 BItmap 的一些信息。Options 中有一個屬性:inSampleSize。咱們經過修改 inSampleSize 能夠縮小圖片的長寬,從而減小 BItma p 所佔內存。須要注意的是這個 inSampleSize 大小須要是 2 的冪次方,若是小於 1,代碼會強制讓inSampleSize爲1。
- 減小像素所佔內存。Options 中有一個屬性 inPreferredConfig,默認是 ARGB_8888,表明每一個像素所佔尺寸。咱們能夠經過將之修改成 RGB_565 或者 ARGB_4444 來減小一半內存。
大圖加載
加載高清大圖,好比清明上河圖,首先屏幕是顯示不下的,並且考慮到內存狀況,也不可能一次性所有加載到內存。這時候就須要局部加載了,Android中有一個負責局部加載的類:BitmapRegionDecoder。使用方法很簡單,經過BitmapRegionDecoder.newInstance()建立對象,以後調用decodeRegion(Rect rect, BitmapFactory.Options options)便可。第一個參數rect是要顯示的區域,第二個參數是BitmapFactory中的內部類Options。
OkHttp
OkHttp源碼分析
Retrofit
Retrofit源碼分析
RxJava
RxJava源碼分析
Glide
Glide源碼分析
EventBus
EventBus源碼分析
閱讀更多
程序員如何寫一份更好的簡歷?
你這樣介紹項目,輕鬆搞定面試官|offer加分必備
NDK項目實戰—高仿360手機助手之卸載監聽
(Android)面試題級答案(精選版)
裸辭後,從Android轉戰Web前端的學習以及求職之路