android WMS服务

android WMS服务

WMS的定义

窗口的分类

WMS的启动

WindowManager

Activity、Window、DecorView、ViewRootImpl 之间的关系

WindowToken


WMS的定义

WMS是WindowManagerService的简称,它是android系统的核心服务之一,它在android的显示功能中扮演着极为重要的角色。一般来说,WMS具有以下四个重要的功能:

  • 窗口管理:负责响应进程的添加、移除窗口、启动窗口的业务,以及管理窗口的坐标、层级、大小、令牌等属性。
  • 窗口动画:负责处理窗口切换时的动画效果。
  • 事件处理:负责处理系统按键、触摸事件给合适的窗口去处理,以及处理部分输入法的交互逻辑。
  • Surface管理:为所有window分配合适的surface,并将排序后的surface交给SurfaceFlinger做进一步的显示工作。

窗口的分类

应用窗口,层级:1~99

子窗口,层级:1000~1999

系统窗口,层级:2000~2999

其中应用窗口层级最低,范围在1~99,系统窗口层级最高,2000~2999,层级越高,意味着越靠近用户,高层级的窗口会覆盖底层级的窗口。

WMS的启动

WMS和AMS,PKMS一样,都是由SystemServer进程启动的,我们看一下代码:

private void startOtherServices(@NonNull TimingsTraceAndSlog t) {
    ...
    wm = WindowManagerService.main(context, inputManager, !mFirstBoot, mOnlyCore,
            new PhoneWindowManager(), mActivityManagerService.mActivityTaskManager);
    ServiceManager.addService(Context.WINDOW_SERVICE, wm, /* allowIsolated= */ false,
            DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PROTO);
    ServiceManager.addService(Context.INPUT_SERVICE, inputManager,
            /* allowIsolated= */ false, DUMP_FLAG_PRIORITY_CRITICAL);
    t.traceEnd();

    t.traceBegin("SetWindowManagerService");
    mActivityManagerService.setWindowManager(wm);
    t.traceEnd();

    t.traceBegin("WindowManagerServiceOnInitReady");
    wm.onInitReady();
    t.traceEnd();
    ...
}

通过上面代码可知,通过main方法启动服务,然后注册到ServiceManager中,

我们继续跟踪到main方法:

public static WindowManagerService main(final Context context, final InputManagerService im,
                                        final boolean showBootMsgs, final boolean onlyCore, WindowManagerPolicy policy,
                                        ActivityTaskManagerService atm) {
    return main(context, im, showBootMsgs, onlyCore, policy, atm,
            SurfaceControl.Transaction::new, Surface::new, SurfaceControl.Builder::new);
}


@VisibleForTesting
public static WindowManagerService main(final Context context, final InputManagerService im,
                                        final boolean showBootMsgs, final boolean onlyCore, WindowManagerPolicy policy,
                                        ActivityTaskManagerService atm, Supplier<SurfaceControl.Transaction> transactionFactory,
                                        Supplier<Surface> surfaceFactory,
                                        Function<SurfaceSession, SurfaceControl.Builder> surfaceControlFactory) {
    DisplayThread.getHandler().runWithScissors(() ->
            sInstance = new WindowManagerService(context, im, showBootMsgs, onlyCore, policy,
                    atm, transactionFactory, surfaceFactory, surfaceControlFactory), 0);
    return sInstance;
}

可以看到main方法中就new了一个WindowManagerService对象并返回出去。

WindowManager

Window 是一个抽象类,代表一个窗口,其具体的实现类为 PhoneWindow ,它对 View进行管理。
WindowManager 是一个接口类,继承自接口 ViewManager,它是用来管理 Window 的。它的具体实现类为 WindowManagerImpI。
WindowManagerGlobal 是实际操作的类,是一个单例,每个进程中只有一个实例对象,该实例对象在 WindowManagerGlobal 中。
在 WindowManagerGlobal 中,会创建 ViewRootImpl 实例对象,每个根 View 对应一个 ViewRootImpl 实例对象。
想要对 Window (View)进行添加、更新和删除操作,可以使用 WindowManager 来执行。最终的操作是通过 Binder 交给 WMS 来执行的。

我们正常需要添加一个view的视图:

// 获取 WindowManager 
WindowManager wm = (WindowManager) getApplicationContext().getSystemService(WINDOW_SERVICE); 
// 获取需要添加的View 
View view = View.inflate(MainActivity.this, R.layout.item, null); 
WindowManager.LayoutParams params = new WindowManager.LayoutParams(); 
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; 
// 设置不拦截焦点 
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | 
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; 
params.width = (int) (60 * getResources().getDisplayMetrics().density); 
params.height = (int) (60 * getResources().getDisplayMetrics().density); 
// 且设置坐标系 左上角 
params.gravity = Gravity.LEFT | Gravity.TOP; 
params.format = PixelFormat.TRANSPARENT; 
int width = wm.getDefaultDisplay().getWidth(); 
int height = wm.getDefaultDisplay().getHeight(); 
params.y = height / 2 - params.height / 2; 
wm.addView(view, params);

