1.三者的关系

一个Activity包含了一个Window对象,这个对象是由PhoneWindow来实现的。PhoneWindow将DecorView作为整个应用窗口的根View,而这个DecorView又将屏幕划分为两个区域:一个是TitleView,另一个是ContentView,而平时所写的布局文件就是展示在ContentView中的。

android dialog window设置详解 android activity window_System

 因此,概括来说就是,Activity管理window,window管理view。

android dialog window设置详解 android activity window_java_02

 从上图可以看出Activity中包含Window,Window包含View。

2.源码分析

Activity.java:
public class Activity extends ContextThemeWrapper implements LayoutInflater.Factory2, Window.Callback, KeyEvent.Callback,OnCreateContextMenuListener, ComponentCallbacks2, Window.OnWindowDismissedCallback, WindowControllerCallback, AutofillManager.AutofillClient {
     ....
    private Window mWindow;
    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) {
    ……
    mWindow = new PhoneWindow(this, window, activityConfigCallback); // 对PhoneWindow进行实例化(Window类是个抽象类,实现类是PhoneWindow)
    mWindow.setWindowControllerCallback( mWindowControllerCallback);
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback( this);
    mWindow.getLayoutInflater().setPrivateFac tory(this);
    ....        
    //调用setWindowManager
    mWindow.setWindowManager( (WindowManager)context.getSystemService( Context.WINDOW_SERVICE), mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
   ....
}
在Activity的attach()方法中创建了Window的实现类PhoneWindow,并通过mWindow.setCallback(this);实现了Window对activity的代理,当window有对应事件产生时activity可以进行相应的处理。
PhoneWindow.java:
public PhoneWindow(Context context, Window preservedWindow, ActivityConfigCallback activityConfigCallback) {
    this(context);
    mUseDecorContext = true;
    if (preservedWindow != null) {
        mDecor = (DecorView) preservedWindow.getDecorView(); // 在PhoneWindow的构造方法中获取DecorView
           ……
    }
}

Window是一个抽象类,用来描述顶层window的外观和行为策略,它的实现类PhoneWindow作为顶层view添加到WindowManager里,提供标准的UI策略,如背景、标题区域、默认key处理等。

有了window后,之后的view都是通过该window进行添加的。

可以看出:Activity并不是一个view controller,因为它没有继承ViewParent或ViewManager接口,它只负责生命周期和处理事件。一个Activity对象包含一个Window对象,在PhoneWindow构造方法中获取DecorView。Activity的行为像一个controller,协调view的显示和添加,并通过回调与view/window进行交互。

在Activity的attach()方法中创建了PhoneWindow后,又调用setWindowManager方法,将系统WindowManager传给PhoneWindow。WindowManager用于对Window进行管理,即对Window的添加、更新和删除的操作。对于Window的操作,最终都是交由WMS来进行处理。

Window.class:
public void setWindoeManager(WindowManager wm, IBinder appToken, String appName) {
    setWindoeManager(wm, appToken, appName, false);
}
public void setWindowManager(WindowManager wm, IBinder appToken, String appName, boolean hardwareAccelerated) {
    ……
    if (wm == null) {     
        wm = (WindowManager)mContext.getSyste mService(Context.WINDOW_SERVICE);//通过Binder机制获取WMS
    }
    mWindowManager = ((WindowManagerImpl) wm).createLocalWindowManager(this);
}
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
    return new WindowManagerImpl(mContext, parentWindow);
}
这个方法创建了WindowManagerImpl对象,这时WindowManager真正和Window关联起来。
 
在Activity的setContentView方法中调用了getWindow().setContentView,即Activity将setContentView的操作交给了PhoneWindow,接下来看其实现过程:
public void setContentView(int layoutResID) {
    if (mContentParent == null) {
        installDecor(); //初始化DecorView和mContentParent
    }
    mLayoutInflater.inflate(layoutResID, mContentParent); //重点,从指定的xml资源展开新的视图层次结构
   ……
}

首先调用installDecor()方法初始化DecorView和mContentParent。然后调用mLayoutInflater.inflate(layoutResID, mContentParent); 从指定的xml资源展开新的视图层次结构,把setContentView方法传入的布局添加到mContentParent中。

