Activity的Window创建和添加过程-源码分析

之前介绍过Activity的启动过程,一直讲到了Actiivty的oCreate方法执行,Activity的启动过程完成。本文就顺着启动过程的完成,介绍下Activity的Window创建和添加的过程。为什么讲这个呢,因为Activity启动过程完成到onCreate方法的执行,Activity的页面还没有显示出来,而Activity的页面布局的显示,就是由Window、WIndowManager和WindowManagerService这一套机制来管理的,也就是通常说的WMS机制。
在Android系统中,一个Activity通常就表示一个页面,这个页面实际是由Window来管理的。每个Activity都对应着一个Window。Window是一个抽象类,具体实现类是PhoneWindow,它对View进行管理。确切的来讲是,PhoneWindow包含一个DecorView类型的成员,它代表这个Window的顶层View,是一个FrameLayout。DecorView的布局结构包含两部分:标题栏(title)和内容栏(content)。根据设置的主题不同,这两部分也会有不同的呈现。但内容栏是一定存在的,并且它的id是固定的,完整id都是android.R.id.content。

需要说明一下:本文的源码分析是基于Android 8.0版本,也就是API Level26的源码。

Window的创建过程

熟悉Activity启动过程的同学应该都记得,在ActivityThread的performLaunchActivity方法,会调用Activity的attach方法。与Activity相关联的Window对象就是在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, IBinder assistToken) {
    attachBaseContext(context);
    ...
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    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);
    }
    ...
    mWindow.setWindowManager(
            (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
            mToken, mComponent.flattenToString(),
            (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
     ...

    mWindow.setColorMode(info.colorMode);
}

可以看到,在attach方法中创建了一个PhoneWindow对象,并为它设置回调接口Callback和WindowManager。由于Activity类实现了Window.Callback接口,因此当Window接收到相关的事件触发时就会调用Activity的相应方法。Callback接口中的方法很多,有几个是我们比较常见的,比如dispatchTouchEvent、onAttachedToWindow、onDetachedFromWindow等。

Window的添加过程

上面已经介绍了Activity关联的Window对象是在什么时候创建的,接下来,我们看看Activity的Window的添加过程。
在日常开发中,我们都会在Activity的onCreate方法中,通过setContentView来给Activity设置视图布局,来指定页面显示什么内容。
我们需要先看看ActivityThread的performLaunchActivity方法

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ...
        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, r.configCallback);
        ...
        if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }
}

在Activity的attach方法返回后,程序会调用mInstrumentation.callActivityOnCreate方法,而这个方法最终会触发Activity的onCreate回调。而在onCreate中,会调用setContentView方法,开始Activity的Window添加过程。

public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}

Activity的setContentView方法内部调用的mWindow的setContentView方法,这个mWindow对象就是在attach方法中创建的PhoneWindow对象。所以,接下来要看到PhoneWindow的setContentView方法

public void setContentView(int layoutResID) {
    if (mContentParent == null) {
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }


    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                getContext());
        transitionTo(newScene);
    } else {
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
    mContentParentExplicitlySet = true;
}

PhoneWindow的setContentView中,首先判断mContentParent是否存在,否则调用installDecor方法。这个mContentParent指的就是DecorView的内容栏。它的赋值就只有一个地方,就是在installDecor方法中。

private void installDecor() {
    mForceDecorInstall = false;
    if (mDecor == null) {
        mDecor = generateDecor(-1);
        mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        mDecor.setIsRootNamespace(true);
        if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
            mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
        }
    } else {
        mDecor.setWindow(this);
    }
    if (mContentParent == null) {
        mContentParent = generateLayout(mDecor);
    ...
}

如果mDecor为null,就先调用generateDecor方法创建DecorView

protected DecorView generateDecor(int featureId) {
    ...
    return new DecorView(context, featureId, this, getAttributes());
}

DecorView对象创建之后,再判断mContentParent对象是否存在,否则调用generateLayout方法

protected ViewGroup generateLayout(DecorView decor) {
    ...
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    ...
    return contentParent;
}

