目錄:
- 網絡:分層模型、TCP、UDP、HTTP、HTTPS
- 算法:數據結構、常用算法
- Java 基礎:StringBuilder、泛型擦除、Exception、IO、容器
- Java 同步:volatile、wait、synchronized、可重入鎖、樂觀鎖、死鎖
- Java 設計模式:六大原則、23 種設計模式、動態代理
- Java 虛擬機:內存模型、內存結構、GC、四種引用、ClassLoader
- Android 基礎:Activity、View 繪製、動畫、Window、SurfaceView、事件分發
- Android 通信:Handler、Parcelable、IPC、Binder
- Android 系統:系統架構、Dalvik、ART、系統啓動、類加載器、Apk 打包、Apk 安裝
- Android 優化:網絡優化、卡頓優化、內存優化、瘦包、內存泄漏、ANR、Native Crash
- 其他:解析 XML、進程保活、播放器、Lint、CI、CD、AOP、JetPack
小編已將這些Android面試知識點整理成了文檔形式,如果大家有需要的話可以私信我【666】獲取這個面試知識點大禮包,可看文末圖;
網絡:分層模型、TCP、UDP、HTTP、HTTPS
分層模型
- 應用層:負責處理特定的應用程序細節,如 HTTP、FTP、DNS
- 運輸層:爲兩臺主機提供端到端的基礎通信,如 TCP、UDP
- 網絡層:控制分組傳輸、路由選擇等,如 IP
- 鏈路層:操作系統設備驅動程序、網卡相關接口
UDP
- UDP 頭結構:來源端口、目的端口、長度域、校驗和
- 特點:不可靠、無序、面向報文、速度快、輕量
- 適用場景:適用於即時通訊、視頻通話等
- 應用:DHCP、DNS、QUCI、VXLAN、GTP-U、TFTP、SNMP
TCP
- TCP 頭結構:來源端口、目的端口、序號、確認序號、SYN/ACK 等狀態位、窗口大小、校驗和、緊急指針
- 特點:面向字節流、有擁塞和流量控制、可靠、有序、速度慢、較重量,通過滑動窗口實現流量控制、用塞控制
- 適用場景:文件傳輸、瀏覽器等
- 應用:HTTP、HTTPS、RTMP、FTP、SMTP、POP3
- 三次握手:
1. C->S:SYN,seq=x(你能聽到嗎?)
2. S->C:SYN,seq=y,ack=x+1(我能聽到,你能聽到嗎?)
3. C->S:ACK,seq=x+1,ack=y+1(我能聽到,開始吧)
兩方都要能確保:我說的話,你能聽到;你說的話,我能聽到。所以需要三次握手
- 四次揮手:
1. C->S:FIN,seq=p(我說完了)
2. S->C:ACK,ack=p+1(我知道了,等一下,我可能還沒說完)
3. S->C:FIN,seq=q,ACK,ack=p+1(我也說完了)
4. C->S:ACK,ack=q+1(我知道了,結束吧)
S 收到 C 結束的消息後 S 可能還沒說完,沒法立即回覆結束標示,只能等說完後再告訴 C :我說完了
HTTP
- 超文本傳輸協議,明文傳輸,默認 80 端口
- POST 和 GET:Get 參數放在 url 中;Post 參數放在 request Body 中
- 訪問網頁過程:DNS 域名解析、TCP 三次握手建立連接、發起 HTTP 請求
HTTPS
- 默認 443 端口,使用 SSL 協議對 HTTP 傳輸數據進行了加密,安全
- 加密過程:Client/Server 通過非對稱加密生成密鑰,然後用這個密鑰去對稱加密傳輸數據
算法:數據結構、常用算法
數據結構
- 數組、鏈表
- 棧、隊列
- 散列表
- 樹、堆、圖
常用算法
- 排序
- 雙指針、滑動窗口、字符串
- 遞歸、分治、二分
- 回溯、貪心、動態規劃
Java 基礎:StringBuilder、泛型擦除、Exception、IO、容器
StringBuilder
- StringBuffer 線程安全,StringBuilder 線程不安全
- 實際上是用 StringBuilder 來實現的,所以非循環體可以直接用 +,循環體不行,因爲會頻繁創建 StringBuilder
- String.concat 實質是 new String ,效率也低,耗時排序:StringBuilder < StringBuffer < concat < +
泛型擦除
- 修飾成員變量等類結構相關的泛型不會被擦除
- 容器類泛型會被擦除
Exception 和 Error
- Exception 和 Error 都繼承自 Throwable
- Error 大部分是指不可恢復的錯誤狀態,比如 OOM,所以也不需要捕獲
- Exception 分爲 CheckedException 和 UnCheckedException CheckedException:必須顯式捕獲,受編譯器檢查,比如 io 操作 UnCheckedException:不用顯示捕獲,比如空指針、數組越界等
IO 、 NIO、 OKIO
- IO 是面向流的,一次一個字節的處理,NIO 是面向緩衝區的,一次產生或消費一個數據塊
- IO 是阻塞的,NIO 是非阻塞的
- NIO 支持內存映射方式
- okio 相比 io 和 nio,api 更簡單易用
- okio 支持超時機制
- okio 引入 ByteString 空間換時間提高性能
- okio 採用 segment 機制進行內存共享,節省 copy 時間消耗
ArrayList、LinkedList
- ArrayList 基於數組實現,查找快:o(1),增刪慢:o(n) 初始容量爲10,擴容通過 System.arrayCopy 方法
- LinkedList 基於雙向鏈表實現,查找慢:o(n),增刪快:o(1) 封裝了隊列和棧的調用
HashMap 、HashTable、HashSet
- HashMap(允許 key/value 爲 null) 基於數組和單向鏈表實現,數組是 HashMap 的主體;鏈表是爲解決哈希衝突而存在的,存放的是key和value結合的實體 數組索引通過 key.hashCode(還會二次 hash) 得到,在鏈表上通過 key.equals 索引 哈希衝突落在同一個桶中時,直接放在鏈表頭部(java1.8後放到尾部) JAVA 8 中鏈表數量大於 8 時會轉爲紅黑樹存儲,查找時間由 O(n) 變爲 O(logn) 數組長度總是2的n次方:這樣就能通過位運算實現取餘,從而讓 index 能落在數組長度範圍內 加載因子(默認0.75)表示添加到多少填充比時進行擴容,填充比大:鏈表較長,查找慢;填充比小:鏈表短,查找快 擴容時直接創建原數組兩倍的長度,然後將原有對象再進行hash找到新的index,重新放
- HashTable(不允許 key/value 爲 null) 數據結構和 HashMap 一樣 線程安全
- HashSet 基於 HashMap 實現,元素就是 HashMap 的 key,Value 傳入了一個固定值
ArrayMap、SparseArray
- ArrayMap 基於兩個數組實現,一個存放 hash;一個存放鍵值對 存放 hash 的數組是有序的,查找時使用二分法查找 發生哈希衝突時鍵值對數組裏連續存放,查找時也是通過 key.equals索引,找不到時先向後再向前遍歷相同hash值的鍵值對數組 擴容時不像 HashMap 直接 double,內存利用率高;也不需要重建哈希表,只需要調用 system.arraycopy 數組拷貝,性能較高 不適合存大量數據(1000以下),因爲數據量大的時候二分查找相比紅黑樹會慢很多
- SparseArray 基於 ArrayMap,key 只能是特定類型
Concurrent 集合
- ConcurrentHashMap 數據結構跟 HashMap 一樣,還是數組加鏈表 採用 segment 分段鎖技術,不像 HashTable 無腦直接同步 put 和 get 操作 get 操作沒有加鎖,因爲 value 用 volatile 修飾來保證可見行,性能很高 java1.8 後去除分段鎖,採用 CAS 樂觀鎖加 synchronized 來實現
LRUCache 原理
- 基於訪問順序排序的 LinkedHashMap 實現,最近訪問的會排在最後
Java 同步:volatile、wait、synchronized、可重入鎖、樂觀鎖、死鎖
volatile 關鍵字
- 只能用來修飾變量,適用修飾可能被多線程同時訪問的變量
- 相當於輕量級的 synchronized,volatitle 能保證有序性(禁用指令重排序)、可見性
- 變量位於主內存中,每個線程還有自己的工作內存,變量在自己線程的工作內存中有份拷貝,線程直接操作的是這個拷貝
- 被 volatile 修飾的變量改變後會立即同步到主內存,保持變量的可見性
- 雙重檢查單例,爲什麼要加 violate? volatile想要解決的問題是,在另一個線程中想要使用instance,發現instance!=null,但是實際上instance還未初始化完畢這個問題。將instance = newInstance();拆分爲3句話是。1.分配內存2.初始化3.將instance指向分配的內存空間,volatile可以禁止指令重排序,確保先執行2,後執行3
wait 和 sleep
- sleep 是 Thread 的靜態方法,可以在任何地方調用
- wait 是 Object 的成員方法,只能在 synchronized 代碼塊中調用,否則會報 IllegalMonitorStateException 非法監控狀態異常
- sleep 不會釋放共享資源鎖,wait 會釋放共享資源鎖
wait、notify、notifyAll
- 鎖池:某個對象的鎖已被線程A擁有,其他線程要執行該對象的 synchronized 方法獲取鎖時就會進入該對象的鎖池,鎖池中的線程回去競爭該對象的鎖
- 等待池:某個線程調用了某個對象的 wait 方法,該線程就會釋放該對象的鎖,進入該對象的等待池,等待池中的線程不會去競爭該對象的鎖
- 調用 notify 會隨機喚醒等待池中的一個線程,喚醒後會進入到鎖池
- 調用 notifyAll 會喚醒等待池中的所有線程,喚醒後會都進入到鎖池
lock 和 synchronized
- synchronized 是 Java 關鍵字,內置特性;Lock 是一個接口
- synchronized 會自動釋放鎖;lock 需要手動釋放,所以需要寫到 try catch 塊中並在 finally 中釋放鎖
- synchronized 無法中斷等待鎖;lock 可以中斷
- Lock 可以提高多個線程進行讀/寫操作的效率
- 競爭資源激烈時,lock 的性能會明顯的優於 synchronized
Synchronized 原理
- 每個對象都有一個監視器鎖:monitor,同步代碼塊會執行 monitorenter 開始,motnitorexit 結束
- Wait/notify 就依賴 monitor 監視器,所以在非同步代碼塊中執行會報 IllegalMonitorStateException 異常
可重入鎖
- 定義:已經獲取到鎖後,再次調用同步代碼塊/嘗試獲取鎖時不必重新去申請鎖,可以直接執行相關代碼
- ReentrantLock 和 synchronized 都是可重入鎖
公平鎖
- 定義:等待時間最久的線程會優先獲得鎖
- 非公平鎖無法保證哪個線程獲取到鎖,synchronized 就是非公平鎖
- ReentrantLock 默認時非公平鎖,可以設置爲公平鎖
樂觀鎖和悲觀鎖
- 悲觀鎖:線程一旦得到鎖,其他線程就掛起等待,適用於寫入操作頻繁的場景;synchronized 就是悲觀鎖
- 樂觀鎖:假設沒有衝突,不加鎖,更新數據時判斷該數據是否過期,過期的話則不進行數據更新,適用於讀取操作頻繁的場景
- 樂觀鎖 CAS:Compare And Swap,更新數據時先比較原值是否相等,不相等則表示數據過去,不進行數據更新
- 樂觀鎖實現:AtomicInteger、AtomicLong、AtomicBoolean
死鎖 4 個必要條件
- 互斥
- 佔有且等待
- 不可搶佔
- 循環等待
Java 設計模式:六大原則、23 種設計模式、動態代理
六大原則
- 開閉原則:對拓展開放,對修改關閉
- 單一指責原則:一個類指責單一
- 里氏替換原則:引用基類的地方都能替換成子類對象
- 依賴倒置原則:高層次模塊不依賴低層次模塊的具體實現,抽象不應該依賴細節
- 接口隔離原則:類之間的依賴關係應該建立在最小的接口上
- 迪米特原則:一個對象對其他對象應該有儘量少的瞭解
Java 23 種設計模式(按目的分類爲:5+7+11)
1995 年 GoF(四人組)出了一本設計模式的書,收錄了 23 種設計模式,樹立設計模式里程碑,也叫:GoF 設計模式
- 創建型(5):描述怎麼創建對象
- 單例模式
- 原型模式:對象的拷貝
- 建造者模式
- 工廠模式:建立一個工廠方法來製造新的對象
- 抽象工廠模式
- 結構型(7):描述如何將類或對象按某種規則組成更大的結構 1.橋接模式:對於兩個或以上緯度獨立變化的場景,將抽象與具體實現分離,實例:用不同顏色畫不同形狀 2.外觀模式:對外有一個統一接口,外部不用關心內部子系統的具體實現,這是"迪米特原則"的典型應用 3.適配器模式:改變類的接口,使原本由於接口不匹配而無法一起工作的兩個類能夠在一工作,實例:RecycleView 的 Adapter 不管什麼類型的 View 都返回 ViewHolder 4.代理模式:由代理對象控制對原對象的引用,包括靜態代理和動態代理 5.組合模式:將對象組成樹形結構,用於對單個對象和組合對象的使用具有一致性,實例:ViewGroup 6.裝飾模式:對對象包裝一層,動態的增加一些額外功能,實例:ContextWrapper 包裝 Context 7.享元模式:複用對象,實例:java 的常量池(比如 String),線程池,Message.obtain 等
- 行爲型(11):描述類或對象之間怎麼相互協作,怎樣分配指責
- 觀察者模式:一對多依賴關係,多個觀察者可以同時監聽某一個對象,實例:jetpack 的 lifeCycle 添加生命週期觀察者
- 中介者模式:定義一箇中介對象封裝一系列對象的交互,解耦這些對象,實例:MVP 的 P
- 訪問者模式:將作用於某數據結構中各元素的操作分離出來封裝成獨立的類,對這些元素添加新的操作,但不改變原數據結構,實例:asm 中的 classVisitor 中再分別對類註解、變量、方法等進行處理
- 狀態模式:行爲由狀態決定,不同狀態下由不同行爲,與策略模式類似,實例:不同狀態下有同一種操作的不同行爲的子類實現
- 命令模式:將一個請求封裝爲一個對象發出,交給別的對象去處理請求,實例:Handler 發送定義好的消息事件
- 策略模式:將一系列的算法封裝起來,方便替換,實例:動畫的時間插值器
- 責任鏈模式:讓多個對象都有機會處理一個事件,實例:View 事件傳遞機制
- 備忘錄模式:保存對象之前的狀態,方便後面恢復
- 迭代器模式:提供一種方法遍歷容器中的元素,而不需要暴露該對象的內部表示,實例:集合的迭代器
- 解釋器模式:多次出現的問題有一定規律,就可以歸納成一種簡單的語言來解釋,實例:AndroidManifest 文件、GLES 着色器語言
- 模版方法模式:定義一套固定步驟,方便直接執行,實例:AsyncTask
動態代理原理及實現
- InvocationHandler 接口,動態代理類需要實現這個接口
- Proxy.newProxyInstance,用於動態創建代理對象
- Retrofit 應用: Retrofit 通過動態代理,爲我們定義的請求接口都生成一個動態代理對象,實現請求
JVM:內存模型、內存結構、GC、四種引用、ClassLoader
JVM
- 定義:可以理解成一個虛構的計算機,解釋自己的字節碼指令集映射到本地 CPU 或 OS 的指令集,上層只需關注 Class 文件,與操作系統無關,實現跨平臺
- Kotlin 就是能解釋成 Class 文件,所以可以跑在 JVM 上
JVM 內存模型
- Java 多線程之間是通過共享內存來通信的,每個線程都有自己的本地內存
- 共享變量存放於主內存中,線程會拷貝一份共享變量到本地內存
- volatile 關鍵字就是給內存模型服務的,用來保證內存可見性和順序性
JVM 內存結構
- 線程私有: 1.程序計數器:記錄正在執行的字節碼指令地址,若正在執行 Native 方法則爲空 2.虛擬機棧:執行方法時把方法所需數據存爲一個棧幀入棧,執行完後出棧 3.本地方法棧:同虛擬機棧,但是針對的是 Native 方法
- 線程共享: 1.堆:存儲 Java 實例,GC 主要區域,分代收集 GC 方法會吧堆劃分爲新生代、老年代 2.方法區:存儲類信息,常量池,靜態變量等數據
GC
- 回收區域:只針對堆、方法區;線程私有區域數據會隨線程結束銷燬,不用回收
- 回收類型: 1.堆中的對象:分代收集 GC 方法會吧堆劃分爲新生代、老年代。 新生代:新建小對象會進入新生代;通過複製算法回收對象;老年代:新建大對象及老對象會進入老年代;通過標記-清除算法回收對象。 2.方法區中的類信息、常量池
- 判斷一個對象是否可被回收: 1.引用計數法:有循環引用的缺點 2.可達性分析法:從 GC ROOT 開始搜索,不可達的對象都是可以被回收的。其中 GC ROOT 包括虛擬機棧/本地方法棧中引用的對象、方法區中常量/靜態變量引用的對象。
Minor GC/Major GC/Full GC
- Minor GC(Young GC):即新生代(分爲一個 Eden 區和兩個 Survivor 區)的垃圾回收 Eden 區無用對象被回收,存活對象會移到 Survivor 區 Survivor 區的存活對象會被複制到另一個 Survivor 區,複製次數也記做年齡,年齡足夠大時(15)會移到老年代 如果 Survivor 區已滿,則存活對象會被提前移動到老年代(過早提升),如果老年代也無法容納,則會觸發 Full GC(提升失敗) 老年代的對象可能引用新生代對象,所以這個引用會被作爲 GC Roots
- Major GC:通常是跟 Full GC 等價的,回收整個堆
- Full GC:回收整個堆,包括新生代和老年代 當要在老年代分配空間但無法容納時觸發 當主動調用 System.gc 時觸發
四種引用
- 強引用:不會被回收
- 軟引用:內存不足時會被回收
- 弱引用:gc 時會被回收
- 虛引用:無法通過虛引用得到對象,可以監聽對象的回收
ClassLoader
- 類的生命週期:
- 加載;
- 驗證;
- 準備;
- 解析;
- 初始化;
- 使用;
- 卸載
- 類加載過程:
- 加載:獲取類的二進制字節流;生成方法區的運行時存儲結構;在內存中生成 Class 對象
- 驗證:確保該 Class 字節流符合虛擬機要求
- 準備:初始化靜態變量
- 解析:將常量池的符號引用替換爲直接引用
- 初始化:執行靜態塊代碼、類變量賦值
- 類加載時機:
- 實例化對象
- 調用類的靜態方法
- 調用類的靜態變量(放入常量池的常量除外)
- 類加載器:負責加載 class 文件
- 引導類加載器 - 沒有父類加載器
- 拓展類加載器 - 繼承自引導類加載器
- 系統類加載器 - 繼承自拓展類加載器
- 雙親委託模型: 當要加載一個 class 時,會先逐層向上讓父加載器先加載,加載失敗纔會自己加載 爲什麼叫雙親?不考慮自定義加載器,系統類加載器需要網上詢問兩層,所以叫雙親 判斷是否是同一個類時,除了類信息,還必須時同一個類加載器 優點:防止重複加載,父加載器加載過了就沒必要加載了;安全,防止篡改核心庫類
Android 基礎:Activity、View 繪製、動畫、Window、SurfaceView、事件分發
Activity 生命週期
- A 打開 B 界面,會先執行 A 的 onPause,再執行 B 的 onCreate、onStart、onResume,再執行 A 的 onStop
- B 界面的打開依賴 A 界面 onPause 方法執行完,所以不要在 onPause 中做耗時操作
Activity 啓動模式
- standard 標準模式
- singleTop 棧頂複用模式,適用於推送點擊消息界面
- singleTask 棧內複用模式,適用於 App 首頁
- singleInstance 單例模式,單獨位於一個任務棧中,適用於撥打電話界面
- 細節: taskAffinity:任務相關性,用於指定任務棧名稱,默認爲應用包名 allowTaskReparenting:允許轉移任務棧
View 工作原理
- ViewRoot 的 performTraversals 方法調用觸發開始 View 的繪製,然後會依次調用: performMeasure:遍歷 View 的 measure 測量尺寸 performLayout:遍歷 View 的 layout 確定位置 performDraw:遍歷 View 的 draw 繪製
MeasureSpec 測量規則
- EXACTLY:父 View 指定了子 View 確切的大小
- AT_MOST:父 View 指定一個大小,子 View 不能超過這個值
- UNSPECIFIEND: 父 View 不對子 View 有任何限制
View 動畫、幀動畫及屬性動畫
- View 動畫: 作用對象是 View,可用 xml 定義,建議 xml 實現比較易讀 支持四種效果:平移、縮放、旋轉、透明度
- 幀動畫: 通過 AnimationDrawable 實現,容易 OOM
- 屬性動畫: 可作用於任何對象,可用 xml 定義,Android 3 引入,建議代碼實現比較靈活 包括 ObjectAnimator、ValuetAnimator、AnimatorSet 時間插值器:根據時間流逝的百分比計算當前屬性改變的百分比,系統預置勻速、加速、減速等插值器 類型估值器:根據當前屬性改變的百分比計算改變後的屬性值,系統預置整型、浮點、色值等類型估值器 使用注意事項:避免使用幀動畫,容易OOM;界面銷燬時停止動畫,避免內存泄漏;開啓硬件加速,提高動畫流暢性 硬件加速原理:將 cpu 一部分工作分擔給 gpu ,使用 gpu 完成繪製工作;從工作分攤和繪製機制兩個方面優化了繪製速度
Window 、WindowManager、WMS、SurfaceFlinger
- WIndow:抽象概念不是實際存在的,而是以 View 的形式存在,通過 PhoneWindow 實現
- WindowManager:外界訪問 Window 的入口,內部與 WMS 交互是個 IPC 過程
- WMS:管理窗口 Surface 的佈局和次序,作爲系統級服務單獨運行在一個進程
- SurfaceFlinger:將 WMS 維護的窗口按一定次序混合後顯示到屏幕上
SurfaceView、TextureView、SurfaceTexture、GLSurfaceView
- SurfaceView:使用雙緩衝機制,有自己的 surface,在一個獨立的線程裏繪製,Android7.0之前不能平移、縮放
- TextureView:持有 SurfaceTexture,將圖像處理爲 OpenGL 紋理更新到 HardwareLayer,必須開啓硬件加速,Android5.0之前在主線程渲染,之後有獨立的渲染線程,可以平移、旋轉、縮放
- SurfaceTexture:將圖像流轉爲 OpenGL 外部紋理,不直接顯示
- GLSurfaceView:加入 EGL 管理,自帶 GL 上下文和 GL 渲染線程
事件分發機制
- 一個 MotionEvent 產生後,按 Activity -> Window -> decorView -> View 順序傳遞,View 傳遞過程就是事件分發,主要依賴三個方法:
- dispatchTouchEvent:用於分發事件,只要接受到點擊事件就會被調用,返回結果表示是否消耗了當前事件
- onInterceptTouchEvent:用於判斷是否攔截事件,當 ViewGroup 確定要攔截事件後,該事件序列都不會再觸發調用此 ViewGroup 的 onIntercept
- onTouchEvent:用於處理事件,返回結果表示是否處理了當前事件,未處理則傳遞給父容器處理
- 細節: 一個事件序列只能被一個 View 攔截且消耗 View 沒有 onIntercept 方法,直接調用 onTouchEvent 處理 OnTouchListener 優先級比 OnTouchEvent 高,onClickListener 優先級最低 requestDisallowInterceptTouchEvent 可以屏蔽父容器 onIntercept 方法的調用
Android 通信:Handler、Parcelable、IPC、Binder
Handler、MessageQueue、Looper 及 postDelayed 原理
- Handler:開發直接接觸的類,內部持有 MessageQueue 和 Looper
- MessageQueue:消息隊列,內部通過單鏈表存儲消息
- Looper:內部持有 MessageQueue,循環查看是否有新消息,有就處理,沒就阻塞
- postDelayed 其實就是調用 postAtTime 實現的,傳入的時間戳基於 SystemClock.uptimeMillis,即 boot 時間
- 進一步會調用 MessageQueue#enqueueMessage 將消息插入到隊列
- 插入消息時會根據消息執行時刻 Message#when 來決定插入到什麼位置,when 爲 0 或最早執行就會插入到鏈表頭,否則按執行時刻排序插入
- 插入後如果正在阻塞則會嘗試喚醒,插入到頭部則會喚醒,插入到隊列中則再根據其他條件判斷是否需要喚醒
- Looper#loop 中調用 MessageQueue#next 取消息,next 方法除非是即將銷燬時會返回 null,否則就會返回消息,沒有消息就阻塞。如果當前時刻還沒到消息的執行時刻 when,就會再阻塞這個時間差的時間
- 阻塞是調用 nativePollOnce 實現,基於 Linux epoll 事件管理機制
- Looper#loop 中取出消息後通過 Message#target 拿到 handler,然後調用 Handler#dispatchMessage 分發處理消息
Serializable、Parcelable
- Serializable :Java 序列化方式,適用於存儲和網絡傳輸,serialVersionUID 用於確定反序列化和類版本是否一致,不一致時反序列化回失敗
- Parcelable :Android 序列化方式,適用於組件通信數據傳遞,性能高,因爲不像 Serializable 一樣有大量反射操作
Linux IPC 方式
- 管道
- socket
- 信號量:常作爲一種鎖機制,防止某進程正在訪問共享資源時,其他進程也訪問該資源。因此,主要作爲進程間以及同一進程內不同線程之間的同步手段
- 信號:不適用於信息交換,更適用於進程中斷控制,比如非法內存訪問,殺死某個進程等(Android 中的 Kill Process 採用的就是 signal(信號)機制)
- 消息隊列:信息複製兩次,額外的 CPU 消耗;不合適頻繁或信息量大的通信
- 共享內存:無須複製,共享緩衝區直接付附加到進程虛擬地址空間,速度快;但進程間的同步問題操作系統無法實現,必須各進程利用同步工具解決
Binder
- Android 中基於 C/S 結構的一種面向對象的進程間通信的機制
- 主要用在 system_server 進程與上層 App 層的 IPC 交互
- 包含:Client,Server,Binder 驅動和 ServiceManager 四部分
Android 爲什麼選擇 binder
- 性能:使用 mmap 一次數據拷貝實現 IPC,傳統 IPC:用戶 A 空間->內核->用戶 B 空間;mmap 將內核與用戶 B 空間映射,實現直接從用戶 A 空間->用戶B空間,而 Linux 的管道、消息隊列、Socket 都需要拷貝兩次,binder 僅次於共享內存
- 穩定性:基於C/S架構,架構清晰,穩定性好,不像共享內存實現方式複雜,需要充分考慮訪問臨界資源的併發同步問題
- 安全:傳統Linux IPC的接收方無法獲得對方進程可靠的UID/PID,從而無法鑑別對方身份
Android IPC 方式
- Intent extras、Bundle:要求傳遞數據能被序列化,實現 Parcelable、Serializable ,適用於四大組件通信
- 文件共享:適用於交換簡單的數據實時性不高的場景
- AIDL:AIDL 接口實質上是系統提供給我們可以方便實現 Binder 的工具 Android Interface Definition Language,可實現跨進程調用方法 服務端:將暴漏給客戶端的接口聲明在 AIDL 文件中,創建 Service 實現 AIDL 接口並監聽客戶端連接請求 客戶端:綁定服務端 Service ,綁定成功後拿到服務端 Binder 對象轉爲 AIDL 接口調用 RemoteCallbackList 實現跨進程接口監聽,同個 Binder 對象做 key 存儲客戶端註冊的 listener 監聽 Binder 斷開:1.Binder.linkToDeath 設置死亡代理;2. onServiceDisconnected 回調
- Messenger:基於 AIDL 實現,服務端串行處理,主要用於傳遞消息,適用於低併發一對多通信
- ContentProvider:基於 Binder 實現,適用於一對多進程間數據共享
- Socket:TCP、UDP,適用於網絡數據交換
Android 系統:系統架構、Dalvik、ART、系統啓動、類加載器、Apk 打包、Apk 安裝
Android 系統架構
Android 系統架構
- 應用層
- Framework 框架層
- 本地 Native 庫和 Android 運行時環境
- HAL
- Linux 內核
Dalvik 和 ART
- Dalvik 谷歌設計專用於 Android 平臺的 Java 虛擬機,可直接運行 .dex 文件,適合內存和處理速度有限的系統 JVM 指令集是基於棧的;Dalvik 指令集是基於寄存器的,代碼執行效率更優
- ART Dalvik 每次運行都要將字節碼轉換成機器碼;ART 在應用安裝時就會轉換成機器碼,執行速度更快 ART 存儲機器碼佔用空間更大,空間換時間
Android 系統啓動流程
- 按電源鍵 -> 加載引導程序 BootLoader 到 RAM -> 執行 BootLoader 程序啓動內核 -> 啓動 init 進程 -> 啓動 Zygote 和各種守護進程 -> 啓動 System Server 服務進程開啓 AMS、WMS 等 -> 啓動 Launcher 應用進程
Android 類加載器
- BootClassLoader(加載 Framework 級別的類)
- PathClassLoader(加載系統類和 data/app 應用目錄下的 dex 文件)
- DexClassLoader(加載自定義的 dex 文件或 jar,支持從 sd 卡中進行加載)
APK 打包流程
- aapt 打包資源文件生成 R.java 文件;aidl 生成 java 文件
- 將 java 文件編譯爲 class 文件
- 將工程及第三方的 class 文件轉換成 dex 文件
- 將 dex 文件、so、編譯過的資源、原始資源等打包成 apk 文件
- 簽名
- 資源文件對齊,減少運行時內存
App 安裝過程
- 首先要解壓 APK,資源、so等放到應用目錄
- Dalvik 會將 dex 處理成 ODEX ;ART 會將 dex 處理成 OAT;
- OAT 包含 dex 和安裝時編譯的機器碼
Android 優化:網絡優化、卡頓優化、內存優化、瘦包、內存泄漏、ANR、Native Crash
網絡優化及檢測
- 速度:
- GZIP 壓縮(okhttp 自動支持);
- Protocol Buffer 替代 json;
- 優化圖片/文件流量;
- IP 直連省去 DNS 解析時間
- 成功率:失敗重試策略;
- 流量:
- GZIP 壓縮(okhttp 自動支持);
- Protocol Buffer 替代 json;
- 優化圖片/文件流量;
- 文件下載斷點續傳 ;
- 緩存
- 協議層的優化,比如更優的 http 版本等
- 監控:Charles 抓包、Network Monitor 監控流量
UI卡頓優化
- 減少佈局層級及控件複雜度,避免過度繪製
- 使用 include、merge、viewstub
- 優化繪製過程,避免在 Draw 中頻繁創建對象、做耗時操作
內存優化
- 內存問題 內存泄漏 內存抖動:頻繁創建臨時對象 Bitmap 大內存:規避位圖超標 代碼質量:intdef 代替枚舉,使用 SparseArray 代替 HashMap
- 檢測工具 MAT(Memory Analysis Tools) ,可分析 Java 堆數據,可查看實例佔用空間、引用關係等 Android Studio 自帶的 Profiler LeakCanary:通過弱引用和引用隊列監控對象是否被回收,比如 Activity 銷燬時開始監控此對象,檢測到未被回收則主動 gc ,然後繼續監控
瘦包
- 資源方面:資源在線化、圖片使用 webp 格式、tint 着色生成不同色調的切、使用 icon font
- so 庫:保留一個 cpu 架構的 so 文件
- AS Inspect Code 清除無用代碼和資源
- 代碼混淆:使用 ProGuard 可以移除無用的類、字段、方法(壓縮),移除無用字節碼指令
- 不保留行號:使用 ProGuard 配置不保留行號
- 開啓 shrinkResources:移除無用資源
- 資源混淆:使用 AndResGuard 縮短資源長度,對資源進行 7z 壓縮等(直接對apk操作)
- 代碼結構簡化,比如用 intdef 代替 枚舉(一個枚舉有1~1.4kb大小)
- 使用 compileOnly 在只需編譯時依賴的場景,不會打到 apk 裏
- 使用 thinR 插件剔除 R 文件,將引用 R 字段的地方替換成對應常量
- Android 7.0 使用 V2(apksigner) 代替 V1(jarsigner) 簽名工具
- 動態加載 so 庫(System.load加載絕對路徑文件)、插件化技術、App Bundle
- 使用 facebook 的 redex
內存泄漏場景及規避
- 靜態變量、單例強引跟生命週期相關的數據或資源,包括 EventBus
- 遊標、IO 流等資源忘記主動釋放
- 界面相關動畫在界面銷燬時及時暫停
- 內部類持有外部類引用導致的內存泄漏 handler 內部類內存泄漏規避:
- 使用靜態內部類+弱引用
- 界面銷燬時清空消息隊列 檢測:Android Studio Profiler
ANR 問題及分析
- anr 分類 主線程 5s 內沒有處理完輸入事件 service 阻塞 20s 前臺廣播阻塞 10s 或後臺廣告阻塞 20s ContentProvider publish 在 20s 內沒有處理完
- anr 發生過程
- 捕獲到 anr,發送 linux 信號量 3
- 進程接受到信號量將 anr 信息寫入 data/anr/traces.txt 文件
- Log 打印 anr 信息
- 進程進入 anr 狀態,彈出 anr 提示框
- 監控 anr
- Android 5.0 以下監聽 traces.txt 文件寫入
- 每隔 5s 向主線程發送消息判斷主線程是否阻塞
- 分析 anr 查看 cpu 負載是否是 cpu 資源緊張導致 查看堆棧看是否是我們的代碼耗時過長
- 避免 anr 主線程中不要做耗時操作,注意使用 IntentService 降低子線程優先級,讓主線程可以更多的獲取到 cpu 資源
Native Crash
- 崩潰過程:native crash 時操作系統會向進程發送信號,崩潰信息會寫入到 data/tombstones 下,並在 logcat 輸出崩潰日誌
- 定位:so 庫剝離調試信息的話,只有相對位置沒有具體行號,可以使用 NDK 提供的 addr2line 或 ndk-stack 來定位
- addr2line:根據有調試信息的 so 和相對位置定位實際的代碼處
- ndk-stack:可以分析 tombstone 文件,得到實際的代碼調用棧
其他:解析 XML、進程保活、播放器、Lint、CI、CD、AOP、JetPack
Android 解析 XML
- SAX:流式解析
- DOM:先把 XML 全部讀取到內存,再訪問樹形結構,很消耗內存
- PULL:流式解析,Android 內置的默認解析方式
熱修復、插件化、組件化
- 熱修復原理: Native Hook(AndFix):直接在 native 層進行方法的結構體信息對換 分包(QFix):插入新 dex 到 dexElements[],利用 ClassLoader 通過遍歷 dexElements[] 來 findClass 的特性 Java Hook(Robust):hook 每個方法,在每個方法裏埋好準備替換的邏輯
- 插件化:DexClassLoader 動態加載,四大組件未註冊問題通過 hook AMS、Instrumentation 等解決,VirtualAPK 源碼分析
- 組件化:ARoute 路由實現:通過 APT 解析 @Route 等註解,結合 JavaPoet 生成路由表,即路由與 Activity 的映射關係
進程保活
- 進程優先級:
- 前臺進程 ;
- 可見進程;
- 服務進程;
- 後臺進程;
- 空進程
- 進程被 kill 場景:
- 切到後臺內存不足時被殺;
- 切到後臺廠商省電機制殺死;
- 用戶主動清理
- 保活方式:
- Activity 提權:掛一個 1像素 Activity 將進程優先級提高到前臺進程
- Service 提權:啓動一個前臺服務(API>18會有正在運行通知欄)
- 廣播拉活
- Service 拉活
- JobScheduler 定時任務拉活
- 雙進程拉活
播放器原理
- 視頻播放原理:(mp4、flv)-> 解封裝 -> (mp3/aac、h264/h265)-> 解碼 -> (pcm、yuv)-> 音視頻同步 -> 渲染播放
- 音視頻同步: 選擇參考時鐘源:音頻時間戳、視頻時間戳和外部時間三者選擇一個作爲參考時鐘源(一般選擇音頻,因爲人對音頻更敏感,ijk 默認也是音頻) 通過等待或丟幀將視頻流與參考時鐘源對齊,實現同步
- IjkPlayer 原理 集成了 MediaPlayer、ExoPlayer 和 IjkPlayer 三種實現,其中 IjkPlayer 基於 FFmpeg 的 ffplay 音頻輸出方式:AudioTrack、OpenSL ES;視頻輸出方式:NativeWindow、OpenGL ES
Lint
- Android Lint 是 Google 提供給 Android 開發者的靜態代碼檢查工具
- 使用 Lint 對 Android 工程代碼進行掃描和檢查,可以發現代碼潛在的問題,提醒程序員及早修正
- 基於 Detector、IssueRegistry 實現,通過 lintChecks project 引入
CI
- Continuous integration(持續集成,簡稱CI):頻繁的將代碼集成到主幹,防止分支大幅偏離主幹,方便快速發現錯誤
- Continuous delivery(持續交付):頻繁地將軟件的新版本,交付給質量團隊或者用戶,以供評審
- Continuous deployment(持續部署):持續交付的下一步,指的是代碼通過評審以後,自動部署到生產環境
- 交付後需要進行構建,將源碼轉換爲可以運行的實際代碼,常用的構建工具有 Jenkins、Strider
AOP
- 基於 Gradle Transform API 創建 TransForm ,其執行時機在 class 被打包成 dex 之前
- 在 TransForm 中通過 javassist 或 asm 修改字節碼
- 基於 Gradle Plugin API 自定義插件,應用自定義的 TransForm
JetPack
- LiveData 感知聲明週期原理:像 Glide 一樣給界面添加了無視圖的 Fragment
- ViewModel 界面旋轉短暫銷燬重建時保存數據原理: ViewModel 保存在 ViewModelStore 中 當 Activity 配置變更銷燬時,系統會調用 onRetainNonConfigurationInstance 保存 NonConfigurationInstances,而 ViewModel 就保存在 NonConfigurationInstances 中 重建時 onCreate 方法通過 getLastNonConfigurationInstance 方法獲取到 NonConfigurationInstances,從而獲取到 ViewModelStore
- JetPack 與 MVVM: 先了解下 MVP:Model:處理數據;
- View:控制視圖;Presenter:分離 Activity 和 Model
- 再看 MVVM:Model:處理獲取保存數據;
- View:控制視圖;
- ViewModel:數據容器 使用 Jetpack 組件架構的 LiveData、ViewModel 可以便捷的實現 MVVM
下面是已整理好的Android面試知識點文檔和一些面試題、視頻學習資料等,想要獲取的朋友可以私信我【666】領取。