WindowManager呼叫流程原始碼分析

HitenDev發表於2018-03-30

前兩天寫Activity啟動流程時挖個坑,ActivityThread呼叫Activity.resume後,緊接著呼叫WindowManager.addView()用來正在的顯示View,之前講的很草率,現在感覺有必要寫一下WindowManager的呼叫流程。

本文基於android8.1.0_r15分支分析

WindowManager簡單介紹

首先WindowManager是一個介面繼承自ViewManager,它作用是用於視窗的管理,我們平時的使用比較多的是addView方法和LayoutParams引數,addView定義在基類ViewManager中,原始碼:

ViewManager

public interface ViewManager
{
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}
複製程式碼

一共三個方法比較簡單,注意看引數型別,我們平時用WindowManager時候傳的是WindowManager.LayoutParams,這裡定義的是ViewGroup.LayoutParams params,我想肯定前者繼承後者吧,啥都不說,看看WindowManager.LayoutParams定義;

WindowManager呼叫流程原始碼分析
果不其然,LayoutParams中還定義了一個十分重要的熟悉:type,type代表視窗的型別,系統一共提供三種型別的視窗

  • 應用程式視窗 :頂級視窗
  • 子視窗 :與一個頂級視窗關係的子視窗
  • 系統視窗 :系統視窗,比如toast

常用的type值

WindowManager呼叫流程原始碼分析

WindowManager畢竟只是一個介面,真正的實現在哪裡?回想在ActivityThread.handleResumeActivity()中,呼叫了activity.getWindowManager(),我們定位到Activity:

Activity#getWindowManager

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

在Activity#getSystemService()中同樣可以得到

 @Override
 public Object getSystemService(@ServiceName @NonNull String name) {
     if (getBaseContext() == null) {
         throw new IllegalStateException(
                 "System services not available to Activities before onCreate()");
     }
    //WINDOW_SERVICE比較特殊,不呼叫super的
     if (WINDOW_SERVICE.equals(name)) {
         return mWindowManager;
     } else if (SEARCH_SERVICE.equals(name)) {
         ensureSearchManager();
         return mSearchManager;
     }
     return super.getSystemService(name);
 }
複製程式碼

我們注意到在呼叫getSystemService時,WINDOW_SERVICE比較特殊,沒有呼叫super,直接返回mWindowManager,為什麼呢,我們留到後文說;

mWindowManager什麼時候賦值的呢,我們在Activity.attach中找到了程式碼

Activity.attach

