Android Window源码分析

在Android中所有的视图都是通过Window来呈现的,Window是View的直接管理者,每一个Activity都对应着一个Window,Activity的视图DecorView会被添加到其Window中;另外,如果我们想要实现悬浮窗的效果,那么也离不开Window的开发。Android为我们提供了WindowManager类可以用来管理Window,WindowManager可以通过Activity的getWindowManager()获得,WindowManager实现了ViewManager接口,这个接口有三个方法:addView()、updateViewLayout()、removeView(),通过这三个方法就可以完成Window的添加、更新和删除操作,接下来我们分别看看这三个方法的执行流程。

Window的添加过程

我们通过Activity对Window的处理来分析下Window的添加流程。在启动一个Activity时,会调用到ActivityThread的performLaunchActivity()创建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) {
    ...
    mWindow = new PhoneWindow(this); 
    ...
    mWindow.setWindowManager(
            (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
            mToken, mComponent.flattenToString(),
            (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
    ...
    mWindowManager = mWindow.getWindowManager();
    ...
}

该方法首先会创建一个Window实例并赋值给mWindow,Window 是个抽象的概念, 具体实现类是 PhoneWindow,接着会获取一个WindowManager对象,WindowManager是一个接口类型,具体实现是WindowManagerImpl,最后调用Window.setWindowManager()给Window设置WindowManager对象,如下所示:

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

方法内通过调用WindowManager的createLocalWindowManager()创建了一个新的WindowManager对象并赋值给mWindowManager,该方法内部直接new了一个WindowManagerImpl对象,因此可以知道每个Window都会对应一个WindowManagerImpl对象,方法如下所示:

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

在Activity初始化完成后会调用Activity的onCreate(),而调用onCreate()时会调用setContentView()设置布局,其内部会调用刚刚创建的Window的setContentView(),如下所示:

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

可以看到内部会调用installDecor(),该方法会根据不同的Theme创建不同的DecorView,DecorView 是一个 FrameLayout,setContentView()最终会把布局设置到DecorView中id为content的View上。到这里创建了 PhoneWindow和DecorView,但目前二者也没有任何关系。当Activity的状态变成resume时,最终会调用到ActivityThread的handleResumeActivity(),如下所示:

final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) {
    ActivityClientRecord r = performResumeActivity(token, clearHide);
    if (r != null) {
        final Activity a = r.activity;
        boolean willBeVisible = !a.mStartedActivity;
        ...
        if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow();
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            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) {
                a.mWindowAdded = true;
                wm.addView(decor, l);
            }
        }
        ...
}

该方法首先会调用performResumeActivity()执行Activity的onResume(),接着获取到前面创建的WindowManagerImpl对象并调用其addView()创建DecorView对应的Window,接下来看下WindowManagerImpl的内部实现:

public final class WindowManagerImpl implements WindowManager {
    public void addView(View view, ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

    public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.updateViewLayout(view, params);
    }

    public void removeView(View view) {
        mGlobal.removeView(view, false);
    }
    ...
}

WindowManagerImpl实现了WindowManager的方法,但并没有具体实现具体的操作,而是调用WindowManagerGlobal对应的方法来完成,接下来看一下WindowManagerGlobal的addView():

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
    ...
    root = new ViewRootImpl(view.getContext(), display);
    view.setLayoutParams(wparams);
    mViews.add(view);
    mRoots.add(root);
    mParams.add(wparams);
    ...
    root.setView(view, wparams, panelParentView);
    ...
}

这个过程创建一个ViewRootImpl,并将View、ViewRootImpl以及LayoutParams等参数保存到一个列表中。最后会调用ViewRootImpl的setView(),如下所示:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    ...
    mView = view;
    ...
    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
          getHostVisibility(), mDisplay.getDisplayId(),
          mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
          mAttachInfo.mOutsets, mInputChannel);
    ...
    view.assignParent(this);
    ...
}

该方法首先将传入的View赋值给ViewRootImpl的成员变量mView,这里传入的View是DecorView,这样DecorView就交由ViewRootImpl进行管理了;接着调用mWindowSession.addToDisplay(),mWindowSession是个Binder对象,会与WMS通信由WMS完成Window的创建;最后会调用传入的View的assignParent(),该方法会将View的mParent设为当前的ViewRootImpl,mParent是ViewParent类型,ViewRootImpl和ViewGroup都实现了ViewParent接口,当ViewGroup(如DecorView)调用addView()添加子View时,会将子View的mParent置为该ViewGroup,这样就形成了ViewRootImpl -> DecorView -> ViewGroup -> ... -> View这样的树形结构。ViewParent接口最常见的一个方法是requestLayout(),当调用View或ViewGroup的requestLayout()时,会找到View树的根节点ViewRootImpl,执行ViewRootImpl的requestLayout(),执行该方法时会依次执行performMeasure()、performLayout()、performDraw(),它们的内部会分别调用mView的measure()、layout()、draw(),上面已经说到这里的mView就是DecorView,DecorView会遍历其子View向下传递事件,这样一个View树的工作流程就启动了。
ViewRootImpl可以理解成是WindowManager和DecorView的纽带,它们的关系如下如所示:


