本篇文章來自作者瀟風寒月,文章主要介紹了他面試Android開發工程師整個過程中整理的知識體系,相信會對大家有所幫助!By the way,作者說這是初始Android工程師的面經,我認爲可不是,這難度已經把我嚇到了。。
Java基礎
什麼是樂觀鎖?
樂觀鎖:假設每次去拿數據都認爲別人不會修改,所以不會上鎖。但是在更新的時候會判斷一下此期間別人有沒有去更新這個數據。一般用在讀比較多,寫比較少的情況。
悲觀鎖:假設每次都是最壞情況,每次去拿數據時別人都會修改,所以每次拿數據的時候都會上鎖,這樣別人想拿這個數據就會被阻塞直到它拿到鎖,多寫少讀時使用。
volatile關鍵字
- 保證可見性,不保證原子性
- 禁止指令重排序
- 不緩存,每次都是從主存中取
hashmap 原理,紅黑樹是什麼?
- 數組+鏈表,鏈表過長時,會導致查詢效率退化
- 數組+鏈表+紅黑樹,當鏈表長度大於8轉爲紅黑樹
- HashMap 的默認初始大小爲 16,初始化大小必須爲 2 的冪,最大大小爲 2 的 30 次方。數組中存儲的鏈表節點 Entry 類實現於 Map.Entry 接口,它實現了對節點的通用操作。HashMap 的閾值默認爲 “容量 * 0.75f”,當存儲節點數量超過該值,則對 map 進行擴容處理。
- 線程不安全的容器,解決併發問題使用ConcurrentHashMap(高效)或者是Collections.synchronizedMap()。Collections.synchronizedMap()其實就是每個方法加一個synchronize,其實和HashTable 差不多。
紅黑樹
- 平衡的二叉查找樹
- 節點是紅色或者是黑色
- 根節點是黑色
- 每個葉子的節點都是黑色的空節點(NULL)
- 每個紅色節點的兩個子節點都是黑色的
- 從任意節點到其每個葉子的所有路徑都包含相同的黑色節點
- 插入時會涉及到變色和旋轉
jvm內存分配
- 程序計數器
- Java虛擬機棧
- 本地方法棧
- Java堆
- 方法區
- 運行時常量池
- 直接內存
String,StringBuffer,StringBuilder 區別
String,StringBuffer,StringBuilder最終底層存儲與操作的都是char數組。但是String裏面的char數組是final的,而StringBuffer,StringBuilder不是,也就是說,String是不可變的,想要新的字符串只能重新生成String。而StringBuffer和StringBuilder只需要修改底層的char數組就行。相對來說,開銷要小很多。
String的大多數方法都是重新new一個新String對象返回,頻繁重新生成容易生成很多垃圾。StringBuffer是線程安全的,StringBuilder是線程不安全的,因爲StringBuffer的方法是加了synchronized鎖起來了的,而StringBuilder沒有。增刪比較多時用StringBuffer或StringBuilder(注意單線程與多線程)。實際情況按需而取吧,既然已經知道了裏面的原理。
安卓基礎
安卓各版本大變化(Android 6.0到10.0),兼容適配
Android 5.0
- Material Design
- ART虛擬機
Android 6.0
- 應用權限管理
- 官方指紋支持
- Doze電量管理
- 運行時權限機制->需要動態申請權限
Android 7.0
- 多窗口模式
- 支持Java 8語言平臺
- 需要使用FileProvider訪問照片
- 安裝apk需要兼容
Android 8.0
- 通知
- 畫中畫
- 自動填充
- 後臺限制
- 自適應桌面圖標->適配
- 隱式廣播限制
- 開啓後臺Service限制
Android 9.0
- 利用 Wi-Fi RTT 進行室內定位
- 劉海屏 API 支持
- 多攝像頭支持和攝像頭更新
- 不允許調用hide api
- 限制明文流量的網絡請求 http
Android 10
- 暗黑模式
- 隱私增強(後臺能否訪問定位)
- 限制程序訪問剪貼板
- 應用黑盒
- 權限細分需兼容
- 後臺定位單獨權限需兼容
- 設備唯一標示符需兼容
- 後臺打開Activity 需兼容
- 非 SDK 接口限制 需兼容
熱修復原理
原理
- 安卓在加載class時會通過雙親委託機制去加載一個類,先讓父類去加載,如果找不到再讓子類去加載某個類。
-
通過查看ClassLoader源碼發現findClass方法是由每個子類自己實現的,比如BootClassLoader或者BaseDexClassLoader。而PathClassLoader是繼承自BaseDexClassLoader的,它的findClass也是在BaseDexClassLoader裏面實現的。
-
BaseDexClassLoader的findClass裏面使用了另一個對象DexPathList去查找對應的class,這是安卓裏面特有的實現。在DexPathList對象裏面有一個屬性dexElements,dexElements是用於存放加載好了的dex數組的,查找class是從這個dexElements數組裏面去找的。
-
dexElements裏面存放的是Element對象,findClass最終會交給Element去實現,Element又會交給Element裏面的一個屬性DexFile去實現。我看了下,最終是用native實現的。
- 回到上面的第3步中的DexPathList對象從dexElements數組裏面查找class,從數組的前面往後找,找到了就返回結果,不再繼續查找。
- 所以當我們把修復好bug了的class,搞成dex,然後通過反射等技術放到dexElements的最前面,這樣系統在通過PathClassLoader找到class時,就能先找到我們放置的修復好bug的class,然後就不會再往後找了,相當於實現了熱修復。這樣有bug的class就不會被用了。應了一句古話,近水樓臺先得月。
- 第6點中的反射,流程是:獲取到PathClassLoader,然後反射獲取到父類中的DexPathList對象,然後再反射到DexPathList對象中的dexElements數組。然後將補丁(dex)轉爲Element對象,插入到dexElements數組的前面(先複製出來,再合併,再通過反射放回去)。
一句話總結。將修復好的類放在dexElements的最前面,這樣在加載類的時候就會被優先加載到而達到修復的目的。
MVC,MVP,MVVM
首先需要知道的是爲什麼要進行技術框架的設計?肯定是爲了低耦合,提高開發效率是吧。所以不要爲了設計而設計。
MVC
在Android中View和Controller一般就是被Activity充當了,當邏輯非常多,操作非常複雜時,Activity代碼量非常龐大,不易維護。
- Model : 模型層,業務邏輯+數據存儲等
- View : 用戶界面,一般就是xml+Activity
- Controller : 控制層,一般就是Activity
MVP
我個人角度,現在(2019年10月29日20:02:49)大多是使用這種方式,既不復雜也解耦合了。
- Model:模型層,業務邏輯+數據存儲+網絡請求
- View:視圖層,View繪製和用戶交互等,一般是Activity
- Presenter:呈現層,連接V層和M層,完成他們之間的交互
MVVM
爲了更加分離M,V層,所以有了MVVM。
- Model:模型層,業務邏輯+數據存儲+網絡請求
- View:視圖層,View繪製和用戶交互等,一般是Activity
- ViewModel:其實就是Presenter和View的數據模型的合體。雙向綁定,View的變動會反應到ViewModel中,數據的變動也會反應到View上。
組件化的好處
- 任意修改都需要編譯整個工程,效率低下。
- 解耦,有利於多人團隊協作開發
- 功能複用
app啓動流程
- Launcher startActivity
- AMS startActivity
- Zygote fork進程
- Activity main()
- ActivityThread 進程loop循環
- 開啓Activity,開始生命週期回調…
Activity啓動流程
- Activity startActivityForResult
- Instrumentation execStartActivity
- AMS startActivity
- ApplicationThread scheduleLaunchActivity
- ActivityThread.H handleMessage -> performLaunchActivity
- Activity attach
- Instrumentation callActivityOnCreate
app體積優化
可以使用lint工具,檢測出沒有用的文件。同時可以開啓資源壓縮,自動刪除無用的資源。儘量多使用可繪製對象,某些圖像不需要靜態圖像資源,框架可以在運行時動態繪製圖像。儘量自己寫Drawable,能不用UI切圖就不用,佔用空間小。
重用資源,比如一個三角按鈕,點擊前三角朝上代表收起的意思,點擊後三角朝下,代表展開,一般情況下,我們會用兩張圖來切換,我們其實完全可以用旋轉的形式去改變。比如同一圖像的着色不同,我們可以用android:tint和tintMode屬性,低版本可以使用ColorFilter。
壓縮PNG和JPEG文件,可以減少PNG文件的大小,而不會丟失圖像質量。使用WebP文件格式,可以使用WebP文件格式,而不是使用PNG或JPEG文件。可以使用AS將現有的BMP、JPG、PNG或靜態GIF圖像轉換成WebP格式。使用矢量圖形.svg;代碼混淆,使用proGuard代碼混淆器工具,它包括壓縮,優化,混淆等功能。這個大家太熟悉。插件化,將功能模塊放服務器上,按需下載,可以減少安裝包大小。
app啓動優化
利用提前展示出來的Window,快速展示出來一個節目,給用戶快速反饋的體驗。障眼法,治標不治本。
避免在啓動時做密集沉重的初始化(Heavy app initialization)。某些SDK初始化放在異步去加載(比如友盟,bugly這樣的業務非必要可以異步加載),比如地圖,推送等,非第一時間需要的可以在主線程做延時啓動(比如閃屏頁),當程序已經啓動起來之後,再進行初始化。對於網絡,圖片請求框架就必須在主線程中初始化了。啓動時,避免I/O操作,反序列化,網絡操作,佈局嵌套等耗時操作。
app佈局優化
- 如果父控件有顏色,也是自己需要的顏色,那麼就不必在子控件加背景顏色
- 如果子控件有背景顏色,並且能完全覆蓋父控件,那麼父控件不用設置背景顏色
- 儘量減少不必要的嵌套
- 能用LinearLayout和FrameLayout,就不要用RelativeLayout,因爲RelativeLayout相對比較複雜,測繪也相對耗時。
- include和merge一起使用,增加複用,減少層級
- ViewStub按需加載,更加輕便
- 複雜界面選擇ConstraintLayout,可有效減少層級
app內存優化
頻繁使用字符串拼接用StringBuilder或者StringBuffer;ArrayMap、SparseArray替換HashMap;避免內存泄漏。
- 集合類泄漏(集合一直引用着被添加進來的元素對象)
- 單例/靜態變量造成的內存泄漏(生命週期長的持有了生命週期短的引用)
- 匿名內部類/非靜態內部類
- 資源未關閉造成的內存泄漏
- 檢測內存泄漏的幾個工具: LeakCanary,TraceView,Systrace,Android Lint和Memory Monitor+mat
內存泄漏有哪些
- 集合類泄漏(集合一直引用着被添加進來的元素對象)
- 單例/靜態變量造成的內存泄漏(生命週期長的持有了生命週期短的引用)
- 匿名內部類/非靜態內部類
- 資源未關閉造成的內存泄漏
- 網絡,文件等流忘記關閉
- 手動註冊廣播時,退出時忘記unregisterReceiver()
- Service執行完成後忘記stopSelf()
- EventBus等觀察者模式的框架忘記手動解除註冊
app線程優化
線程池避免存在大量的Thread,重用線程池內部的線程,從而避免了線程的創建和銷燬帶來的性能開銷,同時能有效控制線程池的最大併發數,避免大量線程因互相搶佔系統資源而導致阻塞線現象發生。
分類
- FixedThreadPool 數量固定的線程池
- CachedThreadPool 只有非核心線程,數量不定,空閒線程有超時機制,比較適合執行大量耗時較少的任務
- ScheduledThreadPool 核心線程數量固定,非核心線程沒有限制。主要用於執行定時任務和具有固定中週期的重複任務。
- SingleThreadPool 只有一個核心線程,確保所有的任務在同一個線程順序執行,統一外界任務到一個線程中,這使得在這些任務之間不需要處理線程同步的問題。
優點
- 減少在創建和銷燬線程上所花的時間以及系統資源的開銷
- 不使用線程池有可能造成系統創建大量的線程而導致消耗完系統內存以及"過度切換"
注意點
- 如果線程池中的數量未達到核心線程的數量,則直接啓動一個核心線程來執行任務
- 如果線程池中的數量已經達到或超過核心線程的數量,則任何會被插入到任務隊列中等待執行
- 如果2中的任務無法插入到任務隊列中,由於任務隊列已滿,這時候如果線程數量未達到線程池規定的最大值,則會啓動一個非核心線程來執行任務
- 如果3中的線程數量已經達到線程池最大值,則會拒絕執行此任務,ThreadPoolExecutor會調用RejectedExecutionHandler的rejectedExecution()方法通知調用者
Android換膚如何實現,原理
重新設置LayoutInflater的Factory2,從而攔截創建View的過程,然後搞成自己的控件,想怎麼換膚就怎麼換膚。
fresco原理,glide原理,兩者區別,哪個更省內存
這塊暫時不懂,加入todo。
Handler原理,Android 消息機制
之前寫過一篇文章,死磕Android_Handler機制你需要知道的一切,裏面介紹得很詳細,順便說了一下爲什麼loop不會阻塞主線程問題。
Handler機制的關鍵在於對於ThreadLocal原理的理解,線程私有數據。利用ThreadLocal機制將Looper存放到線程內部,perfect !
Android 系統架構
應用層,應用框架層,系統運行庫層,硬件抽象層和Linux內核層。
常用佈局有哪些
- FrameLayout
- LinearLayout
- RelativeLayout
- ConstraintLayout
- CoordinatorLayout
Android數據存儲有幾種方式
- SharedPreferences: 小東西,最終是xml文件中,key-value的形式存儲的.
- 文件
- 數據庫
- ContentProvider
- 網絡
View,SurfaceView
- View是Android中所有控件的基類
- View適用於主動更新的情況,而SurfaceView則適用於被動更新的情況,比如頻繁刷新界面。
- View在主線程中對頁面進行刷新,而SurfaceView則開啓一個子線程來對頁面進行刷新。
- View在繪圖時沒有實現雙緩衝機制,SurfaceView在底層機制中就實現了雙緩衝機制。
jni調用流程
我之前也寫過簡單的demo,JNI Java與C的相互調用與基本操作。
組件之間相互引用,如何解決
- 調用其他組件的對外提供的方法:之前看到過一種思路,利用"接口+實現"的方式,定義一個ComponentBase 中間層,然後裏面有每個組件對外提供方法調用的Interface,每個組件在初始化的時候就把這些Interface給實現了,然後其他組件需要用的時候就從ComponentBase裏面取。
- 界面跳轉:ARouter
Android 數字簽名
校驗用戶身份,校驗數據的完整性。
fragment用在哪裏,與Activity的區別
- 當Activity需要模塊化的時候
- 不同設備上的適配,比如平臺和手機
- Activity相對Fragment而言,非常笨重,一般小界面小模塊用Fragment比較合適,或者首頁的tab之類的。
View繪製原理
主要是分析measure,layout,draw的過程,之前寫過一篇比較完整的,如下。死磕Android_View工作原理你需要知道的一切:
Retrofit和OkHttp原理,攔截器
- Retrofit的話,源碼寫的非常非常棒。主要是通過動態代理+獲取方法上面的註解等,然後組裝請求網絡的參數,最後用OkHttp去請求網絡。
- OkHttp的攔截器鏈設計得非常巧妙,是典型的責任鏈模式。並最終由最後一個鏈處理了網絡請求,並拿到結果。
點擊事件傳遞機制,事件分爲哪幾種
事件傳遞大體過程:Activity--> Window-->DecorView --> View樹從上往下,傳遞過程中誰想攔截就攔截自己處理。MotionEvent是Android中的點擊事件。主要事件類型:
- ACTION_DOWN 手機初次觸摸到屏幕事件
- ACTION_MOVE 手機在屏幕上滑動時觸發,會回調多次
- ACTION_UP 手指離開屏幕時觸發
需要關注的幾個方法。
- dispatchTouchEvent(event);
- onInterceptTouchEvent(event);
- onTouchEvent(event);
上面3個方法可以用以下僞代碼來表示其關係:
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume = false;//事件是否被消費
if (onInterceptTouchEvent(ev)) {//調用onInterceptTouchEvent判斷是否攔截事件
consume = onTouchEvent(ev);//如果攔截則調用自身的onTouchEvent方法
} else {
consume = child.dispatchTouchEvent(ev);//不攔截調用子View的dispatchTouchEvent方法
}
return consume;//返回值表示事件是否被消費,true事件終止,false調用父View的onTouchEvent方法
}
anr如何產生,Service觸發anr是多長時間(20秒),如何解決anr?如何解決那種莫名其妙的anr?
我覺得anr就是在主線程做了耗時操作,比如io、讀寫文件、數據庫操作等等。anr發生之後一般會有日誌,在/data/anr/traces.txt裏面。可以參考我的這篇文章拿anr日誌,Android 未root查看ANR異常:
Dialog和Activity是同一個Window?
不是同一個。
- Activity的attach方法,這裏是爲Activity實例化了一個PhoneWindow實例
- Dialog的構造方法裏面也是實例化了一個PhoneWindow實例
Window,Activity,Dectorview之間的關係
Activity裏面實例化了一個Window,Window裏面有一個DecorView(根佈局)。看一下這篇文章,Android Window機制探索:
ConstraintLayout和RelativeLayout在繪製方面有何差別?
todo。
onClick事件和onTouchListener在哪裏回調?
如果一個View需要處理事件,它設置了OnTouchListener,那麼OnTouchListener的onTouch方法會被回調。如果onTouch返回false,則onTouchEvent會被調用,反之不會。在onTouchEvent方法中,事件爲Action.UP的時候會回調OnClickListener的onClick方法,可見OnClickListener的優先級很低。
LinearLayout是如何測量(measure)的?如果有weight又是如何測量的?
先做一次測量,做完之後有空間剩餘,有weight的View再測量一下,分一下剩餘的空間。
屏幕適配
先前有鴻洋的AndroidAutoLayout,根據寬高進行控件縮放,非常經典,很多項目可能都還在使用,但是已經停止更新了。然後就是有名的今日頭條方案,出來還是有點時間了。原理其實就是更改density。
屏幕的寬度=設計稿寬度 * density。
然後有AndroidAutoSize庫,將今日頭條方案融合進去還完善了很多問題,易用,完美。
其他
Java四種引用
- 強引用,默認就是,寧願OOM,也不回收
- 弱引用,內存不夠會被回收
- 軟引用,GC時會被回收
- 虛引用,它的作用在於跟蹤垃圾回收過程,在對象被收集器回收時收到一個系統通知。
項目中遇到的最困難的事情是什麼?如何解決的?
每個人遇到的情況不同,這個提前思考一下自己做過的項目最有挑戰的地方。
Kotlin優勢
- 完全兼容java
- 空安全
- 支持lambda表達式
- 支持擴展函數
- 更少的代碼量,更快的開發速度
缺點就是有時候代碼閱讀性可能會降低。
Kotlin 協程是什麼?
就是一個線程框架,提供了一套操作線程的api。
二叉樹,廣度優先遍歷,深度優先遍歷
推薦小灰的漫畫算法。還有其他的一些,隨便聊聊:
- tcp,http,https,socket
- 敏捷開發
- 你經常使用哪些設計模式,常見設計模式的運用
- 3年之後工資怎麼想的
- 你的優勢
- 職業規劃(3年後幹啥,5年後幹啥)
- 應用,進程,線程之間的區別
最後
有些東西你不僅要懂,而且要能夠很好地表達出來,能夠讓面試官認可你的理解,例如Handler機制,這個是面試必問之題。有些晦澀的點,或許它只活在面試當中,實際工作當中你壓根不會用到它,但是你要知道它是什麼東西。
對於程序員來說,要學習的知識內容、技術有太多太多,要想不被環境淘汰就只有不斷提升自己,從來都是我們去適應環境,而不是環境來適應我們!
文章中每一個部分都包含一系列BAT面試的面試點,這些點構建了一個完整的知識體系。後面,我會細化裏面的知識,如果 大家覺得有問題,請聯繫我~
這裏附上上述的技術知識點相關的幾十套騰訊、頭條、阿里、美團等公司19年的面試題,把技術點整理成了視頻和PDF(實際上比預期多花了不少精力),包含知識脈絡 + 諸多細節,由於篇幅有限,這裏以圖片的形式給大家展示一部分。
相信它會給大家帶來很多收穫:
上述【高清技術腦圖】以及【配套的架構技術PDF】可以 加我wx:X1524478394 免費獲取
當程序員容易,當一個優秀的程序員是需要不斷學習的,從初級程序員到高級程序員,從初級架構師到資深架構師,或者走向管理,從技術經理到技術總監,每個階段都需要掌握不同的能力。早早確定自己的職業方向,才能在工作和能力提升中甩開同齡人。
- 無論你現在水平怎麼樣一定要 持續學習 沒有及湯,別人看起來的毫不費力,其實費了很大力,這四個字就是我的建議!!!!!!!!!
- 我希望每一個努力生活的IT工程師,都會得到自己想要的,因爲我們很辛苦,我們應得的。