![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBHL0FWby9mZvwVZnFWbp1zczV2YvJHctM3cv1Ce-M2RjFXND10dVRUZ3FkaNhHOG9ENRpmW0cGRNlXWq5kaopnT1k0VNNTRy0UaoRVWoJERPl3ZE9UaopnTpFTaNJTOTJmdO1GTuFzVh9GcuxEeNdVY3lTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
前言
這篇文章非常的幹!覆寫了安卓面試的大多數知識點,值得收藏反複檢視!
安逸久了就容易迷失方向,多看看高品質的面試題找找差距,然後查漏補缺!
由于題目答案較長篇幅較長,是以隻是放上了部分的問題答案,其餘的答案我都整理成了一個PDF,有想法看其餘答案的夥伴們可以文末領取一下,還有騰訊、頭條、阿裡、美團等公司19年的面試題PDF,其中包含知識脈絡 + 諸多細節。
問題區:
- Activity的啟動過程,AMS、PMS源碼
- View的繪制過程,MeasureSpec測量模式分别代表什麼意思,draw是哪裡來的?自定義view
- view的事件分發機制
- hashmap原理,arraylist,linklist原理
- 你在開發過程中常用設計模式有哪些,單例設計模式的雙重校驗的目的?去掉第一個判空或第二個判空有啥不同?工廠模式解決了什麼問題?使用了哪些設計原則?
- retrofit,okhttp,rxjava原理,okhttp用到了哪些設計模式,連接配接池的實作原理,rxjava線程切換的原理,eventbus原理
- jvm模型,java記憶體模型,垃圾回收機制,垃圾回收哪個區域,對象在記憶體哪個區域等等
- startService和bindService差別,多次啟動會調用哪些方法?
- Activity旋轉會調用哪些方法(橫豎屏切換)
- 資料結構和算法,比較少會去寫,要求手寫 冒泡或者快速希爾排序等排序,最少要會一種
- 你都做過哪些記憶體優化,apk優化等
- 哪些會導緻記憶體洩漏,如何檢測,以及解決辦法,記憶體洩漏和溢出有啥不同
- 圖檔優化,一個大圖(10M,100M)如何去展示。
- 一些程式運作的結果,一般考的是重載,多态的,或者各種 i++ ++i 的結果的
- 圖檔緩存架構的原理,你字迹是否有實作過圖檔緩存架構,怎麼實作的
- mvp,mvc差別,mvvm有木有了解的?
- 适配方案
- 跨程序通信方式,以及AIDL原理
- 子線程與子線程通信方式,handler怎麼去實作子線程之間的通信
- Message、Handler、MessageQueue、Lopper,以及Looper既然是死循環的,為毛不會導緻UI線程的阻塞
- android動畫
- 多線程同步問題,鎖lock,syc等
答案區:
1.Activity的啟動過程,AMS、PMS 源碼
- Activity啟動過程:
1.點選APP圖示後通過startActivity()遠端調用到AMS中,AMS将進啟動的Activity以 activityrecord 的結構壓入Activity棧中,并通過遠端binder回調到程序,使得原程序進入pause狀态,原程序pause後通知AMS :“我 pause 了"
2.此時AMS再根據棧中Activity的啟動 意圖(intent) 中的flag是否含有 new_task 的标簽判斷是否需要啟動新程序(啟動新程序調用 startProcessXXX 方法)
3.啟動新程序後通過反射調用ActivityThread的main方法,mian方法中調用looper.prepare 和 looper.loop 啟動消息隊列循環機制。最後遠端告知AMS:“我啟動了”,然後AMS再回調 handleLauncherActivity() 方法加載Activity,在該方法中通過反射調用Application的onCreate()和Activity的onCreate(),然後在handleResumeActivity() 中反射調用Activity的onResume()方法。
2. View的繪制過程,MeasureSpec測量模式分别代表什麼意思,draw是哪裡來的?自定義View
- View的繪制過程
關于View的繪制過程,可以簡單的分成三個步驟:
1. measure過程:
-
- 作用:測量View的寬、高
- 流程:performMeasure() --- measure() --- onMeasure() --- 子View的measure()
- 備注:在onMeasure() 方法中會對所有的子元素進行measure過程
2. layout過程:
-
- 作用:通過确定View四個頂點的位置,進而确定View的位置
- 流程:performLayout() --- layout() --- onLayout() ---子View的layout過程
- 備注:在OnLayout()方法中會對所有子元素進行layout過程
3.draw過程:
-
- 作用:将View繪制在螢幕上
- 流程:performDraw() --- draw() --- onDraw() --- 子View的draw過程
- MeasureSpec測量模式分别代表什麼意思
- MeasureSpec封裝了父布局傳遞給子布局的布局要求,每個MeasureSpec代表了一組寬度和高度的要求。
- View在測量過程中會使用到MeasureSpec
其中MeasureSpec有三個測量模式:
- UNSPECIFIED 模式:父容器不會對子View有限制,子View要多大就給多大
- EXACTLY 模式:表示精确模式,View的大小已經确定,為SpecSize(規格大小)所指定的值
- AT_MOST 模式:表示不确定子View的大小,指定一個最大值,子View可在該範圍内任意取值設為自己的大小
PS:若對MeasureSpec不熟悉可以閱讀 https://www.jianshu.com/p/c1f8df587985
- draw是哪裡來的
View在經過 測量大小(measure過程)和 位置确定(layout過程) 後接下來就是 View的繪制(draw過程)
- 自定義View
3. View的事件分發機制
- 事件的分發機制可以簡單的分為
- 當點選螢幕時觸發 MotionEvent.ACTION_DOWN事件 時,事件分發器(dispatchTouchEvent)開始對事件的分發
- 在分發器找到對應的View時,攔截器(onInterceptTouch)會對事件的分發進行攔截,停止分發器繼續向下分發事件的操作
- 接下來我們通過移動接觸螢幕的手指等操作觸發 MotionEvent.ACTION_MOVE 或 MotionEvent.ACTION_UP事件這些事件都會回調給onTouch或onTouchEvent方法,對事件做出響應
- 分發機制的流程:Activity → ViewGroup → view
- 分發事件最開始從 Actvity的dispatchTouchEvent()方法 開始:
- 在該方法裡面最主要的是判斷 getWindow().superDispatchTouchEvent() 方法的傳回結果(true或false),若傳回true則點選事件停止傳遞,傳遞過程結束,即找到了對應的View 。其中 getWindow.superDispatchTouchEvent() 方法實際會調用 ViewGroup層的dispatchTouchEvent() 方法。
- 在ViewGroup層會判斷是否對事件進行攔截,
- 若為true,則對事件進行攔截,然後調用 父類的dispatchTouchView() 方法,同時回調自身的onTouch()方法。
- 若為false,則周遊子View,找到點選對應的View。然後攔截分發,同時調用 View控件的dispatchTouchEvent
3.在View控件的 dispatchTouchEvent() 方法中主要對View控件進行3個條件的判斷:
public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
-
- 條件1 mOnTouchListener !=null:即我們隻要給View控件注冊了Touch事件,mOnATouchListener就不為空
- 條件 2 (mViewFlags & ENABLED_MASK) == ENABLED:判斷目前View控件是否enable(很多View預設enable)
- 條件 3 mOnTouchListener.onTouch(this,event):即控件注冊Touch事件時的onTouch()
由此可見,隻有給View控件添加點選事件同時在點選事件中的onTouch()傳回true時,dispatchTouchEvent()方法才能傳回true,分發才結束。否則進入onTouchEvent()方法。
4. HashMap、ArrayList、LinkList原理
5. 你在開發過程中常用設計模式有哪些,單例設計模式的雙重校驗的目的?去掉第一個判空或第二個判空有啥不同?工廠模式解決了什麼問題?使用了哪些設計原則?
-
設計模式
1. 單例模式
2. Build建造者模式
3. 觀察者模式
4. 原型模式
5. 政策模式
6. 工廠模式
這裡隻列舉出幾個常用的設計模式,需要更多請點選下面的連接配接https://www.jianshu.com/p/457e81b3d8d2
- 單例模式雙重校驗的目的
在說單例的雙重校驗目的之前,先看一下單例的雙重校驗長什麼樣子
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
可以看到在getInstance()方法裡面,先判斷目前執行個體是否為空,然後進入同步處理後又判斷一次執行個體是否為空。前後兩次判斷校驗了兩次。這個就是雙重校驗
去掉第一個判斷為空:即懶漢式(線程安全),這會導緻所有線程在調用getInstance()方法的時候,不管三七二十一就直接排隊等待同步鎖,然後等到排到自己的時候進入同步處理時,才去校驗執行個體是否為空,這樣子做會耗費很多時間(即線程安全,但效率低下)。
//去掉判斷第一個為空
public static synchronized SingleTon getInstance(){
if (instance==null){
instance = new SingleTon();
}
return instance;
}
去掉第二個判斷為空:即懶漢式(線程不安全),這會出現 線程A先執行了getInstance()方法,同時線程B在因為同步鎖而在外面等待,等到A線程已經建立出來一個執行個體出來并且執行完同步處理後,B線程将獲得鎖并進入同步代碼,如果這時B線程不去判斷是否已經有一個執行個體了,然後直接再new一個。這時就會有兩個執行個體對象,即破壞了設計的初衷。(即線程不安全,效率高)
//去掉第二個判斷為空
public static SingleTon getInstance(){
if (instance==null){
instance = new SingleTon();
}
return instance;
}
雙重校驗的目的:除了第一次執行個體化需要進行加鎖同步,之後的線程隻要進行第一層的if判斷不為空即可直接傳回,而不用每一次擷取單例都加鎖同步,是以相比前面兩種懶漢式,雙重檢驗鎖更佳。(雙重校驗鎖結合了 兩種懶漢式 的優點)
本小節參考文章:
https://www.jianshu.com/p/b093d6741ef3
https://www.cnblogs.com/android-blogs/p/5530239.html
6. retrofit,okhttp,rxjava原理,okhttp用到了哪些設計模式,連接配接池的實作原理,rxjava線程切換的原理,eventbus原理
retrofit 、 okhttp 、 rxjava原理 :
7. jvm模型,java記憶體模型,垃圾回收機制,垃圾回收哪個區域,對象在記憶體哪個區域等等
- jvm模型、java記憶體模型:
- 垃圾回收機制 :
主要分為兩個步驟:
- 檢測垃圾
- 回收垃圾
檢測垃圾又有兩種:
-
引用計數法(已過時)
給對象一個添加一個引用計數器,每當有一個地方引用該對象時,計數器加1,反之當引用無效時,計數器減1 。在任何時候,當計數器為0時,即沒有任何地方引用該對象,表示該對象無用,即為回收器回收的對象。(因為這個方法無法解決對象之間互相循環引用的問題,是以被淘汰。)
-
可達性分析
通過GC root根節點往外周遊(可以想象樹形圖),當一個與root根節點可達的節點A所代表的對象持有另外一個節點B所代表的對象的引用,則視節點B為可達的。反之,如果某個節點是不可達的,則為可回收的對象。
回收垃圾
-
标記 - 清除法(mark - sweep)
标記所有需要回收的對象,然後統一清除。該方法簡單粗暴,但是清除完會導緻記憶體空間中出現大量碎片。
-
複制(copying)
把記憶體中的空間平分為兩個,然後每次隻使用任意一個。當回收垃圾時,周遊目前該記憶體區域,将正在使用的對象複制到另外一個記憶體區域中(複制過來後會自動整理,不會出現碎片的問題),然後再清空原來的記憶體區域。該方法通過兩個記憶體區域的方法解決了碎片的問題,同時又迎來了新的問題,即提高了記憶體的空間要求,舍棄了空間換取了效率。
-
标記 - 整理(mark - compact)
第一階段:從根節點标記所有能被引用的對象,即标記有用的對象。
第二階段:周遊整個堆中的對象,清除沒有被标記的對象,并把剩下的 “壓縮” 到堆中的其中一塊,按順序排放。
該方法避免了 “ 标記 - 清除 ” 所造成的碎片問題,也解決了 “ 複制 ” 對空間的要求高的問題。
-
分代收集算法
根據每個對象生命周期不同的特點,将對象劃分到不同代上,使用不同的垃圾回收方式。
新生代:新建立的對象都是使用新生代配置設定記憶體。新生代裡面又有三個區域(1個Eden區和2個Survivor區),建立的對象會放再Eden區,當Eden區滿了就會執行 Minor GC ,然後把存活的對象轉移到任意一個Survivor區。
老年代:經過多次 Minor GC後依然存活的對象便送到該代,當該代記憶體被占滿時就會觸發Full GC回收整個記憶體。
持久代:顧名思義。永生不死,相當于吸血鬼。用于存放java類等
- 垃圾回收在哪個區域:
- 要了解垃圾回收到底是回收哪個區域,就得先了解JAVA記憶體管理
- 記憶體的管理即對對象的配置設定和釋放,釋放即回收。
JAVA記憶體配置設定政策
1. 靜态配置設定:主要存在靜态變量,這塊在編譯時就已經配置設定好了,在整個程式運作期間存在。
2. 棧式配置設定:當方法被執行時,方法體内部的局部變量(基本資料類型,對象的引用)都會放進棧記憶體中。當方法執行結束,配置設定給該方法的記憶體空間也會被釋放。
3. 堆式配置設定:又稱動态配置設定,通常指對象的執行個體,這部分記憶體在不用的時候會被GC回收。
通過上面三個配置設定政策可知,靜态配置設定在整個程式運作過程中都在存在,棧式配置設定的記憶體在方法體執行結束後會自動釋放。使用這兩種配置設定政策的對象都不用進行回收,隻有使用堆式配置設定的對象需要進行GC回收。
8. startService和bindService差別,多次啟動會調用哪些方法?
-
startService和bindService的差別
startService:
作用:啟動服務
生命周期:onCreate() → onStartCommand() → onDestory()
bindService:
作用:啟動服務
生命周期:onCreate() → onBind() → onUnbind() → onDestory()
- 差別:
- 從通訊角度看,使用startService()方法啟動的服務不能與Activity進行通訊,而使用bindService()方法啟動的服務可以與Activity進行通訊。
- 從生命周期看,startService()方法啟動服務是通過startCommand()方法,而bindService()方法是通過onBind()方法。
- 通過startService()方法啟動的服務,當調用者退出後,服務仍然可以運作,而使用bindService()方法啟動的服務則不行。
- onCreate()方法在生命周期中隻調用一次,若在服務已經啟動的前提下,多次調用startService()方法或者調用bindService()方法,都不會再執行onCreate()方法,在使用starService()方法啟動服務的情況下,會多次調用onStart()方法。
9. Activity旋轉會調用哪些方法
Activity橫豎屏切換的生命周期根據清單配置檔案中的屬性“ android:configChanges ”的值的不同而不同。
android:configChanges =" orientation "消除橫豎屏的影響
android:configChanges=" keyboardHidden " :消除鍵盤的影響
android:configChanges=" screenSize " :消除螢幕大小的影響
情況1:當 android:configChanges =" orientation " 或者android:configChanges =" orientation | keyboardHidden "或者不設定該屬性時,其切換螢幕的生命周期如下:
onPause() → onSaveInstanceState() → onStop() → onDestory() → onCreate() → onStart() → onRestoreInstanceState() → onResume()
情況2:當android:configChanges=" orientation | screenSize | keyboardHidden "時,其切換螢幕不會調用任何一個生命周期方法。
情況3:當android:configChanges=" orientation | screenSize "時,其切換螢幕時不會調用任何一個生命周期方法,而是調用onConfigurationChanged()方法。
10.資料結構和算法,比較少會去寫,要求手寫 冒泡或者快速希爾排序等排序,最少要會一種
排序相對基礎一點,這裡本着複習的目的,就貼出冒泡排序的代碼。使用Ecplise寫的
Scanner sd =new Scanner(System.in);
String[] temp=sd.nextLine().split(" ");
//這裡就是排序的代碼
for(int i=0;i<temp.length;i++) {
for(int j=temp.length-1;j>i;j--) {
if(Integer.parseInt(temp[j])<Integer.parseInt(temp[j-1])) {
String str=temp[j];
temp[j]=temp[j-1];
temp[j-1]=str;
}
}
}
//這裡是周遊列印出來
for(String str:temp) {
System.out.print(str+" ");
}
11. 你都做過哪些記憶體優化,apk優化等
12,哪些會導緻記憶體洩漏,如何檢測,以及解決辦法,記憶體洩漏和溢出有啥不同
- 哪些會導緻記憶體洩漏
12.1 單例模式:
在該模式中,需要外部傳入一個context來擷取該類執行個體,一般我們在Activity中直接寫入this做為contetx來擷取單例執行個體,此時該單例持有Activity的context強引用。這樣的話,即使Activity早已退出,該Activity的記憶體也不會被回收,這樣就造成了記憶體洩漏。
避免單例模式造成的記憶體洩漏就是在Activity擷取單例執行個體的時候,将getApplicationContext替換this作為傳入的context。這樣,該執行個體實際上擷取的是整個應用的引用,就不會出現記憶體洩漏的情況了。
12.2 非靜态内部類/匿名類:
非靜态内部類和匿名類都預設持有外部類的強引用,且其生命周期甚至比外部類長。當外部類退出了,就導緻了記憶體洩漏。
解決的辦法就是實作靜态内部類。
12.3 集合:
集合類添加元素後,仍引用着集合元素的對象,導緻該集合元素中的對象無法被回收,進而導緻記憶體洩漏。
解決的辦法也相對簡單,在集合元素使用後從集合中删除,等所有元素都使用完後,将集合置空。
12.4 其他情況:
-
-
需要手動關閉的對象沒有關閉:
網絡、檔案流 忘記關閉
手動注冊廣播,退出時忘記解除注冊
Service執行完後忘記stopSelf()
EventBus等觀察者模式的架構忘記解除注冊
- static 關鍵字修飾的成員變量
- ListView的item洩漏
-
- 記憶體洩漏跟記憶體溢出的差別
要了解兩者的差別,首先得知道其概念。
記憶體洩漏:如果一個長生命周期的對象持有另一個短生命周期的對象,當短生命周期的對象退出時,因為長生命周期對象持有短生命周期對象的引用,是以實際上短生命周期對象所占用的記憶體不會被回收,這樣就造成了記憶體洩漏。(說白了,就是該回收的記憶體沒有被回收)
記憶體溢出:俗稱記憶體不夠,就是我們在啟動或者建立一個對象的時候,記憶體中所剩可用的記憶體比我們建立對象所需的記憶體還要小的時候,就會記憶體溢出。
從上面我們就可以看出,
記憶體溢是因為系統已經不能再配置設定出你所需要的空間。
記憶體洩漏就是你用資源的時候為他開辟了一段空間,當你用完時忘記釋放資源了,這時記憶體還被占用着,一次沒關系,但是記憶體洩漏次數多了就會導緻記憶體溢出
關于記憶體洩漏和記憶體溢出的詳細可以看 記憶體溢出和記憶體洩漏的差別。
文末
有些東西你不僅要懂,而且要能夠很好地表達出來,能夠讓面試官認可你的了解,例如Handler機制,這個是面試必問之題。有些晦澀的點,或許它隻活在面試當中,實際工作當中你壓根不會用到它,但是你要知道它是什麼東西。
這裡放上一份安卓程式員學習基礎知識體系粗略大綱,詳細完整的大綱由于圖檔過大就不在這裡放了,後面檢視詳細大綱:
最近我也根據上述的技術體系圖搜集了幾十套騰訊、頭條、阿裡、美團等公司19年的面試題,把技術點整理成了視訊和PDF(實際上比預期多花了不少精力),包含知識脈絡 + 諸多細節,由于篇幅有限,這裡給大家展示一部分。
這份路線圖和資料适合的人群:
1.沒有工作經驗,但基礎非常紮實,對四大元件,性能優化,常用Android開發架構掌握熟練的。
2.最近要參加面試的Android程式員,查漏補缺,以便盡快彌補短闆;
3.想了解“一線網際網路公司”最新招聘需求/技術要求,對比找出自身的長處和弱點所在,評估自己在現有市場上的競争力如何;
4.做了幾年Android開發,但還沒形成系統的Android知識體系,缺乏清晰的提升方向和學習路徑的程式員。
相信它會給大家帶來很多收獲
這裡是關于我自己的Android 學習,面試文檔,視訊收集大整理,有興趣的夥伴們可以看看~
當程式員容易,當一個優秀的程式員是需要不斷學習的,從初級程式員到進階程式員,從初級架構師到資深架構師,或者走向管理,從技術經理到技術總監,每個階段都需要掌握不同的能力。早早确定自己的職業方向,才能在工作和能力提升中甩開同齡人。
- 無論你現在水準怎麼樣一定要 持續學習 沒有雞湯,别人看起來的毫不費力,其實費了很大力,這四個字就是我的建議!!!!!!!!!
- 我希望每一個努力生活的IT工程師,都會得到自己想要的,因為我們很辛苦,我們應得的。