Android面試題《思考與解答》11月刊
阿新 • • 發佈:2020-12-02
又來更新啦,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.