Framework 原始碼解析知識梳理(2) 應用程式與 WMS 的通訊實現

澤毛發表於2017-12-21

一、前言

Framework 原始碼解析知識梳理(1) - 應用程式與 AMS 的通訊實現 這篇文章中,我們分析了應用程式和AMS之間的通訊實現,我們今天討論一下應用程式和WindowManagerService之間的通訊實現。

在之前的分析中,我們分兩個部分來介紹了應用程式與AMS之間的通訊:

  • 應用程式傳送訊息到AMS程式
  • AMS傳送訊息到應用程式

現在,我們也按照一樣的討論,分為這兩個方向來介紹應用程式與WMS之間的通訊實現,整個通訊的過程會涉及到下面的這些類,其中加粗的線就是整個通訊實現的呼叫路徑。

Framework 原始碼解析知識梳理(2)   應用程式與 WMS 的通訊實現

二、WindowManagerImpl & WindowManagerGlobal

AMS的討論中,我們以在應用程式中啟動Activity為例子進行了介紹,今天,我們選取另一個大家很常見的例子:Activity啟動之後,是如何將介面新增到螢幕上的。

2.1 WindowManagerImpl

View 繪製體系知識梳理(2) - setContentView 原始碼解析 這篇文章中,我們介紹了DecorView的相關知識,它就對應於我們需要新增到螢幕上的View的根節點,而這一新增的過程就需要涉及到和WMS之間的通訊,它是在ActivityThread的下面這個方法中實現的:

<!-- ActivityThread.java -->

final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        r = performResumeActivity(token, clearHide, reason);
        if (r != null) {
            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 (r.mPreserveWindow) {
                    a.mWindowAdded = true;
                    r.mPreserveWindow = false;
                    ViewRootImpl impl = decor.getViewRootImpl();
                    if (impl != null) {
                        impl.notifyChildRebuilt();
                    }
                }
                if (a.mVisibleFromClient && !a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                }
            } else if (!willBeVisible) {
                if (localLOGV) Slog.v(
                    TAG, "Launch " + r + " mStartedActivity set");
                r.hideForNow = true;
            }
        }
    }
複製程式碼

上面的程式碼中,關鍵的是下面這幾個步驟:

//(1) 通過 Activity 獲得 Window 的實現類 PhoneWindow
r.window = r.activity.getWindow();

//(2) 通過 PhoneWindow 獲得 DecorView
View decor = r.window.getDecorView();

//(3) 通過 Activity 獲得 ViewManager 的實現類 WindowManagerImpl
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();

//(4) 通過 WindowManagerImpl 新增 DecorView
wm.addView(decor, l);
複製程式碼

(1) 通過 Activity 獲得 Window 的實現類 PhoneWindow

這裡首先呼叫了ActivitygetWindow()方法:

<!-- Activity.java -->

public Window getWindow() {
    return mWindow;
}
複製程式碼

而這個mWindow是在Activity.attach(xxxx)中被賦值的,它其實是Window的實現類PhoneWindowPhoneWindow的建構函式中傳入了Activity以及parentWindow

<!-- Activity.java -->

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,
            Window window) {
        //....
        mWindow = new PhoneWindow(this, window);
}
複製程式碼

第一步的分析就結束了,大家要記得一個結論:

通過ActivitygetWindow()返回的是PhoneWindow物件,如果以後需要檢視mWindow呼叫的函式,那麼應當首先去PhoneWindow.java中檢視是否有對應的實現,如果沒有,那麼再去Window.java中尋找。

對應於整個流程圖的中的這個部分:

Framework 原始碼解析知識梳理(2)   應用程式與 WMS 的通訊實現
**(2) 通過 PhoneWindow 獲得 DecorView **

在第二步中,通過第一步返回的PhoneWindow獲得DecorView,這個mDecor就是我們在 View 繪製體系知識梳理(2) - setContentView 原始碼解析 所介紹的DecorView

<!-- PhoneWindow.java -->

@Override
public final View getDecorView() {
    if (mDecor == null || mForceDecorInstall) {
        installDecor();
    }
    return mDecor;
}
複製程式碼

(3) 通過 Activity 獲得 ViewManager 的實現類 WindowManagerImpl

下面,我們看第三步,這裡通過ActivitygetWindowManager()返回了一個ViewManager的實現類:

<!-- Activity.java -->

public WindowManager getWindowManager() {
    return mWindowManager;
}
複製程式碼

mWindow類似,它也是在Activityattach方法中賦值的:

<!-- Activity.java -->

    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,
            Window window) {
        //...
        mWindow = new PhoneWindow(this, window);
        //...
        mWindow.setWindowManager( 
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, 
                mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        mWindowManager = mWindow.getWindowManager();
        mCurrentConfig = config;
    }
複製程式碼

它通過WindowgetWindowManager()返回,我們看一下Window的這個方法:

<!-- Window.java -->

public WindowManager getWindowManager() {
    return mWindowManager;
}
複製程式碼

PhoneWindowActivity類似,也有一個mWindowManager變數,我們再去看一下它被賦值的地方:

<!-- Activity.java -->

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);
}
複製程式碼

