本文基于Android 13源码分析

当试图阅读AOSP源码的时候往往无从下手,本文试图从设置窗口高斯模糊效果为目的来梳理Framework中一些重要类的各种关系,比如从Activity根View的创建到window的加载以及和ViewRootImpl的关系,最后在自定义Window中模拟Activity高斯模糊的实现

窗口高斯模糊接口

从Android 12开始, Activity或者Dialog支持窗口的高斯模糊,Android接口如下

//android.view.Window.java
public void setBackgroundBlurRadius(int blurRadius)

在Activity或者Dialog中, 可以直接获取Window对象, 然后调用上面的接口设置高斯模糊

这里的Window对象是: android.view.Window.java 它是一个抽象类

    public Window getWindow() {
        return mWindow;
    }

如果我们通过WindowManager.addView的形式创建一个自定义窗口例如:

        View windowView = LayoutInflater.from(mContext).inflate(R.layout.window, null, false);
        WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
        wm.addView(windowView, lp);

通过查看API发现,无论是WindowManager还是View,都没有提供Window接口,View只提供了一个 AttachInfo的window,明显不是我们需要的

    /**
     * Return the window this view is currently attached to.
     * @hide
     */
    protected IWindow getWindow() {
        return mAttachInfo != null ? mAttachInfo.mWindow : null;
    }

所以,wm创建的窗口是无法直接设置窗口的高斯模糊效果的

Activity类的window对象来源分析

这得从Activity的创建开始分析,在ActivityThread中:

    /**  Core implementation of activity launch. */
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ActivityInfo aInfo = r.activityInfo;
        ...
        Activity activity = null;
    try {
        Application app = r.packageInfo.makeApplicationInner(false, mInstrumentation);

        if (activity != null) {
            CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
            Window window = null;
			...
            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.activityConfigCallback,
                    r.assistToken, r.shareableActivityToken);

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, ActivityConfigCallback activityConfigCallback, IBinder assistToken,
            IBinder shareableActivityToken) {
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    mWindow.setWindowControllerCallback(mWindowControllerCallback);
    mWindow.setCallback(this);

PhoneWindow

activity的window其实是PhoneWindow对象

Framework下全局搜索,也发现系统中PhoneWindow是Window的唯一实现类

sequenceDiagram ActivityThread -->> ActivityThread: performLaunchActivity() ActivityThread ->> Activity: attach() Activity ->> Window: new PhoneWindow Window -->> Activity: mWindow note over Activity,Window:mWindow实现类PhoneWindow

PhoneWindow对高斯模糊的实现

入口函数 setBackgroundBlurRadius

    @Override
    public final void setBackgroundBlurRadius(int blurRadius) {
        super.setBackgroundBlurRadius(blurRadius);
        if (CrossWindowBlurListeners.CROSS_WINDOW_BLUR_SUPPORTED) {
            if (mBackgroundBlurRadius != Math.max(blurRadius, 0)) {
                mBackgroundBlurRadius = Math.max(blurRadius, 0);
                mDecor.setBackgroundBlurRadius(mBackgroundBlurRadius);
            }
        }
    }

内部调用了DecorView的同名函数 setBackgroundBlurRadius

    void setBackgroundBlurRadius(int blurRadius) {
        mOriginalBackgroundBlurRadius = blurRadius;
        if (blurRadius > 0) {
				...
                updateBackgroundBlurRadius();
            }
        }
    }

updateBackgroundBlurRadius


    private void updateBackgroundBlurRadius() {
        if (getViewRootImpl() == null) return;
    mBackgroundBlurRadius = mCrossWindowBlurEnabled && mWindow.isTranslucent()
            ? mOriginalBackgroundBlurRadius : 0;
    if (mBackgroundBlurDrawable == null && mBackgroundBlurRadius > 0) {
        mBackgroundBlurDrawable = getViewRootImpl().createBackgroundBlurDrawable();
        updateBackgroundDrawable();
    }

    if (mBackgroundBlurDrawable != null) {
        mBackgroundBlurDrawable.setBlurRadius(mBackgroundBlurRadius);
    }
}

到这里,可以发现,DecorView最终是有一个 mBackgroundBlurDrawable,设置了高斯模糊参数,DecorView作为顶层FrameLayout View,只要有了高斯模糊那么整个窗口就有了高斯模糊效果

sequenceDiagram Window -->> Window: updateBackgroundBlurRadius Window ->> DecorView:setBackgroundBlurRadius DecorView -->> DecorView:updateBackgroundBlurRadius() DecorView ->> ViewRootImpl:createBackgroundBlurDrawable ViewRootImpl -->> DecorView:mBackgroundBlurDrawable note over DecorView: 生成destDrawable DecorView -->> DecorView:updateBackgroundDrawable DecorView ->> DecorView:setBackground(destDrawable)

DecorView

创建Activity的时候在生命周期首先会调用setContentView, decorView就在此时创建

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

根据前面的分析,getWindow会返回PhoneWindow,进一步调用installDecor函数

    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();
        } 
    private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
        } else {
            mDecor.setWindow(this);
        }

最终创建DecorView, 同时持有Window对象

    protected DecorView generateDecor(int featureId) {
        return new DecorView(context, featureId, this, getAttributes());
    }
