1. 程式人生 > >Android面試題《思考與解答》11月刊

Android面試題《思考與解答》11月刊

又來更新啦,Android面試題《思考與解答》11月刊奉上。 ## 說說View/ViewGroup的繪製流程 View的繪製流程是從`ViewRoot`的`performTraversals`開始的,它經過`measure,layout,draw`三個過程最終將View繪製出來。 performTraversals會依次呼叫`performMeasure,performLayout,performDraw`三個方法,他們會依次呼叫`measure,layout,draw`方法,然後又呼叫了`onMeasure,onLayout,dispatchDraw`。 * measure : 對於自定義的單一view的測量,只需要根據父 view 傳遞的` MeasureSpec `進行計算大小。 對於ViewGroup的測量,一般要重寫`onMeasure`方法,在onMeasure方法中,父容器會對所有的子View進行`Measure`,子元素又會作為父容器,重複對它自己的子元素進行Measure,這樣`Measure`過程就從DecorView一級一級傳遞下去了,也就是要遍歷所有子View的的尺寸,最終得出出總的viewGroup的尺寸。Layout和Draw方法也是如此。 * layout :根據 `measure` 子 View 所得到的佈局大小和佈局引數,將子View放在合適的位置上。 對於自定義的單一view,計算本身的位置即可。 對於ViewGroup來說,需要重寫`onlayout`方法。除了計算自己View的位置,還需要確定每一個子View在父容器的位置以及子view的寬高(getMeasuredWidth和getMeasuredHeight),最後呼叫所有子view的`layout`方法來設定子view的位置。 * draw :把 View 物件繪製到螢幕上。 draw()會依次呼叫四個方法: 1)`drawBackground()`,根據在 layout 過程中獲取的 View 的位置引數,來設定背景的邊界。 2)`onDraw()`,繪製View本身的內容,一般自定義單一view會重寫這個方法,實現一些繪製邏輯。 3) `dispatchDraw()`,繪製子View 4)` onDrawScrollBars(canvas)`,繪製裝飾,如 滾動指示器、滾動條、和前景 ## 說說你理解的MeasureSpec `MeasureSpec`是由父View的MeasureSpec和子View的`LayoutParams`通過簡單的計算得出一個針對子View的測量要求,這個測量要求就是MeasureSpec。 * 首先,`MeasureSpec`是一個大小跟模式的組合值,MeasureSpec中的值是一個整型(32位)將size和mode打包成一個Int型,其中高兩位是mode,後面30位存的是size ```java // 獲取測量模式 int specMode = MeasureSpec.getMode(measureSpec) // 獲取測量大小 int specSize = MeasureSpec.getSize(measureSpec) // 通過Mode 和 Size 生成新的SpecMode int measureSpec=MeasureSpec.makeMeasureSpec(size, mode); ``` * 其次,每個子View的`MeasureSpec`值根據子View的佈局引數和父容器的MeasureSpec值計算得來的,所以就有一個父佈局測量模式,子檢視佈局引數,以及子view本身的`MeasureSpec`關係圖: ![](https://upload-images.jianshu.io/upload_images/944365-76261325e6576361.png?imageMogr2/auto-orient/strip|imageView2/2/w/751/format/webp) 其實也就是`getChildMeasureSpec`方法的原始碼邏輯,會根據子View的佈局引數和父容器的MeasureSpec計算出來單個子view的MeasureSpec。 * 最後是實際應用時: 對於自定義的單一view,一般可以不處理`onMeasure`方法,如果要對寬高進行自定義,就重寫onMeasure方法,並將算好的寬高通過`setMeasuredDimension`方法傳進去。 對於自定義的ViewGroup,一般需要重寫`onMeasure`方法,並且呼叫`measureChildren`方法遍歷所有子View並進行測量(measureChild方法是測量具體某一個view的寬高),然後可以通過`getMeasuredWidth/getMeasuredHeight`獲取寬高,最後通過setMeasuredDimension方法儲存本身的總寬高。 ## Scroller是怎麼實現View的彈性滑動? * 在`MotionEvent.ACTION_UP`事件觸發時呼叫startScroll()方法,該方法並沒有進行實際的滑動操作,而是記錄滑動相關量(滑動距離、滑動時間) * 接著呼叫`invalidate/postInvalidate()`方法,請求View重繪,導致View.draw方法被執行 * 當View重繪後會在draw方法中呼叫`computeScroll`方法,而computeScroll又會去向Scroller獲取當前的scrollX和scrollY;然後通過scrollTo方法實現滑動;接著又呼叫`postInvalidate`方法來進行第二次重繪,和之前流程一樣,如此反覆導致View不斷進行小幅度的滑動,而多次的小幅度滑動就組成了彈性滑動,直到整個滑動過成結束。 ```java mScroller = new Scroller(context); @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_UP: // 滾動開始時X的座標,滾動開始時Y的座標,橫向滾動的距離,縱向滾動的距離 mScroller.startScroll(getScrollX(), 0, dx, 0); invalidate(); break; } return super.onTouchEvent(event); } @Override public void computeScroll() { // 重寫computeScroll()方法,並在其內部完成平滑滾動的邏輯 if (mScroller.computeScrollOffset()) { scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); invalidate(); } } ``` ## OKHttp有哪些攔截器,分別起什麼作用 `OKHTTP`的攔截器是把所有的攔截器放到一個list裡,然後每次依次執行攔截器,並且在每個攔截器分成三部分: * 預處理攔截器內容 * 通過`proceed`方法把請求交給下一個攔截器 * 下一個攔截器處理完成並返回,後續處理工作。 這樣依次下去就形成了一個鏈式呼叫,看看原始碼,具體有哪些攔截器: ```java Response getResponseWithInterceptorChain() throws IOException { // Build a full stack of interceptors.