其实我们调用的就是WindowManagerImpl中的addview,我们跟踪一下,看看这个创建窗口的过程是怎么发送给WMS的。

WindowManagerImpl:

 
public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

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

WindowManagerGlobal


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

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
    // ...省略代码...
    // 1.根节点的LayoutParams必须为WindowManager.LayoutParams类型,因为确定根View的大小需要使用。
    if (!(params instanceof WindowManager.LayoutParams)) {
        throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
    }

    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
    if (parentWindow != null) {
    	// 2.如果这个窗口有父窗口,则需要调整 wparams 的大小,使 wparams 的大小不超过父容器的大小。
        parentWindow.adjustLayoutParamsForSubWindow(wparams);
    } else {
        // ...省略代码...
    }
    
    ViewRootImpl root;
    View panelParentView = null;
    synchronized (mLock) {
    	// ...省略代码...

		// 3.将传入的根View添加到ViewRootImpl对象中(一个根View 对应一个 ViewRootImpl)。
        root = new ViewRootImpl(view.getContext(), display);
        // 4.将调整后的 wparams 赋值给根 View。
        view.setLayoutParams(wparams);
		
		// 5.将根 View、根View对应的ViewRootImpl、根View的布局参数LayoutParams分别存入三个集合中。
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);

        try {
        	// 6.执行 ViewRootImpl.setView() 方法。
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
            // ...省略代码...
        }
    }
}

ViewRootImpl

// 用于远程通信的Binder
IWindowSession mWindowSession = WindowManagerGlobal.getWindowSession();

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
        if (mView == null) {
            // ...省略代码...
            // 1.调用requestLayout方法进行绘制。
            requestLayout();
            try {
                mOrigWindowType = mWindowAttributes.type;
                mAttachInfo.mRecomputeGlobalAttributes = true;
                collectViewAttributes();
                // 2.获取远程服务进行通信(IWindowSession对象的获取在第4部分分析)
                res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,
                        getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
                        mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                        mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
                        mTempInsets);
                setFrame(mTmpFrame);
            } catch (RemoteException e) {
               // ...省略代码...
            }
            // ...省略代码...
		}
    }
}

public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        // 该方法的分析过程请看本文 “scheduleTraversals() 执行流程” 部分。
        scheduleTraversals();
    }
}

从代码中我们可以看到,在 setView() 方法中,会调用 mWindowSession.addToDisplayAsUser() 来与远程服务进行通信 (mWindowSession 是一个 Binder 对象),我们继续看:

Session

public int addToDisplayAsUser(IWindow window, int seq, WindowManager.LayoutParams attrs,
        int viewVisibility, int displayId, int userId, 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, userId);
     }

WindowManagerService

// WindowManagerService.class
public int addWindow(Session session, IWindow client, int seq,
     	LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,
        Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
        DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
        InsetsState outInsetsState) {
    int[] appOp = new int[1];
    // 1.mPolicy其实是PhoneWindowManager,根据Window的属性来检测权限。
    int res = mPolicy.checkAddPermission(attrs, appOp);
    // 没有权限就直接返回。
    if (res != WindowManagerGlobal.ADD_OKAY) {
        return res;
    }
	...
	
    synchronized (mGlobalLock) {
		...
		/*
		 * 2.通过 displayId 来获得窗口要添加到哪个 DisplayContent 上,如果没有找到DisplayContent,
		 *  则返回 WindowManagerGlobal.ADD_INVALID_DISPLAY 这一状态,其中 DisplayContent 用来描述一块屏幕。
		 */
        final DisplayContent displayContent = getDisplayContentOrCreate(displayId, attrs.token);
        if (displayContent == null) {
            return WindowManagerGlobal.ADD_INVALID_DISPLAY;
        }
        ...

		// 3. 1000 =< type <= 1999,则该Window属于 SubWindow。
        if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
            parentWindow = windowForClientLocked(null, attrs.token, false);
            
            // ...省略2个条件判断代码(1.依附的parentWindow不能为空;2.parentWindow类型也不能是子窗口类型)...
            // 依附的parentWindow不能为空
            if (parentWindow == null) {
                ProtoLog.w(WM_ERROR, "Attempted to add window with token that is not a             window: "+ "%s.  Aborting.", attrs.token);
                return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
            }
            if (parentWindow.mAttrs.type >= FIRST_SUB_WINDOW
                          && parentWindow.mAttrs.type <= LAST_SUB_WINDOW) {
                ProtoLog.w(WM_ERROR, "Attempted to add window with token that is a sub-window: " + "%s.  Aborting.", attrs.token);
                return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
            }
        }
        ...

        AppWindowToken atoken = null;
        final boolean hasParent = parentWindow != null;
        // 4.获取 WindowToken
        WindowToken token = displayContent.getWindowToken(hasParent ? parentWindow.mAttrs.token : attrs.token);
        // If this is a child window, we want to apply the same type checking rules as the
        // parent window type.
        final int rootType = hasParent ? parentWindow.mAttrs.type : type;
		...
        if (token == null) {
            ...
            // 5.没有获取到WindowToken就自己创建一个。
            token = new WindowToken(this, binder, type, false, displayContent,
                    session.mCanAddInternalSystemWindow, isRoundedCornerOverlay);
        } else if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
        	// 应用程序的窗口类型,就将WindowToken转换为AppWindowToken类型。
            atoken = token.asAppWindowToken();
            ...
        }
		...
		
		// 7.每个WindowState 都代表一个窗口。
        final WindowState win = new WindowState(this, session, client, token, parentWindow,
                appOp[0], seq, attrs, viewVisibility, session.mUid,
                session.mCanAddInternalSystemWindow);
        ...
        
        final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
        // 8.根据窗口的 type 对窗口的 LayoutParams 的一些参数进行修改。
        displayPolicy.adjustWindowParamsLw(win, win.mAttrs, Binder.getCallingPid(), Binder.getCallingUid());
        win.setShowToOwnerOnlyLocked(mPolicy.checkShowToOwnerOnly(attrs));
        // 9.准备将窗口添加到系统中
        res = displayPolicy.prepareAddWindowLw(win, attrs);
        ...
        
        win.attach();
        // 10.将 WindowState 添加到 mWindowMap 中
        mWindowMap.put(client.asBinder(), win);
        ...
        
		// 11.将 WindowState 添加到该 WindowState 对应的 WindowToken 中。
        win.mToken.addWindow(win);
        
        ...
    }
	...
 
    return res;
}

