Android面試必問:事件分發機制你肯定得懂! 總結 結尾

金三銀四,是時候該複習一波了~

文章中出現的源碼均基於8.0

前言

事件分發機制不僅僅是核心知識點更是難點,並且還是View的一大難題滑動衝突解決方法的理論基礎,因此掌握好View的事件分發機制是十分重要的。

一、基本認識

1. 事件分發的對象

事件分發的對象是點擊事件(Touch事件),而當用戶觸摸屏幕時,將產生點擊事件。

事件類型分爲四種,如下所示:

類型 說明
MotionEvent.ACTION_DOWN 手指剛接觸屏幕,一般爲事件的開始
MotionEvent.ACTION_MOVE 手指在屏幕移動,在移動的過程中會產生多個move事件
MotionEvent.ACTION_UP 手指從屏幕上鬆開的一瞬間
MotionEvent.ACTION_CANCEL 結束事件,非人爲原因

同一個事件序列:指從手指剛接觸屏幕,到手指離開屏幕的那一刻結束,在這一過程產生的一系列事件,這個序列一般以down事件開始,中間含有多個move事件,最終以up事件結束

2. 事件分發的本質

事件分發的本質,其實就是將點擊事件(MotionEvent)傳遞到某個具體的View處理的整個過程

3. 事件分發的順序

事件傳遞的順序:Activity->Window->DecorView->ViewGroup->View。一個點擊事件發生後,總是先傳遞給當前的Activity,然後通過Window傳給DecorView再傳給ViewGroup,最終傳到View。

在《開發藝術探索》中的事件分發的順序是:Activity->Window->View,而有的博客上的順序是:Activity->ViewGroup->View,不過其實兩者是一樣的(下列會從源碼進行分析)。Window是抽象類,其唯一實現類爲PhoneWindow,PhoneWindow將事件直接傳遞給DecorView,而DecorView繼承FrameLayout,FrameLayout又是ViewGroup的子類,所以兜兜轉轉最後也可以認爲Window事件分發的實現其實是ViewGroup來實現的。所以也可以認爲事件傳遞的順序是:Activity->ViewGroup->View。

二、核心方法

View的事件分發機制主要由事件分發->事件攔截->事件處理三步來進行邏輯控制,很巧的這三步剛好對應了三個核心方法

1. 事件分發:dispatchTouchEvent

用來進行事件的分發,如果事件能夠傳遞給當前View,則該方法一定會被調用。返回結果受當前View的onTouchEvent和下級的dispatchTouchEvent的影響,表示是否消耗當前事件。

原型:public boolean dispatchTouchEvent(MotionEvent ev)

return:

  • ture:當前View消耗所有事件
  • false:停止分發,交由上層控件的onTouchEvent方法進行消費,如果本層控件是Activity,則事件將被系統消費,處理

2. 事件攔截:onInterceptTouchEvent

需注意的是在Activity,ViewGroup,View中只有ViewGroup有這個方法。故一旦有點擊事件傳遞給View,則View的onTouchEvent方法就會被調用

在dispatchTouch Event內部使用,用來判斷是否攔截事件。如果當前View攔截了某個事件,那麼該事件序列的其它方法也由當前View處理,故該方法不會被再次調用,因爲已經無須詢問它是否要攔截該事件。

原型:public boolean onInterceptTouchEvent(MotionEvent ev)

return:

  • ture:對事件攔截,交給本層的onTouchEvent進行處理
  • false:不攔截,分發到子View,由子View的dispatchTouchEvent進行處理
  • super.onInterceptTouchEvent(ev):默認不攔截

3. 事件處理:onTouchEvent

在dispatchTouchEvent中調用,用來處理點擊事件,返回結果表示是否消耗當前事件,如果不消耗,則在同一事件序列中,當前View無法再接受到剩下的事件,並且事件將重新交給它的父元素處理,即父元素的onTouchEvent會被調用

原型:public boolean onTouchEvent(MotionEvent ev)

return:

  • true:表示onTouchEvent處理後消耗了當前事件
  • false:不響應事件,不斷的傳遞給上層的onTouchEvent方法處理,直到某個View的onTouchEvent返回true,則認爲該事件被消費,如果到最頂層View還是返回false,則該事件不消費,將交由Activity的onTouchEvent處理。
  • super.onTouchEvent(ev):默認消耗當前事件,與返回true一致。

三、事件分發機制

