android WMS服务

news2024/11/27 13:29:05

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的合法情况。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

YOLOv9改进策略 :卷积魔改 | 感受野注意力卷积运算(RFAConv)

💡💡💡本文改进内容:感受野注意力卷积运算(RFAConv),解决卷积块注意力模块(CBAM)和协调注意力模块(CA)只关注空间特征,不能完全解决卷积核参数共享的问题 💡💡💡使用方法:代替YOLOv9中的卷积,使得更加关注感受野注意力,提升性能 💡💡💡RFAConv…

vue3:通过【自定义指令】实现自定义的不同样式的tooltip

一、效果展示 vue3自定义不同样式的tooltip 二、实现思路 1.ts文件 在ts文件中创建一个全局容器 import一个容器组件&#xff0c;用于存放自定义的各式组件 创建一个指令并获取到指令传递的数据&#xff0c;并为容器组件传值 2.容器组件 用于存放自定义Tooltip样式的组件…

最新2024年增强现实(AR)营销指南(完整版)

AR营销是新的最好的东西&#xff0c;就像元宇宙和VR营销一样。利用AR技术开展营销活动可以带来广泛的利润优势。更不用说&#xff0c;客户也喜欢AR营销&#xff01; 如果企业使用AR&#xff0c;71%的买家会更多地购物。40%的购物者准备在他们可以在AR定制的产品上花更多的钱。…

详解Java线程的状态

一、观察线程的所有状态 线程的状态是⼀个枚举类型 Thread.State public class ThreadState {public static void main(String[] args) {for (Thread.State state : Thread.State.values()) {System.out.println(state);}} } NEW: 安排了⼯作, 还未开始⾏动 RUNNABLE: 可⼯…

JavaSE day16笔记 - string

第十六天课堂笔记 学习任务 Comparable接口★★★★ 接口 : 功能的封装 > 一组操作规范 一个抽象方法 -> 某一个功能的封装多个抽象方法 -> 一组操作规范 接口与抽象类的区别 1本质不同 接口是功能的封装 , 具有什么功能 > 对象能干什么抽象类是事物本质的抽象 &…

MYSQL——索引概念索引结构

索引 索引是帮助数据库高效获取数据的排好序的数据结构。 有无索引时&#xff0c;查询的区别 主要区别在于查询速度和系统资源的消耗。 查询速度&#xff1a; 在没有索引的情况下&#xff0c;数据库需要对表中的所有记录进行扫描&#xff0c;以找到符合查询条件的记录&#…

《深入理解计算机系统》学习(9):链接和执行

目录 一、链接1.1 编译器驱动程序1.2 链接任务 二、目标文件2.1 目标文件三种形式2.2 可重定位目标文件 三、符号3.1 符号表3.2 符号解析3.3 链接器解析多重定义的全局符号 四、重定位4.1 重定位条目4.2 重定位符号引用 五、可执行目标文件5.1 可执行文件结构5.2 加载可执行目标…

设置asp.net core WebApi函数请求参数可空的两种方式

以下面定义的asp.net core WebApi函数为例&#xff0c;客户端发送申请时&#xff0c;默认三个参数均为必填项&#xff0c;不填会报错&#xff0c;如下图所示&#xff1a; [HttpGet] public string GetSpecifyValue(string param1,string param2,string param3) {return $"…

C++格式化输入和输出

格式化输入与输出 除了条件状态外&#xff0c;每个iostream对象还维护一个格式状态来控制IO如何格式化的细节。 格式状态控制格式化的某些方面&#xff0c;如整型值是几进制、浮点值的精度、一个输出元素的宽度等。 标准库定义了一组操纵符来修改流的格式状态。 一个操纵符…

【进程IO】详细讲解文件描述符fd

文章目录 前言什么叫文件描述符FILE与fd的关系 再次理解文件为什么要有文件的方法列表呢&#xff1f; 进程和struct file的关系再次理解open操作 前言 C语言的关于文件操作的各种函数实际上是对系统调用的封装。那么从进程的角度看&#xff0c;一个文件到底是如何被描述的呢&a…