首先看installDecor方法:
private void installDecor() {
    mForceDecorInstall = false;
    //初始化mDecor
    if (mDecor == null) {
        mDecor = generateDecor(-1);
        ……
    } else {
        mDecor.setWindow(this);
    }
    //初始化mContentParent
    if (mContentParent == null) {
        mContentParent = generateLayout(mDecor);
        ...
    }
}

可以看出PhoneWindow中默认有一个DecorView(实际上是一个FrameLayout),在DecorView中默认自带一个mContentParent(ViewGroup)。用户自定义的布局是被添加到mContentParent中的,因此经过setContentView之后,PhoneWindow内部的View关系如下所示:

android dialog window设置详解 android activity window_android_03

现在PhoneWindow中只是创建出了一个DecorView,并在DecorView中填充了Activity中传入的layoutId布局,可是DecorView还没有跟Activity建立任何联系,也没有被绘制到界面上显示。那DecorView是何时被绘制到屏幕上的呢?

其实,Activity执行到onCreate时并不可见,只有执行完onResume之后Activity中的内容才是屏幕可见状态。造成这种现象的原因就是,onCreate阶段只是初始化了Activity需要显示的内容,而在onResume阶段(当界面要与用户进行交互时,会调用ActivityThread的handleResumeActivity方法)才会将PhoneWindow中的DecorView真正的绘制到屏幕上。

3.ActivityThread.handleResumeActivity
在ActivityThread的handleResumeActivity中会调用WindowManager的addView方法将DecorView添加到WMS(WindowManagerService) 上:
ActivityThread.java:
public void handleResumeActivity( ActivityClientRecord r, boolean finalStateRequest, boolean isForward, String reason) {
    if(!performResumeActivity(r, finalStateRequest, reason)) {
        return;
    } //注意这里,是执行了performResumeActivity后才执行的wm.addView(decor, l);把decorView添加到WindowManager
    ...
    final Activity a = r.activity;
    if(r.window == null && !a.mFinished && willBeVisible) {
        r.window = r.activity.getWindow(); //从Activity获取对应的window
        View decor = r.window.getDecorView(); //从window获取对应的decorView
        ViewManager wm = a.getWindowManager();
        ...
        if (a.mVisibleFromClient) {
            if (!a.mWindowAdded) {
                a.mWindowAdded = true;
                wm.addView(decor, l);//调用WindowManager的addView方法
            } else {
                a.onWindowAttributesChanged(l);
            }
        }
    }
    ...
}

注意:在ActivityThread的handleResumeActivity方法里,先执行的performResumeActivity后才执行mw.addView(decor),也就是说Activity的onResum方法执行的时候,decorview可能还没有添加到window上,这也就是在onResume里获取view宽高时可能为0的原因。

ActivityThread的handleResumeActivity方法调用了WindowManager的addView()方法,WindowManger的addView()结果有两个:DecorView被渲染绘制到屏幕上显示;DecorView可以接收屏幕触摸事件。

WindowManager是一个接口,同时它实现了ViewManager接口,而WindowManagerImpl实现了WindowManager接口。所以WindowManager的addView方法在WindowManagerImpl里。

WindowManagerImpl.java:
public void addView(View view, ViewGroup.LayoutParams params) {
    mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow, mContext.getUserId());
}
WindowManagerImpl.addView()方法调用了WindowManagerGlobal的addView()方法。
WindowManagerGlobal.java:
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow, int userId) {
    ...
    ViewRootImpl root;
    View panelParentView = null;
    synchronized (mLock) {
        root = new ViewRootImpl(view.getContext(), display);//创建了一个最关键的ViewRootImpl对象
        view.setLayoutParams(wparams);
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);
        //最后执行此操作,因为它会发出消息开始执行操作
        try {
            root.setView(view, wparams, panelParentView, userId);//将view添加到WMS中
        } catch (RuntimeException e) {
        }
    }
}

WindowMangerGlobal是一个单例,在addView方法中创建了一个最关键的ViewRootImpl对象。然后通过root.setView方法将view(也就是decorview)添加到WMS中。