//設定系統的WindowManager
 mWindow.setWindowManager(
        //獲取系統的WindowManager
         (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
         mToken, mComponent.flattenToString(),
         (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
 if (mParent != null) {
     mWindow.setContainer(mParent.getWindow());
 }
 //拿Window物件的
 mWindowManager = mWindow.getWindowManager();

複製程式碼

也是直接獲取系統的,但是經過mWindow轉手了,這是為什麼呢,Window關於setWindowManager()定義

Window#setWindowManager

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

正在的實現是WindowManagerImpl,我們檢視WindowManagerImpl.addView方法:

@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    呼叫mGlobal
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}

 @Override
 public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
     applyDefaultToken(params);
     mGlobal.updateViewLayout(view, params);
 }
 
private void applyDefaultToken(@NonNull ViewGroup.LayoutParams params) {
 if (mDefaultToken != null && mParentWindow == null) {
 if (!(params instanceof WindowManager.LayoutParams)) {
         throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
     }
     
     final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
     if (wparams.token == null) {
         wparams.token = mDefaultToken;
     }
 }
}
複製程式碼

方法體首先執行applyDefaultToken,對wparams賦token值,然後直接呼叫mGlobal對應的方法,mGlobal是WindowManagerGlobal型別,它也繼承自WindowManager麼?接下來分析WindowManagerGlobal

WindowManagerGlobal

WindowManagerGlobal並不是繼承WindowManager,但是有著一樣的方法名,這不是代理模式麼,它同樣有addView、updateViewLayout、removeView方法,我們從addView方法分析

public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {
    if (view == null) {
        throw new IllegalArgumentException("view must not be null");
    }
    if (display == null) {
        throw new IllegalArgumentException("display must not be null");
    }
    if (!(params instanceof WindowManager.LayoutParams)) {
        throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
    }
    //轉成WindowManager.LayoutParams物件
    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
    //顯示Activity的話window肯定不為空
    if (parentWindow != null) {
        parentWindow.adjustLayoutParamsForSubWindow(wparams);
    } else {
        final Context context = view.getContext();
        if (context != null
                && (context.getApplicationInfo().flags
                        & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
            wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
        }
    }
    ViewRootImpl root;
    View panelParentView = null;
    synchronized (mLock) {
        // Start watching for system property changes.
        if (mSystemPropertyUpdater == null) {
            mSystemPropertyUpdater = new Runnable() {
                @Override public void run() {
                    synchronized (mLock) {
                        for (int i = mRoots.size() - 1; i >= 0; --i) {
                            mRoots.get(i).loadSystemProperties();
                        }
                    }
                }
            };
            SystemProperties.addChangeCallback(mSystemPropertyUpdater);
        }
        //從mViews集合查詢有沒有新增過
        int index = findViewLocked(view, false);
        //有則呼叫對應的ViewRootImpl.doDie
        if (index >= 0) {
            if (mDyingViews.contains(view)) {
                mRoots.get(index).doDie();
            } else {
                throw new IllegalStateException("View " + view
                        + " has already been added to the window manager.");
            }
            // The previous removeView() had not completed executing. Now it has.
        }
        // If this is a panel window, then find the window it is being
        // attached to for future reference.
        if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
            final int count = mViews.size();
            for (int i = 0; i < count; i++) {
                if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
                    panelParentView = mViews.get(i);
                }
            }
        }
        //建立ViewRootImpl物件
        root = new ViewRootImpl(view.getContext(), display);
        //引數最終給了decor
        view.setLayoutParams(wparams);
        mViews.add(view);//把Decor存起來
        mRoots.add(root);//把ViewRootImpl純起來
        mParams.add(wparams);//把引數存起來
        // do this last because it fires off messages to start doing things
        try {
            //設定View
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
            // BadTokenException or InvalidDisplayException, clean up.
            if (index >= 0) {
                removeViewLocked(index, true);
            }
            throw e;
        }
    }
}
複製程式碼

簡單分析addView作用:

  1. WindowManagerGlobal中定義了三個List,分別存放傳遞進來的View、params和生成的ViewRootImpl
  2. 如果判斷已經新增過,則呼叫該view對應的ViewRootImpl.doDie()
  3. 最後執行了root.setView();

繼續分析updateViewLayout和removeView方法:

WindowManagerGlobal#updateViewLayout#removeView

public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
    if (view == null) {
        throw new IllegalArgumentException("view must not be null");
    }
    if (!(params instanceof WindowManager.LayoutParams)) {
        throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
    }
    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
    view.setLayoutParams(wparams);
    synchronized (mLock) {
        //去除之前的view對應的下標
        int index = findViewLocked(view, true);
        //快取的root
        ViewRootImpl root = mRoots.get(index);
        //mParams集合重新新增
        mParams.remove(index);
        mParams.add(index, wparams);
        //最終呼叫viewRootImpl的setLayoutParams
        root.setLayoutParams(wparams, false);
    }
}

public void removeView(View view, boolean immediate) {
    if (view == null) {
        throw new IllegalArgumentException("view must not be null");
    }
    synchronized (mLock) {
        //集合裡的全都刪除掉
        int index = findViewLocked(view, true);
        View curView = mRoots.get(index).getView();
        removeViewLocked(index, immediate);
        if (curView == view) {
            return;
        }
        throw new IllegalStateException("Calling with view " + view
                + " but the ViewAncestor is attached to " + curView);
    }
}

