最新 Android 面試點梳理,我收藏了你呢?

總覽

  • 網絡:分層模型、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

網絡:分層模型、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):描述怎麼創建對象

    • 1.單例模式

    • 2.原型模式:對象的拷貝

    • 3.建造者模式

    • 4.工廠模式:建立一個工廠方法來製造新的對象

    • 5.抽象工廠模式:

  • 結構型(7):描述如何將類或對象按某種規則組成更大的結構

    • 1.橋接模式:對於兩個或以上緯度獨立變化的場景,將抽象與具體實現分離,實例:用不同顏色畫不同形狀

    • 2.外觀模式:對外有一個統一接口,外部不用關心內部子系統的具體實現,這是"迪米特原則"的典型應用

    • 3.適配器模式:改變類的接口,使原本由於接口不匹配而無法一起工作的兩個類能夠在一工作,實例:RecycleView 的 Adapter 不管什麼類型的 View 都返回 ViewHolder

    • 4.代理模式:由代理對象控制對原對象的引用,包括靜態代理和動態代理

    • 5.組合模式:將對象組成樹形結構,用於對單個對象和組合對象的使用具有一致性,實例:ViewGroup

    • 6.裝飾模式:對對象包裝一層,動態的增加一些額外功能,實例:ContextWrapper 包裝 Context

    • 7.享元模式:複用對象,實例:java 的常量池(比如 String),線程池,Message.obtain 等

  • 行爲型(11):描述類或對象之間怎麼相互協作,怎樣分配指責

    • 1.觀察者模式:一對多依賴關係,多個觀察者可以同時監聽某一個對象,實例:jetpack 的 lifeCycle 添加生命週期觀察者

    • 2.中介者模式:定義一箇中介對象封裝一系列對象的交互,解耦這些對象,實例:MVP 的 P

    • 3.訪問者模式:將作用於某數據結構中各元素的操作分離出來封裝成獨立的類,對這些元素添加新的操作,但不改變原數據結構,實例:asm 中的 classVisitor 中再分別對類註解、變量、方法等進行處理

    • 4.狀態模式:行爲由狀態決定,不同狀態下由不同行爲,與策略模式類似,實例:不同狀態下有同一種操作的不同行爲的子類實現

    • 5.命令模式:將一個請求封裝爲一個對象發出,交給別的對象去處理請求,實例:Handler 發送定義好的消息事件

    • 6.策略模式:將一系列的算法封裝起來,方便替換,實例:動畫的時間插值器

    • 7.責任鏈模式:讓多個對象都有機會處理一個事件,實例:View 事件傳遞機制

    • 8.備忘錄模式:保存對象之前的狀態,方便後面恢復

    • 9.迭代器模式:提供一種方法遍歷容器中的元素,而不需要暴露該對象的內部表示,實例:集合的迭代器

    • 10.解釋器模式:多次出現的問題有一定規律,就可以歸納成一種簡單的語言來解釋,實例:AndroidManifest 文件、GLES 着色器語言

    • 11.模版方法模式:定義一套固定步驟,方便直接執行,實例: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

  • 類的生命週期:1.加載;2.驗證;3.準備;4.解析;5.初始化;6.使用;7.卸載

  • 類加載過程:1.加載:獲取類的二進制字節流;生成方法區的運行時存儲結構;在內存中生成 Class 對象 2.驗證:確保該 Class 字節流符合虛擬機要求 3.準備:初始化靜態變量 4.解析:將常量池的符號引用替換爲直接引用 5.初始化:執行靜態塊代碼、類變量賦值

  • 類加載時機:1.實例化對象 2.調用類的靜態方法 3.調用類的靜態變量(放入常量池的常量除外)

  • 類加載器:負責加載 class 文件 1.引導類加載器 - 沒有父類加載器 2.拓展類加載器 - 繼承自引導類加載器 3.系統類加載器 - 繼承自拓展類加載器

  • 雙親委託模型:

    • 當要加載一個 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 系統架構

  • 應用層

  • 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 打包流程

  • 1.aapt 打包資源文件生成 R.java 文件;aidl 生成 java 文件

  • 2.將 java 文件編譯爲 class 文件

  • 3.將工程及第三方的 class 文件轉換成 dex 文件

  • 4.將 dex 文件、so、編譯過的資源、原始資源等打包成 apk 文件

  • 5.簽名

  • 6.資源文件對齊,減少運行時內存

App 安裝過程

  • 首先要解壓 APK,資源、so等放到應用目錄

  • Dalvik 會將 dex 處理成 ODEX ;ART 會將 dex 處理成 OAT;

  • OAT 包含 dex 和安裝時編譯的機器碼

Android 優化:網絡優化、卡頓優化、內存優化、瘦包、內存泄漏、ANR、Native Crash

