为了账号安全,请及时绑定邮箱和手机立即绑定

【Android】Window和WindowManager那些事儿

标签:
Android

每一个Activty都包含一个Window对象,Window作为抽象类,具体的处理逻辑都在其子类PhoneWindow中来处理,PhoneWindow将DecorView设置为应用窗口的根View,DecorView继承自FrameLayout,是最顶层视图,DecorView只有一个子元素为LinearLayout。代表整个Window界面,包含通知栏,标题栏,内容显示栏三块区域。


webp

盗张图.png

下面我们先插播一段setContentView的流程,有利于后面对于Window的理解。

1.setContentView的流程

我们在Activity的onCreate里添加布局文件setContentView时,其实在DecorView中默认会添加一个id为content的根布局,我们setContentView的layout正是放在这个布局里面的,下面来分析一下setContentView的流程,我们从这里开始:

@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
}

查看setContentView源码:

@Overridepublic void setContentView(@LayoutRes int layoutResID) {
    getDelegate().setContentView(layoutResID);
}

我们发现getDelegate()方法,它是AppCompatDelegate类型,类似代理的作用,看下getDelegate()的源码:

    @NonNull
    public AppCompatDelegate getDelegate() {        if (mDelegate == null) {
            mDelegate = AppCompatDelegate.create(this, this);
        }        return mDelegate;
    }

点击create继续追踪,最终在AppCompatDelegateImplV9类中找到了setContentView的具体实现逻辑:

    public void setContentView(View v) {
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        contentParent.addView(v);
        mOriginalWindowCallback.onContentChanged();
    }

表面上看着代码量不大,咱们一行一行来看,首先看这个ensureSubDecor()方法,精简版源码如下:

    private void ensureSubDecor() {        if (!mSubDecorInstalled) {
            mSubDecor = createSubDecor();
        }
    }

继续追踪createSubDecor()方法,源码如下:

private ViewGroup createSubDecor() {        //获取Activity的主题
        TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);        //进行一系列的主题匹配,确定主题类型
        if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) {
            requestWindowFeature(Window.FEATURE_NO_TITLE);
        } else if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBar, false)) {
            requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR);
        }
        mWindow.getDecorView();        //拿到inflater,后面要用
        final LayoutInflater inflater = LayoutInflater.from(mContext);
        ViewGroup subDecor = null;        //如果设置的主题是有title的,进入判断逻辑
        if (!mWindowNoTitle) {            if (mIsFloating) {                //mIsFloating表示类似于Dialog这种类型的窗口,inflater对应的系统默认布局
                subDecor = (ViewGroup) inflater.inflate(
                        R.layout.abc_dialog_title_material, null);
                mHasActionBar = mOverlayActionBar = false;
            } else if (mHasActionBar) {
                TypedValue outValue = new TypedValue();
                mContext.getTheme().resolveAttribute(R.attr.actionBarTheme, outValue, true);                //mHasActionBar表示有Actionbar的主题,inflater对应的系统默认布局
                subDecor = (ViewGroup) LayoutInflater.from(themedContext)
                        .inflate(R.layout.abc_screen_toolbar, null);                //根据ID拿到根布局的View
                mDecorContentParent = (DecorContentParent) subDecor
                        .findViewById(R.id.decor_content_parent);
                mDecorContentParent.setWindowCallback(getWindowCallback());
            }
        } else {            if (mOverlayActionMode) {                //Action mode相关
                subDecor = (ViewGroup) inflater.inflate(
                        R.layout.abc_screen_simple_overlay_action_mode, null);
            } else {                //表示no title的情况,进入判断逻辑
                subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
            }
        }        if (mDecorContentParent == null) {
            mTitleView = (TextView) subDecor.findViewById(R.id.title);
        }        //取出ContentFrameLayout也就是一个FrameLayout布局
        final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
                R.id.action_bar_activity_content);        //取出PhoneWindow中id为content的ViewGroup,看后面的代码是把此ViewGroup的子View取出放在contentView中,然后把contentView设置id为android.R.id.content。
        final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);        if (windowContentView != null) {            while (windowContentView.getChildCount() > 0) {                final View child = windowContentView.getChildAt(0);
                windowContentView.removeViewAt(0);
                contentView.addView(child);
            }
            windowContentView.setId(View.NO_ID);
            contentView.setId(android.R.id.content);
        }        //最后把布局subDecor与PhoneWindow绑定
        mWindow.setContentView(subDecor);        return subDecor;
    }