generateLayout方法的代码很多,我们先只关注相关的重点,generateLayout最后会返回一个ViewGroup对象contentParent,而contentParent的id是ID_ANDROID_CONTENT,它的值就是com.android.internal.R.id.content。这就是DecorView的内容栏id为android.R.id.content的由来,也是Activity的SetContentView方法名的由来。
现在回头看到PhoneWindow的setContentView方法,调用installDecor方法,创建好DecorView和mContentParent之后,会调用 mLayoutInflater.inflate(layoutResID, mContentParent),将Activity的布局视图添加到mContentParent中。然后回调Activity的onContentChanged方法通知Activity,视图已经发生改变。
以上就是,Activity的视图布局已经添加到DecorView的过程。这个过程是在Activity的onCreate方法中完成的。但这个时候,Activity的视图布局还没有显示出来。这是因为,DecorView还没有被WindowManager正式添加到窗口中。这里说的窗口,并不是指WIndow和它的子类PhoneWindow,而是一个抽象的概念。窗口更多表示的是一种抽象的功能集合。虽然在Activity的attach方法中,WIndow对象就已经被创建了,但这个时候由于DecorView并没有被WindowManager识别,所以此时的WIndow无法提供具体功能,因为它还无法接受外界的输入信息。我们知道,在Activity执行onResume方法之后,视图才能完全显示,并和用户正常交互,那onResume方法是在什么地方回调了呢,熟悉Activity启动过程的同学,应该会有印象,在ActivityThread的handleLaunchActivity方法中,

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
    ...
    Activity a = performLaunchActivity(r, customIntent);
    if (a != null) {
    r.createdConfig = new Configuration(mConfiguration);
    reportSizeConfigurations(r);
    Bundle oldState = r.state;
    handleResumeActivity(r.token, false, r.isForward,
            !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
    ...
}

执行完performLaunchActivity方法返回一个Activity的实例,接下来判断如果创建的Activity实例不为null,就会执行handleResumeActivity方法

final void handleResumeActivity(IBinder token,
        boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
    ...
    r = performResumeActivity(token, clearHide, reason);
    if (r != null) {
        final Activity a = r.activity;
        ...
        if (r.activity.mVisibleFromClient) {
            r.activity.makeVisible();
        }
    ...
}

在handleResumeActivity方法中,会调用performResumeActivity方法,这个方法中又经过层层调用,最终会回调Activity的onResume方法。这就是Activity的onResume方法的回调时机。这里不是本文的重点,就没有列出相关的源码。
handleResumeActivity中,在performResumeActivity方法执行之后,会调用Activity的makeVisible方法

void makeVisible() {
    if (!mWindowAdded) {
        ViewManager wm = getWindowManager();
        wm.addView(mDecor, getWindow().getAttributes());
        mWindowAdded = true;
    }
    mDecor.setVisibility(View.VISIBLE);
}

在makeVisible方法中,会调用WindowManager的addView方法,将DecorView正式添加到窗口中。同时DecorView设置为可见。
这里先介绍下WindowManager,它是一个接口,继承自ViewManager。从ViewManager接口继承了三个方法addView、updateViewLayout和removeView。这三个方法都与窗口管理直接相关。而WindowManager的实现类是WindowManagerImpl。所以Activity的makeVisible方法中,调用的实际上是WindowManagerImpl的addView方法

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

在WindowManagerImpl的addView方法内部,调用的是WindowManagerGlobal的addView方法。WindowManagerImpl通过桥接模式,将功能实现委托给了WindowManagerGlobal。WindowManagerGlobal是一个单例,说明一个进程中只有一个WindowManagerGlobal实例。而每一个Window都会有一个相关联的WindowManagerImpl实例。WindowManagerGlobal的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");
    }
    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
    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;
    ...
        int index = findViewLocked(view, false);
        if (index >= 0) {
            if (mDyingViews.contains(view)) {
                // Don't wait for MSG_DIE to make it's way through root's queue.
                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 (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);
                }
            }
        }
        root = new ViewRootImpl(view.getContext(), display);
        view.setLayoutParams(wparams);
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);
        try {
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
            // BadTokenException or InvalidDisplayException, clean up.
            if (index >= 0) {
                removeViewLocked(index, true);
            }
            throw e;
        }
    }
}

WindowManagerGlobal的addView方法主要完成三个步骤:

  1. 检查参数是否合法,如果是子Window还需要调整一些布局参数
  2. 创建ViewRootImpl,并将传进来的View添加到mViews列表里
  3. 通过ViewRootImpl来更新界面并完成WIndow的添加过程。

上来的代码中,涉及到WindowManagerGlobal类几个重要的列表

private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams =
        new ArrayList<WindowManager.LayoutParams>();
private final ArraySet<View> mDyingViews = new ArraySet<View>();

简单说明一下,mViews存放的是所有窗口所对应的View,比如Activity的窗口所对应的DecorView。mRoots存放的是所有窗口所对应的ViewRootImpl对象。mParams存放的是所有窗口所对应的布局参数。而在这三个列表中,索引值相同的一组View、ViewRootImpl和LayoutParam对象,可以看做是一个窗口的概念。mDyingViews则存储了正在被删除的View,或者说是那些已经调用了removeView方法但删除操作还没有完成的Window对象。
View和LayoutParams应该都不会陌生,关于ViewRootImpl,熟悉View绘制机制的同学,也会对它有印象。通俗的说法是,一个页面的绘制流程,就是从ViewRootImpl开始。而实际上ViewRootImpl承担的职责包括以下几点:

  • 作为View层级(View hierarchy)的顶层(top-level)管理View层级
  • 触发View的测量、布局和绘制
  • 输入事件的中转站
  • 负责与WMS进程间通信
    介绍完WindowManagerGlobal中这几个重要的列表之后,我们接着看addView方法。将View、ViewRootImpl和LayoutParams对象分别添加到列表之后,会调用ViewRootImpl的setView方法
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    ...
    requestLayout();
    ...
    try {
    mOrigWindowType = mWindowAttributes.type;
    mAttachInfo.mRecomputeGlobalAttributes = true;
    collectViewAttributes();
    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
            getHostVisibility(), mDisplay.getDisplayId(),
            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
            mAttachInfo.mOutsets, mInputChannel);
    ...
}