網絡優化及檢測

  • 速度:1.GZIP 壓縮(okhttp 自動支持);2.Protocol Buffer 替代 json;3.優化圖片/文件流量;4.IP 直連省去 DNS 解析時間

  • 成功率:1.失敗重試策略;

  • 流量:1.GZIP 壓縮(okhttp 自動支持);2.Protocol Buffer 替代 json;3.優化圖片/文件流量;5.文件下載斷點續傳 ;6.緩存

  • 協議層的優化,比如更優的 http 版本等

  • 監控:Charles 抓包、Network Monitor 監控流量

UI卡頓優化

  • 減少佈局層級及控件複雜度,避免過度繪製

  • 使用 include、merge、viewstub

  • 優化繪製過程,避免在 Draw 中頻繁創建對象、做耗時操作

內存優化

  • 內存問題

    • 內存泄漏

    • 內存抖動:頻繁創建臨時對象

    • Bitmap 大內存:規避位圖超標

    • 代碼質量:intdef 代替枚舉,使用 SparseArray 代替 HashMap

  • 檢測工具

    • MAT(Memory Analysis Tools) ,可分析 Java 堆數據,可查看實例佔用空間、引用關係等

    • Android Studio 自帶的 Profiler

    • LeakCanary:通過弱引用和引用隊列監控對象是否被回收,比如 Activity 銷燬時開始監控此對象,檢測到未被回收則主動 gc ,然後繼續監控

瘦包

  • 1.資源方面:資源在線化、圖片使用 webp 格式、tint 着色生成不同色調的切、使用 icon font

  • 2.so 庫:保留一個 cpu 架構的 so 文件

  • 3.AS Inspect Code 清除無用代碼和資源

  • 4.代碼混淆:使用 ProGuard 可以移除無用的類、字段、方法(壓縮),移除無用字節碼指令

  • 5.不保留行號:使用 ProGuard 配置不保留行號

  • 6.開啓 shrinkResources:移除無用資源

  • 7.資源混淆:使用 AndResGuard 縮短資源長度,對資源進行 7z 壓縮等(直接對apk操作)

  • 8.代碼結構簡化,比如用 intdef 代替 枚舉(一個枚舉有1~1.4kb大小)

  • 9.使用 compileOnly 在只需編譯時依賴的場景,不會打到 apk 裏

  • 10.使用 thinR 插件剔除 R 文件,將引用 R 字段的地方替換成對應常量

  • 11.Android 7.0 使用 V2(apksigner) 代替 V1(jarsigner) 簽名工具

  • 12.動態加載 so 庫(System.load加載絕對路徑文件)、插件化技術、App Bundle

  • 13.使用 facebook 的 redex

內存泄漏場景及規避

  • 1.靜態變量、單例強引跟生命週期相關的數據或資源,包括 EventBus

  • 2.遊標、IO 流等資源忘記主動釋放

  • 3.界面相關動畫在界面銷燬時及時暫停

  • 4.內部類持有外部類引用導致的內存泄漏

    • handler 內部類內存泄漏規避:1.使用靜態內部類+弱引用 2.界面銷燬時清空消息隊列

    • 檢測:Android Studio Profiler

ANR 問題及分析

  • anr 分類

    • 主線程 5s 內沒有處理完輸入事件

    • service 阻塞 20s

    • 前臺廣播阻塞 10s 或後臺廣告阻塞 20s

    • ContentProvider publish 在 20s 內沒有處理完

  • anr 發生過程

    • 1.捕獲到 anr,發送 linux 信號量 3

    • 2.進程接受到信號量將 anr 信息寫入 data/anr/traces.txt 文件

    • 3.Log 打印 anr 信息

    • 4.進程進入 anr 狀態,彈出 anr 提示框

  • 監控 anr

    • 1.Android 5.0 以下監聽 traces.txt 文件寫入

    • 2.每隔 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 的映射關係

進程保活

  • 進程優先級:1.前臺進程 ;2.可見進程;3.服務進程;4.後臺進程;5.空進程

  • 進程被 kill 場景:1.切到後臺內存不足時被殺;2.切到後臺廠商省電機制殺死;3.用戶主動清理

  • 保活方式:

    • 1.Activity 提權:掛一個 1像素 Activity 將進程優先級提高到前臺進程

    • 2.Service 提權:啓動一個前臺服務(API>18會有正在運行通知欄)

    • 3.廣播拉活

    • 4.Service 拉活

    • 5.JobScheduler 定時任務拉活

    • 6.雙進程拉活

播放器原理

  • 視頻播放原理:(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

原文鏈接文章的知識點將持續更新及補充,如果你對文中涉及到的知識點有疑問,歡迎在原文鏈接文章中評論或後臺留言哦


技術交流,歡迎加我微信:ezglumes ,拉你入技術交流羣。

推薦閱讀:

推薦一款強大的 Android OpenGL ES 調試工具

OpenGL ES 學習資源分享

從Bitmap中獲取YUV數據的兩種方式

喜歡就點個「在看」 ▽

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章