程式設計師:我想換工作,讓我琢磨琢磨這幾個值得深入思考的面試問答
馬上就要年末啦,大家包袱款款回家過個熱鬧年,拿完年終獎,又到了跳槽的好機會
俗話說,機會總是給有準備的人,現在就可以看看面試題了。
這裡給大家不定期更新大廠面試真題,今天來分享一下Android面試中幾個值得我們深入思考的面試題還有解析,希望可以幫助到即將面試的小夥伴們,祝面試順利~
文末還有大廠面試專題資料包免費分享~
接下來是正文:
1. 事件分發機制大家應該都熟記於心,預設事件分發是逆序的,有哪些方法可以修改分發順序?
記得曾經有位朋友做貼紙應用時,有RT 的需求。
預設事件分發為逆序,遍歷子 View 為 (childCount ~ 0 ],有哪些方式可以修改這一策略,比如修改遍歷方式為[0,childCount)?
修改事件分發順序的話,在日常開發中基本遇不到,因為現在的逆序遍歷,是跟View的層級顯示相匹配的,隨便更改反而不太合理。
如果非要修改這個順序,很多同學首先會想到:
重寫dispatchTouchEvent方法,然後在裡面一個for迴圈,從0開始一個個呼叫子View的dispatchTouchEvent。
這個方法,不是說絕對不行,只是你要做的事情很多,就比如 觸控座標的轉換:
我們都知道,ViewGroup在分派事件的時候,會 檢查子View是否應用過屬性動畫的(位移、縮放、旋轉等),如果有的話還要把座標給對映回去 。
接著,還會把相對於這個ViewGroup本身的觸控座標 轉換成 相對於對應子View的觸控座標。
這樣說可能有點繞,舉個例子:
比如:當手指在螢幕中按下,ViewGroup中收到的event座標(getX,getY)假設是【500,500】,剛好在這個位置上有個子View,那接下來肯定會把事件傳給這個子View的dispatchTouchEvent,這時候如果座標不轉換直接傳的話,那子View收到的event座標(getX,getY)也是【500,500】,這明顯是不對的,正確的座標應該要分別減去它的left和top。
這看起來好像沒什麼大的影響,但如果你的子View沒有重寫onTouchEvent方法的話(比如子View是常用的ImageView,TextView之類的),你的OnClickListener就會無效了,因為預設的onTouchEvent在處理ACTION_MOVE的時候,會檢查event的座標是否已經脫離了View的邊界範圍,如果在邊界範圍之外的話,pressed將會失效(認為沒有被按下),當ACTION_UP時,如果pressed為false,就不會執行PerformClick。
那難道沒有方法可以完美地做到了嗎?
在ViewGroup的dispatchTouchEvent方法中,雖然它是逆序的for,但是呢,它把子View拿出來的時候,卻不是直接操作的mChildren陣列,而是通過一個getAndVerifyPreorderedView方法來獲得,這個方法會把當前索引傳進去,還有一個preorderedList。
@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) { // ... final ArrayList<View> preorderedList = buildTouchDispatchChildList(); final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); final View[] children = mChildren; for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = getAndVerifyPreorderedIndex( childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView( preorderedList, children, childIndex); ... }
如果傳進去的preorderedList不為空,那麼就會直接從它裡面去取。
preorderedList怎麼來?
通過呼叫buildOrderedChildList方法獲取的。
buildOrderedChildList方法是怎麼樣的?
ArrayList<View> buildOrderedChildList() { final int childrenCount = mChildrenCount; if (childrenCount <= 1 || !hasChildWithZ()) return null; if (mPreSortedChildren == null) { mPreSortedChildren = new ArrayList<>(childrenCount); } else { // callers should clear, so clear shouldn't be necessary, but for safety... mPreSortedChildren.clear(); mPreSortedChildren.ensureCapacity(childrenCount); } final boolean customOrder = isChildrenDrawingOrderEnabled(); for (int i = 0; i < childrenCount; i++) { // add next child (in child order) to end of list final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder); final View nextChild = mChildren[childIndex]; final float currentZ = nextChild.getZ(); // insert ahead of any Views with greater Z int insertIndex = i; while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) { insertIndex--; } mPreSortedChildren.add(insertIndex, nextChild); } return mPreSortedChildren; }
它裡面是通過一個getAndVerifyPreorderedIndex方法來獲取對應的子VIew索引,這個方法要傳進去一個叫customOrder的boolean。
這個customOrder,看名字可以知道,是自定義順序的意思,如果它為true的話,接著會通過getChildDrawingOrder(int childCount, int i)方法來獲取對應的索引,而且,這個方法是protected的,所以我們可以通過重寫這個方法並根據引數"i"來決定返回哪一個View所對應的索引,從而改變分發的順序。
protected int getChildDrawingOrder(int childCount, int i) { return i; }
那這個customOrder,什麼時候為true呢?
在buildOrderedChildList方法裡可以看到這麼一句:
final boolean customOrder = isChildrenDrawingOrderEnabled();
emmmm,也就是說,如果要自定義這個順序的話,還需要呼叫setChildrenDrawingOrderEnabled(true)來開啟。
重新捋一捋流程:
1. setChildrenDrawingOrderEnabled(true)來開啟自定義順序;
2. 重寫getChildDrawingOrder方法來決定什麼時候要返回哪個子View;
2. AppCompatTextView 與 TextView 有什麼區別?
1. compat庫是如何將TextView替換為AppCompatTextVew的?
2. 為什麼要進行替換?
3. 根據替換相關原理,我們可以做哪些事情?
先從第二問開始吧:
AppCompatTextView繼承自TextView,是對TextView的一種擴充套件,因為在5.0中首次推出了MaterialDesign這種設計風格。
但是眾所周知的,5.0推出不可能所有的裝置全都一下子更新到最新版本,為了在早期版本上實現新的功能(這些新功能比如從原始碼註釋中解讀到比如backgroundTint屬性,根據文字內容自適應大小等).
即為了新特性同樣可以相容老版本,framework在建立TextView例項的時候,自動幫我們進行了替換。
其它的AppCompatXXX與XXX的關係也是如此。
第一問:
然後第一問,如何完成替換的,我們這裡只拿最直觀的流程舉例,且儘可能的簡化原始碼過程,在討論這個問題之前,先了解幾個預備知識:
View是怎麼被解析建立出來的:
1.LayoutInflater:將佈局XML檔案例項化為其對應的View物件,我們在Activity中通過setContentView傳入一個Layout的資原始檔id,最終該方法最終會呼叫到PhoneWindow的setContentView方法,這個方法裡面有呼叫到
mLayoutInflater.inflate(layoutResID, mContentParent);
2.inflate方法,該方法的作用是將指定的XML檔案填充到View的層次結構中去,最終無論通過什麼途徑呼叫到inflate方法,都會走到三個引數的過載方法這裡:
return inflate(parser, root, attachToRoot);
parser你可以認為持有將Layout.XML解析後的資料。 後兩個引數的意義如下:
1. root為null,attchToRoot無意義,inflate返回的是當前XML對應的根佈局。
2. root不為null且attachToRoot為true,則整個XML對應的佈局就設定了根佈局是root。
3. root不為null且attachToRoot為false,則會將root的layoutParames設定給當前XML的佈局。
知道了LayoutInflate.inflate做了什麼,再往下,inflate中會呼叫createViewFromTag,從方法名就能知道,繼續往下走,我們離答案越來越近了。
createViewFromTag做的事情非常有意思:
先看到787行這個if-else,條件是name中有沒有"."字元,如果有我們會執行onCreateView,如果沒有會執行createView。
name啥時候有點?
自定義控制元件的時候。
當是系統控制元件的時候,createView會有一個填充了第二個引數的呼叫:
createView(name, "android.view.", attrs);補上了View控制元件的全路徑名,而自定義控制元件則不需要,因為傳入的name就是一個全路徑名。
為什麼要全路徑名?
因為View控制元件物件的建立是通過反射來實現的:
clazz = mContext.getClassLoader().loadClass( prefix != null ? (prefix + name) : name).asSubclass(View.class); ... constructor = clazz.getConstructor(mConstructorSignature); constructor.setAccessible(true); sConstructorMap.put(name, constructor);// ...args[1] = attrs;final View view = constructor.newInstance(args);
下面對這幾步做一個總結:
XML中儲存了ViewTree的結構和View的相關標籤資訊(包括View的型別和一些屬性值),然後這些資訊會在後面通過反射的方式(如果沒有Factory2和Factory的話)建立例項物件,如果建立的是ViewGroup,則會對它的子View遍歷重複建立步驟,建立完View物件後,會add到對應的ViewGroup中。
其中相關方法的呼叫流程是:
inflate->rInflate->createViewFromTag->createView。
好像還是沒有看到替換?
還是上一張圖,我們只解釋了後半部分,沒有解釋前半部分, 那麼什麼是Factory?
繼續往下看:
createViewFromTag中會先判斷有沒有 Factory 或者 Factory2 的物件,如果有,則呼叫Factory的onCreateView方法。
這兩個類都是介面,其中Factory2是Factory的子介面,都只有唯一一個onCreateView方法。
不同之處在於Factory2的onCreateView方法傳入了parentView。
該方法的作用就是你可以藉助它來改造XML中已經存在了的Tag的值。所以Factory2可以達到改造parentView的目的。
但是我們在日常中根本就沒有任何地方接觸到了Factory(2)呀,那麼它是不是就直接是null呢?
到這裡又是一番原始碼調來調去,為了便於理解,只需要知道,這個東西(Factory2),在最開始AppCompatActivity(為了相容低版本,我們現在Activity預設都是繼承自它)中的onCreate方法中就已經通過層層呼叫被設定好了。
既然現在Factory2不為空,那麼就應該去走它的onCreateView方法了,這裡又是層層呼叫,最終來到了 AppCompatViewInflater**** 的 createView 方法:
答案就在這裡:
如果建立的是非相容控制元件(系統控制元件那麼多,實現相容的只是常用的一些控制元件),那麼就會是143行,在146中通過反射建立View物件。
囉裡囉唆扯了一大堆,還是沒回答第一個問題:
compat庫是如何將TextView替換為AppCompatTextVew的?
個人對這個的理解:在將XML檔案解析成包含ViewTree資訊之後,開始利用這些資訊去建立每一個View節點,在建立View物件的時候,如果發現這個節點是屬於支援相容的控制元件比如TextView,那麼就會去呼叫到new AppCompatTextView()來建立一個相容的View物件,也就是在建立的時候,及已經實現了替換。
第三問:
根據替換相關原理,我們可以做哪些事情?
整個替換從圖一所示的原始碼中可以看到,能夠被替換的關鍵是Factory(2)存在,那麼我覺得,其實問題問的是Factory(2)可以用來做什麼吧?
那麼這個時候,就適合去問站長大人了:
- 探究 LayoutInflater setFactory
3. getWidth, getMeasuredWidth 有什麼區別?
getWidth和getMeasuredWidth的區別:
getMeasuredWidth方法返回的是測量後的寬度,這個寬度是當setMeasuredDimension方法( measure方法最終會呼叫setMeasuredDimension)被呼叫後重新整理的 。
而getWidth返回的是最終layout出來的寬度,在View程式碼中返回的是【mRight - mLeft】,這個mRight和mLeft,是在setFrame方法被呼叫後賦值的 (layout方法最終會呼叫setFrame )。
也就是說, getMeasuredWidth返回值的大小,取決於setMeasuredDimension,而getWidth,則取決於layout。
傳說中一個是 View 寬度,一個是 View 中的內容寬度,這個解答對嗎?
在常規的View中,比如TextView,ImageView這些,如果沒有明確指定寬度的話,那麼他們的getMeasuredWidth返回的寬度,確實就是實際內容的寬度。
但如果在xml佈局裡或自定義View中故意把寬度設定的很大,或者很小,比如設定寬度為9999999,這種情況就不算了。
所以我的回答是:如果這個View和它所在的ViewGroup(在ViewGroup中的onMeasure也可做手腳),都遵守規矩的話,那麼這句話就是對的。
4.butterknife 中的黑科技
很多時候大家在剖析butterknife原始碼的時候,更多的是講解其中的apt等,在library中使用buttterknife的時候,會使用R2.id.xxx
class ExampleActivity extends Activity { @BindView(R2.id.user) EditText username; @BindView(R2.id.pass) EditText password; ... }
而非R.id.xxx.
最後
上面幾個題都值得深入思考,大家可以關注我,轉發收藏文章
面試合集可以看這裡: https://zhuanlan.zhihu.com/p/96805654
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69952849/viewspace-2668157/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 設計模式這話題,我面試又被問了設計模式面試
- 面試了一個 39 歲程式設計師,我有點慌……面試程式設計師
- 面試了一個 31 歲程式設計師,讓我有所觸動,30歲以上的程式設計師該何去何從?面試程式設計師
- 我們公司給新人的README,值得每個程式設計師一讀程式設計師
- 答 《部落格作者呀,我想採訪你這 9 個問題!》 問卷
- 程式設計師體驗——我在 RightCapital 的工作程式設計師API
- 面試了一個 39 歲程式設計師後,我被罵了……面試程式設計師
- 面試Java後端開發之後想和Java程式設計師談談我的感受面試Java後端程式設計師
- 我,35歲程式設計師,沒想到今年找工作這麼難程式設計師
- 我為我是個程式設計師而驕傲程式設計師
- 面試時這麼問你Spring Boot,你能答對幾個?面試Spring Boot
- 我招了個“水貨”程式設計師程式設計師
- 我的程式設計師之路程式設計師
- 自學程式設計的朋友,我想給你們這 5 個建議程式設計
- 我從程式設計面試中學到的程式設計面試
- 為什麼想來我們公司工作?- 面試常見問題解析面試
- 我瞭解的那些大師級程式設計師,都在用這些工作法【分享】程式設計師
- 【譯】我是一個平庸的程式設計師程式設計師
- 面試官:你還有什麼想問我的?面試
- 幾次面試後,我的一些思考和總結面試
- 程式設計師想獨立賺錢的幾個注意點程式設計師
- 我是程式設計師,我用這種方式銘記歷史程式設計師
- 我對程式設計師35歲這道坎的看法程式設計師
- 如何讓設定更“值錢”?或許該思考這幾個問題
- 我不是一個成功的人,但是我想做一個優秀的程式設計師程式設計師
- 程式設計師面試時遇到的高深問題與入職後的工作程式設計師面試
- 程式設計師小白的個人思考程式設計師
- 【程式設計師面試系列】手把手教你如何面試,你要的我都有(技術篇)程式設計師面試
- 80個讓你笑爆肚皮的程式設計師段子,不好笑算我輸!程式設計師
- 笑了,面試官問我知不知道非同步程式設計的Future。面試非同步程式設計
- 你好,我是程式設計師程式設計師
- 面試官“你的期望薪資是多少?”聰明的程式設計師都是這樣答的!面試程式設計師
- 找工作時,我們應該思考的幾件事情。
- 一個兩年的程式設計師,面5家斬獲點我達,網易offer的面試總結程式設計師面試
- 測試問題思考,有些問題我自己有答案,但是我想聽聽大家的見解,謝謝!
- 程式設計師想月薪過萬?這些面試準備你做好了嗎?程式設計師面試
- 面試官問我:建立執行緒有幾種方式?我笑了面試執行緒
- 答面試官問:如何設計API介面面試API