盤:從Handler到并發(fā)任務(wù)調(diào)度的實(shí)戰(zhàn)指南)
2023年春招季我完整走了一遍小滿科技Android研發(fā)崗的筆試流程。這家公司在外貿(mào)CRM領(lǐng)域做了很多年移動(dòng)端業(yè)務(wù)復(fù)雜度不算低所以筆試題目也比較貼近真實(shí)業(yè)務(wù)場(chǎng)景。整場(chǎng)筆試線上進(jìn)行90分鐘包含了客觀題、編程題、簡(jiǎn)答題和一道開(kāi)放設(shè)計(jì)題??纪陱?fù)盤下來(lái)我的感受是題型不偏但每類題都在考察你是否真的做過(guò)項(xiàng)目而不是單純背過(guò)面試題。這篇東西就把我記錄的題目方向、答題思路和一些踩坑經(jīng)驗(yàn)整理出來(lái)給后面準(zhǔn)備Android春招的朋友做個(gè)參考。1. 筆試整體情況與題型結(jié)構(gòu)分析1.1 考試形式、環(huán)境與客觀題分布小滿這場(chǎng)筆試用的在線考試系統(tǒng)瀏覽器里直接寫代碼不能本地切IDE所以平時(shí)習(xí)慣依賴Android Studio自動(dòng)補(bǔ)全的人會(huì)稍微難受一點(diǎn)。我進(jìn)場(chǎng)前特意把常用API的拼寫和關(guān)鍵方法名在腦子里過(guò)了一遍避免寫代碼時(shí)卡在記憶細(xì)節(jié)上。建議后面參加筆試的同學(xué)也提前看下考試平臺(tái)支不支持本地編輯器如果不支持提前適應(yīng)純手寫代碼的節(jié)奏??陀^題部分是整套卷子的基礎(chǔ)盤我當(dāng)時(shí)數(shù)了一下大概25題左右覆蓋了Java基礎(chǔ)、Android基礎(chǔ)、網(wǎng)絡(luò)協(xié)議和數(shù)據(jù)結(jié)構(gòu)四大塊。Java部分考了集合源碼、線程安全、JVM內(nèi)存分區(qū)、類加載流程Android部分集中在Activity生命周期、啟動(dòng)模式、Handler、Binder、View繪制流程這些經(jīng)典考點(diǎn)。網(wǎng)絡(luò)層問(wèn)到了TCP三次握手、HTTP緩存機(jī)制、HTTPS證書校驗(yàn)流程都是老熟人。整體難度不算高但多選題有個(gè)比較坑的規(guī)則少選得分、錯(cuò)選不得分所以拿不準(zhǔn)的選項(xiàng)我寧可空著也不亂選。題型分布我整理成了下面這張表可以直接對(duì)照著查漏補(bǔ)缺題型題量考察范圍難度感受單選/多選約25題Java、Android、網(wǎng)絡(luò)、數(shù)據(jù)結(jié)構(gòu)中等偏易編程題11題貪心/動(dòng)態(tài)規(guī)劃、邊界處理中等LeetCode中等偏下編程題21題并發(fā)任務(wù)調(diào)度、線程池、回調(diào)中等偏上區(qū)分度高簡(jiǎn)答題約2題Handler線程模型、組件通信偏原理靠理解開(kāi)放設(shè)計(jì)題1題模塊化架構(gòu)、Jetpack選型靠項(xiàng)目經(jīng)驗(yàn)1.2 編程題與簡(jiǎn)答題的難度分級(jí)兩道編程題第一道是區(qū)間調(diào)度問(wèn)題給定一組商品上架的時(shí)間區(qū)間求最多能安排多少個(gè)不重疊的區(qū)間本質(zhì)是經(jīng)典貪心按結(jié)束時(shí)間排序后逐個(gè)取。這題LeetCode上類似題很多平時(shí)刷過(guò)基本沒(méi)問(wèn)題。第二道題就很有Android特色了——讓實(shí)現(xiàn)一個(gè)支持并發(fā)數(shù)限制和結(jié)果回調(diào)的任務(wù)執(zhí)行器。這道題沒(méi)有標(biāo)準(zhǔn)答案考察的是線程池參數(shù)、任務(wù)隊(duì)列、FutureTask、回調(diào)線程切換這些實(shí)戰(zhàn)能力我在第3章會(huì)專門展開(kāi)講。簡(jiǎn)答題有一道是“如何設(shè)計(jì)一個(gè)支持多模塊的App”不要求寫代碼但要有清晰的方案。這類題的核心是架構(gòu)分層和模塊邊界劃分能看出來(lái)面試官在乎的是候選人有沒(méi)有系統(tǒng)設(shè)計(jì)意識(shí)。另一道是Handler相關(guān)要求描述從子線程發(fā)送消息到主線程處理消息的完整鏈路并說(shuō)明哪些環(huán)節(jié)容易出現(xiàn)內(nèi)存泄漏。1.3 這場(chǎng)筆試想篩選什么人從整套題目的結(jié)構(gòu)反推小滿的用人畫像比較清晰第一基礎(chǔ)要扎實(shí)Java和Android底層機(jī)制不能停留在“會(huì)用”層面得能說(shuō)出原理第二要有真實(shí)項(xiàng)目經(jīng)驗(yàn)內(nèi)存泄漏排查、列表卡頓優(yōu)化、組件化改造這些經(jīng)歷過(guò)的最加分第三要有設(shè)計(jì)意識(shí)哪怕沒(méi)做過(guò)大型項(xiàng)目也要能對(duì)著業(yè)務(wù)場(chǎng)景畫出清晰的模塊邊界和數(shù)據(jù)流。這三個(gè)點(diǎn)后面我會(huì)逐個(gè)展開(kāi)怎么準(zhǔn)備。2. 核心考點(diǎn)拆解從四大組件到Handler機(jī)制2.1 Activity啟動(dòng)模式不要只背四種模式Activity的四種啟動(dòng)模式——standard、singleTop、singleTask、singleInstance是Android筆試的固定嘉賓。但小滿這批題問(wèn)得更細(xì)直接把我問(wèn)懵了一下singleTask配合taskAffinity使用時(shí)Activity會(huì)被轉(zhuǎn)移到哪個(gè)任務(wù)棧launchMode在Intent中設(shè)置flag之后誰(shuí)優(yōu)先生效onNewIntent的回調(diào)時(shí)機(jī)和觸發(fā)條件是什么我建議后來(lái)備考的朋友不要死記定義而是從任務(wù)棧操作的角度去理解。standard模式就是每次startActivity都new一個(gè)實(shí)例壓入當(dāng)前任務(wù)棧singleTop是“如果要啟動(dòng)的Activity已經(jīng)在棧頂直接復(fù)用”singleTask是“目標(biāo)Activity所在任務(wù)棧里如果已存在該實(shí)例就把它上面的Activity全部出?!眘ingleInstance是整個(gè)系統(tǒng)只有一個(gè)實(shí)例并且這個(gè)Activity獨(dú)占一個(gè)任務(wù)棧。理解了這四個(gè)場(chǎng)景再結(jié)合taskAffinity、FLAG_ACTIVITY_NEW_TASK、FLAG_ACTIVITY_CLEAR_TOP去記憶搭配效果會(huì)順手很多。具體來(lái)說(shuō)taskAffinity默認(rèn)跟application一致singleTask模式下如果指定了不同的taskAffinityActivity會(huì)嘗試去那個(gè)任務(wù)棧找實(shí)例找不到就在那個(gè)棧創(chuàng)建。Intent里設(shè)置的FLAG_ACTIVITY_NEW_TASK優(yōu)先級(jí)要高于Manifest里的launchMode這個(gè)細(xì)節(jié)是單選題的??臀耶?dāng)時(shí)看到題目第一反應(yīng)就是“好在專門記過(guò)這一條”。2.2 Handler、Binder與線程模型把消息機(jī)制講成一條鏈路Handler機(jī)制幾乎是所有Android筆試必考小滿也不例外。我備考時(shí)習(xí)慣把這一塊講成一條完整鏈路應(yīng)用啟動(dòng)時(shí)ActivityThread會(huì)創(chuàng)建主線程的LooperLooper內(nèi)部維護(hù)一個(gè)MessageQueueHandler通過(guò)sendMessage將Message入隊(duì)Looper.loop()循環(huán)取出消息回調(diào)Handler的handleMessage。整個(gè)過(guò)程用一句話概括就是用MessageQueue連接了生產(chǎn)者和消費(fèi)者用ThreadLocal保證了每個(gè)線程只有一個(gè)Looper。筆試?yán)镪P(guān)于Handler的高頻坑有兩個(gè)。第一個(gè)主線程能不能在子線程創(chuàng)建Handler答案是不能直接創(chuàng)建除非在該子線程中先調(diào)用Looper.prepare()再調(diào)用Looper.loop()這樣這個(gè)子線程才有了自己的Looper和消息隊(duì)列。第二個(gè)內(nèi)存泄漏怎么產(chǎn)生的內(nèi)部類Handler默認(rèn)持有外部Activity引用如果MessageQueue里有延遲消息就會(huì)阻止Activity回收。標(biāo)準(zhǔn)答法是“使用靜態(tài)內(nèi)部類加WeakReference并在onDestroy中移除回調(diào)”但如果你能補(bǔ)充一句“還可以用Lifecycle把任務(wù)綁定到生命周期或者用協(xié)程的MainScope來(lái)代替Handler”那就能讓面試官看到你對(duì)現(xiàn)代寫法的關(guān)注。Binder部分考了一道關(guān)于進(jìn)程間通信的選擇題。我的理解是Binder基于mmap機(jī)制做了一次數(shù)據(jù)拷貝相比傳統(tǒng)的管道和共享內(nèi)存它在安全性和性能之間做了很好的平衡。SystemServer進(jìn)程作為核心服務(wù)管家AMS、WMS、PackageManager都跑在里面App進(jìn)程通過(guò)Binder代理調(diào)用這些服務(wù)。這一套如果能在簡(jiǎn)答題里畫出來(lái)從App調(diào)用到AMS仲裁再到應(yīng)用啟動(dòng)的鏈路基本就把面試官的胃口吊起來(lái)了。2.3 JVM、并發(fā)與引用類型Java基礎(chǔ)題的深層邏輯客觀題里Java并發(fā)相關(guān)的比例不低。synchronized和Lock的區(qū)別、volatile的可見(jiàn)性、ThreadLocal的實(shí)現(xiàn)原理、線程池的參數(shù)含義這些幾乎年年都考。準(zhǔn)備這一塊時(shí)別只背結(jié)論要去想“為什么”。比如volatile它有三個(gè)語(yǔ)義保證可見(jiàn)性、禁止指令重排、但不保證原子性。筆試?yán)锶绻紇olatile和synchronized的區(qū)別別只答“volatile不能保證原子性”最好能舉出一個(gè)具體場(chǎng)景——兩個(gè)線程對(duì)一個(gè)long類型變量做自增用volatile修飾后仍然可能丟值因?yàn)樽栽霾僮鞅旧聿皇窃硬僮?。這就是“會(huì)說(shuō)”和“會(huì)答”的區(qū)別。JVM部分小滿的題集中問(wèn)了內(nèi)存分區(qū)堆、棧、方法區(qū)/元空間、程序計(jì)數(shù)器、本地方法棧、對(duì)象創(chuàng)建過(guò)程、GC Roots、四種引用類型的區(qū)別。關(guān)于引用類型Android開(kāi)發(fā)中更常接觸到的是內(nèi)存緩存和圖片庫(kù)我建議結(jié)合LruCache和Bitmap的例子去理解軟引用與弱引用的適用場(chǎng)景。比如Bitmap在Android 2.3時(shí)代經(jīng)常用SoftReference做內(nèi)存緩存后來(lái)因?yàn)檐浺脤?duì)象被GC回收得過(guò)于積極才換成了LruCache這種基于LRU算法的強(qiáng)引用緩存方案。這樣答出來(lái)會(huì)比干巴巴背定義豐富得多。3. 代碼題、架構(gòu)題與Android Studio工具鏈考察3.1 手寫代碼題從算法到異步任務(wù)調(diào)度筆試環(huán)境里寫代碼最重要的不是你寫得多花哨而是邏輯清晰、邊界完整。第一道區(qū)間調(diào)度題我的思路是貪心把所有區(qū)間按結(jié)束時(shí)間升序排序然后遍歷如果當(dāng)前區(qū)間的開(kāi)始時(shí)間大于等于上一個(gè)已選區(qū)間的結(jié)束時(shí)間就選取它。我在提交前還特意補(bǔ)了兩個(gè)邊界用例——空數(shù)組和只有一個(gè)區(qū)間的情況并且把時(shí)間復(fù)雜度和空間復(fù)雜度寫在注釋里。這些細(xì)節(jié)在線上筆試的判分里未必直接加分但面試官看回放時(shí)會(huì)注意到。第二道題“實(shí)現(xiàn)一個(gè)支持并發(fā)數(shù)限制和結(jié)果回調(diào)的任務(wù)執(zhí)行器”是整套卷子真正拉開(kāi)差距的地方。我的實(shí)現(xiàn)方案是public class LimitedTaskExecutor { private final ExecutorService executor; private final Handler mainHandler; public LimitedTaskExecutor(int maxConcurrent) { this.executor new ThreadPoolExecutor( maxConcurrent, maxConcurrent, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue(100), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy()); this.mainHandler new Handler(Looper.getMainLooper()); } public T void submit(CallableT task, CallbackT callback) { FutureTaskT futureTask new FutureTask(() - { T result task.call(); mainHandler.post(() - callback.onSuccess(result)); return result; }); executor.execute(futureTask); } public interface CallbackT { void onSuccess(T result); } }這套方案用有界隊(duì)列加CallerRunsPolicy既能限制并發(fā)數(shù)又不會(huì)因?yàn)槿蝿?wù)堆積導(dǎo)致OOM回調(diào)切回主線程也符合Android UI更新的要求。能想到這層的候選人不多我當(dāng)時(shí)注意到這場(chǎng)筆試?yán)锖芏嗳说诙}都是空著的所以把這題寫完整非常值。3.2 架構(gòu)設(shè)計(jì)題Jetpack MVVM與模塊化思路“如何設(shè)計(jì)一個(gè)支持多模塊的App”這道簡(jiǎn)答題我把它當(dāng)成了一場(chǎng)小型系統(tǒng)設(shè)計(jì)的口頭表達(dá)。我的答題框架是從分層架構(gòu)開(kāi)始——數(shù)據(jù)層用Repository封裝Room、Retrofit的數(shù)據(jù)來(lái)源領(lǐng)域?qū)臃臮seCase展示層用ViewModel加Compose或者XML加DataBinding再做模塊化拆分——按業(yè)務(wù)拆成訂單、客戶、報(bào)表等獨(dú)立模塊模塊間通過(guò)路由框架解耦跳轉(zhuǎn)最后補(bǔ)充組件化編譯優(yōu)化思路。這里有個(gè)容易被忽略的點(diǎn)Jetpack 組件之間的生命周期綁定原理。LiveData和ViewModel為什么能自動(dòng)感知生命周期ViewModelStore里存有ViewModel實(shí)例配置變更時(shí)Activity被重建但ViewModelStore會(huì)在舊Activity銷毀之前傳遞給新的ActivityLiveData的observe是在主線程回調(diào)數(shù)據(jù)變化時(shí)通過(guò)LifecycleOwner判斷當(dāng)前是否活躍不活躍時(shí)不會(huì)回調(diào)。這兩個(gè)細(xì)節(jié)我在筆試?yán)锒紝懥顺鰜?lái)這種“知道但說(shuō)不清”的點(diǎn)恰恰是區(qū)分簡(jiǎn)歷水分的關(guān)鍵。模塊化問(wèn)題如果只答到“分成多個(gè)module、用ARouter跳轉(zhuǎn)”就太淺了。我當(dāng)時(shí)補(bǔ)充了資源前綴管理、依賴版本統(tǒng)一、模塊間通信使用接口而不是直接依賴實(shí)現(xiàn)類以及基礎(chǔ)庫(kù)和業(yè)務(wù)庫(kù)的分層關(guān)系。這些具體方案比空談原則更能打動(dòng)面試官。3.3 工具鏈與測(cè)試AGP、R8、單元測(cè)試這些容易被忽略的點(diǎn)熱搜詞里出現(xiàn)了很多Android Studio相關(guān)的詞比如AGP版本、Android Studio Hedgehog等。筆試雖然不會(huì)直接問(wèn)“AGP 8和7有什么區(qū)別”但在代碼壓縮、構(gòu)建配置相關(guān)的題目里會(huì)隱晦地涉及。我的經(jīng)驗(yàn)是至少要熟悉AGP從3.4之后默認(rèn)用R8作為壓縮器minifyEnabled控制是否開(kāi)啟代碼壓縮shrinkResources可以配合R8移除無(wú)用資源。如果簡(jiǎn)歷寫了“精通構(gòu)建優(yōu)化”這幾點(diǎn)就是必答項(xiàng)。R8的keep規(guī)則也是熱門考點(diǎn)我在備考時(shí)整理了項(xiàng)目里常用的一份筆試?yán)镉蒙狭?keep class com.example.api.** { *; } -keepclassmembers class * { android.webkit.JavascriptInterface methods; } -keepattributes Signature, InnerClasses, EnclosingMethod這句話的意思是被接口定義或反射調(diào)用的類不能被混淆帶注解的方法要保留原名。能主動(dòng)寫出具體規(guī)則比只會(huì)說(shuō)“加keep”要扎實(shí)得多。測(cè)試相關(guān)的小滿分也考了問(wèn)的是如何保證核心業(yè)務(wù)代碼質(zhì)量。我答了單元測(cè)試用JUnit加MockitoUI測(cè)試用Espresso關(guān)鍵路徑用Robolectric在本地跑數(shù)據(jù)層網(wǎng)絡(luò)請(qǐng)求用MockWebServer模擬。筆試后面試官還追問(wèn)了一句ViewModel要測(cè)什么答案是測(cè)狀態(tài)變化和生命周期行為不涉及Android Framework依賴所以JUnit環(huán)境下可以直接跑。這個(gè)補(bǔ)充點(diǎn)一下就接上了話題。4. 進(jìn)階與冷門考點(diǎn)性能優(yōu)化、系統(tǒng)服務(wù)、硬件交互4.1 性能優(yōu)化內(nèi)存泄漏、ANR、卡頓排查怎么答性能優(yōu)化幾乎是所有大廠筆試題的常駐嘉賓小滿這場(chǎng)也不例外??陀^題里有一道問(wèn)“ListView和RecyclerView的緩存機(jī)制差異”簡(jiǎn)答題則讓描述一次線上OOM的排查過(guò)程。我當(dāng)時(shí)的答題思路是先通過(guò)logcat確認(rèn)異常線程和堆棧再用Android Studio的Profiler抓內(nèi)存快照用LeakCanary輔助定位泄漏對(duì)象最后結(jié)合MAT分析引用鏈。這個(gè)流程是最常規(guī)、最穩(wěn)妥的答法。ANR的排查也要會(huì)。筆試問(wèn)的是“App主線程卡頓太久會(huì)觸發(fā)什么為什么”答案是Activity超過(guò)5秒未處理完輸入事件、BroadcastReceiver超時(shí)10秒、Service超時(shí)20秒都會(huì)觸發(fā)ANR。排查時(shí)先用adb shell dumpsys activity processes查看ANR日志位置再通過(guò)trace文件定位主線程阻塞點(diǎn)。如果能在答案里點(diǎn)名“主線程不能做IO、不能做復(fù)雜計(jì)算、不能用同步鎖等待子線程”就已經(jīng)及格了。關(guān)于卡頓優(yōu)化RecyclerView是繞不開(kāi)的話題。我補(bǔ)充了三種優(yōu)化手段用DiffUtil減少無(wú)謂的notify用ConstraintLayout減少嵌套層級(jí)用setHasFixedSize避免重新測(cè)量布局。這些點(diǎn)不一定全考但答出來(lái)能讓面試官覺(jué)得你不是只背了面試題而是真的在項(xiàng)目里處理過(guò)卡頓問(wèn)題。4.2 AMS與系統(tǒng)服務(wù)多進(jìn)程與Binder的隱性考點(diǎn)熱搜詞里出現(xiàn)了android ams這批筆試雖然沒(méi)有直接考AMS的單獨(dú)題目但在開(kāi)放設(shè)計(jì)題“如何設(shè)計(jì)一個(gè)多進(jìn)程App”里會(huì)隱性地用到。多進(jìn)程場(chǎng)景下AMS是進(jìn)程間通信的中樞。Activity的啟動(dòng)、任務(wù)棧管理、進(jìn)程調(diào)度、ContentProvider的發(fā)布都要經(jīng)過(guò)AMS統(tǒng)一仲裁。如果你能提到AMS通過(guò)Binder和App進(jìn)程通信system_server進(jìn)程持有AMS實(shí)例Binder線程池的競(jìng)爭(zhēng)會(huì)影響啟動(dòng)速度那這題基本就穩(wěn)了。ContentProvider作為進(jìn)程間數(shù)據(jù)共享的組件也被問(wèn)到了。關(guān)鍵在于ContentProvider的onCreate時(shí)機(jī)是App冷啟動(dòng)時(shí)比Application.onCreate更早所以MultiDex和初始化的任務(wù)經(jīng)常放在ContentProvider里做。這種藏著坑的題沒(méi)有真正經(jīng)歷過(guò)組件初始化順序問(wèn)題的人很難答對(duì)。4.3 藍(lán)牙、IoT與系統(tǒng)更新硬件與系統(tǒng)機(jī)制的延伸問(wèn)題現(xiàn)代Android開(kāi)發(fā)離不開(kāi)硬件交互筆試?yán)镆渤霈F(xiàn)了關(guān)于藍(lán)牙的低頻考點(diǎn)問(wèn)的是經(jīng)典藍(lán)牙和低功耗藍(lán)牙的核心差異。我答的方向是經(jīng)典藍(lán)牙RFCOMM通道適合持續(xù)傳輸大數(shù)據(jù)BLE基于GATT協(xié)議主打低功耗和短連接連接間隔可配置適合物聯(lián)網(wǎng)傳感器場(chǎng)景。藍(lán)牙權(quán)限從Android 12開(kāi)始做了細(xì)化ACCESS_FINE_LOCATION在部分版本上仍然是掃描藍(lán)牙必須的權(quán)限這個(gè)點(diǎn)我在實(shí)際項(xiàng)目里踩過(guò)坑寫出來(lái)明顯比其他候選人更有說(shuō)服力。OTA和APEX這類系統(tǒng)級(jí)更新話題更多出現(xiàn)在Framework開(kāi)發(fā)和系統(tǒng)應(yīng)用崗位。作為應(yīng)用崗筆試不一定會(huì)出題但如果簡(jiǎn)歷里寫了了解系統(tǒng)機(jī)制就可能被追問(wèn)。我對(duì)APEX的理解是Android 10引入的一種系統(tǒng)組件封裝格式相當(dāng)于把系統(tǒng)服務(wù)從系統(tǒng)分區(qū)里獨(dú)立出來(lái)允許以類似App更新的方式去升級(jí)不需要完整OTA。這個(gè)知識(shí)點(diǎn)可以當(dāng)業(yè)余加分項(xiàng)準(zhǔn)備不建議占用太多時(shí)間。5. 時(shí)間分配、答題策略與踩坑實(shí)錄5.1 90分鐘怎么分配這場(chǎng)筆試我最想強(qiáng)調(diào)的就是時(shí)間分配。我給自己定的預(yù)算是客觀題35分鐘、編程題30分鐘、簡(jiǎn)答題和開(kāi)放題25分鐘最后留5分鐘檢查??陀^題里多選題如果是少選得分、錯(cuò)選不得分遇到不確定的選項(xiàng)寧可少選。編程題哪怕沒(méi)法完全AC也一定要把思路寫進(jìn)注釋里框架代碼先搭好再逐步細(xì)化這樣至少能拿到大部分過(guò)程分。時(shí)間分配表如下模塊建議時(shí)間操作要點(diǎn)單選/多選35分鐘不確定的選項(xiàng)不選快速推進(jìn)編程題110分鐘貪心/DP題先寫邊界再寫主邏輯編程題220分鐘并發(fā)調(diào)度題把類結(jié)構(gòu)和回調(diào)先搭好簡(jiǎn)答題10分鐘用流程圖或分段回答別長(zhǎng)編大論開(kāi)放設(shè)計(jì)題10分鐘畫架構(gòu)圖寫模塊邊界和數(shù)據(jù)流最后檢查5分鐘確認(rèn)沒(méi)有空題檢查編譯錯(cuò)誤這套時(shí)間分配的關(guān)鍵是不要把時(shí)間卡在選擇題上選擇題做得再漂亮也就單選兩分編程題的一步思路可能就抵得上好幾道選擇。5.2 遇到不會(huì)的題怎么處理有些題真的不會(huì)怎么辦我的做法是“分步拆解給出可推理的部分”。比如有一道關(guān)于RecyclerView預(yù)取機(jī)制的問(wèn)題我記得原理是Prefetch在空閑幀預(yù)創(chuàng)建和綁定下一幀要顯示的ViewHolders但細(xì)節(jié)記不清了。我就先把“預(yù)取發(fā)生在空閑期”“減少了首幀的創(chuàng)建耗時(shí)”寫上去再結(jié)合LinearLayoutManager的布局時(shí)機(jī)補(bǔ)充了兩句。基于已知推理未知比空著不答強(qiáng)得多。另一道關(guān)于LiveData粘性事件的題一開(kāi)始也把我卡住了。粘性事件的意思是先設(shè)置數(shù)據(jù)、后注冊(cè)觀察者觀察者也能立刻收到最后一次數(shù)據(jù)。如果不想讓新注冊(cè)的觀察者收到歷史數(shù)據(jù)可以引入SingleLiveEvent或者用Flow的SharedFlow配合replay0處理。這類題要是平時(shí)沒(méi)接觸過(guò)現(xiàn)場(chǎng)很難推出來(lái)但至少能先承認(rèn)見(jiàn)過(guò)類似方案再往“生命周期分發(fā)時(shí)機(jī)”的方向上靠。5.3 筆試后的復(fù)盤與面試銜接筆試結(jié)束不等于萬(wàn)事大吉。我在交卷后馬上把還記得的題目記錄了一遍挨個(gè)對(duì)照官方文檔和源碼確認(rèn)答案。這里特別推薦重新看一遍Handler和ActivityThread的源碼因?yàn)楹?jiǎn)答題里寫到的Looper.loop()和handleMessage的回調(diào)時(shí)機(jī)源碼里就是最準(zhǔn)確的答案。這個(gè)復(fù)盤過(guò)程同時(shí)是為后面的面試做準(zhǔn)備——筆試中考過(guò)的點(diǎn)面試官很喜歡接著追問(wèn)。我個(gè)人的體會(huì)是整套小滿試卷的傾向其實(shí)和很多中大廠一致基礎(chǔ)理論要扎實(shí)、項(xiàng)目經(jīng)驗(yàn)要真實(shí)、設(shè)計(jì)思路要清晰。如果你正在準(zhǔn)備Android春招不妨按這個(gè)方向把知識(shí)體系過(guò)一遍尤其是把每個(gè)知識(shí)點(diǎn)都能講出一個(gè)具體的業(yè)務(wù)場(chǎng)景。能講出場(chǎng)景才說(shuō)明你真的用過(guò)能用原理解釋場(chǎng)景才說(shuō)明你真的理解。這兩步都做到了不管是小滿的筆試還是其他公司的面試都會(huì)輕松很多。