前言
相信很多同學都會有這樣的感受,前三天剛剛複習的知識點,今天問的時候怎麼就講不出個是以然了呢?

本文的目的就是緻力于幫助大家盡可能的建立Android知識體系,希望大家會喜歡~
考慮到上傳完的腦圖都被壓縮過,高清腦圖下載下傳位址:
連結: pan.baidu.com/s/1bUQccZiu… 密碼:wyc8
必讀
知識結構
覆寫的知識點有Android、Java、Kotlin、Jvm、網絡和設計模式。
面向人群
正在求職的中進階Android開發
食用指南
和大部分人一樣,我在複習完第一遍Android知識的情況下,看到相關的知識回答的仍然不能夠令自己滿意。
在第二遍系統複習的時候,我着重記住每個知識點的關鍵字,根據這些關鍵字拼湊出大概的知識點,最後看到每個知識點的時候,就知道大概會問哪些内容,達到這種境界以後,你就可以從容的面對每次面試了。
簡單的做法就是為每個知識點建立腦圖,盡可能把自己想到的關鍵點羅列出來,也就是下面每個章節前面的腦圖。
除此以外,我還為大家提供了可能會問到的面試題。
一、Android基礎
Android基礎知識點比較多,看圖。
建議閱讀:
《Android開發藝術探索》
1. Activity
Activity的四大啟動模式,以及應用場景?
Activity
的四大啟動模式:
-
:标準模式,每次都會在活動棧中生成一個新的standard
執行個體。通常我們使用的活動都是标準模式。Activity
-
:棧頂複用,如果singleTop
執行個體已經存在棧頂,那麼就不會在活動棧中建立新的執行個體。比較常見的場景就是給通知跳轉的Activity
設定,因為你肯定不想前台Activity
已經是該Activity
的情況下,點選通知,又給你再建立一個同樣的Activity
。Activity
-
:棧内複用,如果singleTask
執行個體在目前棧中已經存在,就會将目前Activity
執行個體上面的其他Activity
執行個體都移除棧。常見于跳轉到主界面。Activity
-
:單執行個體模式,建立一個新的任務棧,這個活動執行個體獨自處在這個活動棧中。singleInstance
Activity中onStart和onResume的差別?onPause和onStop的差別?
首先,
Activity
有三類:
- 前台
:活躍的Activity
,正在和使用者互動的Activity
Activity
- 可見但非前台的
:常見于棧頂的Activity
背景透明,處在其下面的Activity
就是可見但是不可和使用者互動。Activity
- 背景
:已經被暫停的Activity
,比如已經執行了Activity
方法。onStop
是以,
onStart
和
onStop
通常指的是目前活動是否位于前台這個角度,而
onResume
onPause
從是否可見這個角度來講的。
2. 螢幕适配
平時如何有使用螢幕适配嗎?原理是什麼呢?
平時的螢幕适配一般采用的頭條的螢幕适配方案。簡單來說,以螢幕的一邊作為适配,通常是寬。
原理:裝置像素
px
和裝置獨立像素
dp
之間的關系是
px = dp * density
假設UI給的設計圖螢幕寬度基于360dp,那麼裝置寬的像素點已知,即px,dp也已知,360dp,是以
density = px / dp
,之後根據這個修改系統中跟
density
相關的知識點即可。
3. Android消息機制
Android消息機制介紹?
Android消息機制中的四大概念:
-
:目前線程存儲的資料僅能從目前線程取出。ThreadLocal
-
:具有時間優先級的消息隊列。MessageQueue
-
:輪詢消息隊列,看是否有新的消息到來。Looper
-
:具體處理邏輯的地方。Handler
過程:
- 準備工作:建立
,如果是在子線程中建立,還需要調用Handler
,在Looper#prepare()
的構造函數中,會綁定其中的Handler
Looper
MessageQueue
- 發送消息:建立消息,使用
發送。Handler
- 進入
:因為MessageQueue
中綁定着消息隊列,是以Handler
很自然的被放進消息隊列。Message
-
輪詢消息隊列:Looper
是一個死循環,一直覺察有沒有新的消息到來,之後從Looper
取出綁定的Message
,最後調用Handler
中的處理邏輯,這一切都發生在Handler
循環的線程,這也是Looper
能夠在指定線程處理任務的原因。Handler
Looper在主線程中死循環為什麼沒有導緻界面的卡死?
- 導緻卡死的是在Ui線程中執行耗時操作導緻界面出現掉幀,甚至
,ANR
這個操作本身不會導緻這個情況。Looper.loop()
- 有人可能會說,我在點選事件中設定死循環會導緻界面卡死,同樣都是死循環,不都一樣的嗎?Looper會在沒有消息的時候阻塞目前線程,釋放CPU資源,等到有消息到來的時候,再喚醒主線程。
- App程序中是需要死循環的,如果循環結束的話,App程序就結束了。
《Android中為什麼主線程不會因為Looper.loop()裡的死循環卡死?》
IdleHandler介紹?
介紹: IdleHandler是在Hanlder空閑時處理空閑任務的一種機制。
執行場景:
-
沒有消息,隊列為空的時候。MessageQueue
-
屬于延遲消息,目前沒有消息執行的時候。MessageQueue
會不會發生死循環: 答案是否定的,
MessageQueue
使用計數的方法保證一次調用
MessageQueue#next
方法隻會使用一次的
IdleHandler
集合。
4. View事件分發機制和View繪制原理
剛哥的《Android開發藝術探索》已經很全面了,建議閱讀。
5. Bitmap
Bitmap的記憶體計算方式?
在已知圖檔的長和寬的像素的情況下,影響記憶體大小的因素會有資源檔案位置和像素點大小。
像素點大小: 常見的像素點有:
- ARGB_8888:4個位元組
- ARGB_4444、ARGB_565:2個位元組
資源檔案位置: 不同dpi對應存放的檔案夾
比如一個一張圖檔的像素為
180*180px
dpi
(裝置獨立像素密度)為320,如果它僅僅存放在
drawable-hdpi
,則有:
橫向像素點 = 180 * 320/240 + 0.5f = 240 px
縱向像素點 = 180 * 320/240 + 0.5f = 240 px
如果 如果它僅僅存放在
drawable-xxhdpi
橫向像素點 = 180 * 320/480 + 0.5f = 120 px
縱向像素點 = 180 * 320/480 + 0.5f = 120 px
是以,對于一張
180*180px
的圖檔,裝置dpi為320,資源圖檔僅僅存在
drawable-hdpi
,像素點大小為
ARGB_4444
,最後生成的檔案記憶體大小為:
橫向像素點 = 180 * 320/240 + 0.5f = 240 px
縱向像素點 = 180 * 320/240 + 0.5f = 240 px
記憶體大小 = 240 * 240 * 2 = 115200byte 約等于 112.5kb
《Android Bitmap的記憶體大小是如何計算的?》
Bitmap的高效加載?
Bitmap的高效加載在Glide中也用到了,思路:
- 擷取需要的長和寬,一般擷取控件的長和寬。
- 設定
中的BitmapFactory.Options
為true,可以幫助我們在不加載進記憶體的方式獲得inJustDecodeBounds
的長和寬。Bitmap
- 對需要的長和寬和Bitmap的長和寬進行對比,進而獲得壓縮比例,放入
BitmapFactory.Options
屬性。inSampleSize
-
BitmapFactory.Options
為false,将圖檔加載進記憶體,進而設定到控件中。inJustDecodeBounds
二、Android進階
Android進階中重點考察
Android Framework
、性能優化和第三方架構。
1. Binder
Binder的介紹?與其他IPC方式的優缺點?
Binder是Android中特有的IPC方式,引用《Android開發藝術探索》中的話(略有改動):
從IPC角度來說,Binder是Android中的一種跨程序通信方式;Binder還可以了解為虛拟的實體裝置,它的裝置驅動是/dev/binder;從來講,Binder是
Android Framework
連接配接各種
Service Manager
和對應的
Manager
的橋梁。從面向對象和CS模型來講,
ManagerService
通過Binder和遠端的
Client
進行通訊。
Server
基于Binder,Android還實作了其他的IPC方式,比如
AIDL
、
Messenger
ContentProvider
與其他IPC比較:
- 效率高:除了記憶體共享外,其他IPC都需要進行兩次資料拷貝,而因為Binder使用記憶體映射的關系,僅需要一次資料拷貝。
- 安全性好:接收方可以從資料包中擷取發送發的程序Id和使用者Id,友善驗證發送方的身份,其他IPC想要實驗隻能夠主動存入,但是這有可能在發送的過程中被修改。
Binder的通信過程?Binder的原理?
圖檔:
其實這個過程也可以從AIDL生成的代碼中看出。
原理:
Binder的結構:
Client
:服務的請求方。
Server
:服務的提供方。
Service Manager
:為
Server
提供
Binder
的注冊服務,為
Client
Binder
的查詢服務,
Server
Client
Service Manage
r的通訊都是通過Binder。
Binder驅動
:負責Binder通信機制的建立,提供一系列底層支援。
從上圖中,Binder通信的過程是這樣的:
- Server在Service Manager中注冊:Server程序在建立的時候,也會建立對應的Binder實體,如果要提供服務給Client,就必須為Binder實體注冊一個名字。
- Client通過Service Manager擷取服務:Client知道服務中Binder實體的名字後,通過名字從Service Manager擷取Binder實體的引用。
- Client使用服務與Server進行通信:Client通過調用Binder實體與Server進行通信。
更詳細一點?
Binder通信的實質是利用記憶體映射,将使用者程序的記憶體位址和核心的記憶體位址映射為同一塊實體位址,也就是說他們使用的同一塊實體空間,每次建立Binder的時候大概配置設定128的空間。資料進行傳輸的時候,從這個記憶體空間配置設定一點,用完了再釋放即可。
2. 序列化
Android有哪些序列化方式?
為了解決Android中記憶體序列化速度過慢的問題,Android使用了
Parcelable
對比 | | |
---|---|---|
易用性 | 簡單 | 不是很簡單 |
效率 | 低 | 高 |
場景 | IO、網絡和資料庫 | 記憶體中 |
3. Framework
Zygote孕育程序過程?
Activity的啟動過程?
《3分鐘看懂Activity啟動流程》
App的啟動過程?
介紹一下App程序和System Server程序如何聯系:
-
:依賴于Ui線程,實際處理與ActivityThread
中互動的工作。AMS
-
:負責ActivityManagerService
Activity
等的生命周期工作。Service
-
:ApplicationThread
程序中System Server
的服務端,幫助ApplicatonThreadProxy
程序跟App程序交流。System Server
-
:Android核心的程序,掌管着Android系統中各種重要的服務。System Server
具體過程:
- 使用者點選App圖示,
程序通過Lanuacher
聯系到Binder
程序發起System Server
startActivity
-
通過System Server
Socket
Zygote
出一個新的App程序。fork
- 建立出一個新的App程序以後,
啟動App程序的Zygote
ActivityThread#main()
- 在
中,調用ActivtiyThread
進行AMS
的綁定。ApplicationThread
-
發送建立AMS
的消息給Application
,進而轉交給ApplicationThread
ActivityThread
,它是一個H
,接着進行Handler
的建立工作。Application
-
以同樣的方式建立AMS
,接着就是大家熟悉的建立Activity
的工作了。Activity
Apk的安裝過程?
《Android Apk安裝過程分析》
Activity啟動過程跟Window的關系?
《簡析Window、Activity、DecorView以及ViewRoot之間的錯綜關系》
Activity、Window、ViewRoot和DecorView之間的關系?
《總結UI原理和進階的UI優化方式》
4. Context
關于Context的了解?
《Android Context 上下文 你必須知道的一切》
5. 斷點續傳
多線程斷點續傳?
基礎知識:
- Http基礎:在Http請求中,可以加入請求頭
,下載下傳指定區間的檔案數。Range
-
:支援随機通路,可以從指定位置進行資料的讀寫。RandomAccessFile
有了這個基礎以後,思路就清晰了:
-
擷取檔案長度。HttpUrlConnection
- 自己配置設定好線程進行制定區間的檔案資料的下載下傳。
- 擷取到資料流以後,使用
進行指定位置的讀寫。RandomAccessFile
6. 性能優化
平時做了哪些性能優化?
《Android 性能優化最佳實踐》
7. 第三方庫
一定要在熟練使用後再去檢視原理。
Glide
Glide考察的頻率挺高的,常見的問題有:
- Glide和其他圖檔加載架構的比較?
- 如何設計一個圖檔加載架構?
- Glide緩存實作機制?
- Glide如何處理生命周期?
- ...
《Glide最全解析》 《面試官:履歷上最好不要寫Glide,不是問源碼那麼簡單》
OkHttp
OkHttp常見知識點:
- 責任鍊模式
-
interceptors
的差別?networkInterceptors
建議看一遍源碼,過程并不複雜。
Retrofit
Retrofit常見問題:
- 設計模式和封層解耦的理念
- 動态代理
RxJava
RxJava難在各種操作符,我們了解一下大緻的設計思想即可。
建議尋找一些RxJava的文章。
Android Jetpack(非必須)
我主要閱讀了Android Jetpack中以下庫的源碼:
-
:觀察者模式,元件生命周期中發送事件。Lifecycle
-
:核心就是利用LiveData或者Observablexxx實作的觀察者模式,對16進制的狀态位更新,之後根據這個狀态位去更新對應的内容。DataBinding
-
:觀察者模式,事件的生産消費模型。LiveData
-
:借用Activty異常銷毀時存儲隐藏Fragment的機制存儲ViewModel,保證資料的生命周期盡可能的延長。ViewModel
-
:設計思想。Paging
以後有時間再給大家做源碼分析。
《Android Jetpack源碼分析系列》
8. 插件化群組件化
這個我基本沒用過,等用過了,再和大家分享。
三、Java基礎
Java基礎中考察頻率比較高的是
Object
String
、面向對象、集合、泛型和反射。
1. Object
equals和==的差別?equals和hashcode的關系?
- ==:基本類型比較值,引用類型比較位址。
-
:預設情況下,equals
作為對象中的方法,比較的是位址,不過可以根據業務,修改equals
equals
equals
hashcode
之間的關系:
預設情況下,
equals
相等,
hashcode
必相等,
hashcode
equals
不是必相等。hashcode基于記憶體位址計算得出,可能會相等,雖然幾率微乎其微。
2. String
String、StringBuffer和StringBuilder的差別?
-
String
屬于不可變對象,每次修改都會生成新的對象。String
-
:可變對象,非多線程安全。StringBuilder
-
:可變對象,多線程安全。StringBuffer
大部分情況下,效率是:
StringBuilder
>
StringBuffer
String
3. 面向對象的特性
Java中抽象類和接口的特點?
共同點:
- 抽象類和接口都不能生成具體的執行個體。
- 都是作為上層使用。
不同點:
- 抽象類可以有屬性和成員方法,接口不可以。
- 一個類隻能繼承一個類,但是可以實作多個接口。
- 抽象類中的變量是普通變量,接口中的變量是靜态變量。
- 抽象類表達的是
的關系,接口表達的是is-a
的關系。like-a
關于多态的了解?
多态是面向對象的三大特性:繼承、封裝和多态之一。
多态的定義:允許不同類對同一消息做出響應。
多态存在的條件:
- 要有繼承。
- 要有複寫。
- 父類引用指向子類對象。
Java中多态的實作方式:接口實作,繼承父類進行方法重寫,同一個類中的方法重載。
4. 集合
HashMap的特點是什麼?HashMap的原理?
HashMap的特點:
- 基于Map接口,存放鍵值對。
- 允許key/value為空。
- 非多線程安全。
- 不保證有序,也不保證使用的過程中順序不會改變。
簡單來講,核心是數組+連結清單/紅黑樹,HashMap的原理就是存鍵值對的時候:
- 通過鍵的Hash值确定數組的位置。
- 找到以後,如果該位置無節點,直接存放。
- 該位置有節點即位置發生沖突,周遊該節點以及後續的節點,比較
值,相等則覆寫。key
- 沒有就新增節點,預設使用連結清單,相連節點數超過8的時候,在jdk 1.8中會變成紅黑樹。
- 如果Hashmap中的數組使用情況超過一定比例,就會擴容,預設擴容兩倍。
當然這是存入的過程,其他過程可以自行查閱。這裡需要注意的是:
-
的hash值計算過程是高16位不變,低16位和高16位取抑或,讓更多位參與進來,可以有效的減少碰撞的發生。key
- 初始數組容量為16,預設不超過的比例為0.75。
5. 泛型
說一下對泛型的了解?
泛型的本質是參數化類型,在不建立新的類型的情況下,通過泛型指定不同的類型來控制形參具體限制的類型。也就是說在泛型的使用中,操作的資料類型被指定為一個參數,這種參數可以被用在類、接口和方法中,分别被稱為泛型類、泛型接口和泛型方法。
泛型是Java中的一種文法糖,能夠在代碼編寫的時候起到類型檢測的作用,但是虛拟機是不支援這些文法的。
泛型的優點:
- 類型安全,避免類型的強轉。
- 提高了代碼的可讀性,不必要等到運作的時候才去強制轉換。
什麼是類型擦除?
不管泛型的類型傳入哪一種類型實參,對于Java來說,都會被當成同一類處理,在記憶體中也隻占用一塊空間。通俗一點來說,就是泛型隻作用于代碼編譯階段,在編譯過程中,對于正确檢驗泛型結果後,會将泛型的資訊擦除,也就是說,成功編譯過後的class檔案是不包含任何泛型資訊的。
6. 反射
動态代理和靜态代理
靜态代理很簡單,運用的就是代理模式:
聲明一個接口,再分别實作一個真實的主題類和代理主題類,通過讓代理類持有真實主題類,進而控制使用者對真實主題的通路。
動态代理指的是在運作時動态生成代理類,即代理類的位元組碼在運作時生成并載入目前的ClassLoader。
動态代理的原理是使用反射,思路和上面的一緻。
使用動态代理的好處:
- 不需要為
寫一個形式完全一樣的代理類。RealSubject
- 使用一些動态代理的方法可以在運作時制定代理類的邏輯,進而提升系統的靈活性。
四、Java并發
Java并發中考察頻率較高的有線程、線程池、鎖、線程間的等待和喚醒、線程特性和阻塞隊列等。
1. 線程
線程的狀态有哪些(待修改)?
線程的狀态有:
-
:新建立的線程new
-
:準備就緒的線程,由于CPU配置設定的時間片的關系,此時的任務不在執行過程中。Ready
-
:正在執行的任務Running
-
:被阻塞的任務Block
-
:計時等待的任務Time Waiting
-
:終止的任務Terminated
附上一張狀态轉換的圖:
線程中wait和sleep的差別?
wait
方法既釋放cpu,又釋放鎖。
sleep
方法隻釋放cpu,但是不釋放鎖。
線程和程序的差別?
線程是CPU排程的最小機關,一個程序中可以包含多個線程,在Android中,一個程序通常是一個App,App中會有一個主線程,主線程可以用來操作界面元素,如果有耗時的操作,必須開啟子線程執行,不然會出現ANR,除此以外,程序間的資料是獨立的,線程間的資料可以共享。
2. 線程池
線程池的地位十分重要,基本上涉及到跨線程的架構都使用到了線程池,比如說
OkHttp
RxJava
LiveData
以及
協程
等。
與建立一個線程相比,線程池的特點?
- 節省開銷: 線程池中的線程可以重複利用。
- 速度快:任務來了就能開始,省去建立線程的時間。
- 線程可控:線程數量可空和任務可控。
- 功能強大:可以定時和重複執行任務。
線程池中的幾個參數是什麼意思,線程池的種類有哪些?
線程池的構造函數如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
參數解釋如下:
-
:核心線程數量,不會釋放。corePoolSize
-
:允許使用的最大線程池數量,非核心線程數量,閑置時會釋放。maximumPoolSize
-
:閑置線程允許的最大閑置時間。keepAliveTime
-
:閑置時間的機關。unit
-
:阻塞隊列,不同的阻塞隊列有不同的特性。workQueue
線程池分為四個類型:
-
:閑置線程逾時會釋放,沒有閑置線程的情況下,每次都會建立新的線程。CachedThreadPool
-
:線程池隻能存放指定數量的線程池,線程不會釋放,可重複利用。FixedThreadPool
-
:單線程的線程池。SingleThreadExecutor
-
:可定時和重複執行的線程池。ScheduledThreadPool
線程池的工作流程?
圖檔來自
《線程池是怎樣工作的》簡而言之:
- 任務來了,優先考慮核心線程。
- 核心線程滿了,進入阻塞隊列。
- 阻塞隊列滿了,考慮非核心線程(圖上好像少了這個過程)。
- 非核心線程滿了,再觸發拒絕任務。
3. 鎖
死鎖觸發的四大條件?
- 互斥鎖
- 請求與保持
- 不可剝奪
- 循環的請求與等待
synchronized關鍵字的使用?synchronized的參數放入對象和Class有什麼差別?
synchronized
關鍵字的用法:
- 修飾方法
- 修飾代碼塊:需要自己提供鎖對象,鎖對象包括對象本身、對象的Class和其他對象。
放入對象和Class的差別是:
- 鎖住的對象不同:成員方法鎖住的執行個體對象,靜态方法鎖住的是Class。
- 通路控制不同:如果鎖住的是執行個體,隻會針對同一個對象方法進行同步通路,多線程通路同一個對象的synchronized代碼塊是串行的,通路不同對象是并行的。如果鎖住的是類,多線程通路的不管是同一對象還是不同對象的synchronized代碼塊是都是串行的。
synchronized的原理?
任何一個對象都有一個
monitor
與之相關聯,JVM基于進入和退出
mointor
對象來實作代碼塊同步和方法同步,兩者實作細節不同:
- 代碼塊同步:在編譯位元組碼的時候,代碼塊起始的地方插入
指令,異常和代碼塊結束處插入monitorenter
指令,線程在執行monitorexit
指令的時候嘗試擷取monitorenter
對象的所有權,擷取不到的情況下就是阻塞monitor
- 方法同步:
方法在synchronized
結構有method_info
标記,線程在執行的時候擷取對應的鎖,進而實作同步方法AAC_synchronized
synchronized和Lock的差別?
主要差別:
-
是Java中的關鍵字,是Java的内置實作;synchronized
是Java中的接口。Lock
-
遇到異常會釋放鎖;synchronized
需要在發生異常的時候調用成員方法Lock
Lock#unlock()
-
是不可以中斷的,synchronized
可中斷。Lock
-
不能去嘗試獲得鎖,沒有獲得鎖就會被阻塞;synchronized
可以去嘗試獲得鎖,如果未獲得可以嘗試處理其他邏輯。Lock
-
多線程效率不如synchronized
,不過Java在1.6以後已經對Lock
進行大量的優化,是以性能上來講,其實差不了多少。synchronized
悲觀鎖和樂觀鎖的舉例?以及它們的相關實作?
悲觀鎖和樂觀鎖的概念:
- 悲觀鎖:悲觀鎖會認為,修改共享資料的時候其他線程也會修改資料,是以隻在不會受到其他線程幹擾的情況下執行。這樣會導緻其他有需要鎖的線程挂起,等到持有鎖的線程釋放鎖
- 樂觀鎖:每次不加鎖,每次直接修改共享資料假設其他線程不會修改,如果發生沖突就直接重試,直到成功為止
舉例:
-
:典型的悲觀鎖是獨占鎖,有悲觀鎖
synchronized
ReentrantLock
-
:典型的樂觀鎖是CAS,實作CAS的樂觀鎖
為代表的一系列類atomic
CAS是什麼?底層原理?
CAS
全稱Compare And Set,核心的三個元素是:記憶體位置、預期原值和新值,執行CAS的時候,會将記憶體位置的值與預期原值進行比較,如果一緻,就将原值更新為新值,否則就不更新。 底層原理:是借助CPU底層指令
cmpxchg
實作原子操作。
4. 線程間通信
notify和notifyAll方法的差別?
notify
随機喚醒一個線程,
notifyAll
喚醒所有等待的線程,讓他們競争鎖。
wait/notify和Condition類實作的等待通知有什麼差別?
synchronized
與
wait/notify
結合的等待通知隻有一個條件,而Condition類可以實作多個條件等待。
5. 多線程間的特性
多線程間的有序性、可見性和原子性是什麼意思?
-
:執行一個或者多個操作的時候,要麼全部執行,要麼都不執行,并且中間過程中不會被打斷。Java中的原子性可以通過獨占鎖和CAS去保證原子性
-
:指多線程通路同一個變量的時候,一個線程修改了變量的值,其他線程能夠立刻看得到修改的值。鎖和可見性
能夠保證可見性volatile
-
:程式執行的順序按照代碼先後的順序執行。鎖和有序性
能夠保證有序性volatile
happens-before原則有哪些?
Java記憶體模型具有一些先天的有序性,它通常叫做happens-before原則。
如果兩個操作的先後順序不能通過happens-before原則推倒出來,那就不能保證它們的先後執行順序,虛拟機就可以随意打亂執行指令。
happens-before
原則有:
- 程式次序規則:單線程程式的執行結果得和看上去代碼執行的結果要一緻。
- 鎖定規則:一個鎖的
操作一定發生在上一個lock
操作之後。unlock
- volatile規則:對
變量的寫操作一定先行于後面對這個變量的對操作。volatile
- 傳遞規則:A發生在B前面,B發生在C前面,那麼A一定發生在C前面。
- 線程啟動規則:線程的
方法先行發生于線程中的每個動作。start
- 線程中斷規則:對線程的
操作先行發生于中斷線程的檢測代碼。interrupt
- 線程終結原則:線程中所有的操作都先行發生于線程的終止檢測。
- 對象終止原則:一個對象的初始化先行發生于他的
方法的執行。finalize()
前四條規則比較重要。
volatile的原理?
可見性 如果對聲明了
volatile
的變量進行寫操作的時候,JVM會向處理器發送一條
Lock
字首的指令,将這個變量所在緩存行的資料寫入到系統記憶體。
多處理器的環境下,其他處理器的緩存還是舊的,為了保證各個處理器一緻,會通過嗅探在總線上傳播的資料來檢測自己的資料是否過期,如果過期,會強制重新将系統記憶體的資料讀取到處理器緩存。
有序性
Lock
字首的指令相當于一個記憶體栅欄,它確定指令排序的時候,不會把後面的指令拍到記憶體栅欄的前面,也不會把前面的指令排到記憶體栅欄的後面。
6. 阻塞隊列
通常的阻塞隊列有哪幾種,特點是什麼?
-
:基于數組實作的有界的FIFO(先進先出)阻塞隊列。ArrayBlockQueue
-
:基于連結清單實作的無界的FIFO(先進先出)阻塞隊列。LinkedBlockQueue
-
:内部沒有任何緩存的阻塞隊列。SynchronousQueue
-
:具有優先級的無限阻塞隊列。PriorityBlockingQueue
ConcurrentHashMap的原理
資料結構的實作跟HashMap一樣,不做介紹。
JDK 1.8之前采用的是分段鎖,核心類是一個
Segment
Segment
繼承了
ReentrantLock
,每個
Segment對象
管理若幹個桶,多個線程通路同一個元素的時候隻能去競争擷取鎖。
JDK 1.8采用了
CAS + synchronized
,插入鍵值對的時候如果目前桶中沒有Node節點,使用CAS方式進行更新,如果有Node節點,則使用synchronized的方式進行更新。
五、Jvm
Jvm中考察頻率較高的内容有:Jvm記憶體區域的劃分、GC機制和類加載機制。
《深入了解Java虛拟機》
1. Java記憶體模型
Jvm記憶體區域是如何劃分的?
記憶體區域劃分:
-
:目前線程的位元組碼執行位置的訓示器,線程私有。程式計數器
-
:描述的Java方法執行的記憶體模型,每個方法在執行的同時會建立一個棧幀,存儲着局部變量、操作數棧、動态連結和方法出口等,線程私有。Java虛拟機棧
-
:本地方法執行的記憶體模型,線程私有。本地方法棧
-
:所有對象執行個體配置設定的區域。Java堆
-
:所有已經被虛拟機加載的類的資訊、常量、靜态變量和即時編輯器編譯後的代碼資料。方法區
Jvm記憶體模型是怎麼樣的?
- Java規定所有變量的記憶體都需要存儲在主記憶體。
- 每個線程都有自己的工作記憶體,線程中使用的所有變量以及對變量的操作都基于工作記憶體,工作記憶體中的所有變量都從主記憶體讀取過來的。
- 不同線程間的工作記憶體無法進行直接交流,必須通過主記憶體完成。
這是一份面向Android開發者的複習指南
主記憶體和工作記憶體之間的互動協定,即變量如何從主記憶體傳遞到工作記憶體、工作記憶體如何将變量傳遞到主記憶體,Java記憶體模型定義了8種操作來完成,并且每一種操作都是原子的,不可再分的。
類型 | 說明 |
---|---|
| 作用于主記憶體的變量,把一個變量辨別一個線程獨占的狀态 |
| 作用于主記憶體的變量,把一個處于鎖定狀态的變量釋放出來 |
| 把一個變量從主記憶體傳輸到工作記憶體,以便随後的 使用 |
| 把 操作讀取的變量存儲到工作記憶體的變量副本中 |
| 把工作記憶體中的變量的值傳遞給執行引擎,每當虛拟機執行到一個需要使用變量的位元組碼指令的時候都會執行這個操作 |
| 把一個從執行引擎中接收到的變量指派給工作記憶體中的變量,每當虛拟機遇到指派的位元組碼指令都會執行這個操作 |
| 把工作記憶體中的一個變量的值傳遞給主記憶體,以便以後的 |
| 傳遞過來的工作記憶體中的變量寫入到主記憶體中的變量 |
String s1 = "abc"和String s2 = new String("abc")的差別,生成對象的情況
- 指向方法區:
是常量,是以它會在方法區中配置設定記憶體,如果方法區已經給"abc"
配置設定過記憶體,則s1會直接指向這塊記憶體區域。"abc"
- 指向Java堆:
是重新生成了一個Java執行個體,它會在Java堆中配置設定一塊記憶體。new String("abc")
是以s1和s2的記憶體位址肯定不一樣,但是内容一樣。
2. GC機制
如何判斷對象可回收?
判斷一個對象可以回收通常采用的算法是引用幾算法和可達性算法。由于互相引用導緻的計數不好判斷,Java采用的可達性算法。
可達性算法的思路是:通過一些列被成為GC Roots的對象作為起始點,自上往下從這些起點往下搜尋,搜尋所有走過的路徑稱為引用鍊,如果一個對象沒有跟任何引用鍊相關聯的時候,則證明該對象不可用,是以這些對象就會被判定為可以回收。
可以被當作GC Roots的對象包括:
- Java虛拟機棧中的引用的對象
- 方法區中靜态屬性引用的對象
- 方法區中常量引用的對象
- 本地方法中JNI引用的對象
GC的常用算法?
-
:首先标記出需要回收的對象,标記完成後統一回收所有被标記的對象。容易産生碎片空間。标記 - 清除
-
:它将可用的記憶體分為兩塊,每次隻用其中的一塊,當需要記憶體回收的時候,将存活的對象複制到另一塊記憶體,然後将目前已經使用的記憶體一次性回收掉。需要浪費一半的記憶體。複制算法
-
:讓存活的對象向一端移動,之後清除邊界外的記憶體。标記 - 整理
-
:根據對象存活的周期,Java堆會被分為新生代和老年代,根據不同年代的特性,選擇合适的GC收集算法。分代搜集
Minar GC和Full GC的差別?
-
:頻率高、針對新生代。Minar GC
-
:頻率低、發生在老年代、通常會伴随一次Minar GC和速度慢。Full GC
說一下四種引用以及他們的差別?
-
:強引用還在,垃圾搜集器就不會回收被引用的對象。強引用
-
:對于軟引用關聯的對象,在系統發生記憶體溢出異常之前,将會把這些對象列進回收範圍進行第二次回收,如果這次回收還沒有足夠的記憶體,才會抛出記憶體溢出異常。軟引用
-
:被若引用關聯的對象隻能存活到下一次GC之前。弱引用
-
:為對象設定虛引用的目的僅僅是為了GC之前收到一個系統通知。虛引用
3. 類加載
類加載的過程?
類加載的過程可以分為:
-
:将類的全限定名轉化為二進制流,再将二進制流轉化為方法區中的類型資訊,進而生成一個Class對象。加載
-
:對類的驗證,包括格式、位元組碼、屬性等。驗證
-
:為類變量配置設定記憶體并設定初始值。準備
-
:将常量池的符号引用轉化為直接引用。解析
-
:執行類中定義的Java程式代碼,包括類變量的指派動作和構造函數的指派。初始化
-
使用
-
解除安裝
隻有加載、驗證、準備、初始化和解除安裝的這個五個階段的順序是确定的。
類加載的機制,以及為什麼要這樣設計?
類加載的機制是雙親委派模型。大部分Java程式需要使用的類加載器包括:
-
:由C++語言實作,負責加載Java中的核心類。啟動類加載器
-
:負責加載Java擴充的核心類之外的類。擴充類加載器
-
:負責加載使用者類路徑上指定的類庫。應用程式類加載器
雙親委派模型如下:
雙親委派模型要求出了頂層的啟動類加載器之外,其他的類加載器都有自己的父加載器,通過組合實作。
雙親委派模型的工作流程: 當一個類加載的任務來臨的時候,先交給父類加載器完成,父類加載器交給父父類加載器完成,知道傳遞給啟動類加載器,如果完成不了的情況下,再依次往下傳遞類加載的任務。
這樣設計的原因: 雙親委派模型能夠保證Java程式的穩定運作,不同層次的類加載器具有不同優先級,所有的對象的父類Object,無論哪一個類加載器加載,最後都會交給啟動類加載器,保證安全。
六、kotlin
《Kotlin》實戰
1. 基礎
==、===和equal的差別?
==和equal的作用相同,===比較記憶體位址
var和val的差別?
-
:可變引用,具有可讀和可寫權限,值可變,類型不可變var
-
:不可變引用,具有可讀權限,值不可變,但是對象的屬性可變val
2. 函數
Kotlin中預設參數的作用以及原理?
作用:配合@JavaOverloads可以解決Java調用Kotlin函數重載的問題。 原理:Kotlin編譯的預設參數是被編譯到調用的函數中的,是以預設參數改變的時候,是需要重新編譯這個函數的。
Kotlin中頂層函數的原理
頂層函數實質就是Java中的靜态函數,可以通過Kotlin中的
@Jvm:fileName
自動生成對應的Java調用類名。
中綴函數是什麼?注意點?
中綴函數需要是用
infix
關鍵字修飾,如
downTo
public infix fun Int.downTo(to: Int): IntProgression {
return IntProgression.fromClosedRange(this, to, -1)
}
注意點是函數的參數隻能有一個,函數的參與者隻能有兩個。
解構函數的本質?
解構聲明将對象中的所有屬性,解構成一組屬性變量,而且這些變量可以單獨使用,可以單數使用的原因是通過擷取對應的component()方法對應着類中每個屬性的值,這些屬性的值被存儲在局部變量中,是以解構聲明的實質是局部變量。
擴充函數的本質?
擴充函數的本質就是對應Java中的靜态函數,這個靜态函數參數為接受者類型的對象,然後利用這個對象去通路對象中的屬性和成員方法,最後傳回這個對象的本身。
擴充函數和成員函數的差別?
- 實質不同:擴充函數實質是靜态函數,是外部函數,成員函數是内部函數。
- 權限不同:擴充函數通路不了私有的屬性和成員方法,成員函數可以。
- 繼承:擴充函數不可複寫,成員函數可以複寫。
它們的使用方式類似。
3. 類、對象和接口
Kotlin中常用的類的修飾符有哪些?
-
:運作建立子類或者複寫子類的方法。open
-
:不允許建立子類和複寫子類的方法。final
-
:抽象類,必須複寫子類的方法。abstract
在Kotlin中,預設的類和方法的修飾符都是
final
的,如果想讓類和方法能夠被繼承或者複寫,需要顯示的添加
open
修飾符。
Kotlin中可見性修飾符有哪些?
-
:所有地方可見public
-
:子類中可見protected
-
:類中可見private
-
:子產品中可見,一個子產品就是一組一起編譯的Kotlin檔案internal
Java預設的通路權限是包通路權限,Kotlin中預設的通路權限是public。
Kotlin中的内部類和Java中的内部類有什麼不同?
- Kotlin:預設相當于Java中的靜态内部類,如果想通路類中的成員方法和屬性,需要添加
關鍵字修飾。inner
- Java:預設持有外部類引用,可以通路成員方法和屬性,如果想聲明為靜态内部類,需要添加
static
Kotlin屬性代理背後原理?
可以簡單了解為屬性的settter、getter通路器内部實作交給了代理對象來實作,相當于使用一個代理對象代替了原來簡單屬性的讀寫過程,而暴露外部屬性操作還是不變 的,照樣是屬性指派和讀取,隻是setter、getter内部具體實作變了。
object和companion object的一些特點?
共同點: 定義單例的一種方式,提供靜态成員和方法。
-
:用來生成匿名内部類。object
-
:提供工廠方法,通路私有的構造方法。companion object
4. lambda
lambda表達式有幾種?
- 普通表達式:
()->R
- 帶接收者對象的表達式:
,可以通路接收者對象的屬性和成員方法。如T.()->R
apply
kotlin和Java内部類或者lambda表達式通路局部變量有什麼不同?
- Java中的内部類:局部變量必須是
聲明的,無法去修改局部變量的值。final
- Kotlin中lambda表達式:不要求
聲明,對于非final
修飾的lambda表達式,可以修改局部變量的值。final
如果想在Java中的内部類修改外層局部變量的值,有兩種方法:用數組包裝或者提供包裝類,Kotlin中lambda能夠通路并修改局部變量的本質就是提供了一層包裝類:
class Ref<T>(var value:T)
修改局部變量的值就是修改value中的值。
使用lambda表達式通路的局部變量有什麼不同?
預設情況下,局部變量的生命周期會被限制在聲明這個變量的函數中,但是如果它被lambda捕捉了,使用這個變量的代碼可以被存儲并稍後執行。
class Apple {
lateinit var num:(() -> Int)
fun initCount(){
val count = 2
num = fun():Int{
return count * count
}
}
fun res():Int{
return num()
}
}
fun main(args: Array<String>) {
val a = Apple()
a.initCount()
val res = a.res()
println(res)
}
如上面代碼所示,局部變量
count
就被存儲在lambda表達式中,最後通過
Apple#res
方法引用表達式。
原理:當你捕捉final變量的時候,它的值會和lambda代碼一起存儲。對于非final變量,它的值會被封裝在一層包裝器中,包裝器的引用會和lambda代碼一起被存儲。
帶來的問題:預設情況下,lambda表達式會生成匿名内部類,在非顯示聲明對象的情況下可以多次重用,但是如果捕獲了局部變量,每次調用的時候都需要生成新的執行個體。
序列是什麼?集合類和序列的操作符比較?
Sequence
(序列)是一種惰性集合,可以更高效地對元素進行鍊式操作,不需要建立額外的集合儲存過程中産生的中間結果,簡單來講,就是序列中所有的操作都是按順序應用在每一個元素中。比如:
fun main(args: Array<String>) {
val list = mutableListOf<String>("1","2","3","4","5","6","7","8","9")
val l = list.asSequence()
.filter { it.toCharArray()[0] < '4' }
.map { it.toInt() * it.toInt() }
.toList()
}
對于上述序列中的
"1"
,它會先執行
filter
,再執行
map
,之後再對
"2"
重複操作。除此以外,序列中所有的中間操作都是惰性的。
集合和序列操作符的比較:
- 集合類:
map
方法是内聯,不會生成匿名類的執行個體,但每次進行filter
map
都會生成新的集合,當資料量大的時候,消耗的記憶體也比較大。filter
- 序列:
map
非内聯,會生成匿名類執行個體,但不需要建立額外的集合儲存中間操作的結果。fitler
為什麼要使用内聯函數?内聯函數的作用?
使用lambda表達式可能帶來的開銷:
- lambda表達式正常會被編譯成匿名類。
- 正常情況下,使用lambda表達式至少會生成一個對象,如果很不幸的使用了局部變量,那麼每次使用該lambda表達式都會生成一個新的對象,導緻使用lambda的效率比不使用還要低。
使用内聯函數可以減少運作時的開銷。内聯函數主要作用:
- 使用内聯函數可以減少中間類和對象的建立,進而提升性能。主要原因是内聯函數可以做到函數被使用的時候編譯器不會生成函數調用的代碼,而是使用函數實作的真實代碼區替換每一次的調用。
- 結合
實化類型參數,解決泛型類型運作時擦除的問題。reified
5. 類型系統
Kotlin中的基本資料類型的了解?
在Kotlin中,使用的時候是不區分基本類型的,統一如下:
Int
Byte
Short
Long
Float
Double
Char
Boolean
使用統一的類型并不意味着Kotlin中所有的基本類型都是引用類型,大多數情況下,對于變量、參數、傳回類型和屬性都會被編譯成基本類型,泛型類會被編譯成Java中的包裝類,即引用類型。
隻讀集合和可變集合的差別?
在Kotlin中,集合會被分為兩大類型,隻讀集合和可變集合。
- 隻讀集合:對集合隻有讀取權限。
- 可變集合:能夠删除、新增、修改和讀取元素。
但是有一點需要注意,隻讀集合不一定是不可變的,如果你使用的變量是隻讀集合,它可能是衆多集合引用中的一個,任何一個集合引用都有可能是可變集合。
Array和IntArray的差別?
Array<Int>
相當于Java中的
Integer[]
IntArray
對應Java中的
int[]
使用實化類型參數解決泛型擦除的原理是什麼?
内聯函數的原理是編譯器把實作的位元組碼動态插入到每一次調用的地方。實化類型參數也正是基于這個原理,每次調用實化類型參數的函數的時候,編譯器都知道此次作為泛型類型實參的具體類型,是以編譯器每次調用的時候生成不同類型實參調用的位元組碼插入到調用點。
6. 協程
協程是什麼?協程的有什麼特點?
Kotlin官方文檔上說:
協程的本質是輕量級的線程。
為什麼說它是輕量級的線程,因為從官方角度來講,建立十萬個協程沒什麼問題列印任務不會存在問題,建立十萬個線程會造成記憶體問題,可能會造成記憶體溢出。但是這個對比有問題,因為協程本質上是基于Java的線程池的,你去用線程池建立十萬個列印任務是不會造成記憶體溢出的。
從上面我們可以得出結果,協程就是基于線程實作的更上層的Api,隻不過它可以用阻塞式的寫法寫出非阻塞式的代碼,避免了大量的回調,核心就是協程可以幫我自動的切換線程。
協程的原理?
很多人都會講,協程中處理耗時任務,協程會先挂起,執行完,再切回來。我在這就淺顯的分析這兩步。
- 挂起:協程挂起的時候會從挂起處将後面的代碼封裝成續體,協程挂起的時候,将挂起的任務根據排程器放到線程池中執行,會有一個線程監視任務的完成情況。
- 線程切回:監視線程看到任務結束以後,根據需要再切到指定的線程中(主線程or子線程),執行續體中剩餘的代碼。
詳解請檢視:
《Kotlin/JVM 協程實作原理》
七、網絡
掌握網絡知識其實是需要一個系統的過程,在時間充裕的情況下,建議還是系統化的學習。
高頻網絡知識有TCP、HTTP和HTTPS。
《趣談網絡協定》 《圖解Http》
1. HTTP和HTTPS
HTTP是哪一層的協定,常見的HTTP狀态碼有哪些,分别代表什麼意思?
HTTP協定是應用層的協定。
常見的HTTP狀态碼有:
類别 | 解釋 |
---|---|
| 請求已經接收,繼續處理 |
| 伺服器已經正确處理請求,比如 |
| 重定向,需要做進一步的處理才能完成請求 |
| 伺服器無法了解的請求,比如 ,通路的資源不存在 |
| 伺服器收到請求以後,處理錯誤 |
HTTP 1.1 和HTTP 2有什麼差別?
HTTP 2.0基于HTTP 1.1,與HTTP 2.0增加了:
- 二進制格式:HTTP 1.1使用純文字進行通信,HTTP 2.0使用二進制進行傳輸。
- Head壓縮:對已經發送的Header使用鍵值建立索引表,相同的Header使用索引表示。
- 伺服器推送:伺服器可以進行主動推送
- 多路複用:一個TCP連接配接可以劃分成多個流,每個流都會配置設定Id,用戶端可以借助流和服務端建立全雙工進行通信,并且流具有優先級。
這是一份面向Android開發者的複習指南
HTTP和HTTPS有什麼差別?
簡單來說,HTTP和HTTPS的關系是這樣的
HTTPS = HTTP + SSL/TLS
差別如下: HTTP作用于應用層,使用80端口,起始位址是
http://
,明文傳輸,消息容易被攔截,串改。 HTTPS作用域傳輸層,使用443端口,起始位址是
https://
,需要下載下傳CA憑證,傳輸的過程需要加密,安全性高。
SSL/TLS的握手過程?
這裡借用
的圖檔:
HTTPS傳輸過程中是如何處理進行加密的?為什麼有對稱加密的情況下仍然需要進行非對稱加密?
過程和上圖類似,依次獲驗證書,公鑰,最後生成對稱加密的鑰匙進行對稱加密。
對稱加密可以保證加密效率,但是不能解決密鑰傳輸問題;非對稱加密可以解決傳輸問題,但是效率不高。
2. TCP相關
TCP的三次握手過程,為什麼需要三次,而不是兩次或者四次?
隻發送兩次,服務端是不知道自己發送的消息能不能被用戶端接收到。 因為TCP握手是三次,是以此時雙方都已經知道自己發送的消息能夠被對方收到,是以,第四次的發送就顯得多餘了。
TCP的四次揮手過程?
大緻意思就是:
-
:我要斷開連接配接了Client
-
:我收到你的消息了Server
-
:我也要斷開連接配接了Server
-
:收到你要斷開連接配接的消息了Client
之後
Client
等待兩個
MSL
(資料包在網絡上生存的最長時間),如果服務端沒有回消息就徹底斷開了。
TCP和UDP有什麼差別?
-
:基于位元組流、面向連接配接、可靠、能夠進行全雙工通信,除此以外,還能進行流量控制和擁塞控制,不過效率略低TCP
-
:基于封包、面向無連接配接、不可靠,但是傳輸效率高。UDP
總的來說,TCP适用于傳輸效率要求低,準确性要求高或要求有連接配接。而UDP适用于對準确性要求較低,傳輸效率要求較高的場景,比如語音通話、直播等。
TCP為什麼是一種可靠的協定?如何做到流量控制和擁塞控制?
- TCP可靠:是因為可以做到資料包發送的有序、無差錯和無重複。
- 流量控制:是通過滑動視窗實作的,因為發送發和接收方消息發送速度和接收速度不一定對等,是以需要一個滑動視窗來平衡處理效率,并且保證沒有差錯和有序的接收資料包。
- 擁塞控制:慢開始和擁塞避免、快重傳和快恢複算法。這寫算法主要是為了适應網絡中的帶寬而作出的調整。
八、設計模式
經常考察的設計模式不多,但是我們應該在平時業務中應該多多思考,用一些設計模式會不會更好。
《Android 源碼設計模式解析與實戰》
1. 六大原則
設計模式的六大原則是:
- 單一職責:合理配置設定類和函數的職責
- 開閉原則:開放擴充,關閉修改
- 裡式替換:繼承
- 依賴倒置:面向接口
- 接口隔離:控制接口的粒度
- 迪米特:一個類應該對其他的類了解最少
2. 單例模式
單例模式被問到的幾率很大,通常會問如下幾種問題。
單例的常用寫法有哪幾種?
懶漢模式
public class SingleInstance {
private static SingleInstance instance;
private SingleInstance() {}
public static synchronized SingleInstance getInstance() {
if(instance == null) {
instance = new SingleInstance();
}
return instance;
}
}
該模式的主要問題是每次擷取執行個體都需要同步,造成不必要的同步開銷。 DCL模式
public class SingleInstance {
private static SingleInstance instance;
private SingleInstance() {}
public static SingleInstance getInstance() {
if(instance == null) {
synchronized (SingleInstance.class) {
if(instance == null) {
instance = new SingleInstance();
}
}
}
return instance;
}
}
高并發環境下可能會發生問題。 靜态内部類單例
public class SingleInstance {
private SingleInstance() {}
public static SingleInstance getInstance() {
return SingleHolder.instance;
}
private static class SingleHolder{
private static final SingleInstance instance = new SingleInstance();
}
}
枚舉單例
public enum SingletonEnum {
INSTANCE
}
優點:線程安全和反序列化不會生成新的執行個體
DCL模式會有什麼問題?
對象生成執行個體的過程中,大概會經過以下過程:
- 為對象配置設定記憶體空間。
- 初始化對象中的成員變量。
- 将對象指向配置設定的記憶體空間(此時對象就不為null)。
由于Jvm會優化指令順序,也就是說2和3的順序是不能保證的。在多線程的情況下,當一個線程完成了1、3過程後,目前線程的時間片已用完,這個時候會切換到另一個線程,另一個線程調用這個單例,會使用這個還沒初始化完成的執行個體。 解決方法是使用
volatile
關鍵字:
public class SingleInstance {
private static volatile SingleInstance instance;
private SingleInstance() {}
public static SingleInstance getInstance() {
if(instance == null) {
synchronized (SingleInstance.class) {
if(instance == null) {
instance = new SingleInstance();
}
}
}
return instance;
}
}
3. 需要關注的設計模式
重點了解以下的幾種常用的設計模式:
- 工廠模式和抽象工廠模式:注意他們的差別。
- 責任鍊模式:View的事件分發和OkHttp的調用過程都使用到了責任鍊模式。
- 觀察者模式:重要性不言而喻。
- 代理模式:建議了解一下動态代理。
4. MVCMVPMVVM
MVC、MVP和MVVM應該是設計模式中考察頻率最高的知識點了,嚴格意義上來說,它們不能算是設計模式,而是架構。
MVC、MVP和MVVM是什麼?
圖檔已有,不再給出
- MVC:Model-View-Controller,是一種分層解偶的架構,Model層提供本地資料和網絡請求,View層處理視圖,Controller處理邏輯,存在問題是Controller層和View層的劃分不明顯,Model層和View層的存在耦合。
- MVP:Model-View-Presenter,是對MVC的更新,Model層和View層與MVC的意思一緻,但Model層和View層不再存在耦合,而是通過Presenter層這個橋梁進行交流。
- MVVM:Model-View-ViewModel,不同于上面的兩個架構,ViewModel持有資料狀态,當資料狀态改變的時候,會自動通知View層進行更新。
MVC和MVP的差別是什麼?
MVP是MVC的進一步解耦,簡單來講,在MVC中,View層既可以和Controller層互動,又可以和Model層互動;而在MVP中,View層隻能和Presenter層互動,Model層也隻能和Presenter層互動,減少了View層和Model層的耦合,更容易定位錯誤的來源。
MVVM和MVP的最大差別在哪?
MVP中的每個方法都需要你去主動調用,它其實是被動的,而MVVM中有資料驅動這個概念,當你的持有的資料狀态發生變更的時候,你的View你可以監聽到這個變化,進而主動去更新,這其實是主動的。
ViewModel如何知道View層的生命周期?
事實上,如果你僅僅使用ViewModel,它是感覺不了生命周期,它需要結合LiveData去感覺生命周期,如果僅僅使用DataBinding去實作MVVM,它對資料源使用了弱引用,是以一定程度上可以避免記憶體洩漏的發生。
九、算法題
沒什麼好說的,Leetcode + 《劍指Offer》,着重記住一些解決問題的思路。
除此以外,你還得記住一些常用的算法:排序、反轉連結清單、樹的周遊和手寫LruCache,這些都寫不出來,就尴尬了。
如果你不想閱讀書籍,可以參考一下這個Github,親眼見證了從3k Star到34k Star,跪了:
【fucking-algorithm】: github.com/labuladong/…
十、履歷
履歷中最重要的是項目經曆和技能掌握。
可能有的同學會說,我天天在公司擰螺絲,根本沒什麼東西可寫。
是以我們在平時的工作中,不應該僅僅滿足于寫一些業務代碼,而應該常常思考:
- 在結合的業務的情況下,我可以再做一點什麼?
- 對于已經寫完的代碼,我還可以做哪一些優化?
最後
其實Android開發的知識點就那麼多,面試問來問去還是那麼點東西。是以面試沒有其他的訣竅,隻看你對這些知識點準備的充分程度。so,出去面試時先看看自己複習到了哪個階段就好。
上面分享的騰訊、頭條、阿裡、美團、位元組跳動等公司2019-2020年的高頻面試題,部落客還把這些技術點整理成了視訊和PDF(實際上比預期多花了不少精力),包含知識脈絡 + 諸多細節,由于篇幅有限,上面隻是以圖檔的形式給大家展示一部分。
【Android學習PDF+學習視訊+面試文檔+知識點筆記】
【Android思維腦圖(技能樹)】
知識不體系?這裡還有整理出來的Android進階學習的思維腦圖,給大家參考一個方向。
【Android進階架構視訊學習資源】
Android部分精講視訊領取學習後更加是如虎添翼!進軍BATJ大廠等(備戰)!現在都說網際網路寒冬,其實無非就是你上錯了車,且穿的少(技能),要是你上對車,自身技術能力夠強,公司換掉的代價大,怎麼可能會被裁掉,都是淘汰末端的業務Curd而已!現如今市場上初級程式員泛濫,這套教程針對Android開發工程師1-6年的人員、正處于瓶頸期,想要年後突破自己漲薪的,進階Android中進階、架構師對你更是如魚得水,趕快領取吧!
【Android進階學習視訊】、【全套Android面試秘籍】
下載下傳位址!
作者:九心
連結:
https://juejin.im/post/5ea2aebce51d4546fd483065來源:掘金