private void removeViewLocked(int index, boolean immediate) {
    ViewRootImpl root = mRoots.get(index);
    View view = root.getView();

    if (view != null) {
        InputMethodManager imm = InputMethodManager.getInstance();
        if (imm != null) {
            //關掉輸入法
            imm.windowDismissed(mViews.get(index).getWindowToken());
        }
    }
    //呼叫ViewRootImpl.die
    boolean deferred = root.die(immediate);
    if (view != null) {
        view.assignParent(null);
        if (deferred) {
            mDyingViews.add(view);
        }
    }
}

複製程式碼

更新和刪除的程式碼比較簡單,註釋說的很清楚了,只要就是管理集合和呼叫ViewRootImpl的對應程式碼

呼叫順序總結:

  1. Activity.getWindowManger()獲取,返回WindowManagerImpl;
  2. WindowManagerImpl呼叫WindowManagerGlobal對應方法;
  3. WindowManagerGlobal呼叫ViewRootImp對應方法;

意義總結:

  1. WindowManagerImpl作為WindowManager介面的預設實現,順便處理WindowManager.LayoutParams.token
  2. WindowManagerGlobal儲存每一次的View、Params、root,防止重複新增,最後呼叫RootViewImpl實現。

到這裡還沒有真正的看到IPC呼叫,因為一層一層代理,到了RootViewImpl這一層,真假美猴王應該能分曉了吧。

RootViewImpl呼叫IPC

上文分析RootViewImpl在WindowManagerGlobal.addView中構造,並且有三個方法setView()、setLayoutParams()、doDie()被呼叫,但是看了RootViewImpl程式碼頭髮有些發麻,程式碼量太大了。

由於鄙人水平有些,這篇部落格只拿出它和WMS的IPC呼叫過程分析,這程式碼只能一片一片剖析。見諒啊!開始吧。

RootViewImpl構造方法

WindowManager呼叫流程原始碼分析

我們發現RootViewImpl構造方法建立了兩個window相關的物件,分別是

mWindowSession = WindowManagerGlobal.getWindowSession();

mWindow = new W(this);
複製程式碼

跟進WindowManagerGlobal看個究竟

WindowManagerGlobal#getWindowSession#getWindowManagerService