在分析事件分發機制時,應該從事件分發的順序入手一步一步解剖。從上文我們知道事件分發順序爲:Activity->Window->DecorView->ViewGroup->View。由於Window與DecorView可以看作是Activity->ViewGroup的過程,故這裏將從三部分通過源碼來分析事件分發機制:

  • Activity對點擊事件的分發機制
  • ViewGroup對點擊事件的分發機制
  • View對點擊事件的分發機制

1. Activity事件的分發機制

我們知道,當一個點擊事件發生時,事件總是最先傳遞到當前Activity中,由Activity的dispatchTouchEvent來進行事件分發。而Activity會將事件傳遞給Window對象來分發,Window對象再傳遞給DecorView。下面將進行源碼分析來驗證這個過程:

源碼:Activity#dispatchTouchEvent

    public boolean dispatchTouchEvent(MotionEvent ev) {
        //點擊事件的開始一般爲按下事件,所以總是true
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();

        }
        //如果Activity所屬Window的dispatchTouchEvent返回了ture
        //則Activity.dispatchTouchEvent返回ture,點擊事件停止往下傳遞
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        //如果Window的dispatchTouchEvent返回了false,則點擊事件傳遞給Activity.onTouchEvent
        return onTouchEvent(ev);
    }

上面代碼爲Activity中的dispatchTouchEvent的源碼,通過源碼我們可以知道當點擊事件發生時,首先會執行onUserInteraction();這個方法又是什麼呢?不急,我們跟蹤下去。

    //空方法,當該Activity在棧頂時,觸屏點擊home,back,menu會觸發此方法
    public void onUserInteraction() {
    }

從源碼中可以看出在Activity中該方法爲空方法,當該Activity在棧頂時,觸屏點擊home,back,menu會觸發此方法,所以這個方法可以實現屏保功能。讓我們回到Activity中的dispatchTouchEvent方法中,接着調用了getWindow().superDispatchTouchEvent(ev)方法將事件交給Activity所附屬的Window進行分發,如果最終事件被消耗了,則返回true,如果事件沒人處理,則Activity調用在自己的onTouchEvent()方法來處理事件。

getWindow是一個Window對象,在Window源碼中我們可以發現其實Window就是一個抽象類,顯而易見其方法自然是抽象方法,所以我們必須找出其具體實現類。

源碼:Window#superDispatchTouchEvent

/**
 * Abstract base class for a top-level window look and behavior policy.  An
 * instance of this class should be used as the top-level view added to the
 * window manager. It provides standard UI policies such as a background, title
 * area, default key processing, etc.
 *
 * <p>The only existing implementation of this abstract class is
 * android.view.PhoneWindow, which you should instantiate when needing a
 * Window.
 */
public abstract class Window {
    ...
     //抽象方法
    public abstract boolean superDispatchTouchEvent(MotionEvent event);
    ...
}

從源碼中我們可以發現在Window類前面的註釋中是有解釋的,這時候就要考驗偶們的英語能力遼!其實總體上來說還是挺容易理解的,其實我們只要關注後面一部分的註釋就行。

The only existing implementation of this abstract class isandroid.view.PhoneWindow, which you should instantiate when needing a Window.

從這裏我們可以知道它的唯一實現類就是PhoneWindow,廢話不多說,我們直接看看PhoneWindow中superDispatchTouchEvent方法的實現是如何的呢?

源碼:PhoneWindow#superDispatchTouchEvent

   private DecorView mDecor;
   @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

從源碼中可以發現PhoneWindow將事件直接傳遞給了DecorView,而這個DecorView又是何方聖神呢?如下所示

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {    
    ......
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }
    ......
}

@RemoteView
public class FrameLayout extends ViewGroup {
    ......
}

其實DecorView就是我們通過setContentView設置佈局的父容器,我們可以通過getWindow().getDecorView().findViewById(android.R.id.content).getChildAt(0)這個方式就能獲取到setContentView中設置的佈局。從DecorView的源碼中可以發現DecorView是繼承FrameLayout,而FrameLayout又是繼承ViewGroup的,故DecorView的間接父類爲ViewGroup,在DecorView中superDispatchTouchEvent方法是使用super來調用父類的dispatchTouchEvent,故等於調用ViewGroup的dispatchTouchEvent方法(從源碼中我們可以得知FrameLayout並沒有dispatchTouchEvent這個方法),於是DecorView將事件傳遞到了ViewGroup去處理。也可以這麼說,事件已經傳遞到了頂級View也就是Activity中通過setContentView所設置的View(頂級View通常爲ViewGroup)。

流程圖如下:

到這裏,我們也驗證了前面提到的事件分發的順序是:Activity->Window->DecorView->ViewGroup。那麼ViewGroup又是如何將事件傳遞給View呢?讓我們來繼續分析!

