Android-事件分發(fā)上

1.前言

我覺得要弄清楚事件分發(fā)前,還是要先大致了解一下幾個概念。

Window,WindowManager,WindowMangerService,ViewRoot,DecorView他們之間是如何協(xié)同工作的。

2.Activity和Window的關(guān)系

我們知道Activity是支持顯示UI的,那么它是否直接管理view樹或者ViewRoot呢?答案時否定的,Activity并沒有與這兩者產(chǎn)生直接的聯(lián)系,因為這中間還有一個被稱為“Window”的對象。大家可以在Activity的源碼中找到如下代碼:

public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory2,
        Window.Callback, KeyEvent.Callback,
        OnCreateContextMenuListener, ComponentCallbacks2,
        Window.OnWindowDismissedCallback {
...
private Window window;

Window的字面意思是"窗口",這很好地解釋了它存在的意義。Window是基類,根據(jù)不同的產(chǎn)品可以衍生出不同的子類——具體則是由系統(tǒng)在Activity.attach中調(diào)用PolicyManager.makeNewWindow決定的,目前版本的Android系統(tǒng)默認生成的都是PhoneWindow。

3.Window和WindowManagerImpl的關(guān)系

在Android源碼中以“Window”開頭的類有不少,如Window,WindowManager,WindowManagerImpl等,為什么需要這么多相似的類呢?

先來看Window,它是面向Activity的,表示"UI界面的外框";而“框里面”具體的東西包括布局和內(nèi)容等,是由具體的Window子類,如PhoneWindow來規(guī)劃的。

Window的另一層含義是要與WindowManagerService進行通信,但它并沒有直接在自身實現(xiàn)這一功能。原因是:一個應(yīng)用程序中很可能存在多個Window。如果它們都單獨與WMS通信,那么既浪費資源,又會造成管理的混亂。換句話說,它們需要統(tǒng)一的管理。于是就有了WindowManager,它作為Window的成員變量mWindowManager存在。這個WindowManager是一個接口類,其真正的實現(xiàn)是WindowManagerImpl,后者同時也是整個應(yīng)用程序中所有Window的管理者。因而WindowManager與WindowManagerImpl的關(guān)系有點類似于“地方與中央”:地方為實施中央的“政策”提供了一個接口,然后匯總到中央進行管理。

在Window的源碼中與mWindowMager有關(guān)的代碼有如下幾句:

   public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        mAppToken = appToken;
        mAppName = appName;
        mHardwareAccelerated = hardwareAccelerated
                || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
        if (wm == null) {
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }
public WindowManager getWindowManager() {
        return mWindowManager;
    }

然后我們?nèi)indowManagerImpl的代碼中去查看createLocalWindowManager方法的代碼,代碼如下:

    public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
        return new WindowManagerImpl(mDisplay, parentWindow);
    }

從這幾處代碼,大家可以看到Window類中的mWindowManger引用的其實是WindowManagerImpl的實例。

4.ViewRoot和WindowManagerImpl的關(guān)系

由于每個Activity都有自己的窗口“Window”,每個Window都有一個WindowManagerImpl實例的引用。即每個Activity都對應(yīng)一個WindowManagerImpl。

在早期的版本中在WindowMangerImpl內(nèi)部,存在3個全局變量:

public final class WindowManagerImpl implements WindowManager {
    private [View] [] mViews;//樹的根節(jié)點
    private [ViewRoot][] mRoots;//ViewRoot
    private [WindowManager][] mParams;//Window的屬性

由此也可以看出,一個進程中不僅有一個ViewRoot;而Activity與ViewRoot則是一對一的關(guān)系。
自Android4.3開始對此做了修改,WindowManagerImpl不再直接存儲上述三個數(shù)組變量,而是由一個稱為“WindowMangerGlobal”的類統(tǒng)一管理。

public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    private final Display mDisplay;
    private final Window mParentWindow;

public final class WindowManagerGlobal {
    private final ArrayList<View> mViews = new ArrayList<View>();
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
    private final ArrayList<WindowManager.LayoutParams> mParams =
            new ArrayList<WindowManager.LayoutParams>();

5.DecorView與ViewRootImpl

從setContentView說起

一般地,我們在Activity中,會在onCreate()方法中寫下這樣一句:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

顯然,這是為activity設(shè)置一個我們定義好的main.xml布局,我們跟蹤一下源碼,看看這個方法是怎樣做的,Activity#setContentView:

    public void setContentView(int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

    public Window getWindow() {
        return mWindow;
    }

以上代碼我們可以看出,Activity的setContentView,其實是調(diào)用的window的setContentView方法。上面我們了解到Window是基類,我們要找到它的實現(xiàn)類。

final void attach(Context context, ActivityThread aThread,
           Instrumentation instr, IBinder token, int ident,
           Application application, Intent intent, ActivityInfo info,
           CharSequence title, Activity parent, String id,
           NonConfigurationInstances lastNonConfigurationInstances,
           Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
       ...
       //創(chuàng)建Window對象
       mWindow = PolicyManager.makeNewWindow(this);
      ...
   }

我們只看關(guān)鍵部分,在Activity里面有一個attach方法,在這個方法中創(chuàng)建了Window對象,下面看PolicyManager里面的makeNewWindow方法。

public final class PolicyManager {
    ......
    private static final IPolicy sPolicy;

    public static Window makeNewWindow(Context context) {
        return sPolicy.makeNewWindow(context);
    }
}

在這個代碼里面調(diào)用的是IPolicy里面的makeNewWindow方法,一看IPolicy就知道它是一個接口,真正的實現(xiàn)是在它的實現(xiàn)類Policy中完成的,直接看代碼。

public class Policy implements IPolicy {
    ......
    public Window makeNewWindow(Context context) {
        //看到?jīng)]有,是一個PhoneWindow對象
        return new PhoneWindow(context);
    }
    ......
}

這就看到了創(chuàng)建的是一個PhoneWindow對象,也就是說Activity里面的Window屬性引用的就是PhoneWindow對象,調(diào)用Window的方法實質(zhì)調(diào)用的就是PhoneWindow的方法,都在這里實現(xiàn)了。

總結(jié):
1.在啟動Activity的過程中會調(diào)用一個attach函數(shù)
2.在attach函數(shù)中會執(zhí)行PolicyManager.makeNewWindow(this)
3.進一步深入,真正執(zhí)行makeNewWindow的其實是IPolicy,它只是被 PolicyManager進行了包裝。
4.IPolicy是一個接口,所以makeNewWindow的執(zhí)行是由它的實現(xiàn)類Policy來執(zhí)行的。
5.可以看到執(zhí)行創(chuàng)建了一個PhoneWindow對象并且返回,最終也是將這個對象賦值給mWindow。

至此我們知道PhoneWindow是Window的實現(xiàn)類,那么我們在PhoneWindow類里面找到它的setContentView方法,看看它又實現(xiàn)了什么,PhoneWindow#setContentView:

 @Override
    public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {//1
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            mLayoutInflater.inflate(layoutResID, mContentParent);//2
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }

首先判斷了mContentParent是否為null,如果為空則執(zhí)行installDecor()方法,那么這個mContentParent又是什么呢?我們看一下它的注釋:

    // This is the view in which the window contents are placed. It is either
    // mDecor itself, or a child of mDecor where the contents go.
    private ViewGroup mContentParent;

它是一個ViewGroup類型,結(jié)合2號代碼處,可以得知,這個mContentParent是我們設(shè)置的布局(即main.xml)的父布局。注釋還提到了,這個mContentParent是mDecor本身或者是mDecor的一個子元素

這里先梳理一下以上的內(nèi)容:Activity通過PhoneWindow的setContentView方法來設(shè)置布局,而設(shè)置布局之前,會先判斷是否存在mContentParent,而我們設(shè)置的布局文件則是mContentParent的子元素。

DecorView的創(chuàng)建

我們接著看下 installDecor();的源碼 PhoneWindow#installDecor:

private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor();
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
            ...
        }
    }

首先,如果mDecor為空的時候會執(zhí)行g(shù)enerateDecor()代碼,調(diào)用PhoneWindow#generateDecor方法:

protected DecorView generateDecor() {
        return new DecorView(getContext(), -1);
    }

可以看出,這里實例化了DecorView,在源碼中我們看到DecorView是PhoneWindow的內(nèi)部類,