然后看看ViewRootImpl的setView方法:

ViewRootImpl.java:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView, int userId) {
    synchronized (this) {
        if (mView == null) {
            mView = view;
            ...
            int res; 
            requestLayout(); //刷新布局,调用此方法后ViewRootImpl所关联的View会执行measure -> layout ->draw

操作,确保在View被添加到Window上显示到屏幕之前,已经完成测量和绘制操作

mForceDecorViewVisibility = (mWindowAttributes.privateFlags & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
            try {
                ...
                res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame, mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mDisplayCutout, inputChannel, mTempInsets, mTempControls);//将view添加到WMS中
                setFrame(mTmpFrame);
            } catch (RemoteException e) {
            } finally {
                if (restore) {
                    attrs.restore();
                }
            }
        }
    }
}

requestLayout是刷新布局的操作,调用此方法后 ViewRootImpl所关联的View会执行measure -> layout ->draw 操作,确保在View被添加到Window上显示到屏幕之前已经完成测量和绘制操作。

然后调用了mWindowSession的addToDisplay方法,将View添加到WMS中。

其中mWindowSession是new RootViewlmpl对象时传进来的,是通过调用WindowManagerGlobal.getWindowSession()生成的。

public ViewRootImpl(Context context, Display display) {
    this(context, display, WindowManagerGlob al.getWindowSession(),false);
}
调用WindowManagerGlobal.getWindowSession()方法生成了mWindowSession。
WindowSession是WindowManagerGlobal中的单例对象,初始化代码如下:
public static IWindowSession getWindowSession() {
    synchronized(WindowManagerGlobal.class){
        if (sWindowSession == null) {
            IWindowManager windowManager = getWindowManagerService();
            //sWindowSession实际上是IWindowSession类型,是一个Binder类型,真正的实现类是System进程中的Session。用AIDL获取System进程中Session的对象
            sWindowSession = windowManager.openSession(new IWindowSessionCallback.Stub() {
                @Override
               public void onAnimatorScaleChanged( float scale) {
                   ValueAnimator.setDurationScale( scale);
                }
            });
        }
        return sWindowSession;
    }

}

sWindowSession实际上是IWindowSession类型,是一个Binder类型,真正的实现类是System进程中的Session。用AIDL获取System进程中Session对象。

然后看Session.addToDisplay方法:

public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets, Rect outStableInsets, DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel, InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) {
    return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame, outContentInsets, outStableInsets, outDisplayCutout, outInputChannel, outInsetsState, outActiveControls, UserHandle.getUserId(mUid));
}

return mService.addWindow(...);其中mService就是WMS。至此,Window已经成功的被传递给了WMS。剩下的工作就全部转移到系统进程中的 WMS 来完成最终的添加操作。

 

4.相关面试题

在子线程中执行textview.setText()一定会报错吗?

public class MainActivity extends Activity {
    private TextView textview;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        textview = (TextView) findViewById(R.id.textview);
        new MyThread().start(); //开启子线程,在子线程中更新UI
    }
    public class MyThread extends Thread{
        @Override
        public void run() {
            textview.setText("子线程修改的");
        }
    }
}

一般情况下,在子线程中是不能刷新UI的,但是也有例外。

一般activity刷新会导致Window刷新,然后Window刷新带动DecorView刷新,DecorView刷新带动整个View刷新。在这个逻辑里面,如果Window都还没有DecorView,也就是说在整个UI体系里面,这套体系根本就不存在,activity都还没有DecorView,没有DecorView的时候去执行setText,那这个setText的操作当然不能够触发整个activity的view刷新了,也就不会触发checkThread()方法了(ckeckThread方法在ViewRootImpl类中)

由于activity、window、view的关系,刷新UI的时候,整个UI刷新是由window来管理的,而这个window都不存在,activity对应的window都不存在,怎么刷新呢。

同样的,在onResume里开子线程执行setText也没有问题。因为onCreate->onResume这阶段DecorView都还没有跟activity绑定。ActivityThread调用handleResumeActivity,里面会先会通知activity执行onResume,然后再去执行DecorView与activity的绑定。