7月份 Android 面經總結和感受,附加面試題

語言: CN / TW / HK

theme: qklhk-chocolate

作者:黃徵

前言

對過去的兩三年做個總結,或許能幫助到些人,或者從中能得一些建議。這次出去面試主要是兩個星期的時間,第一個星期主要是投簡歷,第二個星期主要是面試,一天安排了2-3個面試。

一丶如何準備面試呢?面試的注意事項有哪些呢?

image.png

下面是我總結的一些準備面試的Tips以及面試必備的注意事項

1.準備一份自己的自我介紹,面試的時候根據面試物件適當進行修改(突出重點,突出自己的優勢在哪裡,切忌流水賬);

2.注意隨身帶上自己的成績單和簡歷影印件; (有的公司在面試前都會讓你交一份成績單和簡歷當做面試中的參考。)

3.如果需要筆試就提前刷一些筆試題,大部分線上筆試的型別是選擇題+程式設計題,有的還會有簡答題。(平時空閒時間多的可以刷一下筆試題目(牛客網上有很多),但是不要只刷面試題,不動手code,程式設計師不是為了考試而存在的。)另外,注意抓重點,因為題目太多了,但是有很多題目幾乎次次遇到,像這樣的題目一定要搞定。

4.提前準備技術面試。 搞清楚自己面試中可能涉及哪些知識點、那些知識點是重點。面試中哪些問題會被經常問到、自己改如何回答。(強烈不推薦背題) 第一: 通過背這種方式你能記住多少?能記住多久? 第二: 背題的方式的學習很難堅持下去!

5.面試之前做好定向複習。 也就是專門針對你要面試的公司來複習。比如你在面試之前可以在網上找找有沒有你要面試的公司的面經。

6.準備好自己的專案介紹。 如果有專案的話,技術面試第一步,面試官一般都是讓你自己介紹一下你的專案。你可以從下面幾個方向來考慮:

