總覽
網絡:分層模型、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 調試工具
喜歡就點個「在看」吧 ▽