
「拿到筆試鏈接的時候說實話我心里是沒底的。」2023年秋招我投的是小紅書Android開發(fā)崗第一批筆試通知來得比預期要早。當時翻遍了??秃透黝惣夹g社區(qū)關于這場筆試的具體內容分享非常少大多只有一句「考得比較基礎」這種模糊描述。真正坐到電腦前開始答題才發(fā)現這套題和市面上流傳的Android八股文差異比想象中大得多。這篇文章我想把整場筆試從收到郵件到交卷的完整過程復盤一遍包括題型結構、每題背后的考察點、考場上踩的坑、以及交卷之后我做了什么。如果你也在準備小紅書或者同類公司Android崗位的秋招筆試這篇文章應該能幫你少走一段彎路。1. 這批筆試的基本盤平臺、題量與時間分配1.1 筆試平臺與考試流程我這場筆試用的是??途W的在線筆試系統正式考試前半小時就要進入考場調試攝像頭、麥克風和屏幕共享。這里有一個非?,F實的提醒??途W對切出頁面、打開本地IDE、切后臺這類行為會有記錄不同公司對此的容忍度不一樣。小紅書的規(guī)則是沒有明確的切屏次數上限但每一筆違規(guī)記錄都會留在后臺后續(xù)是否影響面試篩選誰也說不準。所以我全程只在網頁編輯器里寫題哪怕代碼提示再難用也忍著不切出去。整個筆試時長是120分鐘題量分成四塊單選題、多選題、簡答題、編程題。題量不算夸張但每道題都飄著一種「我知道你復習過但我要看看你是真懂還是假懂」的氣息。選擇題看似簡單實際每個選項都要細看簡答題和編程題則需要留出足夠時間勉強趕完很容易在最后幾道送分題上寫崩。1.2 四類題型的結構分布我按記憶整理了一個大概分布每個批次的題量和分值可能有浮動以實際郵件通知為準模塊題量分值占比建議用時單選題約10題25%20分鐘多選題約5題15%15分鐘簡答題2題20%35分鐘編程題2題40%40分鐘時間分配上我最失策的地方就是前面對多選題糾纏太久導致簡答題后半段寫得很趕。如果重來一次單選題最多20分鐘多選題最多15分鐘遇到猶豫超過1分鐘的題直接先標記跳過等所有會的題做完再回頭。筆試的核心原則永遠是「先拿穩(wěn)分再啃難題」。1.3 第一批筆試的定位差異小紅書的秋招筆試分批次安排提前批投遞的簡歷基本都被分在第一批。第一批筆試的題量未必比其他批次大但特殊性在于它處在秋招最早期大家普遍還沒進入刷題狀態(tài)這時候筆試成績對后續(xù)面試安排的參考權重會比想象中更高。所以別抱著「試試水」的心態(tài)去考每一場正式筆試都值得當面試一樣認真對待。我后來復盤時發(fā)現這場筆試的題目整體難度控制在「基礎扎實就能過、基礎不牢就露餡」的水平這也是大廠校招筆試的主流策略。所謂的高難度題其實并不在于題目有多偏而在于考察鏈路的完整性你只有真正寫過代碼、看過源碼、排過生產環(huán)境的問題才能在簡答題里寫出層次感。2. 選擇與多選基礎知識的考察邊界到底劃在哪2.1 單選覆蓋的知識面單選題部分給我的整體印象是不難但非常細。考點集中在Java/Kotlin基礎、JVM內存布局、并發(fā)與集合、Android四大組件與Handler機制、View繪制流程以及少量網絡協議。幾乎每一題都讓我產生「這題我好像見過」的既視感但選項里一定會埋一個模棱兩可的坑。幾個我印象比較深的知識點HashMap在JDK 8里的put流程包括hash擾動函數、插入鏈表還是插入紅黑樹的判斷條件。很多面試題只背了「鏈表長度超過8轉紅黑樹」但真正準確的答案是鏈表長度超過8且數組長度不小于64時才會樹化否則優(yōu)先擴容。synchronized鎖升級的偏向鎖、輕量級鎖、重量級鎖各自適用的競爭場景。這里最容易丟分的是「偏向鎖在JDK 15開始默認被禁用」這類時間線細節(jié)筆試題目可能故意寫成「JDK 8里偏向鎖默認開啟」來試探你。String、StringBuilder、StringBuffer的差異以及字符串常量池在拼接時的行為。比如用final修飾的字符串變量拼接會被編譯器優(yōu)化進常量池沒用final的引用拼接則走StringBuilder。View的requestLayout和invalidate的區(qū)別。一個會觸發(fā)measure和layout一個只觸發(fā)draw但如果你在子線程里調invalidate會得到CalledFromWrongThreadException而requestLayout正常情況下只允許主線程調用。Handler.postDelayed的消息延遲精度到底受什么因素影響。正確答案是受Looper所在線程的消息隊列排隊情況影響系統會按delay時間戳排序但前一個消息如果執(zhí)行太久后面的消息就會順延。這類題對工作一兩年的Android開發(fā)其實是送分題因為都是一天到晚在用的機制。但對在校同學來說如果只看過《Android開發(fā)藝術探索》的章節(jié)標題而沒有真正擼過源碼很容易被選項里的細節(jié)坑到。2.2 多選題的邊界陷阱多選題是本場筆試真正考驗「知識邊界感」的部分。它不像單選那樣選一個正確答案就結束而是把某一個知識點的不同應用場景分到四個選項里問你哪些說法正確。少選、錯選、多選按規(guī)則都不得分。舉一個方向當例子比如ANR的多選題四個選項分別描述前臺BroadcastReceiver超時時間、后臺Service超時時間、ContentProvider啟動超時是否算ANR、InputDispatching超時的具體閾值。你不僅要記住「前臺廣播10秒、后臺廣播60秒」這種數字還得知道Service前臺是20秒、后臺是200秒更要知道ContentProvider啟動超時在部分版本上也算ANR。一個數字記錯整道題就沒了。我在考場上采用的策略是對沒有絕對把握的選項寧可少選。??拖到y的多選評分規(guī)則通常是選錯不得分、少選得部分分所以「選確定項」比「搶分」要穩(wěn)得多。這個策略在后面的多選模塊幫我至少保住了兩道題的分數。2.3 新特性到底考不考我在考前花了不少時間準備Jetpack Compose、Kotlin協程、Flow這些新東西。結果選擇題幾乎沒怎么涉及ComposeKotlin相關考察也停留在let/apply/run的區(qū)別、協程的launch/async返回值類型這些層面。不是說新特性不重要而是筆試題的視角在告訴你筆試考察的是你「能不能立刻上手干Android的活」而不是「能不能跟上技術新潮流」。新特性大概率會放到面試環(huán)節(jié)去聊。這也意味著大家在準備筆試時優(yōu)先級應該是Java/Kotlin基礎語法與集合框架、Handler/Looper/MessageQueue機制、View繪制流程、四大組件生命周期、網絡協議基礎這些要占掉七成精力。Compose這類新技術了解使用方式就夠了不建議花大把時間死磕。3. 簡答題Handler內存泄漏與ANR層次感決定分差3.1 Handler內存泄漏的完整答案鏈路這套筆試有兩道簡答題第一道就是經典中的經典Handler導致內存泄漏的原因以及處理方案。這個題在面經里被寫爛了但真正能拿高分的答案并不多。如果只寫「用WeakReference包一下」就是送死因為面試官想看到的是完整的分析鏈路。我當時是按「引用鏈描述 根因定位 解決方案 工程化措施」四層來寫的第一層引用鏈描述。非靜態(tài)內部類或匿名內部類默認持有外部類的引用這里的典型場景是Activity里new一個HandlerHandler未處理完的Message被MessageQueue持有Message持有Handler引用Handler持有Activity引用。由于主線程的Looper在App進程存活期間不會退出MessageQueue只要存在這條引用鏈就不會斷裂GC就無法回收Activity。第二層根因定位。核心問題是Message的生命周期可能比Activity更長尤其用postDelayed發(fā)出的延遲消息即使Activity已經finish它也會在消息隊列里躺到延遲時間到達。所以內存泄漏的根源不是Handler本身而是「延遲消息 長生命周期Looper 隱式持有Activity」這三個條件疊加。第三層解決方案。最經典的做法是使用靜態(tài)內部類WeakReference持有Activity同時在onDestroy里調用handler.removeCallbacksAndMessages(null)把消息隊列里與該Handler相關的所有消息一并移除。這兩件事必須同時做只做前者解決不了「消息還在隊列里占內存」的問題只做后者沒法應對Handler可能被其他地方復用的情況。第四層工程化措施。在Kotlin環(huán)境里可以換用協程生命周期感知的Scope替代Handler利用Lifecycle框架在onDestroy時自動取消協程從根上避免這種問題。3.2 ANR的定位、分析與解決第二道簡答圍繞ANR展開什么是ANR、如何定位、如何解決。這道題很容易答成「主線程不能做耗時操作」一句話那就廢了。我用的是「類型判斷-獲取現場-原因分析-修復思考」這個流程。類型判斷上ANR有幾種常見形態(tài)InputDispatchingTimeout是5秒前臺BroadcastReceiver是10秒后臺BroadcastReceiver是60秒前臺Service是20秒后臺Service是200秒ContentProvider啟動超時在部分版本也被視為ANR。不同超時時間背后是不同的調度策略前置知識不能錯。獲取現場方面關鍵動作是從設備導出 /data/anr/traces.txt或者用 adb pull /data/anr/ 抓取ANR發(fā)生的調用棧。如果現場正好在Android 10以上設備可以用perfetto抓取systrace數據看主線程執(zhí)行的時間線。原因分析上常見的主線程阻塞源有主線程直接做磁盤I/O、等待鎖時發(fā)生鎖競爭、Binder調用主線程同步等待、死鎖、廣播接收器里做耗時操作、View層級中的過度測量等。答案必須有具體場景而不是「主線程慢」這種空話。解決思路上要把耗時操作移到子線程或協程中減少主線程消息隊列的積壓避免在持有鎖時做耗時操作。最關鍵的一點ANR機制是Android的自我保護我們要做的是「讓主線程更快消費消息」而不是簡單地把消息隊列清掉。3.3 怎么答題才像「有實戰(zhàn)經驗的人」簡答題在全卷里分值不是最高的但它留給面試官的印象是其他題型比不了的。因為筆試答卷會被當成面試時的參考材料面試官會從你簡答題的排版和邏輯里判斷這個人有沒有debug的習慣。我當時的寫法是分點加粗每個層次一段整體用「現象-原因-定位-解決-驗證」的結構展開。另外提醒一點筆試答題框不是IDE不要在里面寫大段代碼。簡答題的重點是思路清晰、邏輯完整代碼展示反而會顯得你表達不夠凝練。寫上關鍵API名和偽代碼流程就夠了。4. Activity啟動流程與AMS這批題里真正的分水嶺4.1 startActivity到首幀渲染的完整鏈路小紅書這批筆試里有一道讓我印象很深的題核心是讓你描述從startActivity到Activity顯示在屏幕上的完整流程。這種題是典型的Framework源碼題單靠背《Android開發(fā)藝術探索》是答不全的。我當時按這條鏈路寫的應用進程調用startActivity經過Instrumentation的execStartActivity方法通過Binder調用到系統進程的AMS也就是ActivityManagerService。這里要注意的是Android 10以后Activity調度職責被抽到了ATMS即ActivityTaskManagerServiceAMS更偏向進程管理和內存管理但整體上我習慣把它們合稱AMS。ATMS收到請求后會校驗調用方的權限、解析Intent、判斷啟動模式和任務棧配置。如果目標Activity所在進程還沒啟動就需要通過Socket向Zygote進程發(fā)送請求Zygote fork出新的應用進程。新進程啟動后會創(chuàng)建ActivityThread實例調用main方法創(chuàng)建主線程Looper然后調用attach方法把ApplicationThread這個Binder對象注冊給AMS告訴系統「我準備好了」。接下來AMS通過ApplicationThread這個Binder代理通知ActivityThread去創(chuàng)建Activity。ActivityThread通過ClassLoader加載目標Activity類創(chuàng)建實例并調用attachActivity的attach里會創(chuàng)建PhoneWindow和WindowManager。隨后系統調度Activity生命周期依次執(zhí)行onCreate、onStart、onResume。在onCreate里我們通過setContentView設置布局onResume之后ViewRootImpl接管視圖發(fā)起measure、layout、draw流程最終由SurfaceFlinger合成并上屏。這道題的關鍵不僅是把流程背下來還要在每個節(jié)點標注出「這里是Binder跨進程調用」還是「這里是Handler消息驅動」這是能拉開差距的層次。4.2 Binder為什么是Android IPC的默認選擇和啟動流程一起考的還有一個Binder機制的簡答。題目很直白為什么Android選Binder而不是共享內存、管道、Socket來做IPC。我當時的回答包含三個層面第一性能上Binder只有一次數據拷貝。傳統的管道、Socket機制數據從發(fā)送進程到接收進程至少需要在用戶態(tài)和內核態(tài)之間拷貝兩次而Binder通過內核分配的一塊匿名共享內存做映射數據從發(fā)送進程直接拷貝到內核緩沖區(qū)再通過內存映射讓接收進程直接讀取全程只有一次拷貝。第二安全上Binder具備調用方身份校驗。Binder驅動在內核態(tài)就能拿到調用方進程的UID/PID系統服務可以基于這個信息做權限校驗偽造調用方身份比傳統IPC難得多。這套機制對整個Android系統安全模型至關重要。第三語義上Binder是面向對象的遠程過程調用??蛻舳四玫降腂inder代理對象可以像調用本地方法一樣調用遠程服務這種設計讓系統服務上百個接口的管理變得簡單清晰。如果在回答里能補充「Binder一次拷貝但MMAP依舊會有一次內存同步開銷」這種細節(jié)會顯得你真的研究過而不是背框架。4.3 AMS與ATMS的新舊術語辨析Android 10之后啟動流程里最大的變化是Activity的管理職責從AMS拆分到了ATMS。傳統框架在面試題里經常把AMS和Activity管理綁定說但新版系統里ActivityTaskManagerService負責Activity棧、Task和Window的調度AMS則聚焦于進程、內存、廣播隊列的管理。這道題我沒等到面試官問直接在簡答題末尾加了一句話「在Android 10以上的版本中Activity調度被拆分到了ATMS整體架構上是AMSATMS協作完成啟動流程?!购竺婷嬖嚨臅r候面試官確實對這句話產生了興趣追問我ATMS和AMS的邊界到底怎么劃分。從結果看這一句補充的性價比非常高。5. 工程化大題R8、啟動優(yōu)化與包體積控制5.1 R8在構建流程里干了什么小紅書這套筆試里有一道工程化方向的題目題干給了一個和混淆日志相關的現象問應該如何配置保留規(guī)則防止指定類被裁剪或混淆。這題背后的知識點是R8的四個核心動作。R8現在已經替代ProGuard成為Android默認的代碼壓縮與混淆工具它做四件事壓縮刪除不可達的類和成員優(yōu)化對字節(jié)碼做常量折疊、方法內聯等操作混淆把類名方法名改成無意義短名脫敏適配字符串資源對類名的引用。實際配置時最常踩的坑是被反射調用的類、被Gson序列化的模型類、被注解處理器生成的代碼在混淆后找不到。最簡單的處理方式是給這類類加Keep注解或者配置-keep class com.example.model.* { *; }。但更細致的做法是區(qū)分場景反射類要保留類名Gson模型要保留無參構造器和字段名Keep注解在R8里也會被正確識別。如果你在筆試里能寫出-keepclassmembers和-keepattributes的區(qū)別說明你對混淆規(guī)則的顆粒度理解到位。前者是「只保留成員規(guī)則不保留類」后者是「保留注解、Signature等元信息」這也是很多線上崩潰的根因。5.2 啟動優(yōu)化的完整答題閉環(huán)另一道工程題問的是冷啟動優(yōu)化策略。這種題沒有標準答案但答題一定要體現「定位問題-確定瓶頸-動手優(yōu)化-驗證效果」的閉環(huán)而不是堆幾個「異步化」「懶加載」的術語。我的回答分三步第一步用Systrace或Perfetto記錄冷啟動時間線區(qū)分Application的onCreate、Activity的onCreate和首幀渲染各占多少時間。只有數據出來了才知道優(yōu)化精力該投在哪。第二步針對主線程上的耗時點逐一處理非核心SDK初始化移到子線程使用有向無環(huán)圖的任務調度框架做并行初始化SharedPreferences讀取拆分并異步化內容加載改成懶加載。第三步首幀渲染層面簡化布局層級避免過度繪制減少冷啟動階段的主線程磁盤IO。最后加一個驗證手段用adb shell am start -W 包名/.MainActivity查看TotalTime或者用自定義的StartUpTimer埋點統計。有數據對比才能證明優(yōu)化有效。5.3 包體積優(yōu)化的規(guī)?;桨赴w積的題通常不會問你「一張圖能壓多少」而是問「你怎么在團隊里持續(xù)控制包體積增長」。當時我的回答分資源、代碼和CI三條線資源線上開啟shrinkResources配合資源混淆把大圖庫替換成WebP通過資源打包配置按需拆分多語言資源。代碼線上R8裁剪無用的Java代碼按ABI拆分so文件避免全量打包64位和32位兩套庫必要時用動態(tài)特性模塊化把低頻功能做成遠程模塊。持續(xù)化建設上在CI流水線里加上包體積Diff檢查單次PR導致包體積超過閾值就直接攔截。這個思路比任何優(yōu)化技巧都重要因為包體積問題本質上是一個增量的工程管理問題不是一次優(yōu)化就能搞定的。我特意在答案里加了「小紅書作為內容社區(qū)App啟動速度和包體積對用戶留存非常敏感」這個場景化表述讓回答更貼近投遞公司的業(yè)務形態(tài)。6. 編程題兩道題從讀題到提交的完整過程6.1 第一題字符串按字符頻率降序輸出編程題第一道是字符串處理給定一個字符串按字符出現頻率降序輸出頻率相同時按字典序或原順序輸出。這題的核心是HashMap統計加排序思路很直白難點在代碼的穩(wěn)健性。我的Java解法public String frequencySort(String s) { if (s null || s.isEmpty()) { return ; } MapCharacter, Integer freq new HashMap(); for (char c : s.toCharArray()) { freq.put(c, freq.getOrDefault(c, 0) 1); } ListCharacter chars new ArrayList(freq.keySet()); chars.sort((a, b) - { int cf freq.get(b) - freq.get(a); return cf ! 0 ? cf : a.compareTo(b); }); StringBuilder sb new StringBuilder(); for (char c : chars) { int count freq.get(c); for (int i 0; i count; i) { sb.append(c); } } return sb.toString(); }這道題有四個容易丟分的地方字符范圍不止字母可能有數字、下劃線甚至中文所以容器類型必須用Character而不能寫死a到z排序比較的是頻率而不是字母本身空串和單字符的邊界要處理最后拼接字符串的時候注意用capacity預估避免StringBuilder反復擴容。6.2 第二題矩陣連通區(qū)域的最大面積第二道是DFS/BFS的圖論基礎題描述是在一個由0和1組成的二維矩陣中找到相鄰1組成的最大連通區(qū)域面積。核心是DFS每個格子最多遍歷一次時間復雜度O(row*col)。public int maxAreaOfIsland(int[][] grid) { if (grid null || grid.length 0) { return 0; } int rows grid.length; int cols grid[0].length; int max 0; for (int i 0; i rows; i) { for (int j 0; j cols; j) { if (grid[i][j] 1) { max Math.max(max, dfs(grid, i, j, rows, cols)); } } } return max; } private int dfs(int[][] grid, int i, int j, int rows, int cols) { if (i 0 || i rows || j 0 || j cols || grid[i][j] 0) { return 0; } grid[i][j] 0; return 1 dfs(grid, i 1, j, rows, cols) dfs(grid, i - 1, j, rows, cols) dfs(grid, i, j 1, rows, cols) dfs(grid, i, j - 1, rows, cols); }有幾個細節(jié)值得注意DFS會修改原數組來標記已訪問如果題目要求不修改輸入就得額外開布爾數組遞歸方案在大矩陣上可能棧溢出技術上可以用顯式的循環(huán)棧替代遞歸但筆試環(huán)境里遞歸通常夠用判斷順序上越界檢查必須放在訪問數組元素之前。6.3 考場上的一次翻車與補救實話說第二題我在考場上先寫了一個BFS版本結果在出隊時的visited標記時機上卡了幾分鐘。后來我果斷刪掉BFS代碼退回寫法最樸素的DFS一次通過。這在筆試里是很常見的情況不是你不會而是在時間壓力下容易把方案想復雜。我的經驗是筆試編程題的正確性遠比代碼優(yōu)雅重要。如果你沒有十成把握寫出一個復雜的解法就選擇最穩(wěn)的解法先拿分。有的同學在考場上非要寫一個帶剪枝的優(yōu)化版本結果邊界條件處理不完反倒連基礎分都沒拿到。7. 復盤筆試之后我做的幾件事以及一份備考清單7.1 用錯題反向建立知識樹交卷之后我沒有立刻開始刷下一家的題而是花了半天把筆試里所有拿不準的選擇題記錄下來按「Java基礎」「Android機制」「網絡協議」「工程化」四類歸到自己的知識點表格里。筆試里的錯題是最寶貴的復習材料它直接暴露了你的盲區(qū)在哪個具體分支上比隨機刷題高效得多。這次筆試之后我把Android崗的準備重心調整成了這樣幾塊把《Android開發(fā)藝術探索》里Handler、AMS、View繪制三章重讀一遍在源碼層面畫出startActivity和Binder的整體時序圖不求背行號但要能講清楚每一步是誰調用了誰每周固定做兩到三道DFS/BFS、字符串處理和鏈表相關的算法題整理一份「一頁紙答題模板」凡是遇到原理題就按「背景-機制-場景-邊界-解決」來組織文字保證不丟層次。7.2 一份可以直接抄的備考清單結合這場筆試和我整個秋招的經驗整理了一份實用性優(yōu)先的清單Java/Kotlin基礎集合源碼、HashMap的put流程、鎖機制與并發(fā)、String不可變性、Kotlin協程的launch與async差異。Android基礎Handler/Looper/MessageQueue、View測量布局繪制流程、事件分發(fā)機制、四大組件生命周期、任務棧與啟動模式。Framework進階startActivity完整流程、Binder機制、Zygote進程創(chuàng)建、ANR原理、AMS與ATMS的分工。工程化與性能R8壓縮混淆規(guī)則、冷啟動優(yōu)化、包體積控制、內存泄漏檢測、穩(wěn)定性監(jiān)控思路。算法與數據結構字符串、哈希表、隊列與棧、DFS/BFS、鏈表、二叉樹層序遍歷、雙指針。7.3 非技術層面的幾個提醒筆試當天提前調好設備攝像頭、麥克風、網絡都提前試一遍。答題遇到不會的選擇題要學會戰(zhàn)略性跳過把時間留給編程題。我見過不少人在多選題上死磕結果后面簡答題只寫兩行、編程題超時這種失分是最可惜的。筆試本質上考的是「你有沒有真正做過Android開發(fā)」。寫過項目和只刷面試題的差距在簡答題的組織層次和編程題的容錯性上會體現得很明顯。這也是為什么有些同學看起來什么都背過分數卻并不理想。最后說一個我自己的體會小紅書這批筆試結束后大概一周內收到了面試邀約整體推進節(jié)奏比想象中快。如果你筆試感覺還不錯那幾天就開始復盤自己的項目經歷不要等面試通知來了再匆忙準備那時候真的會手忙腳亂。