    private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {

它繼承的是FrameLayout,由此可知它也是一個ViewGroup。
那么,DecroView到底充當了什么樣的角色呢?
其實,DecorView是整個ViewTree的最頂層View,它是一個FrameLayout布局,代表了整個應(yīng)用的界面。在該布局下面,有標題view和內(nèi)容view這兩個子元素,而內(nèi)容view則是上面提到的mContentParent。我們接著看generateLayout(mDecor)代碼,PhoneWindow#generateLayout方法

protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.
        // 從主題文件中獲取樣式信息
        TypedArray a = getWindowStyle();

         ...

        if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
            requestFeature(FEATURE_NO_TITLE);
        } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
            // Don't allow an action bar if there is no title.
            requestFeature(FEATURE_ACTION_BAR);
        }
        ...
        if (...) {
           ...
        }

          ....

        // Inflate the window decor.
        // 加載窗口布局
        int layoutResource;
        int features = getLocalFeatures();
        // System.out.println("Features: 0x" + Integer.toHexString(features));
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            layoutResource = R.layout.screen_swipe_dismiss;
        } else if (...) {
           ....
        }

        mDecor.startChanging();
        //加載layoutResource
        View in = mLayoutInflater.inflate(layoutResource, null);
        //往DecorView中添加子View,即mContentParent
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        mContentRoot = (ViewGroup) in;
        // 這里獲取的就是mContentParent
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

        if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
            ProgressBar progress = getCircularProgressBar(false);
            if (progress != null) {
                progress.setIndeterminate(true);
            }
        }

        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            registerSwipeCallbacks();
        }

        // Remaining setup -- of background and title -- that only applies
        // to top-level windows.
       ...

        mDecor.finishChanging();

        return contentParent;
    }

由以上代碼可以看出,該方法還是做了相當多的工作的,首先根據(jù)設(shè)置的主題樣式來設(shè)置DecorView的風格,比如說有沒有titlebar之類的,接著為DecorView添加子View,而這里的子View則是上面提到的mContentParent,如果上面設(shè)置了FEATURE_NO_ACTIONBAR,那么DecorView就只有mContentParent一個子View,

小結(jié):DecorView是頂級View,內(nèi)部有titlebar和contentParent兩個子元素,contentParent的id是content,而我們設(shè)置的main.xml布局則是contentParent里面的一個子元素。

在DecorView創(chuàng)建完畢后,讓我們回到PhoneWindow#setContentView方法,直接看代碼: mLayoutInflater.inflate(layoutResID, mContentParent);這里加載了我們設(shè)置的main.xml布局文件,并且設(shè)置mContentParent為main.xml的父布局,至于它怎么加載的,這里就不展開來說了。

到目前為止,通過setContentView方法,創(chuàng)建了DecorView和加載了我們提供的布局,但是這時,我們的View還是不可見的,因為我們僅僅是加載了布局,并沒有對View進行任何的測量、布局、繪制工作。在View進行測量流程之前,還要進行一個步驟,那就是把DecorView添加至window中,然后經(jīng)過一系列過程觸發(fā)ViewRootImpl#performTraversals方法,在該方法內(nèi)部會正式開始測量、布局、繪制這三大流程。至于該一系列過程是怎樣的,因為涉及到了很多機制,這里簡單說明一下:

將DecorView添加至Window