createLocalWindowManager返回的是一個WindowManagerImpl物件:

<!-- WindowManagerImpl.java -->

public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
    return new WindowManagerImpl(mContext, parentWindow);
}
複製程式碼

這樣第三步的結論就是:

通過ActivitygetWindowManager()方法返回的是它內部的mWindowManager物件,而這個物件是通過Window中的mWindowManager得到的,它其實是ViewManager介面的實現類WindowManagerImpl

ViewManagerWindowManagerImpl的關係為:

Framework 原始碼解析知識梳理(2)   應用程式與 WMS 的通訊實現

(4) 通過 WindowManagerImpl 新增 DecorView

在第四步中,我們通過ViewManageraddView(View, WindowManager.LayoutParams)方法新增DecorView,經過前面的分析,我們知道它其實是一個WindowManagerImpl物件,因此,我們去看一下它所實現的addView方法:

public final class WindowManagerImpl implements WindowManager {

    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    private final Context mContext;
    private final Window mParentWindow;

    private WindowManagerImpl(Context context, Window parentWindow) {
        mContext = context;
        mParentWindow = parentWindow;
    }

    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }
}
複製程式碼

可以看到WindowManagerImpl什麼都沒有做,它只是一個代理類,真正去做工作的是mGlobal,並且這個mGlobal使用了單例模式,也就是說,同一個程式中的所有Activity,呼叫的是同一個WindowManagerGlobal物件。

那麼,我們下面分析的重點就集中在了WindowManagerGlobal上了。

2.2 WindowManagerGlobal

我們來看WindowManagerGlobaladdView方法,這裡的第一個引數就是前面傳遞過來的mDecor

<!-- WindowManagerGlobal -->

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        //...
        ViewRootImpl root;
        View panelParentView = null;
        //....
        synchronized (mLock) {
            //...
            root = new ViewRootImpl(view.getContext(), display);
            mViews.add(view);
            mRoots.add(root);
        }
        try {
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
            throw e;
        }
    }
複製程式碼

addView(xxx)方法中,會生成一個ViewRootImpl物件,並呼叫它的setView(xxx)方法把它和DecorView和它關聯起來,與WMS通訊的邏輯都是由ViewRootImpl負責的,WindowManagerGlobal則負責用來管理應用程式當中的所有ViewRootImpl,對應於整個框架圖的部分為:

Framework 原始碼解析知識梳理(2)   應用程式與 WMS 的通訊實現
在介紹ViewRootImpl之前,我們還要先講一下在WindowManagerGlobal中比較重要的兩個靜態變數:

<!-- WindowManagerGlobal.java -->

private static IWindowManager sWindowManagerService;
private static IWindowSession sWindowSession;
複製程式碼

(1) sWindowManagerService 為管理者程式在應用程式中的代理物件

<!-- WindowManagerGlobal.java -->

sWindowManagerService = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
複製程式碼

(2) sWindowSession 為應用程式和管理者程式之間的會話

<!-- WindowManagerGlobal.java -->

InputMethodManager imm = InputMethodManager.getInstance();
IWindowManager windowManager = getWindowManagerService();
sWindowSession = windowManager.openSession(
    new IWindowSessionCallback.Stub() {
        @Override
        public void onAnimatorScaleChanged(float scale) {
            ValueAnimator.setDurationScale(scale);
        }
    }, imm.getClient(), imm.getInputContext());
複製程式碼

這個會話的方向為從應用程式到管理者程式,通過這個會話,應用程式就可以向管理者程式傳送訊息,而傳送訊息的邏輯則是通過ViewRootImpl來實現的,下面我們就來看一下這個最重要的類是如何實現的。

三、ViewRootImpl

ViewRootImpl實現了ViewParent介面

Framework 原始碼解析知識梳理(2)   應用程式與 WMS 的通訊實現
在這個類中有三個最關鍵的變數:

<!-- ViewRootImpl.java -->

View mView;
IWindowSession mWindowSession;
IWindow W;
複製程式碼
  • mView(View):這就是我們在WindowManagerGlobal中通過setView()傳遞進來的,對應於Activity中的DecorView
<!-- ViewRootImpl.java -->

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
                //....
            }
        }
}
複製程式碼
  • mWindowSession(IWindowSession):代表了從應用程式到管理者程式的會話,它其實就是WindowManagerGlobal中的sWindowSession,對於同一個程式,會複用同一個會話。
<!-- ViewRootImpl.java -->

