本着針對面試負責任的態度,記錄面試過程中各個知識點,而不是入門系列,如果有不懂的自行學習。
目前總結了以下幾個方面:
- Android優化
- HashMap分析
- Handler源碼分析
- OkHttp分析
- Retrofit分析
- 自定義View
由於篇幅原因拆分成兩個部分分享,上一篇講解了前面三個方面,今天講剩下的三個部分,之後的還會持續更新。
OkHttp分析
Okhttp是對Socket的封裝。有三個主要的類,Request,Response,Call默認使用new OkHttpClient() 創建初client對象。
如果需要初始化網絡請求的參數,如timeout,interceptor等,可以創建Builder,通過builder.build() 創建初client對象。
使用new Request.Builder().url().builder()創建初requst對象。
通過client.newCall()創建出call對象,同步使用call.excute(), 異步使用call,enqueue(). 這裏Call是個接口,具體的實現在RealCall這個實現類裏面。
Okhttp的高效體現在,okhttp內有個Dispatcher類,是okhttp內部維護的一個線程池,對最大連接數,host最大訪問量做了初始定義。維護3個隊列及1個線程池
readyAsyncCalls
待訪問請求隊列,裏面存儲準備執行的請求。
runningAsyncCalls
異步請求隊列,裏面存儲正在執行,包含已經取消但是還沒有結束的請求。
runningSyncCalls
同步請求隊列,正在執行的請求,包含已經取消但是還沒有結束的請求。
ExecutorService
線程池,最小0,最大Max的線程池
在執行call.excute()的時候,調用到realcall類裏的excute方法,這個是同步方法,在方法的第一行就加了鎖,判斷executed標記,如果是true就拋出異常,保證一個請求只被執行一次。false的話繼續向下執行。調用client.dispatcher.excute()進入到dispatcher類中,向runningSyncCalls隊列中添加當前這個請求。執行結束會調用finished方法
如果是異步操作,會創建一個RealCall.AsyncCall對象,AsyncCall繼承的NamedRunnable接口,NamedRunnable是個runnable。進入到Dispatcher的enqueue()方法中,首先判斷線程池中線程的數據,host的訪問量,如果都沒有達到那麼加入到runningAsyncCalls中,並執行。否則加入到readyAsyncCalls隊列中。
finished方法,如果是異步操作,promoteCall方法,promoteCalls()中用迭代器遍歷readyAsyncCalls 然後加入到runningAsyncCalls
RealConnection
真正的連接操作類,對soket封裝,http1/http2的選擇,ssl協議等等信息。一個recalconnection就是一次鏈接
ConnectionPool
鏈接池,管理http1/http2的連接,同一個address共享一個connection,實現鏈接的複用。
StreamAlloction
保存了鏈接信息,address,HttpCodec,realconnection,connectionpool等信息
在realCall中 getResponseWithInterceptorChain()創建並開啓攔截器鏈
Okhttp中的攔截器,默認的分爲5種
-
RetryAndFollowUpInterceptor
做網絡失敗重連,但是並不是所有的請求都需要重連,根據響應碼。MAX_FOLLOW_UPS=20最大重連次數
在intercept方法中創建了StreamAllocation對象,並調用chain.proceed方法,執行下一個攔截器,對request進行處理,並返回response。 -
BirdgeInterceptor
初始化信息,添加請求頭等,例如gzip,keep-alive,返回的response進行解壓 -
CacheInterceptor
內部有Cache類,處理緩存操作,intercache內部類,disklrucache算法等
重點是不緩存非get的請求。
CacheStrategy緩存策略類,通過工廠模式獲取 -
ConnectionInterceptor(建議重點閱讀源碼)
建立鏈接,使用之前創建好的StreamAllocation,初始化httpcodec,realConnection。內部使用了類似gc標記清理算法,對無用的connection進行標記,StramAlloction漸漸變成0,線程池檢測並回收,保證多個健康的keep-alive鏈接 -
CallServerInterceptor
發起真正的網絡請求,解析返回的數據
http寫入網絡IO流,從網絡IO流中讀取返回給客戶端的數據。 -
Network Interceptors
Application interceptors & Network Interceptors區別查看相關資料
okhttp wiki
OkHttp中的設計模式
單例、Builder、策略、責任鏈、觀察者
思考:
策略與簡單工廠的區別
相關面試題:
- 責任鏈模式DEMO
- IO操作流程
- 三級緩存的流程說一遍
- 請求配置都有哪些方法。
- okhttp斷點續傳用什麼保存,怎麼實現斷點續傳流程
Retrofit分析
涉及到的設計模式
外觀模式,構建者模式,工廠模式,代理模式,適配器模式,策略模式,觀察者模式
概括
Retrofit就是一個網絡請求框架的封裝,底層的網絡請求默認使用的Okhttp,本身只是簡化了用戶網絡請求的參數配置等,還能與Rxjava相結合,使用起來更加簡潔方便。
- App應用程序通過Retrofit請求網絡,實際上是使用Retrofit接口層封裝請求參數,之後由OkHttp完成後續的請求操作。
- 在服務端返回數據之後,OkHttp將原始的結果交給Retrofit,Retrofit根據用戶的需求對結果進行解析。
- 完成數據的轉化(converterFactory),適配(callAdapterFactory),通過設計模式進行各種擴展。
使用
@GET("/user/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
//call封裝了整個okhttp的請求
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverteractory.create())
//.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
//converterFactory
//後續交個okhttp
使用Retrofit的七步驟
- 添加Retrofit依賴,網絡權限
- 定義接收服務器返回數據的Bean
- 創建網絡請求的接口,使用註解(動態代理,核心)
- builder模式創建Retrofit實例,converter,calladapter...
- 創建接口實例,調用具體的網絡請求
- call同步/異步網絡請求
- 處理服務器返回的數據
Retrofit網絡通信八步驟
- 創建Retrofit實例
- 定義網絡請求接口,併爲接口中的方法添加註解
- 通過動態代理生成網絡請求對象
- 通過網絡請求適配器將網絡請求對象進行平臺適配
- 通過網絡請求執行器,發送網絡請求(call)
- 通過數據解析器解析數據
- 通過回調執行器,切換線程
- 用戶在主線程處理返回結果
代理
爲其他對象提供一種代理,用以控制對這個對象的訪問
靜態
-
動態
- 在程序運行時創建的代理方式
- 無侵入增強
- jdk動態代理 vs cglib
jdk動態代理
- 只能爲接口動態
- InvocationHandler必須要實現
- invoke的參數中獲取參數
- invoke的返回值返回給使用者
- newProxyInstance中傳入InvocationHandler
總結:
- 運行期
- InvocationHandler接口和Proxy類
- 動態代理與靜態代理的不同
源碼
- serviceMethonCache //緩存,網絡請求對象
- Factory callFactory //默認ok
- HttpUrl baseUrl
- List<Converter.Factory> converterFactories
- List<CallAdapter.Factory> callAdapterFactories
- Executor callbackExecutor //執行回調
- boolean validateEagerly //是否立即解析接口中方法
builder
- platform
單例獲取不同平臺
Android平臺中MainthreadExecutor
callAdapterFactory
通過calladapter將原始Call進行封裝,找到對應的執行器。如rxjavaCallFactory對應的Observable,轉換形式Call<T> --> Observable<T>
converterFactory
數據解析Converter,將response通過converterFactory轉換成對應的數據形式,GsonConverterFactory,FastJsonConverterFactory。
Retrofit
Retrofit核心類,對外提供接口。通過retrofit.create()創建retrofit實例,外觀模式。在create()方法中,使用動態代理模式對請求的接口中方法進行封裝(ServiceMethod),初始化OkhttpCall。
ServiceMethod
核心處理類,解析方法和註解,toRequest()方法中生成HttpRequest。創建responseConverter(將response流轉換爲String或實體),創建callAdapter
OkhttpCall
是對okhttp3.Call的封裝調用
自定義View
自定義View三種方式,組合現有控件,繼承現有控件,繼承View
本文只針對繼承View的方式,另兩種自行學習。
1. 重寫方法
onMeasure、 onLayout、onDraw、onTouchEvent
onMeasure
可能多次觸發,在measure的過程中注意MeasureSpec,specMode、specSize
講到LinearLayout、RelativeLayout源碼
MeasureSpec
MeasureSpec,specMode、specSize
- EXACTLY
表示父佈局希望子佈局的大小應該是由specSize的值來決定的,系統默認會按照這個規則來設置子佈局的大小,開發人員當然也可以按照自己的意願設置成任意的大小。
- AT_MOST
表示子佈局最多隻能是specSize中指定的大小,開發人員應該儘可能小得去設置這個佈局,並且保證不會超過specSize。系統默認會按照這個規則來設置子佈局的大小,開發人員當然也可以按照自己的意願設置成任意的大小。
- UNSPECIFIED
表示開發人員可以將佈局按照自己的意願設置成任意的大小,沒有任何限制。這種情況比較少見,不太會用到。
childParams/parentMode | EXACTLY | AT_MOST | UNSPECIFIED |
---|---|---|---|
dp/px | EXACTLY(childsize) | EXACTLY(childsize) | EXACTLY(childsize) |
match_parent | EXACTLY(parentsize) | AT_MOST(parentsize) | UNSPECIFIED(0) |
wrap_content | AT_MOST(parentsize) | AT_MOST(parentsize) | UNSPECIFIED(0) |
上圖表摘自https://blog.csdn.net/singwhatiwanna/article/details/38426471
onLayout
在ViewGroup中,只觸發一次,決定子View的位置
onDraw
繪製內容,Canvas.drawxxx(),paint
onTouchEvent
處理點擊事件
2. 自定義view與viewgroup的區別
- onDraw(Canvas canvas)
View類中用於重繪的方法,這個方法是所有View、ViewGroup及其派生類都具有的方法,也是Android UI繪製最重要的方法。開發者可重載該方法,並在重載的方法內部基於參數canvas繪製自己的各種圖形、圖像效果。
- onLayout()
重載該類可以在佈局發生改變時作定製處理,這在實現一些特效時非常有用。View中的onLayout不是必須重寫的,ViewGroup中的onLayout()是抽象的,自定義ViewGroup必須重寫。
- dispatchDraw()
ViewGroup類及其派生類具有的方法,控制子View繪製分發,重載該方法可改變子View的繪製,進而實現一些複雜的視效,典型的例子可參見Launcher模塊Workspace的dispatchDraw重載。
- drawChild()
ViewGroup類及其派生類具有的方法,直接控制繪製某局具體的子view,重載該方法可控制具體某個具體子View。
3. View方法執行過程
三次measure,兩次layout和一次draw
http://blog.csdn.net/u012422440/article/details/52972825
Android視圖樹的根節點是DecorView,而它是FrameLayout的子類,所以就會讓其子視圖繪製兩次,所以onMeasure函數會先被調用兩次。
- onResume(Activity)
- onPostResume(Activity)
- onAttachedToWindow(View)
- onMeasure(View)
- onMeasure(View)
- onLayout(View)
- onSizeChanged(View)
- onMeasure(View)
- onLayout(View)
- onDraw(View)
- dispatchDraw()
4. invalidate()、postInvalidate()、requestLayout()
invalidate()
/**
* Invalidate the whole view. If the view is visible,
* {@link #onDraw(android.graphics.Canvas)} will be called at some point in
* the future.
* <p>
* This must be called from a UI thread. To call from a non-UI thread, call
*/
public void invalidate() {
invalidate(true);
}
invalidate方法會執行draw過程,重繪View樹。
當改變view的顯隱性、背景、狀態(focus/enable)等,這些都屬於appearance範疇,都會引起invalidate操作。需要更新界面顯示,就可以直接調用invalidate方法。
注意:
View(非容器類)調用invalidate方法只會重繪自身,ViewGroup調用則會重繪整個View樹。
postInvalidate()
/**
* <p>Cause an invalidate to happen on a subsequent cycle through the event loop.
* Use this to invalidate the View from a non-UI thread.</p>
*
* <p>This method can be invoked from outside of the UI thread
* only when this View is attached to a window.</p>
*/
public void postInvalidate() {
postInvalidateDelayed(0);
}
在子線程中被調用,刷新UI。
requestLayout()
/**
* Call this when something has changed which has invalidated the
* layout of this view. This will schedule a layout pass of the view
* tree. This should not be called while the view hierarchy is currently in a layout
* pass ({@link #isInLayout()}. If layout is happening, the request may be honored at the
* end of the current layout pass (and then layout will run again) or after the current
* frame is drawn and the next layout occurs.
*
* <p>Subclasses which override this method should call the superclass method to
* handle possible request-during-layout errors correctly.</p>
*/
@CallSuper
public void requestLayout() {
}
當View的寬高,發生了變化,不再適合現在的區域,調用requestLayout方法重新對View佈局。
當View執行requestLayout方法,會向上遞歸到頂級父View中,再執行這個頂級父View的requestLayout,所以其他View的onMeasure,onLayout也可能會被調用。
面試大廠複習路線
多餘的話就不講了,接下來將分享面試的一個複習路線,如果你也在準備面試但是不知道怎麼高效複習,可以參考一下我的複習路線,有任何問題也歡迎一起互相交流,加油吧!
這裏給大家提供一個方向,進行體系化的學習:
1、看視頻進行系統學習
前幾年的Crud經歷,讓我明白自己真的算是菜雞中的戰鬥機,也正因爲Crud,導致自己技術比較零散,也不夠深入不夠系統,所以重新進行學習是很有必要的。我差的是系統知識,差的結構框架和思路,所以通過視頻來學習,效果更好,也更全面。關於視頻學習,個人可以推薦去B站進行學習,B站上有很多學習視頻,唯一的缺點就是免費的容易過時。
另外,我自己也珍藏了好幾套視頻,有需要的我也可以分享給你。
2、進行系統梳理知識,提升儲備
客戶端開發的知識點就那麼多,面試問來問去還是那麼點東西。所以面試沒有其他的訣竅,只看你對這些知識點準備的充分程度。so,出去面試時先看看自己複習到了哪個階段就好。
系統學習方向:
架構師築基必備技能:深入Java泛型+註解深入淺出+併發編程+數據傳輸與序列化+Java虛擬機原理+反射與類加載+動態代理+高效IO
Android高級UI與FrameWork源碼:高級UI晉升+Framework內核解析+Android組件內核+數據持久化
360°全方面性能調優:設計思想與代碼質量優化+程序性能優化+開發效率優化
解讀開源框架設計思想:熱修復設計+插件化框架解讀+組件化框架設計+圖片加載框架+網絡訪問框架設計+RXJava響應式編程框架設計+IOC架構設計+Android架構組件Jetpack
NDK模塊開發:NDK基礎知識體系+底層圖片處理+音視頻開發
微信小程序:小程序介紹+UI開發+API操作+微信對接
Hybrid 開發與Flutter:Html5項目實戰+Flutter進階
知識梳理完之後,就需要進行查漏補缺,所以針對這些知識點,我手頭上也準備了不少的電子書和筆記,這些筆記將各個知識點進行了完美的總結。
3、讀源碼,看實戰筆記,學習大神思路
“編程語言是程序員的表達的方式,而架構是程序員對世界的認知”。所以,程序員要想快速認知並學習架構,讀源碼是必不可少的。閱讀源碼,是解決問題 + 理解事物,更重要的:看到源碼背後的想法;程序員說:讀萬行源碼,行萬種實踐。
主要內含微信 MMKV 源碼、AsyncTask 源碼、Volley 源碼、Retrofit源碼、OkHttp 源碼等等。
4、面試前夕,刷題衝刺
面試的前一週時間內,就可以開始刷題衝刺了。請記住,刷題的時候,技術的優先,算法的看些基本的,比如排序等即可,而智力題,除非是校招,否則一般不怎麼會問。
關於面試刷題,我個人也準備了一套系統的面試題,幫助你舉一反三:
總結
改變人生,沒有什麼捷徑可言,這條路需要自己親自去走一走,只有深入思考,不斷反思總結,保持學習的熱情,一步一步構建自己完整的知識體系,纔是最終的制勝之道,也是程序員應該承擔的使命。
以上內容均免費分享給大家,需要完整版的朋友,點這裏可以看到全部內容。或者關注主頁掃描加 微信 獲取。