作者:黃征
前言
對過去的兩三年做個總結,或許能幫助到些人,或者從中能得一些建議,這次出去面試主要是兩個星期的時間,第一個星期主要是投簡歷,第二個星期主要是面試,一天安排了2-3個面試,
一丶如何準備面試呢?面試的注意事項有哪些呢?

下面是我總結的一些準備面試的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 核心筆記等等檔案,希望能幫助到大家學習提升,在面試中能順利通過,如有需要參考的可以直接去我 CodeChina地址:https://codechina.csdn.net/u012165769/Android-T3 訪問查閱,
喜歡本文的話,不妨順手給我點個小贊、評論區留言或者轉發支持一下唄😜😜😜~


轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/290912.html
標籤:其他