2. ViewGroup事件的分發機制

從上面Activity事件的分發機制我們可以知道,ViewGroup事件分發機制是從dispatchTouchEvent()開始的,所以我們從這部分的源碼開始分析,由於該方法代碼量很多,下面將根據需要貼出相關代碼:

源碼:ViewGroup#dispatchTouchEvent

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
            ........
            // Check for interception.
            final boolean intercepted;  //是否攔截
            /*
            * 當事件由ViewGroup的子元素處理時,mFirstTouchTarget會被賦值並指向子元素
            */
            if (actionMasked == MotionEvent.ACTION_DOWN 
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                intercepted = true;
            }
    }

從上面源碼我們可以知道,ViewGroup判斷是否要攔截只會是在ACTION_DOWN的時候,或者是mFirstTouchTarget != null。mFirstTouchTarget 從後面的代碼才能知道其作用。它的作用就是:當事件被ViewGroup的某個子View處理時,mFirstTouchTarget 就會指向這個子View。所以當事件被這個ViewGroup攔截時,子類就不會處理這個事件,因此mFirstTouchTarget =null,那麼這個時候ACTION_MOVE和ACTION_UP事件到來時,由於判斷條件爲false,將導致ViewGroup的onInterceptTouchEvent不會再被調用,然後intercepted被賦予true,所以同一事件序列的其它事件都會默認交給該ViewGroup來處理。在上面源碼中我們還可以發現這麼一句語句:

final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;

FLAG_DISALLOW_INTERCEPT是個標記位,這個標記位是通過 requestDisallowInterceptTouchEvent(boolean disallowIntercept)方法來設置的,一般用在子View中。如果FLAG_DISALLOW_INTERCEPT被設置後,ViewGroup將無法攔截除了ACTION_DOWN以外的其它點擊事件,這是因爲ViewGroup在分發事件中,如果是ACTION_DOWN事件,將會重置FLAG_DISALLOW_INTERCEPT這個標記位。讓我們來看看源碼。

            if (actionMasked == MotionEvent.ACTION_DOWN) {
                // Throw away all previous state when starting a new touch gesture.
                // The framework may have dropped the up or cancel event for the previous gesture
                // due to an app switch, ANR, or some other state change.
                //
                cancelAndClearTouchTargets(ev);
                resetTouchState();  //對FLAG_DISALLOW_INTERCWPT進行重置

            }

上面源碼是在判斷是否攔截的前面的,所以能夠重置標記位,從這裏我們也可以發現,當點擊事件爲ACTION_DOWN時,ViewGroup總是會調用自己的onInterceptTouchEvent來詢問自己是否要攔截事件。

requestDisallowInterceptTouchEvent方法針對的是ACTION_DOWN以外的其他事件,並且是在不攔截ACTION_DOWN事件的情況下才會起作用。

接下來讓我們瞧瞧ViewGroup不再攔截事件的時候,事件的分發情況,源碼如下:

                        final View[] children = mChildren; 
                        for (int i = childrenCount - 1; i >= 0; i--) {  //遍歷ViewGroup的所有子元素
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);

                            // If there is a view that has accessibility focus we want it
                            // to get the event first and if not handled we will perform a
                            // normal dispatch. We may do a double iteration but this is
                            // safer given the timeframe.
                            if (childWithAccessibilityFocus != null) {
                                if (childWithAccessibilityFocus != child) {
                                    continue;
                                }
                                childWithAccessibilityFocus = null;
                                i = childrenCount - 1;
                            }
                            /**
                            ** 判斷子元素是否能夠接受到點擊事件:
                            ** 子元素是否在播動畫和點擊事件的座標是否落在子元素的區域內
                            ** 如果某個元素滿足這兩個條件,則事件交給它來處理
                            **/

                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }

                            newTouchTarget = getTouchTarget(child);
                            if (newTouchTarget != null) {
                                // Child is already receiving touch within its bounds.
                                // Give it the new pointer in addition to the ones it is handling.
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }

                            resetCancelNextUpFlag(child);

                            //dispatchTransformedTouchEvent實際調用的是子元素的dispatchTouchEvent方法

                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // Child wants to receive touch within its bounds.
                                mLastTouchDownTime = ev.getDownTime();
                                if (preorderedList != null) {
                                    // childIndex points into presorted list, find original index
                                    for (int j = 0; j < childrenCount; j++) {
                                        if (children[childIndex] == mChildren[j]) {
                                            mLastTouchDownIndex = j;
                                            break;
                                        }
                                    }
                                } else {
                                    mLastTouchDownIndex = childIndex;
                                }
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();

                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                //記錄ACTION_DOWN事件已經被處理了
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }

                            // The accessibility focus didn't handle the event, so clear
                            // the flag and do a normal dispatch to all children.
                            ev.setTargetAccessibilityFocus(false);
                        }