//獲取WindowSession
public static IWindowSession getWindowSession() {
    synchronized (WindowManagerGlobal.class) {
        if (sWindowSession == null) {
            try {
                InputMethodManager imm = InputMethodManager.getInstance();
                IWindowManager windowManager = getWindowManagerService();
                //通知WMS開啟一個新的Session
                sWindowSession = windowManager.openSession(
                        new IWindowSessionCallback.Stub() {
                            @Override
                            public void onAnimatorScaleChanged(float scale) {
                                ValueAnimator.setDurationScale(scale);
                            }
                        },
                        imm.getClient(), imm.getInputContext());
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
        return sWindowSession;
    }
}

//獲取WindowManagerService
public static IWindowManager getWindowManagerService() {
    synchronized (WindowManagerGlobal.class) {
        if (sWindowManagerService == null) {
            sWindowManagerService = IWindowManager.Stub.asInterface(
                    ServiceManager.getService("window"));
            try {
                if (sWindowManagerService != null) {
                    ValueAnimator.setDurationScale(
                            sWindowManagerService.getCurrentAnimatorScale());
                }
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
        return sWindowManagerService;
    }
}
複製程式碼

再看W這個類,它繼承自IWindow.Stub,如果看過Activity啟動流程的應該知道,這個W和ApplicationThread一樣的道理啊,我們接著看ViewRootImpl關鍵是的三個方法有沒有mWindowSession和mWindow的呼叫

ViewRootImpl#setView

res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
        getHostVisibility(), mDisplay.getDisplayId(),
        mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
        mAttachInfo.mOutsets, mInputChannel);
複製程式碼

ViewRootImpl#doDie

mWindowSession.finishDrawing(mWindow);
複製程式碼

IWindowSession.aild

interface IWindowSession {
    int add(IWindow window, int seq, in WindowManager.LayoutParams attrs,
            in int viewVisibility, out Rect outContentInsets, out Rect outStableInsets,
            out InputChannel outInputChannel);
    int addToDisplay(IWindow window, int seq, in WindowManager.LayoutParams attrs,
            in int viewVisibility, in int layerStackId, out Rect outContentInsets,
            out Rect outStableInsets, out Rect outOutsets, out InputChannel outInputChannel);
    int addWithoutInputChannel(IWindow window, int seq, in WindowManager.LayoutParams attrs,
            in int viewVisibility, out Rect outContentInsets, out Rect outStableInsets);
    int addToDisplayWithoutInputChannel(IWindow window, int seq, in WindowManager.LayoutParams attrs,
            in int viewVisibility, in int layerStackId, out Rect outContentInsets,
            out Rect outStableInsets);
    void remove(IWindow window);
    
    ...
}
複製程式碼

IWindow.aidl

oneway interface IWindow {
    void executeCommand(String command, String parameters, in ParcelFileDescriptor descriptor);
    void resized(in Rect frame, in Rect overscanInsets, in Rect contentInsets,
            in Rect visibleInsets, in Rect stableInsets, in Rect outsets, boolean reportDraw,
            in MergedConfiguration newMergedConfiguration, in Rect backDropFrame,
            boolean forceLayout, boolean alwaysConsumeNavBar, int displayId);
    void moved(int newX, int newY);
    void dispatchAppVisibility(boolean visible);
    void dispatchGetNewSurface();
    void windowFocusChanged(boolean hasFocus, boolean inTouchMode);
    void closeSystemDialogs(String reason);
    ...
}
複製程式碼

Session#addToDisplay

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

WindowManagerService#addWindow

public int addWindow(Session session, IWindow client, int seq,
       WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
       Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
       InputChannel outInputChannel) {
   int[] appOp = new int[1];
   int res = mPolicy.checkAddPermission(attrs, appOp);
   if (res != WindowManagerGlobal.ADD_OKAY) {
       return res;
   }
   boolean reportNewConfig = false;
   WindowState parentWindow = null;
   long origId;
   final int callingUid = Binder.getCallingUid();
   final int type = attrs.type;
   synchronized(mWindowMap) {
       if (!mDisplayReady) {
           throw new IllegalStateException("Display has not been initialialized");
       }
       
   ...
   }
}

複製程式碼

既然涉及到binder雙向通訊,app端肯定得有handle來處理,果然在ViewRootImpl中定義了ViewRootHandler這個類。

ViewRootHandler

final class ViewRootHandler extends Handler {

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
        case MSG_INVALIDATE:
            ((View) msg.obj).invalidate();
            break;
        case MSG_INVALIDATE_RECT:
            final View.AttachInfo.InvalidateInfo info = (View.AttachInfo.InvalidateInfo) msg.obj;
            info.target.invalidate(info.left, info.top, info.right, info.bottom);
            info.recycle();
            break;
        case MSG_PROCESS_INPUT_EVENTS:
            mProcessInputEventsScheduled = false;
            doProcessInputEvents();
            break;
        case MSG_DISPATCH_APP_VISIBILITY:
            handleAppVisibility(msg.arg1 != 0);
            break;
        case MSG_DISPATCH_GET_NEW_SURFACE:
            handleGetNewSurface();
            break;
        case MSG_RESIZED: {
            // Recycled in the fall through...
            SomeArgs args = (SomeArgs) msg.obj;
            if (mWinFrame.equals(args.arg1)
                    && mPendingOverscanInsets.equals(args.arg5)
                    && mPendingContentInsets.equals(args.arg2)
                    && mPendingStableInsets.equals(args.arg6)
                    && mPendingVisibleInsets.equals(args.arg3)
                    && mPendingOutsets.equals(args.arg7)
                    && mPendingBackDropFrame.equals(args.arg8)
                    && args.arg4 == null
                    && args.argi1 == 0
                    && mDisplay.getDisplayId() == args.argi3) {
                break;
            }
            }
           ... 
   }
}

複製程式碼

畫一張圖示識記憶一下吧。

WindowManager呼叫流程原始碼分析

相關文章