通过binder完成了和服务端的通信。addview主要做了这么几件事:

addWindow 方怯主要做了下面 4 件事 :

  • 对添加的窗口进行检查,如果窗口不满足条件,就结束添加逻辑。
  • WindowToken 相关的处理,比如有的窗口类型需要提供 WindowToken ,没有提供的话就不会执行下面的代码逻辑,有的窗口类型则需要由 WMS 隐式创建认 WindowToken。
  • WindowState 的创建和相关处理,将 WindowToken 和 WindowState 相关联 。
  • 创建和配置 DisplayContent,完成窗口添加到系统前的准备工作 。

Activity、Window、DecorView、ViewRootImpl 之间的关系

我们看一下activity的UI视图结构:

Activity

Activity 只负责生命周期的控制和事件的处理,并不负责视图控制,真正控制视图的是 Window。
一个 Activity 包含了一个Window,Window 才是真正代表一个窗口,它用于绘制用户的UI界面

Window

Window 是视图的承载器,内部持有一个 DecorView,而这个DecorView才是 view 的根布局。
Window 是一个抽象类,实际在 Activity 中持有的是其子类 PhoneWindow。

PhoneWindow  

PhoneWindow 中有个内部类 DecorView,通过创建 DecorView 来加载 Activity.setContentView() 设置的 layout 布局。
Window 通过 WindowManager 将 DecorView 加载其中,并将 DecorView 交给 ViewRootImpl,进行视图绘制以及其他交互。

DecorView

DecorView 是所有应用窗口的根节点, 是 FrameLayout 的子类,它可以被认为是 Android 视图树的根视图。

ViewRootImpl

连接 DecorView 和 WindowManagerService 的纽带。
View 的三大流程 (measure、layout、draw) 和事件分发等都是通过 ViewRootImpl 来执行的。

源码分析

