Android中窗口添加的过程

Window的分类

  • 系统级Window : z-ordered为 2000-2999
  • 应用层Window : z-ordered为 1-99
  • 子Window : z-ordered为 1000-1999

根据下面谷歌的Android层级图可以看出来WindowManager是在framework层掌管Window的

从代码上看WindowManager是一个接口,此接口继承自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);
}
@SystemService(Context.WINDOW_SERVICE)
public interface WindowManager extends ViewManager {

    int DOCKED_INVALID = -1;
    int DOCKED_LEFT = 1;
    int DOCKED_TOP = 2;
    int DOCKED_RIGHT = 3;
    int DOCKED_BOTTOM = 4;

    final static String INPUT_CONSUMER_PIP = "pip_input_consumer";
    final static String INPUT_CONSUMER_NAVIGATION = "nav_input_consumer";
    final static String INPUT_CONSUMER_WALLPAPER = "wallpaper_input_consumer";

    public static class BadTokenException extends RuntimeException {
        public BadTokenException() {
        }
        public BadTokenException(String name) {
            super(name);
        }
    }
    public static class InvalidDisplayException extends RuntimeException {
        public InvalidDisplayException() {
        }
        public InvalidDisplayException(String name) {
            super(name);
        }
    }
    public Display getDefaultDisplay();
    public void removeViewImmediate(View view);
    public interface KeyboardShortcutsReceiver {
        void onKeyboardShortcutsReceived(List<KeyboardShortcutGroup> result);
    }
    final int TAKE_SCREENSHOT_FULLSCREEN = 1;
    final int TAKE_SCREENSHOT_SELECTED_REGION = 2;
    public static final String PARCEL_KEY_SHORTCUTS_ARRAY = "shortcuts_array";
    public void requestAppKeyboardShortcuts(final KeyboardShortcutsReceiver receiver, int deviceId);
    @SystemApi
    @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS)
    public Region getCurrentImeTouchRegion();
    public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
        ...
    }

看上面的结构WindowManager内部定义了一些值来定位上下左右的方向,同时又一个LayoutParams

WindowManager的实现者是谁呢?

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

    private IBinder mDefaultToken;
    public WindowManagerImpl(Context context) {
        this(context, null);
    }
    private WindowManagerImpl(Context context, Window parentWindow) {
        mContext = context;
        mParentWindow = parentWindow;
    }
    public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
        return new WindowManagerImpl(mContext, parentWindow);
    }
    public WindowManagerImpl createPresentationWindowManager(Context displayContext) {
        return new WindowManagerImpl(displayContext, mParentWindow);
    }
    public void setDefaultToken(IBinder token) {
        mDefaultToken = token;
    }
    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        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;
            }
        }
    }
    @Override
    public void removeView(View view) {
        mGlobal.removeView(view, false);
    }
    @Override
    public void removeViewImmediate(View view) {
        mGlobal.removeView(view, true);
    }
    @Override
    public void requestAppKeyboardShortcuts(
            final KeyboardShortcutsReceiver receiver, int deviceId) {
        IResultReceiver resultReceiver = new IResultReceiver.Stub() {
            @Override
            public void send(int resultCode, Bundle resultData) throws RemoteException {
                List<KeyboardShortcutGroup> result =
                        resultData.getParcelableArrayList(PARCEL_KEY_SHORTCUTS_ARRAY);
                receiver.onKeyboardShortcutsReceived(result);
            }
        };
        try {
            WindowManagerGlobal.getWindowManagerService()
                .requestAppKeyboardShortcuts(resultReceiver, deviceId);
        } catch (RemoteException e) {
        }
    }
    @Override
    public Display getDefaultDisplay() {
        return mContext.getDisplay();
    }
    @Override
    public Region getCurrentImeTouchRegion() {
        try {
            return WindowManagerGlobal.getWindowManagerService().getCurrentImeTouchRegion();
        } catch (RemoteException e) {
        }
        return null;
    }
}
  1. 实现ViewManager的接口通过WindowManagerGlobal进行代理调用,关于得到Display通过Context代理。
  2. 在构造中传递了两个参数一个是Context我们知道是用于Dispalay的得到,另一个是Window对象看名字是父window,但是在addView和updateViewLayout中调用applyDefaultToken时进行判断要求令牌不能为null并且父Window必须是null此时才将传递进来的ViewGroup.LayoutParams params转化成WindowManager.LayoutParams并且给wparams.token赋值令牌。这说明了只有当令牌不为空,没有父window也就是说必须是子Window才有令牌。

由于上面的信息我们引出了WindowManagerGlobal,由下面代码我们可以知道WindowManagerGlobal是单例,也就是说当前进程的内存中只有一份,也变相说明了就算是持有多个WindowManagerImpl也只要通过进程内唯一的WindowManagerGlobal进行add,remove等操作View,可想而知framework层是对View有个统一管理的,现在我们需要看WindowManagerGlobal内部怎么做。

private WindowManagerGlobal() {
}
public static WindowManagerGlobal getInstance() {
    synchronized (WindowManagerGlobal.class) {
        if (sDefaultWindowManager == null) {
            sDefaultWindowManager = new WindowManagerGlobal();
        }
        return sDefaultWindowManager;
    }
}

我们通过接口进行addView的代码是:

public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);//决定给params.token设没设置令牌
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}

通过传入view,params和通过context得到的Display对象的引用以及父窗口的Window对象。

