本文基于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的唯一实现类
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,只要有了高斯模糊那么整个窗口就有了高斯模糊效果
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());
}
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 && !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) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
上边的performResumeActivity会触发Activity#onResume生命周期的回调,然后将DecorView添加到WindowManager
ViewRootImpl
到目前为止,还没有找到ViewRootImpl有关的代码,实际上,ViewRootImpl的创建是在addView的内部创建的,WindowManger的是ViewManager的子接口,其实现类是WindowManagerImpl.java, addView的最终实现是在WindowManagerGlobal类,是WM的一个单例成员对象
ViewRootImpl创建流
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之后就有高斯模糊效果