具体逻辑代码中的注释,绕了一大圈,最终调用了mWindow.setContentView(subDecor),进入PhoneWindow的setContentView方法:

    @Override
    public void setContentView(View view) {
        setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }

继续追踪:

@Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {        
        if (mContentParent == null) {            //创建DecorView,并给mContentParent赋值
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            view.setLayoutParams(params);            final Scene newScene = new Scene(mContentParent, view);
            transitionTo(newScene);
        } else {            //把咱们的layout布局add进mContentParent里
            mContentParent.addView(view, params);
        }
        mContentParent.requestApplyInsets();        final Callback cb = getCallback();        //布局添加完成,回调onContentChanged方法
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

有人可能会对mContentParent这个对象有疑问,从上面的代码可以看出mContentParent是在installDecor()方法中赋值的,我们来看下这个方法:

private void installDecor() {
        mForceDecorInstall = false;        if (mDecor == null) {
            mDecor = generateDecor(-1);
        } else {
            mDecor.setWindow(this);
        }        if (mContentParent == null) {            //是在这里进行赋值的
            mContentParent = generateLayout(mDecor);
        }
}

接着看下generateLayout()方法:

public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;protected ViewGroup generateLayout(DecorView decor) {        //省略部分代码
        //注意看这句,通过R.id.content拿到了根布局的ViewGroup
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);        if (contentParent == null) {            throw new RuntimeException("Window couldn't find content container view");
        }
}

至此,咱们在onCreate中通过setContentView的布局view,通过mContentParent.addView(view, params)添加到页面中。

2. WindowManager和Window的关系

public interface WindowManager extends ViewManager {      //一些异常定义
      public static class BadTokenException extends RuntimeException {···}      public static class InvalidDisplayException extends RuntimeException {···}      //该方法会获得这个WindowManager实例将Window添加到哪个屏幕上了,换句话说,就是得到WindowManager所管理的屏幕(Display)
      public Display getDefaultDisplay();      //移除View
      public void removeViewImmediate(View view);      public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {            //定义了一些window相关的属性,包括位置坐标,层级,类型等等。
            public static final int TYPE_STATUS_BAR_SUB_PANEL = FIRST_SYSTEM_WINDOW+17;            public static final int TYPE_POINTER = FIRST_SYSTEM_WINDOW+18;
            ·········
      }
}

WindowManager是一个接口,它的作用就是管理窗口,从代码可以看出,它继承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);
}

哎呀妈呀,这种代码看着真舒服,因为短啊,关于view的增删改三个方法,一目了然,它的实现类为WindowManagerImpl。如果我们想要对Window进行添加和删除等操作,就要使用WindowManager了,具体的工作都是由WindowManagerService(WMS)来处理的,WindowManager和WMS通过Binder来进行跨进程通信。

知道了Window和windowManager,那么这俩货是在什么时候创建的呢?

不用多想,肯定和Activity有关,因为window从属于Activity,关于Activity的启动流程,相信大家都见过下面这张图,虽然android版本一直在升级,但整体流程依然变化不大:


webp

图片来源于网络

从startActivity开始,经过一系列的调用流程,最终会调用ActivityThread类的performLaunchActivity方法,performLaunchActivity又会调用attach方法,我们重点来看下attach方法:

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, ActivityConfigCallback activityConfigCallback) {
        attachBaseContext(context);        //创建window对象PhoneWindow
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        ······        //window和WindowManager建立连接,
        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        mWindowManager = mWindow.getWindowManager();
        mCurrentConfig = config;
    }

如果继续追踪会发现,WindowManager最终会调用mContext.getSystemService(Context.WINDOW_SERVICE)来获取,Context.WINDOW_SERVICE其实就是个String常量,类似key的作用:

public static final String WINDOW_SERVICE = "window";