public ViewRootImpl(Context context, Display display) {
    mWindowSession = WindowManagerGlobal.getWindowSession();
    //...
}
複製程式碼
  • mWindow(W):代表了從管理者程式到應用程式的會話,是在ViewRootImpl中定義的一個內部類。
<!-- ViewRootImpl.java -->

    public ViewRootImpl(Context context, Display display) {
        mWindow = new W(this);
    }

    static class W extends IWindow.Stub {

        private final WeakReference<ViewRootImpl> mViewAncestor;
        private final IWindowSession mWindowSession;

        W(ViewRootImpl viewAncestor) {
            mViewAncestor = new WeakReference<ViewRootImpl>(viewAncestor);
            mWindowSession = viewAncestor.mWindowSession;
        }
    }
複製程式碼

3.1 從應用程式到管理者程式

IWindowSession是應用程式到管理者程式的會話,它定義了管理者程式所支援的呼叫介面,通過IWindowSession內部的管理者程式的遠端代理物件,我們就可以實現從應用程式向管理者程式傳送訊息。

而在管理者程式中,通過WindowManagerService來處理來自各個應用程式的訊息,在WMS中有一個Session列表,所有從應用程式到管理程式的會話都儲存在該列表中。

<!-- WindowManagerService.java -->

final ArraySet<Session> mSessions = new ArraySet<>();
複製程式碼

Session則實現了IWindowSession.Stub介面:

Framework 原始碼解析知識梳理(2)   應用程式與 WMS 的通訊實現
當它收到訊息之後,就會回撥IWindowSession的介面方法。

我們舉一個例子,在ViewRootImplsetView(xxx)方法中,呼叫了IWindowSession的下面這個介面方法:

<!-- ViewRootImpl.java -->

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        //....
        res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
            getHostVisibility(), mDisplay.getDisplayId(),
            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
            mAttachInfo.mOutsets, mInputChannel);
    }
複製程式碼

最終這一跨程式的呼叫會回撥到該應用程式在管理者程式中對應的Session物件的回撥方法中:

<!-- Session.java -->

    @Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
            Rect outOutsets, InputChannel outInputChannel) {
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                outContentInsets, outStableInsets, outOutsets, outInputChannel);
    }
複製程式碼

3.2 從管理者程式到應用程式

如果我們希望實現從管理者程式傳送訊息到應用程式,那麼也需要一個應用程式在管理者程式的代理物件。

在呼叫addToDisplay時,我們傳入的第一個引數是mWindow,前面我們介紹過,它實現了IWindow.Stub介面:

Framework 原始碼解析知識梳理(2)   應用程式與 WMS 的通訊實現
這樣管理者程式在SessionaddToDisplay方法被回撥時,就可以獲得一個遠端代理物件,它就可以通過IWindow中定義的介面方法,實現從管理者程式到應用程式的通訊。

SessionaddToDisplay()方法中,會呼叫WMSaddWindow方法,而在addWindow方法中,它會建立一個WindowState物件,一個程式中的每個ViewRootImpl會對應於一個IWindow會話,它們被儲存在WMS的下面這個HashMap中:

<!-- WindowManagerService.java -->

final HashMap<IBinder, WindowState> mWindowMap = new HashMap<>();
複製程式碼

其中key值就表示應用程式在管理者程式中的遠端代理物件,例如我們在WMS中呼叫了下面這個方法:

<!-- WindowState.java -->

mClient.windowFocusChanged(focused, inTouchMode);
複製程式碼

那麼應用程式中IWindow.Stub的實現的ViewRootImpl.W類的對應方法就會被回撥,在該回撥方法中又會呼叫ViewRootImpl的方法:

<!-- ViewRootImpl.java -->

        @Override
        public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {
            final ViewRootImpl viewAncestor = mViewAncestor.get();
            if (viewAncestor != null) {
                viewAncestor.windowFocusChanged(hasFocus, inTouchMode);
            }
        }
複製程式碼

而在ViewRootImplwindowFocusChanged方法中,會通過它內部的一個ViewRootHandler傳送訊息,ViewRootHandlerLooper是和應用程式中的主執行緒所繫結的,因此它就可以在handleMessage進行後續邏輯處理。

<!-- ViewRootImpl.java -->

    final ViewRootHandler mHandler = new ViewRootHandler();

    public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {
        Message msg = Message.obtain();
        msg.what = MSG_WINDOW_FOCUS_CHANGED;
        msg.arg1 = hasFocus ? 1 : 0;
        msg.arg2 = inTouchMode ? 1 : 0;
        mHandler.sendMessage(msg);
    }
複製程式碼

四、小結

做一個簡單的總結,應用程式與WMS之間通訊是通過WindowManagerGlobalViewRootImpl來管理的,ViewRootImpl中的IWindowSession對應於從應用程式到WMS的通訊,而IWindow對應於從管理者程式到應用程式的通訊。


更多文章,歡迎訪問我的 Android 知識梳理系列:

相關文章