作者:橘子樹
此次面試一共4面4小時,中間只有幾分鐘間隔。對持續的面試狀態考驗還是蠻大的。
關于面試的心態,保持悲觀的樂觀主義心態比較好。面前做面試準備時保持悲觀,盡可能的做足準備。面后積極做復盤,樂觀的接受最終結果。
切忌急于下結論,哪怕面試當場明顯感覺好或不好。快思考時我常會放大積極面信息而縮小負面影響,導致做了過于樂觀的抉擇,回來后或者隔一天以后再思考,這時候可以做到慢思考,通過復盤來下一個更為理智的決斷。
面試準備,自我模擬提問:(復盤評價:還是不夠悲觀)
之前貼出JD描述的行為略欠考慮,已刪。雖然多數JD比較通用,不過還是要不貼為好。
JD拆解
組件化的經驗
- 什么是組件化
組件化最早在上世紀被提出用于unix系統模塊設計,后來廣泛應用于前端與后端模塊。
目前在移動端組件化方案是對模塊化的一個升級性的架構方案,提高通用組件,中臺服務,跨業務線組件復用。目的是在多模塊之間形成接口隔離,提供下沉接口而非下沉實現類,或者采用靜默注冊直接提供功能,降低模塊之間的耦合,保持各組件可獨立編譯發布版本。
- 有哪些解決方案
用過ARouter一段時間,了解其核心基礎后,后來針對公司項目體量開發了更輕量的版本,提供可完全自主支持的PRouter。其核心還是通過APT 完成注解代碼生成與路由編排,自動解析路由參數。更像是web頁面路由在native的應用。主要包含activity,fragment,deeplink這幾個入口。
Router庫的組成:processor 用于讀取注解,生成class文件,annotation 庫聲明注解,path公共庫聲明path(復雜場景需要降級拆分)
APM的經驗
應用性能檢測分為2個大的環境,分別是測試環境,生產環境。在再分下面幾個方向
- 異常:崩潰檢測,ANR檢測,網絡異常
- 啟動耗時檢測,頁面(打開速度,幀率),大圖檢測,內測溢出,OOM
- 日志(APM日志與數據統計日志)
既要滿足自身需要又要避免刻意重復造輪子
測試環境自動集成debug組件,靜默初始化。記錄頁面操作路徑,全局攔截異常,在crash時在JVM推出前拉起新進程展示異常信息,可以分享至工作群。
線上環境采用bugly,阿里云EMAS等平臺進行監控,阿里云EMAS相對更穩定。
移動DevOps經驗
DevOps是一種工程師文化,同時也是通過優秀的流程交付優秀的產品的最佳實踐之一,已經廣泛應用在各個前后端產品里,但是移動DevOps開放的解決方案并不多,大廠基本都在自建,需要的人力與技術儲備也較大,這也造成了比較大的一個壁壘。
CI/CD是對DevOps的實現方式。
關于移動CI/CD看過蠻多資料,覺得最好的是Jetbrains類似白皮書的形式的內容,這也是我主要的學習資料。
在整個敏捷開發中有哪些低效率痛點:
開發階段,代碼分支管理,代碼合并前卡點,產物構建環境
測試階段,環境切換,bug修復,代碼合并,新版本提交
發布階段,發布信息,構建正式版本
發布后質量監控,數據監控,性能預警
講一講完成過的CI/CD
- 分支管理包含保護分支合并規則
- 開發發布MP 觸發,Git Hook ,code review 卡點,lint掃描卡點(增量掃描,報告發送至辦公IM),master分支同步卡點。合并成功后觸發構建,構建產物同步,創建構建信息同步至辦公IM @ 負責人
- 本地開發,本地Lint 實時提醒錯誤,實時拉取最新Lint配置
- 測試,debug輔助工具,新包自動推送至測試機
- 產品發布,創建 簡道云發布流程,設定更新信息,更新策略,選定版本上線的開發,測試,運營(一般默認配好的)
- 開發觸發構建正式包,扭轉至測試測試,完成驗收后自動扭轉至運營發布上線,流程扭轉至開發主管合并發布過的分支至master
構建正式包,自動創建tag推送至倉庫,保留版本標記
數據埋點 神策,熱修復阿里云EMAS(因為騰訊bugly與Tinker不穩定)
安卓端性能優化
-
兼容性測試:云測,阿里云,umeng,真機top100 monkey測試(尤其是針對版本更新)
-
Crash
- 可修復性異常
- 可忽略性異常:使用 Looper Hook
- 測試環境多進程 異常捕獲
-
ANR
- 應用層:主線程阻塞(死循環,IO,超級計算,主線線程等待子線程的鎖)
- 系統層:CPU資源優先級過低,導致應用在后臺ANR(當應用進入后臺時,它可能會被系統賦予更低的CPU資源優先級,以便其他前臺應用可以使用更多的資源。但是,這并不意味著應用的CPU資源優先級一定過低,因為系統的資源分配取決于多種因素,包括應用的優先級、當前系統負載等。如果應用在后臺中運行了長時間的工作,并且消耗了大量的CPU資源,那么它可能會受到ANR的影響,即使它在后臺中運行。)
-
ANR觸發場景
- Service forground 綁定前臺通知,5s
- 廣播,前臺10s,后臺60
- 事件輸入反饋,5s(個別手機ROM會自行攔截,無提示)
- contentProvider 也會導致(很少用,未處理過)
-
ANR 檢測方法(local 與線上)
- watch-dog,通過向主線程插入線程并在子線程做等待檢查判斷是否有可能發生了ANR
- Matrix
- 第三方crash平臺,bugly,阿里云emas,umeng等等
-
啟動優化
隱私策略,啟動任務管理(有向無環圖,多線程初始化,多進程管理,延時初始化,遞進初始化),第三方初始化管理,排除ContentProvider流氓初始化行為
- 首屏加載優化
配合啟動優化后,權衡業務與用戶體驗。優化布局,class初始化(減少巨型class,比如retrofit的service),分屏加載,布局預初始化(這部分時間從啟動優化哪里并行扣出來)
- 卡頓
- 卡頓監聽(本地編舞者(測試小米120fps是有水分的),線上阿里云,umeng等)
- UI布局性能,重繪,重測(降低布局層級,自定義view 無xml化,或constrantlayout,禁止relativelayout)
- 內存使用:內存濫用(view 不重用,濫用靜態存儲,數據緩存無釋放策略)主要是導致頻繁GC造成卡頓
- 代碼質量,框架
- 多進程分擔主進程內存壓力
- 內存泄漏:在充分接入 lifecycle 之后這兩年很少再碰到了
- 內存抖動:典型的是draw方法中 創建大量新對象導致,觸發年輕代gc,造成一定的卡頓
- 網絡
- 網絡協議層錯誤上報(http,tcp,dns)
- 業務側接口數據異常解析報錯監聽上報,預警
- 用戶網絡場景質量 ping值
- 網絡服務容災,多域名,多資源(最早是在廣告業務中應用)
研發流程優化,提升研發效率
深入參與不同的項目,深入參與各個環節的協作過程,收集分析問題,優化流程制度。配合工具與團隊協作流程一起發力,核心是:為團隊的每個角色服務,而不是角色為流程服務
Gradle插件,插樁
用kotlin寫極大的降低插件開發門檻,提升效率
在CI建設中大量開發Gradle插件,打包插件,lint插件重寫,lint支持動態從服務器讀取lint配置,支持局部降級容錯,支持增量掃描(自定義掃描范圍)(自定義lint task 繼承自BaseTask,在super.configure之前加載自己重寫的lint gradle 庫,分為 打包發布插件,lint gradle 插件)
appplugin,libraryplugin
插件開發文檔較少除了gradle官方文檔剩下的就是讀源碼,結合androidbuild流程進行方案設計與開發
- 插樁
使用 Transform 與ASM 修改 .class文件
寫過 打印方法耗時的demo,前置需要2個知識點,一個是插件開發,rigisterTransform,一個是會讀字節碼文件(學習jvm時掌握)
transform有class訪問器跟 方法訪問器,來命中目標方法,ASMified可以講目標class的改寫指令進行可視化,copy進行調試。
Flutter開發經驗
去年為團隊培養出了一個flutter中級開發,3個初級開發,制定階梯式學習項目 ladder-flutter,親自參與到有 native 通信,getX狀態管理,多引擎管理,banner+信息流開發。搭建 flutter 網絡框架,利用范型與分層架構提供快速的數據解析能力,與流式API
面試過程:主要是記錄下回答得不好的地方
帶了ipad,用于講解時展示一些圖片資料,與用畫圖的方式更清晰的方式回復面試問題
一面:基礎技術(JVM,多線程偏多)
- 說一下JVM的結構
說了堆,方法區,線程棧三個塊的介紹。這里漏掉了說太快漏掉了程序計數器跟本地方法棧,但在面試官特地的問的時候也正確回答了作用。
- volatile 的作用與實現原理
用于變量修改時在多線程場景下保持修改的可見性,但并不能完全解決多線程并發修改的問題。舉例了兩個線程訪問 i++的例子。
- 那介紹一下為什么說 i++是兩步操作
介紹了i++字節碼指令執行順序以及方法棧執行細節
- Volatile 修飾的變量在修改時具體如何實現可見性的
這一步我直接回復不知道了,沒有特地看過 volatile 修飾的字節碼,不瞎說。面試官便給我進行了簡單的介紹,我當時就想著回來一定要自己也看一看,之前為啥沒看,哎。
首先在字節碼中 針對Volatile修飾的變量會有一個 ACC_VOLATILE 標志:
-
可見性:當前線程修改后其他線程堆修改后的值立即可見,線程修改值之后立即寫入到主存區,不用等到線程出棧的時候才同步到主存區。(線程工作區內存中保存的值要等待線程出棧時才寫回主存區,在此之前其他線程對修改是不可見的)
-
順序性:即讀寫的順序性,而不會出現 讀的過程中寫入,或寫入過程中被讀取
-
不保證原子性:比如i++
// 讀-寫順序性,假設有一個被 ACC_VOLATILE 標志修飾的 volatile 字段 v// 線程 1
getfield v // 讀取字段 v 的值
iconst_2 // 將常量值 2 壓入操作數棧
putfield v // 將操作數棧頂的值存入字段 v 中// 線程 2
getfield v // 讀取字段 v 的值//寫-讀順序性,假設有一個被 ACC_VOLATILE 標志修飾的 volatile 字段 v// 線程 1
iconst_1 // 將常量值 1 壓入操作數棧
putfield v // 將操作數棧頂的值存入字段 v 中// 線程 2
getfield v // 讀取字段 v 的值
- 那如何保證原子性
加鎖跟使用Atomic類
- Atomic 是如何保證原子性的,或者說如何實現CAS的
這里太久沒碰這塊代碼,聽到CAS蒙了,問了一下原來是說CompareAndSet。不過這里依然是沒準備,直接回答了未深入研究。
gpt:CAS底層采用CPU原子指令或鎖機制來實現,多數情況采用cpu原子指令,通過硬件級別的操作來完成對一個內存位置的讀寫比較。
這個快確實深啊,而且這種信息不太容易記憶。作為一個了解性內容吧。并不會產生二次開發所以應用比原理要重要。
- 你剛講到了堆,鎖,那你了解過對象的markword嗎,它里面有什么信息
markword有了解,但是沒有特別深。記得比較清楚的是有age 用于垃圾回收判斷,有鎖的標記位。
- 那對于偏向鎖是如何轉到輕量鎖的
坦誠了一下想不起來,這里我自己雖然學習過,但是沒作為重點知識準備。
markword中32位中會有幾位來標記鎖信息,其中包含當前持鎖的線程ID
分為 無鎖狀態(對象無鎖),偏向鎖(單線程訪問有鎖對象),輕量鎖(多線程訪問有鎖對象),重量級鎖(輕量級鎖無法解決競爭問題時上升到重量級鎖,借助操作系統完成鎖機制)。這里又能展開,鎖競爭原理,鎖開銷不同的原因。默認在競爭鎖時,為獲取到鎖的線程會進行Spin(自旋,最大10次),在無阻塞情況下嘗試獲取鎖,超過次數閾值后,膨脹為重量級鎖,基于操作系統的鎖機制,進行阻塞式等待鎖的釋放,減少自旋開銷(開銷具體是什么?不想挖了)
應用到代碼里完成知識從理解到應用的閉環:
使用synchronized 修飾方法,類來設置輕量級鎖使用 object.lock()或者可重用鎖,讀寫分離鎖,都是重量級鎖
- 說說 age在垃圾回收中如何應用
這里回答比較清晰,從垃圾回收分區到age增長到對象換內存區
- 另外還問了handler消息機制
這部分主要是講了ThreadLocal的特性,以及結合它如何完成多線程通信的。包括messageQueue的消息處理方式。對于native層nativePollOnce機制直接回復了未深入。我也不打算深入,集中在應用層的使用上收益比較大。
-
問了app啟動流程,與啟動優化
-
TLS/TCP的 部分里擁塞控制跟流量控制部分沒有回答上來
傳輸數據時數據包是被切成小塊的(減少重傳次數),切多大則是根據下面的兩個策略進行平衡的。
擁塞控制:這在接受數據方,這部分是因為在做面試準備的時候沒有刻意看,導致面試的時候沒有結構化的進行表述。整體可以總結為在做數據傳輸時,每一個tcp鏈接都有窗口大小平衡每個tcp的的傳輸速率(分配到的帶寬窗口),窗口大小自然是動態的,通過控制窗口大小避免單個tcp占用過大帶寬,導致傳輸擁塞。
流量控制:發送方會根據接收方允許的窗口大小,調整自己發送的數據包大小,避免擁塞,這一步則是流量控制。
日常開發中針對這個優化則是降低接口數據體量,做接口拆分,分批次請求等等。
- Retrofit用過吧,說下他動態代理的實現原理
這里確實很深,我簡單說了下自己學習掌握的原理概論與一些API與使用 ,因為這個要獲取運行時字節碼才可以搞明白。
面試官為我慷慨講解了一些細節,沒太記清楚,但我自己對于字節碼理解還行,所以相對的給了一些臨場反饋,全當學習了。當然,非常感謝他與我分享。就沖這些,這波面試已經不算白來了。
二面:TL
- 問了組件化經驗,實現方式
- DevOps搭建思路
這部分其實是在我的能力與他們目前需要最契合的部分,我也沒有賣關子。直接用ipad連畫帶講,整個分享了一下。對就是分享了一下,結合團隊技術能力,資源掌握情況,項目階段等等給出整體的建設節奏。
- 啟動優化
- 字節碼插樁
- APP 打包流程
- 線程池的的運行原理
這里確實大意了,核心線程,非核心現場,任務隊列三個之間的扭轉關系,就是先入隊再分配執行。
一個是之前學習是更多的是關注針對業務場景做配置,但是沒有深入到源碼去看。
- 對IOS的熟悉程度,主要是CI部分
這里整體流程沒有少說,但是順序是在面試官的提醒下糾正對的。不過我袒露了資源文件合并跟javac編譯的順序可能記錯了。
整體上還是保持坦誠謙虛的態度。
- 動態Dex加載(插件化部分),問加載Dex的類叫啥
- 分代垃圾回收的不同算法
- 什么對象會被回收
其實就是GCRoot,這里沒回答特別好,忘了1-2個比較重要的。
首先是存活的線程,被存活的線程引用的對象。其次還有系統Class,被線程訪問的方法內的局部變量,被當作鎖或在執行wait或notify的對象。還有一些其他的記不住那么多了,主要是用太少了。
- 如何完成Lint二開的,完成了那些功能
三面:HRD
- 詢問了離職原因
- 詢問我對前兩輪的面試表現自我評價如何
他們兩位在各自的領域展現的能力都非常出色,我對自己的整體評價不是特別好。但跟他們聊得蠻好,已經學到了很多知識,并且在后續溝通中對一些技術方案做了交換性討論,這感覺非常好。
- 我咨詢了公司文化,公司歷史。日常工作流程制度,績效考核等信息。
這塊相互交流了一些工作方式方法,大概了解公司目前的協作流程制度,可能是她很忙所以薪資部分還沒聊就到下一面了。
四面:部門BOSS(管理技能與解決問題的閉環能力)
- 如何做團隊人員建設
- 參與極限開發時如何做管理,保證項目按時交付
- 從用戶體驗角度如何優化避免接口長時間的loading
- DevOps是如何建立的,做了哪些計劃與安排
- 薪資期望(坦誠自己的一些預估,別表示愿意再聊,因為在目前這個薪資包范圍下,小浮動我是不太在乎的)
這部分回答整體都沒啥問題,本身也是我較為擅長的部分,幾乎不用做太多準備。
另外延伸聊到了通勤時間看書效率低,都喜歡看紙質書,公司同事周邊租房的一些現狀。
最后面試官送我到了電梯口,幫我按了電梯,我便主動握手告別,算是結束了今天長達4小時的4輪面試。
次日補充,昨天還是寫得過于樂觀輕飄了。
這部分回答雖然是開放式的,但從一個管理問題出發,應該放在管理框架中系統性的進行回復,這樣才能展現自己完整的管理經驗。
團隊scope要說清楚,以結果為導向的目標管理團隊文化基調
人才盤點,根據什么盤點,如何確認招聘目標,招聘安排,面試安排,試用期安排,這都是自己做過的完整的流程
日常管理,人員安排,從知人善用出發,采用績效考核,培訓分享,以及自己在團隊變大后的角色轉變,團隊成員單面能力超過我,我如何輔助他們做的更好,比如提拔為單方向負責人
項目管理,敏捷開發模式,需求評審,技術預研,工時審核,deadline模式下的區別管理
這中間會涉及到研發流程,CI等等,都可以提一下,有興趣面試官自然會細問
自己在這個過程中做得比較好的就是帶領團隊整體完成從過程管理到目標管理的轉變,取消了日報,改為了早晨共享今日todolist
最后引出自己的底層管理思維:為團隊成員服務,通過團隊去拿結果,減少成員工作的干擾
總結:樂觀接受
- java側針對基礎技術還是要做為重點準備,對準備的每個點做做自我窮問,能有效發現自己那塊兒忘記深入。
- Android側 應用層知識不多,不需要花太多功夫,看看之前整理的文章就行
- 在DevOps側要突出表現一些,多引導面試官了解這部分內容,本身自己對這塊興趣最大,思路比較完整。
雖然不一定100%能拿offer,但這次面試非常值,遠超來回100多的車費了😄。
但是整體面下來節奏自己把握的還不錯,自己掌握的東西基本都引導面試官進行了了解。針對面試沒答上來的問題梳理下,覺得需要深入學的已經學習了,不需要深入學的暫時也不該花精力扎進去。
知識梳理完之后,也準備了不少的電子書和面試筆記等學習文檔,這些筆記將各個知識點進行了完美的總結(包含了很多內容:Android 基礎、Java 基礎、Android 源碼相關分析、常見的一些原理性問題等等)。
- Android 知識點匯總:
https://qr18.cn/CyxarU
- 面試題筆記:
https://qr18.cn/CgxrRy