Context的子类ContextImpl中getSystemService方法如下:

    @Override
    public Object getSystemService(String name) {        return SystemServiceRegistry.getSystemService(this, name);
    }    @Override
    public String getSystemServiceName(Class<?> serviceClass) {        return SystemServiceRegistry.getSystemServiceName(serviceClass);
    }

又调用了SystemServiceRegistry的getSystemService方法:

    /**
     * Gets a system service from a given context.
     */
    public static Object getSystemService(ContextImpl ctx, String name) {
        ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);        return fetcher != null ? fetcher.getService(ctx) : null;
    }

发现最终取值是从SYSTEM_SERVICE_FETCHERS中获取的,而SYSTEM_SERVICE_FETCHERS其实就是个Map集合:

private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =            new HashMap<String, ServiceFetcher<?>>();

其实这里的逻辑是SystemServiceRegistry在初始化的时候会注册系统各种服务,通过registerService方法,并把注册的服务put到map中,以供后续程序使用时获取:

private static <T> void registerService(String serviceName, Class<T> serviceClass,
            ServiceFetcher<T> serviceFetcher) {
        SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
        SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
    }

WindowManager注册的代码如下:

registerService(Context.WINDOW_SERVICE, WindowManager.class,                new CachedServiceFetcher<WindowManager>() {            @Override
            public WindowManager createService(ContextImpl ctx) {                return new WindowManagerImpl(ctx);
            }});

所以我们最终获取到的对象就是WindowManagerImpl对象,那么我们此时回到如下这个地方,可以看到此处会把WindowManagerImpl强转为WindowManager:

mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);

查看Window的setWindowManager方法如下:

public void setWindowManager(WindowManager wm, IBinder appToken, String appName,            boolean hardwareAccelerated) {
        ·····省略代码
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }

会调用WindowManagerImpl的createLocalWindowManager方法去创建mWindowManager,而此处WindowManagerImpl持有了Window的引用,也就具备了操作window的能力:

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

最后我们来看下WindowManagerImpl的部分代码:

public final class WindowManagerImpl implements WindowManager {    //WindowManagerGlobal单例类
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();    private final Context mContext;    //持有window对象
    private final Window mParentWindow;    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);
    }    @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);
    }    @Override
    public void removeView(View view) {
        mGlobal.removeView(view, false);
    }    @Override
    public void removeViewImmediate(View view) {
        mGlobal.removeView(view, true);
    }    @Override
    public Display getDefaultDisplay() {        return mContext.getDisplay();
    }
}

WindowManagerImpl代码其实很少,我们发现它的大部分功能都委托给了WindowManagerGlobal来处理,WindowManagerGlobal是一个单例类,说明在一个进程中只有一个WindowManagerGlobal实例。但是WindowManagerImpl可能会实现多个Window,也就是说在一个进程中WindowManagerImpl可能会有多个实例。
上一张UML关系图:


webp

window-UML.png

3.Window的添加过程

从上面我们可以知道,最终在addView的时候是调用的WindowManagerGlobal的addView方法,我们来下它的代码:

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {

        ···
        ViewRootImpl root;
        View panelParentView = null;
        ···
        root = new ViewRootImpl(view.getContext(), display);
        view.setLayoutParams(wparams);
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);        try {            //ViewRootImpl的setview方法开始绘制view
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
        }
    }
}

这里又出现了一个新类ViewRootImpl,ViewRootImpl是干嘛的呢,点开后我们发现这么一段描述:The top of a view hierarchy, implementing the needed protocol between View
and the WindowManager. 意思就是它是ViewManager和View之间重要的协议,它实现了WindowManagerGlobal中大部分的功能,也就是说WindowManagerGlobal的大部分功能都放在了ViewRootImpl中进行实现。
下面来看下ViewRootImpl的setView方法:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
                requestLayout();                try {
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                } catch (RemoteException e) {
                } 
    }