28782908-7672-4693-a47e-ee83f717532c.png

接下来看一下WMS部分的处理逻辑,在调用IWindowSession.addToDisplay()后,会调用到Session的addToDisplay(),其内部又调用了WindowManagerService.addWindow(),该方法主要做了以下几件事:

  • 对所要添加的窗口进行权限及类型的检查,如果窗口不满足一些条件,就不会再执行下面的代码逻辑。
  • WindowToken相关的处理,比如有的窗口类型需要提供WindowToken,没有提供的话就不会执行下面的代码逻辑,有的窗口类型则需要由WMS隐式创建默认windowToken。
  • WindowState的创建和相关处理,将WindowToken和WindowState相关联。WindowState存有窗口的所有的状态信息,在WMS中它表示一个窗口。
  • 创建和配置DisplayContent,完成窗口添加到系统前的准备工作。

在创建WindowState时会传入一个IWindow类型的对象作为参数,这是一个Binder对象,IWindow实现是ViewRootImpl的内部类W,IWindow会将WMS中窗口管理的操作回调给ViewRootlmpl,这样一个Window就创建完成了,创建Window过程中涉及的各个对象也都建立起了联系。

根据上面的分析可以知道创建一个Window的核心在于调用WindowManager.addView()并将Window的视图传递进去;接着会为Window创建一个ViewRootImpl,后续Window视图的事件都交由ViewRootImpl进行管理;最后与WMS通信完成Window创建。在Activity的Window创建中视图正是DecorView。而如果我们需要创建一个子窗口,则需要先创建一个View,再调用WindowManager.addView()即可。但是我们看过源码后发现,调用WindowManager的addView()并不会创建一个PhoneWindow类型的对象,那么为什么窗口会创建了?或者说PhoneWindow的职责究竟是什么?
我理解是表示一个窗口的并不是应用内的PhoneWindow对象,而是WMS内的WindowState对象,前文说到了WindowState有窗口的全部状态信息,而且无论是Activity、Dialog的窗口或是我们创建的子窗口,都会调用WindowManager.addView(),其最终会调用到WMS.addWindow()创建WindowState对象用于表示窗口。既然Window并不真正表示一个窗口,那么PhoneWindow的作用是什么呢?

  • PhoneWindow的一个作用是给view包裹上一层DecorView,而DecorView中的布局结构会根据Theme决定。
  • 我们的Activity和Dialog的布局都比较复杂,比如都可能有appbar(toolbar/actionbar)等,通过PhoneWindow来封装下可以更好的解耦代码。

Dialog和Window一样都使用了PhoneWindow封装了DecorView,因此Dialog的样式也会根据Theme决定,但是PopupWindow以及Toast同样是Window,其内部并没有使用PhoneWindow,而是直接通过WindowManager.addWindow()创建的Window,主要原因是PopupWindow和Toast样式相对较简单,无需通过PhoneWindow进行一层封装。

Window的删除过程

在了解了Window的添加过程后,我们依旧是通过Activity对Window的处理来看下Window的删除过程。在Activity销毁时会调用ActivityThread.handleDestroyActivity(),如下所示:

private void handleDestroyActivity(IBinder token, boolean finishing,
        int configChanges, boolean getNonConfigInstance) {
    ActivityClientRecord r = performDestroyActivity(token, finishing,
            configChanges, getNonConfigInstance);
    if (r != null) {
        cleanUpPendingRemoveWindows(r, finishing);
        WindowManager wm = r.activity.getWindowManager();
        View v = r.activity.mDecor;
        if (v != null) {
            ...
            wm.removeViewImmediate(v);
            ...
        }
        ...
}

首先获得了WindowManager以及Activity的DecorView,接着调用WindowManager的removeViewImmediate()并将DecorView作为参数传入,removeViewImmediate()的实现位于WindowManagerImpl中,如下所示:

public void removeView(View view) {
    mGlobal.removeView(view, false);
}

public void removeViewImmediate(View view) {
    mGlobal.removeView(view, true);
}

removeViewImmediate()同样没有具体实现操作,也是直接调用WindowManagerGlobal.removeView(),另外除了removeViewImmediate(),还有前面说到的ViewManager中的removeView(),这两个接口都是用于删除Window并都会调用WindowManagerGlobal.removeView(),但区别在于前者调用时immediate为true,这个字段标识是否要立即销毁Window,我们后面会讲到。接下来看一下WindowManagerGlobal的removeView(),其内部会找到传入的View在列表中的索引并调用removeViewLocked(),如下所示:

private void removeViewLocked(int index, boolean immediate) {
    ViewRootImpl root = mRoots.get(index);
    View view = root.getView();
    if (view != null) {
        InputMethodManager imm = InputMethodManager.getInstance();
        if (imm != null) {
            imm.windowDismissed(mViews.get(index).getWindowToken());
        }
    }
    boolean deferred = root.die(immediate);
    if (view != null) {
        view.assignParent(null);
        if (deferred) {
            mDyingViews.add(view);
        }
    }
}

首先会获取InputMethodManager并调用windowDismissed()来结束Window输入法相关逻辑,接着会调用ViewRootImpl.die()并传入了前文说到的immediate参数,方法如下所示:

boolean die(boolean immediate) {
    if (immediate && !mIsInTraversal) {
        doDie();
        return false;
    }
    if (!mIsDrawing) {
        destroyHardwareRenderer();
    } else {
        Log.e(mTag, "Attempting to destroy the window while drawing!\n" +
                "  window=" + this + ", title=" + mWindowAttributes.getTitle());
    }
    mHandler.sendEmptyMessage(MSG_DIE);
    return true;
}

首先若immediate为true且mIsInTraversal为false则会执行doDie()并返回,immediate是前文传递下来的,若我们执行的是WindowManager.removeViewImmediate()则该值为true,mIsInTraversal会在ViewRootImpl执行performTraversals()时置为true,执行结束后置为false,因此这里的逻辑就是如果要立即执行(immediate为true)且ViewRootImpl不再执行performTraversals()时会执行doDie(),否则会通过Handler发送一个MSG_DIE消息,而Handler在处理这个消息时就会执行doDie(),因此我们看下doDie()的实现,如下所示:

void doDie() {
    ...
    if (mAdded) {
        dispatchDetachedFromWindow();
    }
    ...
    WindowManagerGlobal.getInstance().doRemoveView(this);
}

上述代码主要关注两个地方,首先是要删除的Window如果有子View会调用dispatchDetachedFromWindow()来销毁View,接着调用WindowManagerGlobal的doRemoveView(),我们先看下WindowManagerGlobal的doRemoveView(),如下所示:

void doRemoveView(ViewRootImpl root) {
    synchronized (mLock) {
        final int index = mRoots.indexOf(root);
        if (index >= 0) {
            mRoots.remove(index);
            mParams.remove(index);
            final View view = mViews.remove(index);
            mDyingViews.remove(view);
        }
    }
    if (ThreadedRenderer.sTrimForeground && ThreadedRenderer.isAvailable()) {
        doTrimForeground();
    }
}

可以看到该方法主要是从列表中移除了与要删除的Window对应的View、ViewRootImpl和LayoutParams,这几个列表在前面的添加过程也看到过。接着我们看下dispatchDetachedFromWindow()的实现,代码如下:

void dispatchDetachedFromWindow() {
    ...
    mWindowSession.remove(mWindow);
    ...
}

dispatchDetachedFromWindow()会进行一些资源的释放,接着调用了IWindowSession类型的remove(),IWindowSession在添加过程中已经看到过,它用于与WMS通信,会调用Session.remove(),其内部又会调用WindowManagerService.removeWindow(),该方法会通过Session和IWindow获取到对应的WindowState,并调用WindowState的removeIfPossible(),最终会调用到WindowState的removeImmediately(),该方法主要会对Window涉及的一些资源进行回收与清理,到这里Window的删除过程就完成了。

Window的更新过程

我们要更新Window时,会调用WindowManager.updateViewLayout(),这个方法的实现在WindowManagerImpl中,代码如下所示:

public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.updateViewLayout(view, params);
}

该方法依旧是调用WindowManagerGlobal.updateViewLayout()处理,代码如下所示:

public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
    ...
    WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
    view.setLayoutParams(wparams);
    synchronized (mLock) {
        int index = findViewLocked(view, true);
        ViewRootImpl root = mRoots.get(index);
        mParams.remove(index);
        mParams.add(index, wparams);
        root.setLayoutParams(wparams, false);
    }
}

首先是根据View找到要更新的Window的索引,再根据索引更新列表中LayoutParams,接着调用与Window对应的ViewRootImpl的setLayoutParams(),其内部会调用scheduleTraversals(),如下所示:

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); //1
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

注释1处的代码添加了一个mTraversalRunnable,mTraversalRunnable是TraversalRunnable类型,它实现了Runnable接口,run()里的代码在下一帧渲染时会被执行,我们看下mTraversalRunnable的代码,如下所示:

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

因此在下一帧渲染时会执行doTraversal(),其内部又会调用performTraversals(),performTraversals()其实我们就很清楚了,它会执行View的measure、layout以及draw流程,因此Window里的视图会执行上述的流程,但performTraversals()除了执行View的工作流程外,还会调用relayoutWindow(),代码如下所示:

private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
            boolean insetsPending) throws RemoteException {
    ...
    int relayoutResult = mWindowSession.relayout(
            mWindow, mSeq, params,
            (int) (mView.getMeasuredWidth() * appScale + 0.5f),
            (int) (mView.getMeasuredHeight() * appScale + 0.5f),
            viewVisibility, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
            mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,
            mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame,
            mPendingMergedConfiguration, mSurface);
    ...
}

这里的mWindowSession已经见过很多次了,它是个IWindowSession类型的Binder对象,用于与WMS通信,会调用到Session.relayout(),其内部会调用WindowManagerService的relayoutWindow(),这样就完成了Window的更新操作。

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