Window窗口零碎知识点

作者: 小阿拉 | 来源:发表于2017-12-19 21:09 被阅读0次

    其实我对这块内容存在很多不懂的地方,所以看源码看博客的时候就跟个好奇宝宝,这边看看那边瞅瞅,觉得这个重要,那个也重要。这篇文章记录的主要是我自己之前没注意的点,参考了很多博客,想到哪写到哪,感觉写的不够系统,对小白来说不太友好,大家选择性的看。如果对大家有帮助,那是极欢喜的。

    1、Activity的职责中并不包含视图控制,真正实现视图控制的是Window,Activity只是控制生命周期和事件处理。一个Activity包含了一个Window,而当Activity不包含Window时,就相当于Service了。这个Window换成中文说法叫做窗口。窗口可泛指我们能看到的界面。

    2、Android 的窗口分为三种:
    Application windows (ranging from FIRST_APPLICATION_WINDOW to LAST_APPLICATION_WINDOW) are normal top-level application windows. For these types of windows, the token must be set to the token of the activity they are a part of (this will normally be done for you if token is null).应用程序窗口 : 包括所有应用程序自己创建的窗口,以及在应用起来之前系统负责显示的窗口。

    Sub-windows (ranging from FIRST_SUB_WINDOW to LAST_SUB_WINDOW) are associated with another top-level window. For these types of windows, the token must be the token of the window it is attached to.子窗口:比如应用自定义的对话框,或者输入法窗口,子窗口必须依附于某个应用窗口。

    System windows (ranging from FIRST_SYSTEM_WINDOW to LAST_SYSTEM_WINDOW) are special types of windows for use by the system for specific purposes. They should not normally be used by applications, and a special permission is required to use them.系统窗口:系统设计的,不依附于任何应用的窗口。比如说,状态栏(Status Bar), 导航栏(Navigation Bar), 壁纸(Wallpaper), 来电显示窗口(Phone),锁屏窗口(KeyGuard), 信息提示窗口(Toast), 音量调整窗口,鼠标光标等等。

    应用窗口对应一个Activity,子窗口不能单独存在,需要附属在父Window上,比如常用的Dialog。系统窗口是需要声明权限再创建的window。window有z-ordered属性,层级越大,越在顶层。应用窗口层级1-99,子窗口1000-1999,系统窗口2000-2999。这此层级对应着windowManager的type参数。

    3、几个Window相关的类

    Window类位于 /frameworks/base/core/java/android/view/Window.java,该类是一个抽象类,提供了绘制窗口的一组通用API。可以将之理解为一个载体,各种View在这个载体上显示。

    那Activity和Window是怎么结合的呢?那就得从Activity的创建开始说起了,Activity的创建最后会调用ActivityThread的performLaunchActivity()方法:

     private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
                ...
                 //创建一个Context对象
                 Context appContext = createBaseContextForActivity(r, activity);
                 ...
                 //将上面创建的appContext传入到activity的attach方法[1],进行Context与Activity进行绑定
                 activity.attach(appContext, this, getInstrumentation(), r.token,
                         r.ident, app, r.intent, r.activityInfo, title, r.parent,
                         r.embeddedID, r.lastNonConfigurationInstances, config,
                         r.referrer, r.voiceInteractor, window);
                ...
                //调用activity.oncreate
                mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                ...
                //调用Activity的onstart方法
                activity.performStart();
                ...
                //调用activitu的OnRestoreInstanceState方法进行Window数据恢复 
                mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
                                        r.persistentState);
                ...
    }
    

    [1]处我们点进去看Activity的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) {
            //进行Context与Activity进行绑定 [2]
            attachBaseContext(context);
    
            mFragments.attachHost(null /*parent*/);
            //在当前Activity创建Window
            mWindow = new PhoneWindow(this, window);[3]
            mWindow.setWindowControllerCallback(this);
            mWindow.setCallback(this);
            mWindow.setOnWindowDismissedCallback(this);
            mWindow.getLayoutInflater().setPrivateFactory(this);
            if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
                mWindow.setSoftInputMode(info.softInputMode);
            }
            if (info.uiOptions != 0) {
                mWindow.setUiOptions(info.uiOptions);
            }
            ...
            //为Window设置WindowManager [4]
            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());
            }
            //通过getWindowManager就可以得到WindowManager实例 [5]
            mWindowManager = mWindow.getWindowManager();
            mCurrentConfig = config;
        }
    

    在我之前的Context及其子类源码分析(一)中有分析过:每个Activity是在ActivityThread类中的performLaunchActivity方法中开始被创建的,紧接着被实例的activity会实例一个ContextImpl对象,通过attach方法,最终将这个ContextImpl对象赋给Activity的父类ContextWrapper(Activity实际继承了ContextThemeWrapper ,而ContextThemeWrapper 继承自ContextWrapper )中的成员变量mBase对象。[2]处的代码就是做的赋值作用。

    文章的开头我们说过,一个Activity包含了一个Window,我们对应Activity的attach()方法的[3]处看看。Window窗口被创建了,而且[4]处Window与WindowManager绑定。

    mWindow是Activity的一个成员变量,而mWindow是一个实例的PhoneWindow对象。我们点进setWindowManager()方法:

     mWindow.setWindowManager(
                    (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                    mToken, mComponent.flattenToString(),
                    (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
    
    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
                boolean hardwareAccelerated) {
            ...
            if (wm == null) {
                wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
            }
            mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
        }
    

    我们还没讲WindowManager是什么,就又出现了WindowManagerImpl。看了WindowManager的代码,发现是个接口。如果你稍微经常看源码,涉及Activity啊Service啊Window这一块的,自然而然地就会想,是不是有Impl结尾的实现类。正好也是有WindowManagerImpl类,大概猜到他实现了WindowManager接口。再点进去WindowManagerImpl类看源码:

    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, mContext.getDisplay(), mParentWindow);
        }
        ...
        @Override
        public void removeView(View view) {
            mGlobal.removeView(view, false);
        }
        ...
    }
    

    发现WindowManagerImpl类里有addView()、removeView()的方法。其实我们通过WindowManager这个名字,可以猜到,它的作用是管理Window的。那这边的addView()、removeView()方法,十有八九就是添加删除View了。而且在WindowManager中还有一个重要的静态类LayoutParams.通过它可以设置和获得当前窗口的一些属性。

    public interface WindowManager extends ViewManager {
            ...
            public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
            /**
             * X position for this window.  With the default gravity it is ignored.
             * When using {@link Gravity#LEFT} or {@link Gravity#START} or {@link Gravity#RIGHT} or
             * {@link Gravity#END} it provides an offset from the given edge.
             */
                  @ViewDebug.ExportedProperty
                  public int x;
    
            /**
             * Y position for this window.  With the default gravity it is ignored.
             * When using {@link Gravity#TOP} or {@link Gravity#BOTTOM} it provides
             * an offset from the given edge.
             */
                  @ViewDebug.ExportedProperty
                  public int y;
                  ...
            }
    }
    

    那因为其实在WindowManager接口中,并没有看到addView()、removeView()方法,所以去看看WindowManager的父类:

    /** Interface to let you add and remove child views to an Activity. To get an instance
      * of this class, call {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.
      */
    public interface ViewManager
    {
        public void addView(View view, ViewGroup.LayoutParams params);
        public void updateViewLayout(View view, ViewGroup.LayoutParams params);
        public void removeView(View view);
    }
    

    瞅瞅瞅瞅,英文注释讲的是ViewManager接口是让你添加和移除activity中View的,可以通过Context.getSystemService()获取实例。

    我们又反过去看前面WindowManagerImpl的代码,其实会发现,WindowManagerImpl并没有直接实现Window的三大操作,而是全部交给了WindowManagerGlobal来处理。形式类似于代理,WindowManagerImpl中实例了WindowManagerGlobal,不同的是WindowManagerGloba并没有实现WindowManager类,而是自己定义了套实现。

    出个UML图表示一下这几个类的关系:


    4、PhoneWindow中有三个setContentView方法。

    第一种,也是我们常用的一种setContentView(int layoutResID):

    @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.初始,mContentParent为空,创建DecorView。
            if (mContentParent == null) {
                installDecor();
            } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
                //移除该mContentParent内所有的所有子View。
                mContentParent.removeAllViews();
            }
    
            if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
                final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                        getContext());
                transitionTo(newScene);
            } else {
                //关键地方!!!inflate自定义layout,并将mContentParent作为其根视图。
                mLayoutInflater.inflate(layoutResID, mContentParent);
            }
            mContentParent.requestApplyInsets();
            final Callback cb = getCallback();
            if (cb != null && !isDestroyed()) {
                cb.onContentChanged();
            }
            mContentParentExplicitlySet = true;
        }
    

    上面展示的代码可以简单分为三个步骤:
    1.如果没有DecorView,在installDecor中的generateDecor()创建DecorView。
    2.将view添加到decorview中的mContentParent中。
    3.回调Activity的onContentChanged接口。
    经过以上操作,DecorView创建了,但还没有正式添加到Window中。

    第二种:

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

    第三种:

    @Override
        public void setContentView(View view, ViewGroup.LayoutParams params) {
            // 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.初始,mContentParent为空,创建DecorView。
            if (mContentParent == null) {
                installDecor();
            } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
                //移除该mContentParent内所有的所有子View。
                mContentParent.removeAllViews();
            }
    
            if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
                view.setLayoutParams(params);
                final Scene newScene = new Scene(mContentParent, view);
                transitionTo(newScene);
            } else {
                //关键地方!!!对比一下第一种setContentView(int layoutResID)方法,因为第一种方法还没解析xml为view,要在方法内调用转换方法。
                //而第三种setContentView(View view, ViewGroup.LayoutParams params) 方法的参数传入的是view对象。
                mContentParent.addView(view, params);
            }
            mContentParent.requestApplyInsets();
            final Callback cb = getCallback();
            if (cb != null && !isDestroyed()) {
                cb.onContentChanged();
            }
            mContentParentExplicitlySet = true;
        }
    

    对比第一种和第三种setContentView方法,可以说代码几乎是一模一样,最大的区别就是我代码注释里写的关键地方。除了说方便多种方式程序员调用外,在使用这两个方法的时候有没有什么需要注意的地方?当然有,如果你对此不了解的话,有可能出Bug哦。
    这篇文章尾部有给一个实践的例子,大家可以看看,来加深印象。Android完美解析setContentView 你真的理解setContentView吗?

    我简单总结一下:
    setContentView(int layoutResID)这个方法通过反射拿到我们的view,但要注意的是每次反射拿到的view都不是同一个view,那么如果你通过findviewById拿控件,控件也是不同的控件了。而setContentView(View view, ViewGroup.LayoutParams params)方法法的view是已经确定了的,不存在不同view的问题。

    我们接着看installDecor()的代码:

    private void installDecor() {
       if (mDecor == null) {
         //new一个DecorView
         mDecor = generateDecor();
         mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
         mDecor.setIsRootNamespace(true);
         if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
           mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
         }
       }
       if (mContentParent == null) {
         //重要代码!!!这一步会设置窗口的修饰文件,并将id为ID_ANDROID_CONTENT的view find出来作为返回值赋值给mContentParent
         mContentParent = generateLayout(mDecor);
            ...
    }
    

    页面都是依附在窗口之上的,而DecorView即是窗口最顶层的视图。什么意思呢?DecorView是整个ViewTree的最顶层View,它是一个FrameLayout布局,代表了整个应用的界面。在该布局下面,有标题view和内容view这两个子元素,而内容view则是上面提到的mContentParent。

    public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
    }
    
    protected ViewGroup generateLayout(DecorView decor) {
    //...
    
    //省略一些设置Window样式的代码,直接来看我们最关心的代码!
     ViewGroup contentParent =(ViewGroup)findViewById(ID_ANDROID_CONTENT);
                        //...           
                        return contentParent;
                    }
             }
    

    Activity在onCreate之前调用attach方法,在attach方法中会创建window对象。window对象创建时并没有创建Decor对象。用户在Activity中调用setContentView,然后调用window的setContentView,这时会检查DecorView是否存在,如果不存在则创建DecorView对象,然后把用户自己的View 添加到DecorView中。最后,当AMS(ActivityManagerService)准备resume一个Activity时,会回调该Activity的handleResumeActivity()方法,该方法会调用Activity的makeVisible方法 ,显示我们刚才创建的DecorView 视图族。

    //ActivityThread类里面
    final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) {  
        ActivityRecord r = performResumeActivity(token, clearHide);  
        //...  
         if (r.activity.mVisibleFromClient) {  
             r.activity.makeVisible();  
         }  
    }
    
    void makeVisible() {  
        if (!mWindowAdded) {  
            ViewManager wm = getWindowManager();   // 获取WindowManager对象  
            wm.addView(mDecor, getWindow().getAttributes());  
            mWindowAdded = true;  
        }  
        mDecor.setVisibility(View.VISIBLE); //使其处于显示状况  
    }
    

    5、将DecorView添加至Window

    在“3、几个Window相关的类”中我们讲到WindowManagerGlobal的addView方法。

    public void addView(View view, ViewGroup.LayoutParams params,
                Display display, Window parentWindow) {
            ...
    
            ViewRootImpl root;
            View panelParentView = null;
    
            synchronized (mLock) {
                ...
                //实例化了ViewRootImpl类
                root = new ViewRootImpl(view.getContext(), display); 
    
                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 {
                //调用ViewRootImpl#setView方法,并把DecorView作为参数传递进去,在这个方法内部,
                //会通过跨进程的方式向WMS(WindowManagerService)发起一个调用,
                //从而将DecorView最终添加到Window上,在这个过程中,ViewRootImpl、DecorView和WMS会彼此关联
                root.setView(view, wparams, panelParentView); 
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                synchronized (mLock) {
                    final int index = findViewLocked(view, false);
                    if (index >= 0) {
                        removeViewLocked(index, true);
                    }
                }
                throw e;
            }
        }
    

    **最后通过WMS调用ViewRootImpl的performTraverals方法开始View的测量、布局、绘制流程。

    6、Window有必要存在吗?
    Window能做的事情,View对象基本都能做:像什么触摸事件啊、显示的坐标及大小啊、管理各个子View啊等等。View已经这么强大了,为什么还多此一举,加个Window。可能有人会说因为WindowManager管理的就是Window对象呀,那么既然这样,Android系统直接让WindowManager去管理View不就好了?

    一个Window对象代表一块显示区域,系统不关心Window里面具体的绘制内容,也不管你Window怎么去绘制。换句话说,站在系统的角度上看,系统是“不知道”有View对象这个说法的。Window为了绘制出用户想要的组件(按钮、文字、输入框等等),系统又不给我!没事,那我自己定义,于是就定义了View机制,给每个View提供Canvas,让不同的View自己绘制具有自己特色的组件。同时,为了更好的管理View,通过定义ViewGroup,等等。

    参考:
    理清Activity、View及Window之间关系
    Android窗口机制(三)Window和WindowManager的创建与Activity

    相关文章

      网友评论

        本文标题:Window窗口零碎知识点

        本文链接:https://www.haomeiwen.com/subject/khlaixtx.html