sequenceDiagram Activity -->> Activity: onCreate Activity ->> Window: setContentView Window ->> Window: installDecor Window -->> Window: generateDecor note over Window: new DecorView(this) Window -->> Window: generateLayout note over Window:生成 mContentParent Window ->> LayoutInflator:inflate LayoutInflator -->> LayoutInflator: addView note over LayoutInflator:mContentParent.addView将setContentView<br>传入的res对应的View添加到DecorView

Window层次结构

根据上面的流程绘制层次图:

将DecorView添加到WindowManager

上面分析了DecorView的创建,PhoneWindow的创建,View想要显示出来,最终要通过WindowManager.addView添加进去,接下来看看如何把DecorView添加到WindowManager

ActivitytThread

在ActivityThread中,当Activity的生命周期走到 onResume,会将DecorView添加到WM里去

public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
            boolean isForward, boolean shouldSendCompatFakeFocus, String reason) {
        // If we are getting ready to gc after going to the background, well
        // we are back active so skip it.
        unscheduleGcIdler();
        mSomeActivitiesChanged = true;
    // TODO Push resumeArgs into the activity for consideration
    // skip below steps for double-resume and r.mFinish = true case.
    if (!performResumeActivity(r, finalStateRequest, reason)) {
        return;
    }
...
    if (r.window == null &amp;&amp; !a.mFinished &amp;&amp; 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) {
            if (!a.mWindowAdded) {
                a.mWindowAdded = true;
                wm.addView(decor, l);

上边的performResumeActivity会触发Activity#onResume生命周期的回调,然后将DecorView添加到WindowManager

sequenceDiagram ActivityThread -->> ActivityThread:handleResumeActivity ActivityThread -->> ActivityThread:performResumeActivity ActivityThread ->> Activity:onResume() ActivityThread ->> WindowManager:addView(DecorView,lp)

ViewRootImpl

到目前为止,还没有找到ViewRootImpl有关的代码,实际上,ViewRootImpl的创建是在addView的内部创建的,WindowManger的是ViewManager的子接口,其实现类是WindowManagerImpl.java, addView的最终实现是在WindowManagerGlobal类,是WM的一个单例成员对象

classDiagram class ViewManager { + addView() } class WindowManager ViewManager <|-- WindowManager:继承 class WindowManagerImpl{ +addView() } WindowManager <|-- WindowManagerImpl:实现 WindowManagerImpl *-- WindowManagerGlobal:单例成员mGlobal

ViewRootImpl创建流

sequenceDiagram WindowManager ->> WindowManagerGlobal: addView(view,lp) WindowManagerGlobal -->> WindowManagerGlobal: root = new ViewRootImpl note over WindowManagerGlobal: ViewRootImpl对象保存在mRoots列表里边<br>view对象保存在mViews列表里边 WindowManagerGlobal ->> ViewRootImpl:setView()

View和ViewRootImpl的关联

在WindowManagerImpl的addView函数中,ViewRootImpl被创建出来之后,最后调用了ViewRootImpl#setView函数

简要分析下setView函数

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
                ...
                mAttachInfo.mRootView = view;   
                                mAdded = true;
                int res; /* = WindowManagerImpl.ADD_OKAY; */
            // Schedule the first layout -before- adding to the window
            // manager, to make sure we do the relayout before receiving
            // any other events from the system.
            requestLayout();
            ...
            Rect attachedFrame = new Rect();
            final float[] compatScale = { 1f };
            res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
                    getHostVisibility(), mDisplay.getDisplayId(), userId,
                    mInsetsController.getRequestedVisibilities(), inputChannel, mTempInsets,
                    mTempControls, attachedFrame, compatScale);
            ...
            view.assignParent(this);

  • AttachInfo:

attachInfo在ViewRootImpl构造函数中实例化,此时做了一些参数赋值,我们可以View中获取attachInfo,进一步获取ViewRootImpl

  • requestLayout:

这个方法会通过mTraversalRunnable触发performTraversals()函数,进而执行onMeasure,onLayout,onDraw回调;在performTraverslas()函数中会调用dispatchAttachedToWindow 将AttachInfo绑定到View

  • mWindowSession:

将window添加到屏幕,session是一个AIDL接口

经过上面的分析,我们直接通过WindowManager.addView创建一个悬浮窗,没有DecorView没有PhoneWindow,一定有ViewRootImpl

虽然WindowManager类的字段中存在一个Window对象,当我们通过context.getSystemService(WINDOW_SERVICE)获取WindowManager,服务在创建的时候window参数传入了null, 另外查找parentWindow发现这个变量并没有提供给外部使用

        registerService(Context.WINDOW_SERVICE, WindowManager.class,
                new CachedServiceFetcher<WindowManager>() {
            @Override
            public WindowManager createService(ContextImpl ctx) {
                return new WindowManagerImpl(ctx);
            }});
            
    public WindowManagerImpl(Context context) {
        this(context, null /* parentWindow */, null /* clientToken */);
    }

实现高斯模糊

通过反射或者是系统api调用ViewRootImpl#createBackgroundBlurDrawable函数,获取到drawable之后,对其设置高斯模糊系数参数,然后创建一个LayerDrawable对象,设置为添加到window顶层的view的background,添加到window之后就有高斯模糊效果

参考文档

春风花气馥,秋月寒江湛