// Activity
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);

    mFragments.attachHost(null /*parent*/);
	// 1.创建了PhoneWindow对象,在Activity中持有了Window。
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    mWindow.setWindowControllerCallback(this);
    // 2.将 Activity 作为参数传递给 Window,所以在 Window 中持有了 Activity 。
    mWindow.setCallback(this);
	// ...省略代码...
	// 3.设置 WindowManager,来关联 Window 和 DecorView。
    mWindow.setWindowManager(
            (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
            mToken, mComponent.flattenToString(),
            (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
    // 4.在 Activity 中持有 WindowManager。
    mWindowManager = mWindow.getWindowManager();
    // ...省略代码...
}

在Activity中,主要做了以下几件事:

  1. 创建一个 PhoneWindow,使 Activity 持有 Window。
  2. 将 Activity作为参数传递给 Window,此时 Window 与 Activity 就相互有了关联。
  3. 给 Window 设置一个 WindowManager,来关联 Window 和 DecorView。
  4. 通过 Window.getWindowManager()获取 WindowManager,使 Activity 持有 WindowManager 的引用。

继续执行onCreate方法:

// Activity 
public void setContentView(@LayoutRes int layoutResID) {
	// getWindow() 其实就是PhoneWindow,所以这里会触发 PhoneWindow.setContentView()方法。
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar(); //创建ActionBar
}


// PhoneWindow.class
public void setContentView(int layoutResID) {
    if (mContentParent == null) {
    	// 1.mContentParent为空,创建一个DecroView。
    	// mContentParent 其实就是DecroView中id=com.android.internal.R.id.content的容器控件。
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
    	// 2.mContentParent不为空,删除其中的View。
        mContentParent.removeAllViews();
    }
	// 3.将 layoutResID 布局文件加载并添加到 mContentParent 容器控件中。
 	mLayoutInflater.inflate(layoutResID, mContentParent);
   	// ...省略代码...
}


// PhoneWindow.class
private void installDecor() {
    mForceDecorInstall = false;
    if (mDecor == null) {
    	// 1.创建DecorView
        mDecor = generateDecor(-1); 
        // ...省略代码...
    } else {
    	// 这里会将当前 Window 传入 DecorView,使 DecorView 与 Window 关联。
        mDecor.setWindow(this);
    }
    if (mContentParent == null) {
     	// 2.为DecorView设置布局格式,并返回mContentParent
        mContentParent = generateLayout(mDecor);
        // ...省略代码...
    }
}

protected DecorView generateDecor(int featureId) {
    // ...省略代码...
    // 创建一个DecorView 根视图 View。
    return new DecorView(context, featureId, this, getAttributes());
}


// PhoneWindow.class
protected ViewGroup generateLayout(DecorView decor) {
    // 从主题文件中获取样式信息
    TypedArray a = getWindowStyle();
    
    // 1.根据样式信息设置Feature特性
	// ...省略代码...

	// 2.根据不同的features加载不同的layout文件
    // Inflate the window decor.
    int layoutResource;
    int features = getLocalFeatures();
    if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
       layoutResource = R.layout.screen_swipe_dismiss;
        setCloseOnSwipeEnabled(true);
    }
    // ...省略代码(条件判断获取layoutResource)...
    
    // 3.加载上面的 layoutResource 文件 
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
    
    // 4.获取 DecorView 中的 id=ID_ANDROID_CONTENT 的容器控件。
	// ID_ANDROID_CONTENT = com.android.internal.R.id.content;
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
	// ...省略代码...
    return contentParent;
}

// DecorView.class
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
	// ...省略代码...
	// 加载 layoutResource 文件
    final View root = inflater.inflate(layoutResource, null);
	// ...省略代码...
    mContentRoot = (ViewGroup) root;
    initializeElevation();
}

这里将 Window 作为参数传入 DecorView,使 DecorView 与 Window 关联。

然后在ActivityThread. handleResumeActivity中

// ActivityThread.class
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) {
	// ...省略代码...

    // TODO Push resumeArgs into the activity for consideration
    // 1.将 Activity 回复到 RESUME 状态。
    final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
	
	// ...省略代码...
    final Activity a = r.activity;
	// ...省略代码...

    if (r.window == null && !a.mFinished && willBeVisible) {
    	// 2.获取在 Activity.attach() 方法中就创建了 PhoneWindow 对象。
        r.window = r.activity.getWindow();
        View decor = r.window.getDecorView();
        // 这里使 Decor 不可见。
        decor.setVisibility(View.INVISIBLE);
        // 3.获取 Activity 中持有的 WindowManager。
        ViewManager wm = a.getWindowManager();
        WindowManager.LayoutParams l = r.window.getAttributes();
        a.mDecor = decor;
        l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
        l.softInputMode |= forwardBit;
        if (r.mPreserveWindow) {
            a.mWindowAdded = true;
            r.mPreserveWindow = false;
            ViewRootImpl impl = decor.getViewRootImpl();
            if (impl != null) {
                impl.notifyChildRebuilt();
            }
        }
        if (a.mVisibleFromClient) {
            if (!a.mWindowAdded) {
            	//将 Activity.WindowAdded 标记为true,避免在 Activity.makeVisible() 是重复进行 Window 添加操作。
                a.mWindowAdded = true;
                // 4.将根 View(DecorView)通过 WindowManager 添加到 Window 中。
                wm.addView(decor, l);
            } else {
                a.onWindowAttributesChanged(l);
            }
        }
    }
	// ...省略代码...
	
	// The window is now visible if it has been added, we are not
    // simply finishing, and we are not starting another activity.
    if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) {
        // ...省略代码...

        r.activity.mVisibleFromServer = true;
        mNumVisibleActivities++;
        if (r.activity.mVisibleFromClient) {
        	// 5.这个方法内部会是 DecorView 可见。
            r.activity.makeVisible();
        }
    }
}