從上面代碼中我們可以知道,不攔截事件時,首先會遍歷ViewGroup的所有子元素,然後判斷子元素是否能夠接受到點擊事件。判斷的依據是:子元素是否在播放動畫和點擊事件的座標是否落在子元素的區域內。如果找到一個目標子View來處理事件時,則調用dispatchTransformedTouchEvent()方法。來看看這個方法重要實現邏輯:

              if (child == null) {
                    handled = super.dispatchTouchEvent(event);
                } else {
                   ........
                    handled = child.dispatchTouchEvent(event);
                }

可以發現由於在上面中的child並不等於null,所以將直接調用子元素的dispatchTouchEvent方法,使得事件傳遞到子View上,然後繼續分發。

你以爲這樣就結束了?答案肯定是沒有,從上面源碼中我們可以發現當子元素的dispatchTouchEvent返回true後,還有相應操作:

newTouchTarget = addTouchTarget(child, idBitsToAssign);
//記錄ACTION_DOWN事件已經被處理了
alreadyDispatchedToNewTouchTarget = true;
break;

這幾行代碼完成了mFirstTouchTarget的賦值並終止了對子元素的遍歷。如果子元素的dispatchTouchEvent返回false,則ViewGroup就會把事件分發給下一個元素(如果還有子元素的話),看到這你也許又納悶了,mFirstTouchTarget的賦值?怎麼沒看見mFirstTouchTarget的影子呢,答案其實在addTouchTarget這個方法中:

    private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        return target;
    }

這個方法可以看出其實mFirstTouchTarget是一種單鏈表結構,首先根據座標點找到了目標子View,然後將子View放在鏈表頭上,從而實現了mFirstTouchTarget!=null。

到這裏就完成了ViewGroup一輪的事件分發了,然而還沒有結束,如果遍歷了所有子元素後事件都沒有被合適處理呢?

沒有合適處理包括了兩種情況:

  • ViewGroup沒有子元素
  • 子元素處理了點擊事件,但是在dispatchTouchEvent中返回了false(默認是返回true,只有重寫View的這個方法或者在onTouchEvent中返回了false)

那麼這時候ViewGroup將會自己處理點擊事件(當ViewGroup攔截了事件時也是做同樣的處理)。

            if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            }

可以看出這時候還是調用了dispatchTransformedTouchEvent方法,不過這時候第三個參數不是child而是null,所以會調用下面這句代碼:

//dispatchTransformedTouchEvent
handled = super.dispatchTouchEvent(event);

super其實就是View中的dispatchTouchEvent方法,所以點擊事件開始交由View來處理。

ViewGroup並沒有調用onTouchEvent,ViewGroup也沒有去重寫onTouchEvent

流程圖如下:

3. View事件的分發機制

從上面對ViewGroup事件分發機制可知,View事件分發機制是從dispatchTouchEvent開始的。

源碼:View#dispatchTouchEvent

    public boolean dispatchTouchEvent(MotionEvent event) {
        ......
        boolean result = false;
        ......
        //判斷窗口是否被遮擋,如果被遮擋則返回false,比如有時候兩個View是會重疊的,導致其中一個被遮擋了。
        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
           //判斷是否設置了mOnTouchListener,如果設置了onTouchListener,且onTouch方法返回了ture,
            //則result = true
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
            //在result = ture情況下,就不會調用onTouchEvent,可見onTouchListener的優先級高於onTouchEvent
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
        ......
        return result;
    }

由於View是一個單獨元素,沒有子元素可以繼續向下傳遞事件,只能自己處理事件,所以代碼也會明顯減少。從上面的源碼中我們可以看到View對點擊事件的處理過程,result代表是否消耗該事件,然後進行onTouchListener的判斷,如果onTouchListenter中的onTouch方法返回了true,那麼就不會再調用onTouchEvent方法,由此可見onTouchListener的優先級高於onTouchEvent。

然後來看看onTouchEvent的實現。

    public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
        final int action = event.getAction();

        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

        //不可用狀態下點擊事件的處理,依然會消耗點擊事件
        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return clickable; 
        }
        //如果VIew設置了代理,將會執行代理的onTouchEvent方法
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    .....
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                          ......
                          //經過種種判斷
                           performClickInternal();
                    break;

                case MotionEvent.ACTION_DOWN:
                    ....
                    break;

                case MotionEvent.ACTION_CANCEL:
                    ....
                    break;

                case MotionEvent.ACTION_MOVE:
                    ....
                    break;
            }
           //若該控件可點擊,就一定返回true
            return true;
        }
        //若該控件不可點擊,就一定返回false
        return false;
    }

