程式設計師:我想換工作,讓我琢磨琢磨這幾個值得深入思考的面試問答
馬上就要年末啦,大家包袱款款回家過個熱鬧年,拿完年終獎,又到了跳槽的好機會
俗話說,機會總是給有準備的人,現在就可以看看面試題了。
這裡給大家不定期更新大廠面試真題,今天來分享一下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.
最後
上面幾個題都值得深入思考,大家可以關注我,轉發收藏文章
面試合集可以看這裡:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69952849/viewspace-2668157/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 我是設計師面試官,你有什麼想問我的?面試
- 我這個程式設計師 (轉)程式設計師
- 10個我最喜歡問程式設計師的面試問題程式設計師面試
- 程式設計師面試,我最喜歡的10個問題程式設計師面試
- shellcode之小小琢磨
- 程式設計師面試:電話面試問答Top 50程式設計師面試
- 結婚養幾個孩子是如何讓我成為一個更好的程式設計師的程式設計師
- 我這幾年程式設計師生涯的一點體會(轉)程式設計師
- iOS程式設計師面試要注意的幾個問題~iOS程式設計師面試
- 為什麼我們程式設計師工作得這麼累?程式設計師
- 自學程式設計的朋友,我想給你們這 5 個建議程式設計
- 讓我們成為更好的程式設計師程式設計師
- 我的丈夫是個程式設計師程式設計師
- 我不是個內向的程式設計師 我只是很忙程式設計師
- 我不是個內向的程式設計師,我只是很忙程式設計師
- 當一個Web前端程式設計師想改我的後臺程式碼時程式設計師的樣子Web前端程式設計師
- 我做這個程式設計師還有意思嗎?程式設計師
- 程式設計師 我是這麼定義的程式設計師
- 我們公司給新人的README,值得每個程式設計師一讀程式設計師
- 設計模式這話題,我面試又被問了設計模式面試
- 面試了一個 31 歲程式設計師,讓我有所觸動,30歲以上的程式設計師該何去何從?面試程式設計師
- 我為我是個程式設計師而驕傲程式設計師
- (轉)我在北京工作這幾年 – 一個軟體工程師的反省軟體工程工程師
- 我是程式設計師,我自豪程式設計師
- 三個月不工作,我才轉行做了程式設計師程式設計師
- 我喜歡程式設計師這份差事!程式設計師
- 面試了一個 39 歲程式設計師,我有點慌……面試程式設計師
- 我招了個“水貨”程式設計師程式設計師
- 我是一個iOS程式設計師iOS程式設計師
- 我就差一個程式設計師了!程式設計師
- 我是一個垃圾程式設計師程式設計師
- 我是一個混蛋程式設計師程式設計師
- 我的程式設計師之路程式設計師
- 我,35歲程式設計師,沒想到今年找工作這麼難程式設計師
- 趣圖:程式設計師值得貼一個這樣的程式設計師
- 面試Java後端開發之後想和Java程式設計師談談我的感受面試Java後端程式設計師
- 我瞭解的那些大師級程式設計師,都在用這些工作法【分享】程式設計師
- 我是程式設計師,我用這種方式銘記歷史程式設計師