public ActivityClientRecord performResumeActivity(IBinder token, boolean finalStateRequest, String reason) {
    // 1.每一个 ActivityClientRecord 都代表着一个 Activity 。
    final ActivityClientRecord r = mActivities.get(token);
    
    // ...省略代码...
    try {
        r.activity.onStateNotSaved();
        r.activity.mFragments.noteStateNotSaved();
        checkAndBlockForNetworkAccess();
        if (r.pendingIntents != null) {
        	// 这里会触发 Activity.onNewIntent()方法。
            deliverNewIntents(r, r.pendingIntents);
            r.pendingIntents = null;
        }
        if (r.pendingResults != null) {
        	// 这里会触发 Activity.onActivityResult()方法。
            deliverResults(r, r.pendingResults, reason);
            r.pendingResults = null;
        }
        // 这里会触发 Activity.onResume()方法。
        r.activity.performResume(r.startsNotResumed, reason);

        r.state = null;
        r.persistentState = null;
        // 这里将当前 Activity 的生命周期状态设置为 ON_RESUME。
        r.setState(ON_RESUME);

        reportTopResumedActivityChanged(r, r.isTopResumedActivity, "topWhenResuming");
    } catch (Exception e) {
        // ...省略代码...
    }
    return r;
}


// Activity.class
void makeVisible() {
    if (!mWindowAdded) {
        ViewManager wm = getWindowManager();
        wm.addView(mDecor, getWindow().getAttributes());
        mWindowAdded = true;
    }
    // 将 DecorView设置为可见。
    mDecor.setVisibility(View.VISIBLE);
}

在rusume生命周期中,主要做了以下这些事

  • 获取 DecorView 对象,并设置 DecorView 可见性为不可见。
  • 获取 Activity 中持有的 WindowManager。
  • 将 DecorView 通过 WindowManager 添加到 Window 中显示。
  • 在 Activity.makeVisible() 方法中,最终将 DecorView 设置为可见。

WindowToken

WindowToken是窗口令牌,是一种特殊的Binder令牌,WMS用它唯一的标识系统中的一个窗口。

class WindowToken extends WindowContainer<WindowState> { 
    ... 
    // The actual token. 
    final IBinder token; 
} 

我们通过源码可知,这个windowToken里有一个IBinder对象token,这个token控制着界面显示,这就是为什么Dialog不能使用Application的Context,我们来分析一下。

在Activity的OnCreate创建一个Dialog:

override fun onCreate(savedInstanceState: Bundle?) { 
    super.onCreate(savedInstanceState) 
    setContentView(R.layout.activity_main) 
    val dialog = AlertDialog.Builder(this) 
    dialog.run{ 
        title = "我是标题" 
        setMessage("我是内容") 
    } 
    dialog.show() 
} 

他的构造参数需要传入一个context对象,这个context的要求不能是ApplicationContext等其他context,只能是 ActivityContext。如果我们使用Application传入会怎么样呢?

override fun onCreate(savedInstanceState: Bundle?) { 
    ... 
    // 注意这里添加了主题 
    val dialog = AlertDialog.Builder(applicationContext,R.style.AppTheme) 
    ... 
} 

 崩溃了: Unable to add window -- token null is not valid; is your activity running?

首先我们看到报错是在ViewRootImpl.setView,我们看这个地方关于token的判断:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { 
    ... 
    int res; 
    ... 
    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, 
                        getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame, 
                        mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, 
                        mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel, 
                        mTempInsets); 
    ... 
    if (res < WindowManagerGlobal.ADD_OKAY) { 
        ... 
        switch (res) { 
            case WindowManagerGlobal.ADD_BAD_APP_TOKEN: 
            case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN: 
                // code1 
                throw new WindowManager.BadTokenException( 
                    "Unable to add window -- token " + attrs.token 
                    + " is not valid; is your activity running?");     
                ... 
        } 
        ... 
    } 
    ... 
} 

我们可以快速看出在code 1的地方抛出了异常,是根据一个变量res来判别的,这个res出自方法addToDisplayAsUser, 那么token的判别肯定在这个方法里面了,res只是一个判别的结果,那么我们是必须进入这个addToDisplayAsUser里去看一下,根据上面源码,继续追踪到WindowManagerService.addView:

public int addWindow(Session session, IWindow client, int seq, 
        LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame, 
        Rect outContentInsets, Rect outStableInsets, Rect outOutsets, 
        DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel, 
        InsetsState outInsetsState) { 
    ... 
    WindowState parentWindow = null; 
    ... 
 // 获取parentWindow 
    parentWindow = windowForClientLocked(null, attrs.token, false); 
    ... 
    final boolean hasParent = parentWindow != null; 
    // 获取token 
    WindowToken token = displayContent.getWindowToken( 
        hasParent ? parentWindow.mAttrs.token : attrs.token); 
    ... 
   // 验证token 
    if (token == null) { 
    if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) { 
          Slog.w(TAG_WM, "Attempted to add application window with unknown token " 
                           + attrs.token + ".  Aborting."); 
            return WindowManagerGlobal.ADD_BAD_APP_TOKEN; 
        } 
       ...//各种验证 
    } 
    ... 
}

从代码中可以得出,当token==null的时候,会进行各种判断,第一个返回的就是 WindowManagerGlobal.ADD_BAD_APP_TOKEN ,这样我们就快速找到token的类型:WindowToken。那 么根据我们这一路跟过来,最终找到token的类型了。

我们回到刚才的WindowManagerGlobal.addView:

public void addView(View view, ViewGroup.LayoutParams params, 
        Display display, Window parentWindow) { 
    ... 
    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params; 
    if (parentWindow != null) { 
        parentWindow.adjustLayoutParamsForSubWindow(wparams); 
    } 
    ... 
    ViewRootImpl root; 
    ... 
    root = new ViewRootImpl(view.getContext(), display); 
    ... 
    try { 
        root.setView(view, wparams, panelParentView); 
    }  
    ... 
} 

这里我们只需要看WindowManager.LayoutParams参数,parentWindow是与windowManagerPhoneWindow, 所以这里肯定不是null,进入到 adjustLayoutParamsForSubWindow 方法进行调整参数。最后执行ViewRootImpl的 setView方法。到这里WindowManager.LayoutParams这个参数还没有被设置token,那么最有可能是在 adjustLayoutParamsForSubWindow 方法中了,我们进去代码看看:

void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) { 
    CharSequence curTitle = wp.getTitle(); 
    if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW && 
        wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) { 
        // 子窗口token获取逻辑 
        if (wp.token == null) { 
            View decor = peekDecorView(); 

            if (decor != null) { 
                wp.token = decor.getWindowToken(); 
            } 
        } 
        ... 
    } else if (wp.type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW && 
                wp.type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) { 
        // 系统窗口token获取逻辑 
        ... 
    } else { 
        // 应用窗口token获取逻辑 
        if (wp.token == null) { 
            wp.token = mContainer == null ? mAppToken : mContainer.mAppToken; 
        } 
        ... 
    } 
    ... 
} 

最终看到了token的赋值了,这里分为三种情况:应用层窗口、子窗口和系统窗口,分别进行token赋值。 应用窗口直接得到的是与WindowManager对应的PhoneWindow的mAppToken,而子窗口是得到DecorView的 token,系统窗口属于比较特殊的窗口,使用Application也可以弹出,但是需要权限,这里不深入讨论。而这里的关键就是:这个dialog是什么类型的窗口?以及windowManager对应的PhoneWindow中有没有token?

而这个判断跟我们前面赋值的不同WindowManagerImpl有直接的关系。那么这里,就需要到Activity和Application 创建WindowManager的过程一看究竟了。 Activity与Application的WindowManager 首先我们看到Activity的window创建流程。这里需要了解Activity的启动流程。跟踪Activity的启动流程,最终会到 ActivityThread的performLaunchActivity:

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { 
    ... 
    // 最终会调用这个方法来创建window 
    // 注意r.token参数 
    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, 
        r.assistToken); 
    ... 
} 

这个方法执行了activity的attach方法来初始化window,同时我们看到参数里有了r.token这个参数,这个token最终会给到哪里,我们赶紧继续看下去:

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) { 
    ... 
    // 创建window 
    mWindow = new PhoneWindow(this, window, activityConfigCallback); 
    ... 
    // 创建windowManager 
    // 注意token参数 
    mWindow.setWindowManager( 
        (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), 
        mToken, mComponent.flattenToString(), 
        (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); 
    mWindowManager = mWindow.getWindowManager(); 
    ... 
}

attach方法里创建了PhoneWindow以及相应的WindowManager,再把创建的windowManager给到activity的 mWindowManager属性。我们看到创建WindowManager的参数里有token,我们继续看下去:

public void setWindowManager(WindowManager wm, IBinder appToken, String appName, 
        boolean hardwareAccelerated) { 
    mAppToken = appToken; 
    mAppName = appName; 
    mHardwareAccelerated = hardwareAccelerated; 
    if (wm == null) { 
        wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); 
    } 
    mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this); 
} 

这里利用应用服务的windowManager给Activity创建了WindowManager,同时把token保存在了PhoneWindow 内。到这里我们明白Activity的PhoneWindow是拥有token的。那么Application呢?

Application执行的是ContextImpl的getSystemService方法,而这个方法返回的是应用服务的windowManager, Application本身并没有创建自己的PhoneWindow和WindowManager,所以也没有给PhoneWindow赋值token的过程。 因此,Activity有自己PhoneWindow、WindowManager,同时它的PhoneWindow含有token;而 Application并没有自己的PhoneWindow,它返回的WindowManager是应用服务windowManager,并没有赋值token的过程。

我们再回过头看一下Dialog的show方法:

public void show() { 
    ... 
    WindowManager.LayoutParams l = mWindow.getAttributes(); 
    ... 
    WindowManager mWindowManager =(WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    mWindowManager.addView(mDecor, l); 
    ... 
}

dialog在调用show方法时,首先会获取一个WindowManager对象,然后通过WindowManager的addView方法,将dialog的PhoneWindow中的decorView添加到窗口中。获取这个WindowManager的时候,是通过一个context获取的,这里的context可能是 Activity,也可能是Application,他们的getSystemService返回的windowManager是不一样的,看代码:

//Activity.class
public Object getSystemService(@ServiceName @NonNull String name) { 
    if (getBaseContext() == null) { 
        throw new IllegalStateException( 
                "System services not available to Activities before onCreate()"); 
    } 
    if (WINDOW_SERVICE.equals(name)) { 
        // 返回的是自身的WindowManager 
        return mWindowManager; 
    } else if (SEARCH_SERVICE.equals(name)) { 
        ensureSearchManager(); 
        return mSearchManager; 
    } 
    return super.getSystemService(name); 
} 

//ContextImpl.class
public Object getSystemService(String name) { 
    return SystemServiceRegistry.getSystemService(this, name); 
}

Activity返回的其实是自己的WindowManager,而Application是执行ContextImpl的方法,返回的是应用服务 windowManager。

当我们使用Activity来弹出dialog的时候,此时Activity的DecorView已经是显示到屏幕上了,也就是我们的Activity是有界面了,这个情况下,它就是属于子窗口的类型被添加到PhoneWindow中,而它的token就是DecorView的 token,此时DecorView已经被显示到屏幕上,它本身是拥有token的;

而如果是第一次显示,也就是应用界面,那么他的token就是Activity初始化传入的token。 但是如果使用的是Application,因为它内部并没有token,那么这里获取到的token等于null,后面到WMS也就会抛出异常了。而这也就使用Activity可以弹出Dialog而Application不可以的原因,因为受到了token的限制。

总结:

  1. token在创建ActivityRecord的时候一起被创建,他是一个IBinder对象,实现了接口IApplicationToken。
  2. token创建后会发送到WMS,在WMS中封装成WindowToken,并存在一个 HashMap。 
  3. token会随着ActivityRecord被发送到本地进程,ActivityThread根据AMS的指令执行Activity启动逻辑。
  4. Activity启动的过程中会创建PhoneWindow和对应的WindowManager,同时把token存在PhoneWindow中。
  5. 通过Activity的WindowManager添加view,弹出dialog时会把PhoneWindow中的token放在窗口LayoutParams 中。
  6. 通过viewRootImpl向WMS进行验证,WMS在LayoutParams拿到IBinder之后就可以在Map中获取 WindowToken。
  7. 根据获取的结果就可以判断该token的合法情况。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/504716.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

作业9:编程练习

1.喝汽水问题 int Func(int money) {int total money;int empty money;while (empty > 1){total total empty / 2;empty empty / 2 empty % 2;}return total; } 2.打印菱形 将菱形看作三部分打印&#xff1a;上三角&#xff0c;中间&#xff0c;下三角 分别列出每一行…

http模块 设置资源类型(mime类型)

虽然浏览器自带websocket功能它会根据响应回来的内容自动去判断资源类型&#xff0c;但是我们加上了mime类型判断代码会更加规范些 一、mime类型概念&#xff1a; 媒体类型是一种标准&#xff0c;它用来表示文档。文件、字节流的性质和格式。HTTP服务可以设置响应头Content-T…

安装VS2022社区版

Visual Studio 2022 平台的使用 1.Visual Studio 的下载地址&#xff1a; https://visualstudio.microsoft.com/zh-hans/downloads/ 2.安装步骤简要记录 耐心等待安装完成 参考链接&#xff1a;Visual Studio 2022安装教程(非常详细)&#xff0c;从零基础入门到精通&…

通过搜索引擎让大模型获取实时数据-实现类似 perplexity 的效果

文章目录 一、前言二、初衷三、实现方式四、总结 一、前言 汇报一下这周末的工作&#xff0c;主要是开发了一门课程&#xff1a;通过搜索引擎让大模型获取实时数据&#xff0c;第一次开发一门课程&#xff0c;难免会有很多不熟悉和做的不好的地方。 已经训练好的大模型有气数…

问卷调查技巧大揭秘:如何设计有效的问题?

做问卷调查技巧有&#xff1a;明确设计问卷的基本原则、制定清晰的研究目标、设计与选择问题、问卷实施和回收。 在实施市场研究、收集用户反馈或进行社会调查时&#xff0c;问卷调查是一种常用的方法。然而&#xff0c;设计和进行问卷调查需要一定的技巧和策略才能确保获得准…

【栈】单调栈与直方图中最大的矩形

一、单调递增栈&#xff1a; 用单调递增栈&#xff0c;当该元素可以入栈的时候&#xff0c;栈顶元素就是它左侧第一个比它小的元素。用于查找所要查找元素左侧第一个比它要小的数&#xff0c;以3 4 2 7 9为例&#xff1a; #include<iostream> #include<stack> usi…

Linux 常见性能分析方法论介绍(业务负载画像、下钻分析、USE方法论,检查清单)

写在前面 博文内容为 《BPF Performance Tools》 读书笔记整理内容涉及常用的性能调优方法论介绍&#xff1a;业务负载画像下钻分析USE方法论检查清单理解不足小伙伴帮忙指正 不必太纠结于当下&#xff0c;也不必太忧虑未来&#xff0c;当你经历过一些事情的时候&#xff0c;眼…

C++刷题篇——07检测热点字符

一、题目 二、解题思路 1、使用map&#xff0c;key为元素&#xff0c;value为出现的次数 2、由于sort不适用于map&#xff0c;因此要将map的key、value放到vector中&#xff0c;再对vector排序 3、对map排序&#xff1a;方法1&#xff1a;使用二维数组vector<vector<>…

第十三届蓝桥杯JavaA组省赛真题 - GCD

解题思路&#xff1a; 找规律 最大的最小公因数就是两数的差值 5 7 gcd2 1 3 gcd2 1 4 gcd3 import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner scan new Scanner(System.in);long a scan.nextLong();long b scan.ne…

Unity类银河恶魔城学习记录11-10 p112 Items drop源代码

Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释&#xff0c;可供学习Alex教程的人参考 此代码仅为较上一P有所改变的代码 【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili ItemObject_Trigger.cs using System.Collections; using System.Collecti…

Django详细教程(一) - 基本操作

文章目录 前言一、安装Django二、创建项目1.终端创建项目2.Pycharm创建项目&#xff08;专业版才可以&#xff09;3.默认文件介绍 三、创建app1.app介绍2.默认文件介绍 四、快速上手1.写一个网页步骤1&#xff1a;注册app 【settings.py】步骤2&#xff1a;编写URL和视图函数对…

练习3-2 计算符号函数的值

对于任一整数n&#xff0c;符号函数sign(n)的定义如下&#xff1a; 请编写程序计算该函数对任一输入整数的值。 输入格式: 输入在一行中给出整数n。 输出格式: 在一行中按照格式“sign(n) 函数值”输出该整数n对应的函数值。 输入样例1: 10 输出样例1: sign(10) 1 输入样例…

文献阅读:通过 NeuronChat 从单细胞转录组推断神经元-神经元通信

文献介绍 「文献题目」 Inferring neuron-neuron communications from single-cell transcriptomics through NeuronChat 「研究团队」 聂青&#xff08;加利福尼亚大学欧文分校&#xff09; 「发表时间」 2023-02-28 「发表期刊」 Nature Communications 「影响因子」 16.6…

React系列之合成事件与事件处理机制

文章目录 React事件处理机制原生事件的事件机制事件代理&#xff08;事件委托&#xff09; 合成事件使用合成事件目的合成事件原生事件区别事件池 原生事件和React事件的执行顺序e.stopPropagation() React17事件机制的修改 React事件处理机制 react 事件机制基本理解&#xf…

数字经济全景解析:数据要素、资源与资产的转化与治理

无极低码 &#xff1a;https://wheart.cn 数字经济全景解析&#xff1a;数据要素、资源与资产的转化与治理—无极低码wheart数字经济全景解析&#xff1a;数据要素、资源与资产的转化与治理https://wheart.cn/so/home?mindex&id67737c2a-ef2f-11ee-8183-525400be6368 为…

【现代企业管理】企业组织结构和组织文化的理论与实践——以华为为例

一、前言 管理是科学和艺术的统一体&#xff0c;它是企业成长的保证。企业管理中&#xff0c;管理者面对的往往不是一个完整的系统&#xff0c;而是各种不具有整体规律性的零碎信息的总和&#xff0c;因此进行信息的整合和研究是管理的重点和关键。 组织管理作为管理的四大职…

RDGCN阅读笔记

Relation-Aware Entity Alignment for Heterogeneous Knowledge Graphs 面向异质知识图谱的关系感知实体对齐 Abstract 实体对齐是从不同的知识图(KGs)中链接具有相同真实世界实体的任务&#xff0c;最近被基于嵌入的方法所主导。这种方法通过学习KG表示来工作&#xff0c;以…

SRS OBS利用RTMP协议实现音视频推拉流;WebRTC 屏幕直播分享工具

一、SRS OBS利用RTMP协议实现音视频推拉流 参考&#xff1a;https://ossrs.net/lts/zh-cn/docs/v5/doc/getting-started 1&#xff09;docker直接运行SRS服务&#xff1a; docker run --rm -it -p 1935:1935 -p 1985:1985 -p 8080:8080 registry.cn-hangzhou.aliyuncs.co…

Netty组件优化之时间轮

关于任务调度有多种方式实现&#xff0c;常见的像Timer,ScheduledThreadPoolExecutor,以及时间轮 Timer原理 timer底层主要是依靠最小堆排序&#xff0c;把任务封装并存储在一个优先级队列中&#xff0c;这个队列底层还是依靠数组和最小堆排序构成。 Timer timer new Timer();…

JavaAgent 技术原理及实战

JavaAgent 技术原理及实战 1、引子2、JavaAgent 简单示例&#xff1a;方法开始和结束时打印日志2.1 创建 Agent2.2 编写验证 agent 功能的测试类2.2.1 使用JavaAgent 静态加载方式2.2.2 使用 JavaAgent 动态加载方式 2.3、小结 3、JavaAgent3.1 JavaAgent是什么&#xff1f;3.2…