從上面代碼可以知道只要View的CLICKABLE,LONG_CLICKABLE,CONTEXT_CLICKABLE有一個爲true,那麼它就會消耗該事件,不管它是不是DISABLE狀態。然後假如控件可點擊,就對四種事件類型進行相對應的處理,這裏值得一說的是ACTION_UP事件,從源碼中可以發現在ACTION_UP事件發生時,會觸發performClickInternal方法。這個方法內部實現又是怎樣的呢?如下:

    private boolean performClickInternal() {
        // Must notify autofill manager before performing the click actions to avoid scenarios where
        // the app has a click listener that changes the state of views the autofill service might
        // be interested on.
        notifyAutofillManagerOnClick();

        return performClick();
    }

我們可以發現最後還是會調用performClick,而在performClick內部中:

    public boolean performClick() {
        // We still need to call this method to handle the cases where performClick() was called
        // externally, instead of through performClickInternal()
        notifyAutofillManagerOnClick();

        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

        notifyEnterOrExitForAutoFillIfNeeded(true);

        return result;
    }                                                         

只要我們通過setOnClickListener爲View註冊點擊事件,那麼就會給li.mOnClickListener賦值,則會調用onClick方法。

流程圖如下:

從流程圖我們可以發現onTouch,onTouchEvent,onClick的優先級:onTouch>onTouchEvent>onClick

總結

到這裏,我們已經通過源碼將點擊事件的分發機制梳理一遍了。事件分發的大致過程如下:

  • 當一個點擊事件發生後,總是先傳遞給當前的Activity,由Activity的dispatchTouchEvent進行分發,而Activity會將事件傳遞給Window,然後由Window的唯一實現類PhoneWindow將事件傳遞給DecorView,接着DecorView將事件傳遞給自己的父類ViewGroup,此時的ViewGroup就是通過setContentView所設置的View,故可以稱爲頂級View,這時候ViewGroup可能是自己處理該事件或者傳遞給子View,但是最終都會調用View的dispatchTouchEvent來處理事件。

  • 在View的dispatchTouchEvent中,如果設置了onTouchListener,會調用其onTouch方法,如果onTouch返回true,則不再調用onTouchEvent。如果有設置點擊事件,則在onTouchEvent會調用onClick方法。如果子View的onTouchEvent返回了false,則表示不消耗事件,事件會回傳給上一級的ViewGroup的onTouchEvent,如果所有的ViewGroup都沒有返回true,則最終會回傳到Activity的onTouchEvent。

結尾

學習技術是一條慢長而艱苦的道路,不能靠一時激情,也不是熬幾天幾夜就能學好的,必須養成平時努力學習的習慣。所以:貴在堅持!

最後在這裏小編分享一份自己收錄整理上述技術體系圖相關的幾十套騰訊、頭條、阿里、美團等公司19年的面試題,把技術點整理成了視頻和PDF(實際上比預期多花了不少精力),包含知識脈絡 + 諸多細節,由於篇幅有限,這裏以圖片的形式給大家展示一部分。

還有 高級架構技術進階腦圖、Android開發面試專題資料,高級進階架構資料 幫助大家學習提升進階,也節省大家在網上搜索資料的時間來學習,也可以分享給身邊好友一起學習。

Android學習PDF+架構視頻+面試文檔+源碼筆記

【Android開發核心知識點筆記】

【Android思維腦圖(技能樹)】

【Android核心高級技術PDF文檔,BAT大廠面試真題解析】

【Android高級架構視頻學習資源】

Android精講視頻領取學習後更加是如虎添翼!進軍BATJ大廠等(備戰)!現在都說互聯網寒冬,其實無非就是你上錯了車,且穿的少(技能),要是你上對車,自身技術能力夠強,公司換掉的代價大,怎麼可能會被裁掉,都是淘汰末端的業務Curd而已!現如今市場上初級程序員氾濫,這套教程針對Android開發工程師1-6年的人員、正處於瓶頸期,想要年後突破自己漲薪的,進階Android中高級、架構師對你更是如魚得水,趕快領取吧!

【Android進階學習視頻】、【全套Android面試祕籍】關注我【主頁簡介】或者【簡信我】查看免費領取方式!

最後,祝願即將跳槽和準備求職的大家都能找到一份好的工作!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章