The addition of Windows, from the user’s point of view, is to open a new interface; From the developer’s point of view, a new Activity or window is created through the API; From the perspective of system implementation, it is not so simple. The purpose of this article is to understand what the system administrator does when adding a window.
1. Add window APIS
WindowManager is provided in Android for apps to create Windows. In terms of structure, WindowManager inherits from ViewManager. In terms of API, it only provides three interfaces for adding or removing Windows to apps:
abstract void addView(View view, ViewGroup.LayoutParams params)
abstract void removeView(View view)
abstract void updateViewLayout(View view, ViewGroup.LayoutParams params)
Copy the code
Method above can also know that, when creating the window we need to consider only the window to display the View, and window properties (such as size, type, all placed in the WindowManager. LayoutParams).
WindowManager. LayoutParams is ViewGroup. A subclass of LayoutParams, this class is used to carry when creating the Window set all of the attributes, such as Window types, properties, format, Flag…
1.1. Create a basic window
To create a Window, just take the following steps:
private void addWindow(a) {
// 1. Get the WindowManager object
WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
// 2. Get the window.layoutParams object
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
// 3. Define the View Window is displaying
LinearLayout linearLayout = (LinearLayout) View.inflate(this,R.layout.window_layout,null);
// 4. Add View
wm.addView(linearLayout, layoutParams);
}
Copy the code
Example above in step 2, create a WindowManager. LayoutParams object when using the default constructor, in addition, the WindowManager API provides seven constructor used to create the WindowManager. LayoutParams:
WindowManager.LayoutParams()
WindowManager.LayoutParams(int _type)
WindowManager.LayoutParams(int _type, int _flags)
WindowManager.LayoutParams(int _type, int _flags, int _format)
WindowManager.LayoutParams(int w, int h, int _type, int _flags, int _format)
WindowManager.LayoutParams(int w, int h, int xpos, int ypos, int _type, int _flags, int _format)
WindowManager.LayoutParams(Parcel in)
Copy the code
These parameters represent:
- _type: Window type, used to calculate the hierarchy. Android defines three categories and more than 20 sub-classes Window type:
-
- Application Window: [FIRST_APPLICATION_WINDOW LAST_APPLICATION_WINDOW], [13] 1.
-
- Sub Window: [FIRST_SUB_WINDOW LAST_SUB_WINDOW], [1000199].
-
- SystemWindows: [FIRST_SYSTEM_WINDOW, LAST_SYSTEM_WINDOW], [2000,2999];
- _flag: flag of the window. Different functions will be set according to the flag value carried.
- _format: Bitmap format, constant defined in PixelFormat;
- Xpos /ypos: the x and y coordinates of the Window.
Start with the WindowManager.addView() method to see how the Window is created.
2. Add Windows
. Below we from WindowManagerImpl addView () method, analyze the add window procedure.
2.1. WindowManagerImpl. AddView ()
In the Framework layer implementation, WindowMananger is an interface, and the WindowMananger object that the client gets is actually a WindowManangerImpl instance, so when the client executes WindowManager.addView(), Will enter the WindowManagerImpl. AddView () :
// frameworks/base/core/java/android/view/WindowManagerImpl.java
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
// If no parent Window exists and a default Token exists, mDefaultToken is set to params.token. Usually null
applyDefaultToken(params);
/ / enter WindowManagerGlobal
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
Copy the code
In the above method, mGlobal#addView() is called. MGlobal is an instance of Windows ManagerGlobal, which is provided to Windows ManagerGlobal singletons:
// frameworks/base/core/java/android/view/WindowManagerImpl.java
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
Copy the code
The WindowManagerGlobal class, in contrast to the WindoweMangerImpl, does not depend on any Context object for its operations. This singleton is also relative to a WindoweMangerImpl. When an Activity starts, the system creates a WindoweMangerImpl object for each Activity or Window, but within a process, There is only one instance of Windows ManagerGlobal class.
2.2. WindowManagerGlobal. AddView ()
WindowManagerGlobal. AddView () as follows:
// frameworks/base/core/java/android/view/WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if(parentWindow ! =null) {
// If there is a parent window, params will be adjusted accordingly. If there is a parent window, wparams.token is set here
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
// Set the HARDWARE_ACCELERATED flag
final Context context = view.getContext();
if(context ! =null&& (context.getApplicationInfo().flags & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) ! =0) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
......
// Create a ViewRootImpl that represents the root View of the Viwe hierarchy
root = new ViewRootImpl(view.getContext(), display);
// Set params to View
view.setLayoutParams(wparams);
mViews.add(view); // Add the View to the mViews list
mRoots.add(root); // Add ViewRootImpl to the mRoots list
mParams.add(wparams); // Add the carried LayoutParam to the mParams list
try {
// Set the View to RootViewImpl
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
}
}
}
Copy the code
Above method, can create ViewRootImpl object, then the view, ViewRootImpl, WindowManager. LayoutParmas objects were added to the corresponding list, and perform ViewRootImpl# setView () method.
2.3. ViewRootImpl. SetView ()
// frameworks/base/core/java/android/view/ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {...// Register VSync signal listener, ready for layout
requestLayout();
try {
// Through WindowSession, the call goes into WMS
res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mDisplayCutout, inputChannel,
mTempInsets, mTempControls);
} catch (RemoteException e) {
}
}
}
Copy the code
In the ViewRootImpl#setView() method, you set a number of properties for the view, and then execute the mwindowssession #addToDisplayAsUser() method.
MWindowSession is obtained when the ViewRootImpl instance is created, so let’s look at how mWindowSession is obtained.
2.3. Get the IWindowSession object
The ViewRootImpl is the manager of each View and carries the heavy burden of interaction between the client side and the WMS server side (via the IWindowSession object and the IWindow object).
// frameworks/base/core/java/android/view/ViewRootImpl.java
public ViewRootImpl(Context context, Display display) {
this(context, display, WindowManagerGlobal.getWindowSession(),
false /* useSfChoreographer */);
}
Copy the code
Here you see that an object of the IWindowSession interface is obtained using the WindowManagerGlobal#getWindowSession() method. ViewRootImpl itself does not have the ability to communicate across processes; when it needs to make interactive requests to WMS, it does so through the IWindowSession instance.
Next look at the WindowManagerGlobal#getWindowSession() method:
// frameworks/base/core/java/android/view/WindowManagerGlobal.java
public static IWindowSession getWindowSession(a) {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
WMS remote object
IWindowManager windowManager = getWindowManagerService();
// Get Session from WMS
sWindowSession = windowManager.openSession(
new IWindowSessionCallback.Stub() {
......
},
imm.getClient(), imm.getInputContext());
} catch (RemoteException e) {
}
}
returnsWindowSession; }}Copy the code
Here, a Session is opened via WMS:
// frameworks/base/core/java/android/view/WindowManagerGlobal.java
@Override
public IWindowSession openSession(IWindowSessionCallback callback) {
return new Session(this, callback);
}
Copy the code
WMS returns a Session object directly.
Since ViewRootImpl holds an IWindowSession instance from WMS, when it needs to interact with WMS, it will do so directly through that instance.
3. Enter the process for adding sysmTEM_server
The client process goes into WindowManagerService through the Session object and eventually through Binder calls, so the rest of the process is executed in System_server.
3.1. The Session. AddToDisplay ()
After entering session.addtodisplay (), the call goes directly to the WMS:
// frameworks/base/services/core/java/com/android/server/wm/Session.java
@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets,
Rect outStableInsets, Rect outOutsets,
DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
InsetsState outInsetsState) {
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel,
outInsetsState);
}
Copy the code
3.2. WindowManagerService# addWindow ()
The wms.addwindow () method looks like this:
// frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
public int addWindow(Session session, IWindow client, int seq,
LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,
Rect outContentInsets, Rect outStableInsets,
DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
InsetsState outInsetsState, InsetsSourceControl[] outActiveControls,
int requestUserId) {...// Get Window type
final int type = attrs.type;
synchronized (mGlobalLock) {
......
ActivityRecord activity = null;
// Check whether a parent window exists for a window whose type is SUB_WINDOW
final booleanhasParent = parentWindow ! =null;
// Get WindowToken objects based on attr. Token
WindowToken token = displayContent.getWindowToken(
hasParent ? parentWindow.mAttrs.token : attrs.token);
final int rootType = hasParent ? parentWindow.mAttrs.type : type;
// If no WindowToken exists, create a new WindowToken
if (token == null) {
finalIBinder binder = attrs.token ! =null ? attrs.token : client.asBinder();
token = new WindowToken(this, binder, type, false, displayContent,
session.mCanAddInternalSystemWindow, isRoundedCornerOverlay);
// If it is an Application type window, the WindowToken descends to an ActivityRecord
} else if(rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) { activity = token.asActivityRecord(); . }// Create a WindowState object
final WindowState win = new WindowState(this, session, client, token, parentWindow,
appOp[0], seq, attrs, viewVisibility, session.mUid, userId,
session.mCanAddInternalSystemWindow);
// Adjust the win.mattrs property
final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
displayPolicy.adjustWindowParamsLw(win, win.mAttrs, callingPid, callingUid);
// Create InputChanel for input event passing and receiving
if (openInputChannels) {
win.openInputChannel(outInputChannel);
}
// From now on, no exceptions or errors allowed!
res = WindowManagerGlobal.ADD_OKAY;
// Add Windowstate and IWindow to mWindowMap
mWindowMap.put(client.asBinder(), win);
// Transition WindowToken down
final ActivityRecord tokenActivity = token.asActivityRecord();
boolean imMayMove = true;
// Add a WindowState object to a WindowToken, which will act as the parent container for WindowState
win.mToken.addWindow(win);
// Window animation object
final WindowStateAnimator winAnimator = win.mWinAnimator;
winAnimator.mEnterAnimationPending = true;
winAnimator.mEnteringAnimation = true; ./ / set an Inset
outInsetsState.set(win.getInsetsState(), win.isClientLocal());
/ / will InputMonitor# mUpdateInputWindowsNeeded attribute set to true, said it will update internal Input
displayContent.getInputMonitor().setUpdateInputWindowsNeededLw();
boolean focusChanged = false;
// If the button can be received
if (win.canReceiveKeys()) {
// Update focus window
focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS,
false /*updateInputWindows*/);
if (focusChanged) {
imMayMove = false; }}// Assign a layer to the WindowState, but this time it does not actually assign a layer
win.getParent().assignChildLayers();
// Update Input Window
if (focusChanged) {
displayContent.getInputMonitor().setInputFocusLw(displayContent.mCurrentFocus,
false /*updateInputWindows*/);
}
displayContent.getInputMonitor().updateInputWindowsLw(false /*force*/);
// If the direction is updated, update the global configuration
if (win.isVisibleOrAdding() && displayContent.updateOrientation()) {
displayContent.sendNewConfiguration();
}
getInsetsSourceControls(win, outActiveControls);
}
return res;
}
Copy the code
In the above method, a lot of verification is carried out first. If the verification is successful, further processing is started. Some processing for special Windows has been omitted. The main work is as follows:
- According to the attrs.token passed in, get the WindowToken object from DisplayContent#mTokenMap
,>
, if not, create a new WindowToken;
- Create a WindowState object;
- Add a WindowState object to a WindowToken, which becomes the parent container of the WindowState object;
- Create an InputChannel for input event distribution.
- Update focus window;
- Update InputWindow;
Let’s look at each of these steps.
3.3. Create a WindowToken object
WindowToken acts as the parent container for WindowState and manages a set of Windows. During the Window addition process, a WindowToken is created on a case-by-case basis, because a WindowToken manages a set of Windows. If a Suitable WindowToken already exists during the Window addition process, it will not be created. Otherwise, WindowToken is created.
WindowToken is constructed as follows:
// frameworks/base/services/core/java/com/android/server/wm/WindowToken.java
WindowToken(WindowManagerService service, IBinder _token, int type, boolean persistOnEmpty,
DisplayContent dc, boolean ownerCanManageAppTokens, int ownerUid,
boolean roundedCornerOverlay, boolean fromClientToken) {
super(service);
token = _token; // IBinder object, as the actual token for a set of Windows
windowType = type; // Window type
mPersistOnEmpty = persistOnEmpty; // Whether the WidowToken is explicitly added
mOwnerCanManageAppTokens = ownerCanManageAppTokens; // Have MANAGE_APP_TOKENS permission
mOwnerUid = ownerUid; // owner id
mRoundedCornerOverlay = roundedCornerOverlay; // Whether to cover rounded corners
mFromClientToken = fromClientToken;
if(dc ! =null) {
dc.addWindowToken(token, this); // Add this object to DisplayContent}}Copy the code
In the above method, after initializing some properties, WindowToken and the corresponding IBinder object are passed to DisplayContent and stored in DisplayContent#mToken as
. Also place the Token in an appropriate container:
// frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java
private void addWindowToken(IBinder binder, WindowToken token) {
// If the WindowToken is already associated with a DisplayContent object, no other DisplayContent object can be associated with it
final DisplayContent dc = mWmService.mRoot.getWindowTokenDisplay(token);
// add WindowToken to DisplayContent#mTokenMap
mTokenMap.put(binder, token);
// Non-activity Windows are added to the corresponding three Containers according to the Window type
if (token.asActivityRecord() == null) {
// WindowToken is associated with DisplayContent
token.mDisplayContent = this;
switch (token.windowType) {
// put the input method Window into mImeWindowsContainers
case TYPE_INPUT_METHOD:
case TYPE_INPUT_METHOD_DIALOG:
mImeWindowsContainers.addChild(token);
break;
case TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY:
mOverlayContainers.addChild(token);
break;
// Other types of Windows are placed in mDisplayAreaPolicy
default:
mDisplayAreaPolicy.addWindow(token);
break; }}}Copy the code
In the above methods, mImeWindowsContainers, mOverlayContainers and mdisplayarepolicy will be managed as the parent containers of WindowToken, where:
- MImeWindowsContainers: ImeContainer class object, as the parent container of the IME window, managing the IME window;
- MOverlayContainers: NonAppWindowContainers class object that acts as a parent container for Windows that are not associated with the application, such as status bars;
- MDisplayAreaPolicy: A DisplayAreaPolicy class object, which is not itself a container, but which as a policy holds an appropriate container, displayArea.token, to manage other types of Windows;
The class structure of the three containers is as follows:
At this point, the WindowToken object is created.
3.4. Create a WindowState object
Once you have a WindowToken object, you create a WindowState object. A WindowState represents a specific Window, and a Corresponding WindowState object is created for each Window that is added.
The WindowState class is constructed as follows:
// frameworks/base/services/core/java/com/android/server/wm/WindowState.java
WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
WindowState parentWindow, int appOp, int seq, WindowManager.LayoutParams a,
int viewVisibility, int ownerId, int showUserId,
boolean ownerCanAddInternalSystemWindow, PowerManagerWrapper powerManagerWrapper) {
super(service); / / execution WindowContainer (WMS)
mSession = s; / / the Session object
mClient = c; // A client object of type IWindow that interacts with the client
mToken = token; / / WindowToken object
mActivityRecord = mToken.asActivityRecord(); // If the token is of ActivityRecord type, it is equivalent to a downward transition
mAttrs.copyFrom(a); // Copy from the incoming attr object to the mAttrs object
mLastSurfaceInsets.set(mAttrs.surfaceInsets);
mViewVisibility = viewVisibility; // Window visibility
mPolicy = mWmService.mPolicy; / / PhoneWindowManager object
mContext = mWmService.mContext;
mSeq = seq;
mPowerManagerWrapper = powerManagerWrapper;
mForceSeamlesslyRotate = token.mRoundedCornerOverlay; // A special marker for the rounded window controls the rotation of the rounded corner
// Set the Binder die proxy
try {
c.asBinder().linkToDeath(deathRecipient, 0);
} catch (RemoteException e) {
}
mDeathRecipient = deathRecipient;
// Set BaseLayer and SubLayer values for Sub Window types
if (mAttrs.type >= FIRST_SUB_WINDOW && mAttrs.type <= LAST_SUB_WINDOW) {
mBaseLayer = mPolicy.getWindowLayerLw(parentWindow)
* TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
mSubLayer = mPolicy.getSubWindowLayerFromTypeLw(a.type);
mIsChildWindow = true; mLayoutAttached = mAttrs.type ! = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; mIsImWindow = parentWindow.mAttrs.type == TYPE_INPUT_METHOD || parentWindow.mAttrs.type == TYPE_INPUT_METHOD_DIALOG; mIsWallpaper = parentWindow.mAttrs.type == TYPE_WALLPAPER; }else {// Set BaseLayer and SubLayer values for Windows that are not subwindow types. }// Whether it is a "floating" window, because these two Windows need to be displayed on other Windows
mIsFloatingLayer = mIsImWindow || mIsWallpaper;
// Create a WindowStateAnimator object
mWinAnimator = new WindowStateAnimator(this);
mWinAnimator.mAlpha = a.alpha;
// Initial width height
mRequestedWidth = 0;
mRequestedHeight = 0;
mLastRequestedWidth = 0;
mLastRequestedHeight = 0;
mLayer = 0;
// Create an InputWindowHandle object
mInputWindowHandle = newInputWindowHandle( mActivityRecord ! =null ? mActivityRecord.mInputApplicationHandle : null,
getDisplayId());
// If it is a child window, it needs to be added to the parent window
if (mIsChildWindow) {
parentWindow.addChild(this, sWindowSubLayerComparator);
}
// Get the WindowProcessController object to manage the process
mWpcForDisplayConfigChanges = (s.mPid == MY_PID || s.mPid < 0)?null
: service.mAtmService.getProcessController(s.mPid, s.mUid);
}
Copy the code
In the above method, many properties in the WindState class are initialized, and two objects are created — a WindowStateAnimator object and an InputWindowHandle object.
The WindowStateAnimator object is used to convert the animation and Surface for the WindowState object. During the drawing process, the animation state and Surface state will be recorded by the object.
The InputWindowHandle object is used for input event distribution, associating InputChannel with WindowState via an internal IBinder type token. The creation of these two objects is relatively simple and will be omitted here.
3.5. Set WindowToken to the parent WindoState container
Next, a WindowState object is added to a WindowToken using the WindowToken#addWindow() method, making WindowToken the parent of WindowState:
// frameworks/base/services/core/java/com/android/server/wm/WindowToken.java
void addWindow(final WindowState win) {
// For child Windows, it is managed by the parent window and does not need to be managed by WindowToken
if (win.isChildWindow()) {
return;
}
// Create a createSurfaceControl object if mSurfaceControl does not exist
if (mSurfaceControl == null) {
createSurfaceControl(true /* force */);
}
// Add to the mChildren list and set WindowStack#mParent to the WindowToken
if(! mChildren.contains(win)) { addChild(win, mWindowComparator); mWmService.mWindowsChanged =true; }}Copy the code
In the above method, the addChild() method adds a WindowState object to the WindowToken#mChildren list and assigns a WindowToken to the WindowState#mParent property.
3.6. To create an InputChannel
InputChannel is used to transmit input events to applications. InputFlinger reads input events from inputFlinger and passes them to applications through sockets.
// frameworks/base/services/core/java/com/android/server/wm/WindowState.java
void openInputChannel(InputChannel outInputChannel) {
String name = getName();
// Create an InputChannel pair
InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
mInputChannel = inputChannels[0];
mClientChannel = inputChannels[1];
// Register InputChannel with the IMS
mWmService.mInputManager.registerInputChannel(mInputChannel);
mInputWindowHandle.token = mInputChannel.getToken();
// Populate the outInputChannel returned to ViewRootImpl
if(outInputChannel ! =null) {
mClientChannel.transferTo(outInputChannel);
mClientChannel.dispose();
mClientChannel = null; }... mWmService.mInputToWindowMap.put(mInputWindowHandle.token,this);
}
Copy the code
In this method, InputChannel pairs are first obtained, inputChannels are registered with IMS, and inputChannels[1] is returned to ViewRootImpl. The part about InputChannel passing input events is explained in more detail in the Input module.
3.7. Update the focus window
If the WindowState can receive key events, then through WMS# updateFocusedWindowLocked () method updates the focus of the global window. When updated, each WindowState is iterated from the top down, from RootWindowContainer → DisplayContent, and finally a WindowState object with focus is retrieved.
The process of updating focus Windows is quite long, and this part of the process is analyzed separately, see Android R WindowManagerService module (5) update focus Windows and InputWindows.
3.8 update InputWindow
“InputWindow” is the window that can receive input events. Before updating the focus window, through InputMonitor# setUpdateInputWindowsNeededLw () method, which will be InputMonitor# mUpdateInputWindowsNeeded attribute set to true, Indicates that the Input window needs to be updated next.
// frameworks/base/services/core/java/com/android/server/wm/InputMonitor.java
void updateInputWindowsLw(boolean force) {
if(! force && ! mUpdateInputWindowsNeeded) {return;
}
scheduleUpdateInputWindows();
}
Copy the code
Starting with Android Q, update InputWindow to InputFlinger with Binder calls in SurfaceFlinger. This process is also longer and analyzed separately, as shown in the Android R WindowManagerService module (5) focus Windows and InputWindows updates.
At this point, the entire Window add process is complete.
The sequence diagram of the whole process is as follows:
4. To summarize
- When adding a Window, the WindowManager object the client gets is a WindowManagerImpl instance, and neither Activity nor Window has its own WindowManagerImpl instance, bound to the Context. Compared to the WindowManagerImpl object, there is only one WindowManagerGlobal object per process;
- As the manager of each View, ViewRootImpl interacts with WMS through IWindowSession and IWindow interfaces.
- In WMS, a WindowState object represents a window, and A WindowToken acts as a parent container for WindowState, managing a set of Windows.
With the WMS#addWindow() method, a window management object relative to system_server is created, but the Surface that needs to display the window’s contents is not yet created.
Within the addView() procedure, there is a requestLayout() operation that listens for the Vsync signal and, upon receipt of the Vsync signal, requests THE WMS to relayout to begin the layout process. The next article will examine the layout process.