setView方法的代码很多,本文中暂时只关注两点。
调用requestLayout方法会触发View层级的绘制遍历。这个对于了解View的绘制过程的同学肯定不陌生。简单介绍下,requestLayout方法内部会调用scheduleTraversals方法。scheduleTraversals方法实际上就是View绘制过程的入口。
然后会调用mWindowSession对象的addToDisplay方法,mWindowSession的类型是IWindowSession,它是一个Binder对象,用于进程间通信,IWindowSession是Client端的代理。它的Server端实现是Session。此前的代码都是运行在Actiivty所在的app进程,而Session的addToDisplay方法则是运行在WMS所在的SystemServer进程中。


image.png

从上图中可以看出,app进程中的ViewRootImpl对象要想与WMS进行通信需要经过Session,确切的说,通过调用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) {
    return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
            outContentInsets, outStableInsets, outOutsets, outInputChannel);
}

addToDisplay方法内部,直接调用了mService的addWindow方法,并将Session对象本身作为第一个参数传进去。这个mService就是WMS的实例。每一个app进程都会对应一个Session对象,它用来表示app进程与WMS的通信渠道。WMS会用ArrayList来存放这些Session对象。接下来的工作就交给WMS来处理。WMS会为这个要添加的窗口分配Surface,并确定窗口的显示次序,而真正负责显示界面视图的是画布Surface,而不是窗口本身。WMS会将它所管理的Surface交由SurfaceFlinger处理,SurfaceFlinger会将这些Surface混合并绘制到屏幕上。
WindowManagerService的addWindow方法代码非常多,有兴趣的同学可以自行查看源码。这里只是概括性的介绍addWindow方法中主要做了四件事:

  • 对要添加的窗口进行检查,确保窗口和一些参数满足条件,否则就直接返回相应的提示信息,不再往下执行。
  • WindowToken的相关处理,比如有的窗口类型需要提供WindowToken,没有提供的话也会直接返回相应的提示信息,不再往下执行。有的窗口类型需要由WMS隐式创建WindowToken。
  • WindowState的创建和相关处理,将WIndowToken和WindowState相关联。
  • 创建和配置DisplayContent,完成窗口添加到系统前的准备工作。

关于WindowToken
要了解WindowToken,首先要了解ActivityRecord的内部类Token,它继承自IApplicationToken.Stub。很明显,它是基于Binder机制。Binder除了用于跨进程通信之外,另一个用途就是在多个进程中标识同一个对象,这里的Token主要是用于后者。一个Token对象(ActivityRecord的成员变量appToken)标识了一个ActivityRecord对象。而WindowToken中,IBinder类型token对象标识的也是ActivityRecord中的这个Token对象appToken。有兴趣的朋友可以由此去了解。

关于Surface、Window和View概念的理解
以下是由安卓framework开发者Dianne Hackborn在stackoverflow给出的关于Window和View的定义,由此可以更好的理解WIndow和View的关系
A Surface is an object holding pixels that are being composited to the screen. Every window you see on the screen (a dialog, your full-screen activity, the status bar) has its own surface that it draws in to, and Surface Flinger renders these to the final display in their correct Z-order. A surface typically has more than one buffer (usually two) to do double-buffered rendering: the application can be drawing its next UI state while the surface flinger is compositing the screen using the last buffer, without needing to wait for the application to finish drawing.
A window is basically like you think of a window on the desktop. It has a single Surface in which the contents of the window is rendered. An application interacts with the Window Manager to create windows; the Window Manager creates a Surface for each window and gives it to the application for drawing. The application can draw whatever it wants in the Surface; to the Window Manager it is just an opaque rectangle.
A View is an interactive UI element inside of a window. A window has a single view hierarchy attached to it, which provides all of the behavior of the window. Whenever the window needs to be redrawn (such as because a view has invalidated itself), this is done into the window's Surface. The Surface is locked, which returns a Canvas that can be used to draw into it. A draw traversal is done down the hierarchy, handing the Canvas down for each view to draw its part of the UI. Once done, the Surface is unlocked and posted so that the just drawn buffer is swapped to the foreground to then be composited to the screen by Surface Flinger.

到此,本文对Activity的Window创建和添加过程分析就讲到这里。

本文参考
《Android开发艺术探索》
《Android进阶解密》
深入理解Activity——Token之旅
https://stackoverflow.com/questions/4576909/understanding-canvas-and-surface-concepts#answers

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容