每一個Activity組件都有一個關(guān)聯(lián)的Window對象,用來描述一個應(yīng)用程序窗口。每一個應(yīng)用程序窗口內(nèi)部又包含有一個View對象,用來描述應(yīng)用程序窗口的視圖。上文分析了創(chuàng)建DecorView的過程,現(xiàn)在則要把DecorView添加到Window對象中。而要了解這個過程,我們首先要簡單先了解一下Activity的創(chuàng)建過程:
首先,在ActivityThread#handleLaunchActivity中啟動Activity,在這里面會調(diào)用到Activity#onCreate方法,從而完成上面所述的DecorView創(chuàng)建動作,當onCreate()方法執(zhí)行完畢,在handleLaunchActivity方法會繼續(xù)調(diào)用到ActivityThread#handleResumeActivity方法,我們看看這個方法的源碼:

 final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume) {
        // If we are getting ready to gc after going to the background, well
        // we are back active so skip it.
        ...

        // TODO Push resumeArgs into the activity for consideration
        // 這里會調(diào)用到Activity的onResume()方法
        ActivityClientRecord r = performResumeActivity(token, clearHide);

            ...
            if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                l.softInputMode |= forwardBit;
                if (a.mVisibleFromClient) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);// 調(diào)用addView方法
                }

            // If the window has already been added, but during resume
            // we started another activity, then don't yet make the
            // window visible.
            ...
        }
    }

在該方法內(nèi)部,獲取該activity所關(guān)聯(lián)的window對象,DecorView對象,以及windowManager對象,而WindowManager是抽象類,它的實現(xiàn)類是WindowManagerImpl,所以后面調(diào)用的是WindowManagerImpl#addView方法,我們看看源碼:

public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    ...
    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }

接著調(diào)用了mGlobal的成員函數(shù),而mGlobal則是WindowManagerGlobal的一個實例,那么我們接著看WindowManagerGlobal#addView方法:

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        ...
        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            // Start watching for system property changes.
           ...
            root = new ViewRootImpl(view.getContext(), display);//1

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
        }

        // do this last because it fires off messages to start doing things
        try {
            root.setView(view, wparams, panelParentView);//2
        } catch (RuntimeException e) {
            // BadTokenException or InvalidDisplayException, clean up.
            synchronized (mLock) {
                final int index = findViewLocked(view, false);
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
            }
            throw e;
        }
    }

先看1號代碼處,實例化了ViewRootImpl類,接著,在2號代碼處,調(diào)用ViewRootImpl#setView方法,并把DecorView作為參數(shù)傳遞進去,在這個方法內(nèi)部,會通過跨進程的方式向WMS(WindowManagerService)發(fā)起一個調(diào)用,從而將DecorView最終添加到Window上,在這個過程中,ViewRootImpl、DecorView和WMS會彼此關(guān)聯(lián),最終通過WMS調(diào)用ViewRootImpl#performTraverals方法開始View的測量、布局、繪制流程

總結(jié):

Window,它是"UI界面的外框",而里面具體內(nèi)容是有其子類,如PhoneWindow來規(guī)劃的。

WindowManager,是統(tǒng)一管理Window而誕生的。其實現(xiàn)是WindowManagerImpl

WindowManagerImpl,直接或間接的存儲DecorView,ViewRoot,WindowManager;

DecorView,是整個ViewTree的最頂層View,它是一個FrameLayout布局,代表了整個應(yīng)用的界面,我們setContentView添加的視圖是被mContentParent"包裹"著添加到DecorView 中的。

ViewRoot是GUI管理系統(tǒng)與GUI呈現(xiàn)系統(tǒng)之間的橋梁,根據(jù)ViewRoot的定義,我們發(fā)現(xiàn)它并不是一個View類型,而是一個Handler。
它的主要作用如下:

  1. 向DecorView分發(fā)收到的用戶發(fā)起的event事件,如按鍵,觸屏,軌跡球等事件;
  2. 與WindowManagerService交互,完成整個Activity的GUI的繪制。


    通訊.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容