这里我们只列出了关键代码,requestLayout()表示添加Window之前要进行一次layout布局过程,以确保在收到任何系统事件之后重新布局,这个方法是具体绘制view的地方:

    @Override
    public void requestLayout() {        if (!mHandlingLayoutInLayoutRequest) {            //检查线程
            checkThread();
            mLayoutRequested = true;            //绘制
            scheduleTraversals();
        }
    }    void checkThread() {        if (mThread != Thread.currentThread()) {            throw new CalledFromWrongThreadException(                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

在requestLayout()方法中调用了checkThread()方法,这段异常提示大家应该很熟悉吧,检查当前线程是否一致,我们平时在子线程更新UI就会抛出这个异常,因为一般在子线程操作UI都会调用到view.invalidate,而View的重绘会触发ViewRootImpl的requestLayout,就会去判断当前线程。

接着调用了scheduleTraversals()方法,在scheduleTraversals()方法中会去执行TraversalRunnable任务:

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

然后调用了doTraversal()-->performTraversals(),真正调用绘制的就是performTraversals()这个方法了,通过measure,layout,draw三个过程最终把View绘制出来,所以View的绘制是由ViewRootImpl完成,另外当手动调用invalidate,postInvalidate,requestInvalidate也会最终调用performTraversals,来重新绘制View:

private void performTraversals() {  
        ······
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
        performLayout(lp, desiredWindowWidth, desiredWindowHeight);
        performDraw();
        ······
    }

requestLayout()之后会调用mWindowSession的addToDisplay方法,mWindowSession是IWindowSession类型,它的实现类是Session类:

    @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);
    }

IWindowSession文件其实是个adil文件,core/java/android/view/IWindowSession.aidl,由此可见,addToDisplay是一次IPC的过程,上面的代码最终调用了WindowManagerService的addView方法,最终远程调用把window添加上去。

final WindowManagerPolicy mPolicy = new PhoneWindowManager();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];        //mPolicy实际上是一个PhoneWindowManager类型,在checkAddPermission方法中,首先判断窗口类型是否是系统级别的
        //如果不是系统级别的窗口,则返回一个ADD_OKAY,否则需要SYSTEM_ALERT_WINDOW或者INTERNAL_SYSTEM_WINDOW权限
        int res = mPolicy.checkAddPermission(attrs, appOp);        if (res != WindowManagerGlobal.ADD_OKAY) {            return res;
        }            synchronized(mWindowMap) {            boolean addToken = false;
            WindowToken token = mTokenMap.get(attrs.token);            if (token == null) {                //如果窗口是子窗口
                if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
         //如果是输入法窗口
                if (type == TYPE_INPUT_METHOD) {                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
         //构造WindowToken对象
                token = new WindowToken(this, attrs.token, -1, false);
                addToken = true;
            } else if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
         // 获取应用的AppWindowToken
                AppWindowToken atoken = token.appWindowToken;                if (atoken == null) {                    return WindowManagerGlobal.ADD_NOT_APP_TOKEN;
                } else if (atoken.removed) {                    return WindowManagerGlobal.ADD_APP_EXITING;
                }                if (type == TYPE_APPLICATION_STARTING && atoken.firstWindowDrawn) {                    if (localLOGV) Slog.v(
                            TAG, "**** NO NEED TO START: " + attrs.getTitle());                    return WindowManagerGlobal.ADD_STARTING_NOT_NEEDED;
                }
            } else if (type == TYPE_INPUT_METHOD) {                if (token.windowType != TYPE_INPUT_METHOD) {
                    //如果是输入法窗口,token的windowType必须是ADD_BAD_APP_TOKEN类型
                      return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            } 
            ....           // 在窗口的有效性检查完成之后,为当前窗口创建一个WindowState对象,来维护窗口的状态以及根据适当的机制来调整窗口的状态
           WindowState win = new WindowState(this, session, client, token,
                    attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent);           // 如果客户端已经被销毁
            if (win.mDeathRecipient == null) {                return WindowManagerGlobal.ADD_APP_EXITING;
            }
                              if (outInputChannel != null && (attrs.inputFeatures
                    & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
          //如果输出Channel的读通道为空,则创建通道
                String name = win.makeInputChannelName();
                InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
                win.setInputChannel(inputChannels[0]);
                inputChannels[1].transferTo(outInputChannel);
          //向InputManager中注册通道,以便当前窗口可以接收到事件
                mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle);
            }
        }
}

下面一张图来总结一下WindowManager添加View的调用流程:

webp



作者:寒小枫
链接:https://www.jianshu.com/p/fb23a33863ae


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消