Postwoman 安装

Postwoman作为Postman的女朋友&#xff0c;具有免费开源、轻量级、快速且美观等特性&#xff0c;是一款非常好用的API调试工具。能帮助程序员节省时间&#xff0c;提升工作效率。 Github地址&#xff1a;GitHub - hoppscotch/hoppscotch: &#x1f47d; Open source API devel…

Qt/QML编程之路:画线及倒车影响(48)

前言: 倒车影像中有一个属性比较实用,那就是倒车线,这条线很明显会在视频图像上叠加显示,或者说在视频上面一个图层画的线。这里有一个画线的Qt示例,用于在一个scene上画一个对角线: #include "mainwindow.h" #include <QApplication> #include <QtW…

ES6 学习(一)-- 基础知识

文章目录 1. 初识 ES62. let 声明变量3. const 声明常量4. 解构赋值 1. 初识 ES6 ECMAScript6.0(以下简称ES6)是JavaScript语言的下一代标准&#xff0c;已经在2015年6月正式发布了。它的目标&#xff0c;是使得」JavaScript语言可以用来编写复杂的大型应用程序&#xff0c;成为…

关系网络c++

题目&#xff1a; 代码&#xff1a; #include<bits/stdc.h>using namespace std;int n,x,y;struct node{int num;//编号 int t;//步数 node(){}node(int sum,int tt){numsum;ttt;} }; int mp[101][101];//图 bool flag[101];//标记 queue<node> q; void bfs() {q…

FLASH的读取与写入

FLASH的写入 结合HAL库所给参数&#xff1a; 查阅具体使用芯片的参考手册。 就不在详细解释&#xff0c;英文自行翻译。具体代码如下&#xff1a; /*FLASH写入程序*/ void WriteFlashTest(uint32_t L, uint32_t addr, uint32_t *Data,int Page) {int i0;/* 1/4解锁FLASH*/HAL…

【Anaconda】Linux下Anaconda安装和虚拟环境配置

Linux下Anaconda安装和虚拟环境配置 一、安装anaconda二、conda虚拟环境管理三、jupyter相关启动部署四、遇到问题 下面介绍整体流程&#xff0c;遇到问题优先看“遇到问题章节”&#xff01; 一、安装anaconda 1.下载anaconda安装包 &#xff08;1&#xff09;可以选择在官网…

文件名目录名或卷标语法不正确:数据恢复策略与预防措施

一、文件名目录名或卷标语法不正确的现象 在日常使用电脑或移动设备时&#xff0c;我们经常会遇到“文件名目录名或卷标语法不正确”的错误提示。这种错误通常发生在尝试访问、修改或删除文件、目录或卷标时&#xff0c;系统会提示无法完成操作&#xff0c;因为文件名、目录名…

JavaScript高级 —— 学习(二)

目录 一、深入对象 &#xff08;一&#xff09;创建对象三种方式 1.利用对象字面量创建 2.利用 new Object() 创建 3.利用构造函数创建 &#xff08;二&#xff09;利用构造函数创建对象 1.构造函数介绍 2.约定 3.实例化执行过程 &#xff08;三&#xff09;实例成员…

动态规划之子序列(一)

300.最长递增子序列 给你一个整数数组 nums &#xff0c;找到其中最长严格递增子序列的长度。 子序列是由数组派生而来的序列&#xff0c;删除&#xff08;或不删除&#xff09;数组中的元素而不改变其余元素的顺序。例如&#xff0c;[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序…

Oracle数据库——子查询五

14.1子查询语法 子查询 (内查询) 在主查询之前一次执行完成。子查询的结果被主查询(外查询)使用 。范例一:谁的工资比 Abel 高? 第一:查询Abel的工资是多少。第二:比较大于这个工资的人数。 注意事项: 子查询要包含在括号内。将子查询放在比较条件的右侧。</