①對專案整體設計的一個感受(面試官可能會讓你畫系統的架構圖;

②在這個專案中你負責了什麼、做了什麼、擔任了什麼角色;

③ 從這個專案中你學會了那些東西,使用到了那些技術,學會了那些新技術的使用;

④專案描述中,最好可以體現自己的綜合素質,比如你是如何協調專案組成員協同開發的或者在遇到某一個棘手的問題的時候你是如何解決的又或者說你在這個專案用了什麼技術實現了什麼功能比如:Android Bitmap壓縮策略;關於HandlerThread的使用場景以及怎樣使用 HandlerThread?

提前知道有哪些技術問題常問: HashMap原始碼分析、熱修復,handler等等問題我覺得面試中實在太常見了,好好準備!後面的文章會我會分類詳細介紹到那些問題最常問。

提前熟悉一些常問的非技術問題: 面試的時候有一些常見的非技術問題比如“面試官問你的優點是什麼,應該如何回答?”、“面試官問你的缺點是什麼,應該如何回答?”、“如果面試官問"你有什麼問題問我嗎?"時,你該如何回答”等等,對於這些問題,如何回答自己心裡要有個數,別面試的時候出了亂子。

6.面試之後記得覆盤。 面試遭遇失敗是很正常的事情,所以善於總結自己的失敗原因才是最重要的。如果失敗,不要灰心;如果通過,切勿狂喜。

二丶面試主要印象比較深的知識點:

  • 棧和堆的區別
  • 介面和抽象類的本質區別
  • Android Jetpack最新的元件原理
  • 註解、反射、泛型
  • Handler訊息機制,生產者和消費者模型
  • View、ViewGroup的事件傳遞機制,如何解決滑動衝突? 回答如何滑動-衝突最好是舉出實際的場景和怎麼解決的
  • View、ViewGroup的繪製流程
  • okHttp、Retrofit的原始碼,原理
  • 解釋一下什麼是MVP架構
  • Https原理,加密演算法
  • RecyclerView的快取機制
  • 常見的設計模式主要問到了這幾個(單例、代理、介面卡、建造者),先說概念,然後面試官會問具體的使用場景
  • 最新的Google AAC架構(ViewModel、LiveData、Room等等)有沒有在使用,以及背後的實現原理 Kotlin有沒有在使用,問這個問題的公司,基本上自己的公司在使用Kotlin開發新App,要麼在使用Kotlin遷移、重構、與java混合在一起
  • Android常見的記憶體洩漏原因,以及檢查工具,主要是問如何使用Android Profile檢查記憶體洩漏的,效能分析怎麼做?以及第三方檢查記憶體洩漏的工具LeakCanary的原理?
  • 開發的App有哪些亮點,難點、如何排查線上的bug,有沒有重構程式碼的經驗android的程序間的通訊方式、多執行緒下載你是怎麼做的?
  • android的程序間的通訊方式、多執行緒下載你是怎麼做的

以上的面試題,主要是Android應用層知識,需要面試之前造造火箭的,還需要平時的耕耘、積累和總結。

三丶真題(附答案)

1.Java中引用型別的區別,具體的使用場景

Java中引用型別分為四類: 強引用、軟引用、弱引用、虛引用。

強引用: 強引用指的是通過new物件建立的引用,垃圾回收器即使是記憶體不足也不會回收強引用指向的物件。

軟引用: 軟引用是通過SoftRefrence實現的,它的生命週期比強引用短,在記憶體不足,丟擲OOM之前,垃圾回收器會回收軟引用引用的物件。軟引用常見的使用場景是儲存一些記憶體敏感的快取,當記憶體不足時會被回收。

弱引用: 弱引用是通過WeakRefrence實現的,它的生命週期比軟引用還短,GC只要掃描到弱引用的物件就會回收。弱引用常見的使用場景也是儲存一些記憶體敏感的快取。

虛引用: 虛引用是通過FanttomRefrence實現的,它的生命週期最短,隨時可能被回收。如果一個物件只被虛引用引用,我們無法通過虛引用來訪問這個物件的任何屬性和方法。它的作用僅僅是保證物件在finalize後,做某些事情。虛引用常見的使用場景是跟蹤物件被垃圾回收的活動,當一個虛引用關聯的物件被垃圾回收器回收之前會收到一條系統通知。

2.volatile

一般提到volatile,就不得不提到記憶體模型相關的概念。我們都知道,在程式執行中,每條指令都是由CPU執行的,而指令的執行過程中,勢必涉及到資料的讀取和寫入。程式執行中的資料都存放在主存中,這樣會有一個問題,由於CPU的執行速度是要遠高於主存的讀寫速度,所以直接從主存中讀寫資料會降低CPU的效率。為了解決這個問題,就有了快取記憶體的概念,在每個CPU中都有快取記憶體,它會事先從主存中讀取資料,在CPU運算之後在合適的時候重新整理到主存中。

這樣的執行模式在單執行緒中是沒有任何問題的,但在多執行緒中,會導致快取一致性的問題。舉個簡單的例子:i=i+1 ,在兩個執行緒中執行這句程式碼,假設i的初始值為0。我們期望兩個執行緒執行後得到2,那麼有這樣的一種情況,兩個執行緒都從主存中讀取i到各自的快取記憶體中,這時候兩個執行緒中的i都為0。線上程1執行完畢得到i=1,將之重新整理到主存後,執行緒2開始執行,由於執行緒2中的i是快取記憶體中的0,所以在執行完執行緒2之後重新整理到主存的i仍舊是1。

所以這就導致了對共享變數的快取一致性的問題,那麼為了解決這個問題,提出了快取一致性協議:當CPU在寫資料時,如果發現操作的是共享變數,它會通知其他CPU將它們內部的這個共享變數置為無效狀態,當其他CPU讀取快取中的共享變數時,發現這個變數是無效的,它會從新從主存中讀取最新的值。

在Java的多執行緒開發中,有三個重要概念:原子性、可見性、有序性。 原子性: 一個或多個操作要麼都不執行,要麼都執行。 可見性: 一個執行緒中對共享變數(類中的成員變數或靜態變數)的修改,在其他執行緒立即可見。 有序性: 程式執行的順序按照程式碼的順序執行。 把一個變數宣告為volatile,其實就是保證了可見性和有序性。 可見性我上面已經說過了,在多執行緒開發中是很有必要的。這個有序性還是得說一下,為了執行的效率,有時候會發生指令重排,這在單執行緒中指令重排之後的輸出與我們的程式碼邏輯輸出還是一致的。但在多執行緒中就可能發生問題,volatile在一定程度上可以避免指令重排。

volatile的原理是在生成的彙編程式碼中多了一個lock字首指令,這個字首指令相當於一個記憶體屏障,這個記憶體屏障有3個作用:

  • 確保指令重排的時候不會把屏障後的指令排在屏障前,確保不會把屏障前的指令排在屏障後。
  • 修改快取中的共享變數後立即重新整理到主存中。
  • 當執行寫操作時會導致其他CPU中的快取無效。

3.程序間通訊的方式有哪幾種

AIDL 、廣播、檔案、socket、管道

4.Android效能優化工具使用(這個問題建議配合Android中的效能優化)

Android中常用的效能優化工具包括這些:Android Studio自帶的Android Profiler、LeakCanary、BlockCanary

Android自帶的Android Profiler其實就很好用,Android Profiler可以檢測三個方面的效能問題:CPU、MEMORY、NETWORK。

LeakCanary是一個第三方的檢測記憶體洩漏的庫,我們的專案整合之後LeakCanary會自動檢測應用執行期間的記憶體洩漏,並將之輸出給我們。

BlockCanary也是一個第三方檢測UI卡頓的庫,專案整合後Block也會自動檢測應用執行期間的UI卡頓,並將之輸出給我們。

5.Android中的類載入器

PathClassLoader,只能載入系統中已經安裝過的apk DexClassLoader,可以載入jar/apk/dex,可以從SD卡中載入未安裝的apk

6.Android中的動畫有哪幾類,它們的特點和區別是什麼

Android中動畫大致分為3類: 幀動畫、補間動畫(View Animation)、屬性動畫(Object Animation)。

幀動畫: 通過xml配置一組圖片,動態播放。很少會使用。

補間動畫(View Animation): 大致分為旋轉、透明、縮放、位移四類操作。很少會使用。

屬性動畫(Object Animation): 屬性動畫是現在使用的最多的一種動畫,它比補間動畫更加強大。屬性動畫大致分為兩種使用型別,分別是ViewPropertyAnimator和ObjectAnimator。前者適合一些通用的動畫,比如旋轉、位移、縮放和透明,使用方式也很簡單通過View.animate()即可得到ViewPropertyAnimator,之後進行相應的動畫操作即可。後者適合用於為我們的自定義控制元件新增動畫,當然首先我們應該在自定義View中新增相應的getXXX()和setXXX()相應屬性的getter和setter方法,這裡需要注意的是在setter方法內改變了自定義View中的屬性後要呼叫invalidate()來重新整理View的繪製。之後呼叫ObjectAnimator.of屬性型別()返回一個ObjectAnimator,呼叫start()方法啟動動畫即可。

補間動畫與屬性動畫的區別

  • 補間動畫是父容器不斷的繪製view,看起來像移動了效果,其實view沒有變化,還在原地。
  • 是通過不斷改變view內部的屬性值,真正的改變view。

7.Handler機制

說到Handler,就不得不提與之密切相關的這幾個類:Message、MessageQueue,Looper。

  • Message。 Message中有兩個成員變數值得關注:target和callback。target其實就是傳送訊息的Handler物件,callback是當呼叫handler.post(runnable)時傳入的Runnable型別的任務。post事件的本質也是建立了一個Message,將我們傳入的這個runnable賦值給建立的Message的callback這個成員變數。

  • MessageQueue。訊息佇列很明顯是存放訊息的佇列,值得關注的是MessageQueue中的next()方法,它會返回下一個待處理的訊息。

  • Looper。Looper訊息輪詢器其實是連線Handler和訊息佇列的核心。首先我們都知道,如果想要在一個執行緒中建立一個Handler,首先要通過Looper.prepare()建立Looper,之後還得呼叫Looper.loop()開啟輪詢。我們著重看一下這兩個方法。

prepare()。這個方法做了兩件事:首先通過ThreadLocal.get()獲取當前執行緒中的Looper,如果不為空,則會丟擲一個RunTimeException,意思是一個執行緒不能建立2個Looper。如果為null則執行下一步。第二步是建立了一個Looper,並通過ThreadLocal.set(looper)。將我們建立的Looper與當前執行緒繫結。這裡需要提一下的是訊息佇列的建立其實就發生在Looper的構造方法中。

loop()。這個方法開啟了整個事件機制的輪詢。它的本質是開啟了一個死迴圈,不斷的通過MessageQueue的next()方法獲取訊息。拿到訊息後會呼叫msg.target.dispatchMessage()來做處理。其實我們在說到Message的時候提到過,msg.target其實就是傳送這個訊息的handler。這句程式碼的本質就是呼叫handler的dispatchMessage()。Handler。上面做了這麼多鋪墊,終於到了最重要的部分。Handler的分析著重在兩個部分:傳送訊息和處理訊息

  • Handler。上面做了這麼多鋪墊,終於到了最重要的部分。Handler的分析著重在兩個部分:傳送訊息和處理訊息

傳送訊息。其實發送訊息除了sendMessage之外還有sendMessageDelayed和post以及postDelayed等等不同的方式。但它們的本質都是呼叫了sendMessageAtTime。在sendMessageAtTime這個方法中呼叫了enqueueMessage。在enqueueMessage這個方法中做了兩件事:通過msg.target = this實現了訊息與當前handler的繫結。然後通過queue.enqueueMessage實現了訊息入隊。

處理訊息。訊息處理的核心其實就是dispatchMessage()這個方法。這個方法裡面的邏輯很簡單,先判斷msg.callback是否為null,如果不為空則執行這個runnable。如果為空則會執行我們的handleMessage方法。

8.Android效能優化

Android中的效能優化在我看來分為以下幾個方面:記憶體優化、佈局優化、網路優化、安裝包優化。

記憶體優化: 下一個問題就是。

佈局優化: 佈局優化的本質就是減少View的層級。常見的佈局優化方案如下

  • 在LinearLayout和RelativeLayout都可以完成佈局的情況下優先選擇RelativeLayout,可以減少View的層級

  • 將常用的佈局元件抽取出來使用 < include > 標籤

  • 通過 < ViewStub > 標籤來載入不常用的佈局

  • 使用 < Merge > 標籤來減少佈局的巢狀層次

網路優化: 常見的網路優化方案如下

  • 儘量減少網路請求,能夠合併的就儘量合併

  • 避免DNS解析,根據域名查詢可能會耗費上百毫秒的時間,也可能存在DNS劫持的風險。可以根據業務需求採用增加動態更新IP的方式,或者在IP方式訪問失敗時切換到域名訪問方式。

  • 大量資料的載入採用分頁的方式

  • 網路資料傳輸採用GZIP壓縮

  • 加入網路資料的快取,避免頻繁請求網路

  • 上傳圖片時,在必要的時候壓縮圖片

安裝包優化: 安裝包優化的核心就是減少apk的體積,常見的方案如下

  • 使用混淆,可以在一定程度上減少apk體積,但實際效果微乎其微

  • 減少應用中不必要的資原始檔,比如圖片,在不影響APP效果的情況下儘量壓縮圖片,有一定的效果

  • 在使用了SO庫的時候優先保留v7版本的SO庫,刪掉其他版本的SO庫。原因是在2018年,v7版本的SO庫可以滿足市面上絕大多數的要求,可能八九年前的手機滿足不了,但我們也沒必要去適配老掉牙的手機。實際開發中減少apk體積的效果是十分顯著的,如果你使用了很多SO庫,比方說一個版本的SO庫一共10M,那麼只保留v7版本,刪掉armeabi和v8版本的SO庫,一共可以減少20M的體積。

9.Android記憶體優化

Android的記憶體優化在我看來分為兩點: 避免記憶體洩漏、擴大記憶體,其實就是開源節流。

其實記憶體洩漏的本質就是較長生命週期的物件引用了較短生命週期的物件。

常見的記憶體洩漏

  • 單例模式導致的記憶體洩漏。 最常見的例子就是建立這個單例物件需要傳入一個Context,這時候傳入了一個Activity型別的Context,由於單例物件的靜態屬性,導致它的生命週期是從單例類載入到應用程式結束為止,所以即使已經finish掉了傳入的Activity,由於我們的單例物件依然持有Activity的引用,所以導致了記憶體洩漏。解決辦法也很簡單,不要使用Activity型別的Context,使用Application型別的Context可以避免記憶體洩漏。

  • 靜態變數導致的記憶體洩漏。 靜態變數是放在方法區中的,它的生命週期是從類載入到程式結束,可以看到靜態變數生命週期是非常久的。最常見的因靜態變數導致記憶體洩漏的例子是我們在Activity中建立了一個靜態變數,而這個靜態變數的建立需要傳入Activity的引用this。在這種情況下即使Activity呼叫了finish也會導致記憶體洩漏。原因就是因為這個靜態變數的生命週期幾乎和整個應用程式的生命週期一致,它一直持有Activity的引用,從而導致了記憶體洩漏。

  • 非靜態內部類導致的記憶體洩漏。 非靜態內部類導致記憶體洩漏的原因是非靜態內部類持有外部類的引用,最常見的例子就是在Activity中使用Handler和Thread了。使用非靜態內部類建立的Handler和Thread在執行延時操作的時候會一直持有當前Activity的引用,如果在執行延時操作的時候就結束Activity,這樣就會導致記憶體洩漏。解決辦法有兩種:第一種是使用靜態內部類,在靜態內部類中使用弱引用呼叫Activity。第二種方法是在Activity的onDestroy中呼叫handler.removeCallbacksAndMessages來取消延時事件。

  • 使用資源未及時關閉導致的記憶體洩漏。 常見的例子有:操作各種資料流未及時關閉,操作Bitmap未及時recycle等等。

  • 使用第三方庫未能及時解綁。有的三方庫提供了註冊和解綁的功能,最常見的就是EventBus了,我們都知道使用EventBus要在onCreate中註冊,在onDestroy中解綁。如果沒有解綁的話,EventBus其實是一個單例模式,他會一直持有Activity的引用,導致記憶體洩漏。同樣常見的還有RxJava,在使用Timer操作符做了一些延時操作後也要注意在onDestroy方法中呼叫disposable.dispose()來取消操作。

  • 屬性動畫導致的記憶體洩漏。 常見的例子就是在屬性動畫執行的過程中退出了Activity,這時View物件依然持有Activity的引用從而導致了記憶體洩漏。解決辦法就是在onDestroy中呼叫動畫的cancel方法取消屬性動畫。

  • WebView導致的記憶體洩漏。 WebView比較特殊,即使是呼叫了它的destroy方法,依然會導致記憶體洩漏。其實避免WebView導致記憶體洩漏的最好方法就是讓WebView所在的Activity處於另一個程序中,當這個Activity結束時殺死當前WebView所處的程序即可,我記得阿里釘釘的WebView就是另外開啟的一個程序,應該也是採用這種方法避免記憶體洩漏。

擴大記憶體,為什麼要擴大我們的記憶體呢?有時候我們實際開發中不可避免的要使用很多第三方商業的SDK,這些SDK其實有好有壞,大廠的SDK可能記憶體洩漏會少一些,但一些小廠的SDK質量也就不太靠譜一些。那應對這種我們無法改變的情況,最好的辦法就是擴大記憶體。

擴大記憶體通常有兩種方法:一個是在清單檔案中的Application下新增largeHeap=”true”這個屬性,另一個就是同一個應用開啟多個程序來擴大一個應用的總記憶體空間。第二種方法其實就很常見了,比方說我使用過個推的SDK,個推的Service其實就是處在另外一個單獨的程序中。

Android中的記憶體優化總的來說就是開源和節流,開源就是擴大記憶體,節流就是避免記憶體洩漏。

10.Binder機制

在Linux中,為了避免一個程序對其他程序的干擾,程序之間是相互獨立的。在一個程序中其實還分為使用者空間和核心空間。這裡的隔離分為兩個部分,程序間的隔離和程序內的隔離。

既然程序間存在隔離,那其實也是存在著互動。程序間通訊就是IPC,使用者空間和核心空間的通訊就是系統呼叫。

Linux為了保證獨立性和安全性,程序之間不能直接相互訪問,Android是基於Linux的,所以也是需要解決程序間通訊的問題。

其實Linux程序間通訊有很多方式,比如管道、socket等等。為什麼Android程序間通訊採用了Binder而不是Linux已有的方式,主要是有這麼兩點考慮:效能和安全

效能。在移動裝置上對效能要求是比較嚴苛的。Linux傳統的程序間通訊比如管道、socket等等程序間通訊是需要複製兩次資料,而Binder則只需要一次。所以Binder在效能上是優於傳統程序通訊的。

安全。傳統的Linux程序通訊是不包含通訊雙方的身份驗證的,這樣會導致一些安全性問題。而Binder機制自帶身份驗證,從而有效的提高了安全性。

Binder是基於CS架構的,有四個主要組成部分。

  • Client。客戶端程序。
  • Server。服務端程序。
  • ServiceManager。提供註冊、查詢和返回代理服務物件的功能。
  • Binder驅動。主要負責建立程序間的Binder連線,程序間的資料互動等等底層操作。

Binder機制主要的流程是這樣的

  • 服務端通過Binder驅動在ServiceManager中註冊我們的服務。

  • 客戶端通過Binder驅動查詢在ServiceManager中註冊的服務。

  • ServiceManager通過Binder驅動返回服務端的代理物件。

  • 客戶端拿到服務端的代理物件後即可進行程序間通訊。

四丶總結

總體發現整個面試下來,投簡歷發現今年996的公司還蠻多的,前兩年沒有這麼多。有的人事,boss會直接說是996,要麼自己面試過程中問是不是996,996的公司是拒絕的,壓根不想去。面試的過程中發現自己的信心不夠,技術能力也不夠、自己也著急。面試想想這幾點要特別注意

  • 像大一點的廠,投簡歷過去,在加上面試的時間回覆,需要兩週。
  • 如果面試官過程中,發現面試官沒有問什麼技術問題,或者問的問題不夠深入,基本上可以斷定這家公司不是靠技術作為驅動公司發展的。
  • 提高自己的信心,自己要會的多,對知識點的理解要深入。

寫程式碼總結以下幾點

1.需要確認需求的,理解有偏差的。寫程式碼之前一定要和產品經理溝通交流。寧願多花時間去和測試的、設計師溝通,也不要去埋頭寫程式碼。同樣的,認真想想怎麼實現這樣一個功能,思路理清了在敲程式碼。 2.養成良好的編碼習慣,風格。多看看Google開源的在github上示例,或者其它知名公司的。 3.六大設計原則、一些常用的設計模式理解透牢記於心,多在編碼過程中使用。 4.程式碼要有思路,寫好註釋,寫的程式碼不單單是自己要看,也是給別人看的。 5.平時學習需要多總結、多體會、程式碼需要多動手敲。

最後

今天關於面試的分享就到這裡,還是那句話,有些東西你不僅要懂,而且要能夠很好地表達出來,能夠讓面試官認可你的理解,例如Handler機制,這個是面試必問之題。有些晦澀的點,或許它只活在面試當中,實際工作當中你壓根不會用到它,但是你要知道它是什麼東西。

小編之前在網上收集整理了一些 Android 開發相關的學習文件、面試題、Android 核心筆記等等文件,希望能幫助到大家學習提升,在面試中能順利通過。如有需要參考的可以直接去我 GitHub 訪問查閱。

喜歡本文的話,不妨順手給我點個小贊、評論區留言或者轉發支援一下唄😜😜😜~