public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
    ...//进行参数的判空
    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
    //如果父Window不为null则进行调整参数
    if (parentWindow != null) {
        parentWindow.adjustLayoutParamsForSubWindow(wparams);
    } else {
        // 如果不是父window则从应用的硬件加速设置中对应到视图的硬件加速中
        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) {
        //开始监视系统属性的更改
        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列表中找到对应的view
        int index = findViewLocked(view, false);
        if (index >= 0) {
            //如果消亡的列表中有此View,则调用对应ViewRootImpl的doDie进行销毁通知操作
            if (mDyingViews.contains(view)) {
                mRoots.get(index).doDie();
            } else {
                //同一个View不能添加两次
                throw new IllegalStateException("View " + view
                        + " has already been added to the window manager.");
            }
            // The previous removeView() had not completed executing. Now it has.
        }

        //如果视图级别在1000-1999之间的话,并且令牌等于当前传递进来View的令牌则把对应view找出来设置成panelParentView
        //说明令牌和级别掌管着子view的父子关系
        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);
                }
            }
        }
        //通过display信息和context得到一个ViewRootImpl对象
        root = new ViewRootImpl(view.getContext(), display);
        //给添加的View设置params
        view.setLayoutParams(wparams);
        //将view带来的参数添加到如些列表中
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);

        try {
            //给ViewRootImpl设置view,参数以及父面板view
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
            if (index >= 0) {
                removeViewLocked(index, true);
            }
            throw e;
        }
    }
}

WindowManagerImpl.addView的操作主要是对view的管理,将添加到的view和此view带来的一些参数添加到内部列表中,其中过程中生成ViewRootImpl才是关键。每一个添加的View都对应一个ViewRootImpl对象。

ViewRootImpl.java

public ViewRootImpl(Context context, Display display) {
    mContext = context;
    mWindowSession = WindowManagerGlobal.getWindowSession();//得到WMS中对应通信的Session通信Binder类
    mDisplay = display;
    mBasePackageName = context.getBasePackageName();//得到包名
    mThread = Thread.currentThread();//得到当前线程
    mLocation = new WindowLeaked(null);
    mLocation.fillInStackTrace();
    mWidth = -1;
    mHeight = -1;
    mDirty = new Rect();
    mTempRect = new Rect();
    mVisRect = new Rect();
    mWinFrame = new Rect();
    mWindow = new W(this);//生成一个W类
    mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
    mViewVisibility = View.GONE;
    mTransparentRegion = new Region();
    mPreviousTransparentRegion = new Region();
    mFirst = true; // true for the first time the view is added
    mAdded = false;
    //通过session,W,dispaly等信息起到信息汇总收集总用
    mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this, context);
    mAccessibilityManager = AccessibilityManager.getInstance(context);
    mAccessibilityManager.addAccessibilityStateChangeListener(mAccessibilityInteractionConnectionManager, mHandler);
    mHighContrastTextManager = new HighContrastTextManager();
    mAccessibilityManager.addHighTextContrastStateChangeListener(mHighContrastTextManager, mHandler);
    mViewConfiguration = ViewConfiguration.get(context);
    mDensity = context.getResources().getDisplayMetrics().densityDpi;
    mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi;
    mFallbackEventHandler = new PhoneFallbackEventHandler(context);
    mChoreographer = Choreographer.getInstance();
    mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);

    if (!sCompatibilityDone) {
        sAlwaysAssignFocus = true;
        sCompatibilityDone = true;
    }
    loadSystemProperties();
}
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
        if (mView == null) {
            mView = view;
            ...
            requestLayout();
            ...
        }
    }
}

所以当add一次view的时候就会触发requestLayout方法

@Override
public void requestLayout() {
    //初始为false
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;//防止重复绘制
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}
void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }

        performTraversals();

        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false;
        }
    }
}

performTraversals这个才是核心方法

private void performTraversals() {
    ......
    //执行测量操作
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    ......
    //执行布局操作
     performLayout(lp, desiredWindowWidth, desiredWindowHeight);
    ......
    //执行绘制操作
    performDraw();
}

对应以下:

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    if (mView == null) {
        return;
    }
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
    try {
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) {
    final View host = mView;
    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    ......
}
private void performDraw() {
    ......
    draw(fullRedrawNeeded);
    ......
}

最后分别抵达用户的onMesure,onLayout,onDraw

最后一个问题,我们从应用层已经知道尾了,我们如何知道头呢?也就是从哪里调用的addView呢?
这个起源对于Activity来说来自于Activity.attach方法生成了PhoneWindow,如果你还想追溯上去,那就了解Activity的创建过程来源于ActivityThread。ActivityThread会调用handleResumeActivity代表要显示这个Activity的界面了。所以attach调用在handleResumeActivity之前就得生成DecorView

final void handleResumeActivity(IBinder token,
    boolean clearHide, boolean isForward, boolean reallyResume) {
    ..................
    if (r.window == null && !a.mFinished && willBeVisible) {
        //获得当前Activity的PhoneWindow对象
        r.window = r.activity.getWindow();
        //获得当前phoneWindow内部类DecorView对象
        View decor = r.window.getDecorView();
        //设置窗口顶层视图DecorView可见度
        decor.setVisibility(View.INVISIBLE);
        //得当当前Activity的WindowManagerImpl对象
        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) {
            //标记根布局DecorView已经添加到窗口
            a.mWindowAdded = true;
            //将根布局DecorView添加到当前Activity的窗口上面
            wm.addView(decor, l);
    .....................

猜你喜欢

转载自blog.csdn.net/wangwei708846696/article/details/80494351