preface
A Window is the concept of a Window, which is the carrier of all views, whether it is an Activity, Dialog, or Toast, whose views are attached to the Window. For example, to display a floating Window on the desktop, you need to use Window to achieve. WindowManager is the portal to access Windows.
Window is an abstract class whose implementation classes are PhoneWidow, DecorView in Activity, and View in Dialog are all created in PhoneWindow. So the Window is actually the direct manager of the View. For example, in event distribution, when a click is received in an Activity, the event is first passed through the Window to the DecorView and then distributed to our View. The SetContentView of an Activity is also done at the bottom through the Window. And findViewById is also the window to call.
In my understanding, the window in the first sentence and the window in the second sentence are not the same thing.
“Window” in the first sentence is an abstract concept that does not really exist. It only exists in the form of View. For example, if you add a Window through WindowManager, the Window is in the form of a View.
The implementation of this class is PhoneWindow, which is used to create the View we need for our page. So this Window can be called the direct manager of the View. The PhoneWindow DecorView ends up being attached to the Window as well.
Because it’s often confused at the beginning, the Window is the View manager and the Window, which is obviously not reasonable. The above is my personal understanding, if you feel wrong, please point out, thank you!
Window and WindowManager
If you want to add and delete Windows, you need to use WindowManager to operate, as follows:
How does WindowManager add Windows?
val textView = TextView(this).apply {
text = "window"
textSize = 18f
setTextColor(Color.BLACK)
setBackgroundColor(Color.WHITE)
}
val parent = WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT, 0.0. PixelFormat.TRANSPARENT ) parent.type = WindowManager.LayoutParams.TYPE_APPLICATION parent.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE parent.gravity = Gravity.END or Gravity.BOTTOM parent.y =500
parent.x = 100
windowManager.addView(textView, parent)
Copy the code
The above code can add a Window at (100,500), where the more important attributes are type and flags.
Type Window properties
The Type parameter indicates the Type of the Window. There are three types of Windows, corresponding to three levels, as follows:
The Window type | Scope of the hierarchy | instructions |
---|---|---|
The application Window | 1-99 | Corresponds to an Activity |
The child Window | 1000 ~ 1999 | Can’t stand alone, needs to be attached to a particular Window, A common PopupDialog, for example, is a child Window. |
The Window system | 2000 ~ 2999 | Windows that need permissions to create For example, Toast and the system status bar are Windows of the system |
- A child Window cannot exist on its own and must depend on a parent Window, for example PopWindow must depend on an Activity
- Window layering. The higher-level Window overrides the lower-level Window when displayed
Flags Indicates the flag of the window
Flags represents the Window property. It has multiple options that inform Window of the properties displayed, for example:
Floags | features |
---|---|
FLAG_NOT_FOCUSABLE | The Window does not need to get focus or input events. This flag also enables FLAG_NOT_TOUCH_MODAL The final event is passed directly to the underlying Window, which has focus. |
FLAG_NOT_TOUCH_MODAL | Pass click events outside the Window area to the underlying Window, Click events in the current Window handle themselves, Otherwise, other Windows cannot receive the click event |
FLAG_SHOW_WHEN_LOCKED | You can display Windows on the lock screen |
FLAG_TURN_SCREEN_ON | Light up the screen when Window is displayed |
WindowManager
Windows Manager provides very simple functions, commonly used only three methods, that is, add View, update View, and delete View.
These three methods are defined in the ViewManager interface, which WindowManager inherits
public interface ViewManager{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
Copy the code
public interface WindowManager extends ViewManager
Copy the code
So it seems that the process of WindowManager operating on a Window is more like operating on a View inside a Window. The simple draggable Window effect is actually quite easy to achieve. Just change the x in LayoutParams. The y value changes the position of the Window. First set the onTouchListener to the View and then update the View position in the onTouch method.
Window internal mechanism
A Window is an abstract concept; each Window corresponds to a VIew and a ViewRootImpl.
The Window and View are linked by View View PL, so the Window doesn’t actually exist, it exists as a View. As you can see from the WindowManager definition, all three methods are provided for views. This means that the View is the Window’s entity.
Windows cannot be accessed directly in real development and must be accessed through WindowManager
Window adding process
Windows are added via addView of WindowManager, which is an interface whose real implementation is WindowManageImpl. As follows:
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
Copy the code
As you can see, Windows Manager ImpL does not implement the Window three operations directly, but hands them all over to Windows ManagerGlobal.
WindowManagerGlobal.addView
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
// Check whether the parameter is valid
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (display == null) {
throw new IllegalArgumentException("display must not be null");
}
if(! (paramsinstanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if(parentWindow ! =null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
//....
}
ViewRootImpl root;
View panelParentView = null;
// Create ViewRootImpl and assign it to root
root = new ViewRootImpl(view.getContext(), display);
// Set the View's params
view.setLayoutParams(wparams);
// Add view, RootRootImpl, wparams to the list
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
try {
// Call ViewRootImpl to update the interface and complete the Window addition process
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throwe; }}Copy the code
In the above code, we create the ViewRootImpl, and then add view, RootRootImpl, and wparams to the list. Finally, add the Window through ViewRootImpl.
These lists are defined as follows:
private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams =
new ArrayList<WindowManager.LayoutParams>();
private final ArraySet<View> mDyingViews = new ArraySet<View>();
Copy the code
- MViews is the View corresponding to all the Windows
- MRoots is the view wrootimPL for all Windows
- MParams stores layout parameters for all Windows
- MDyingViews are the views that are actually being deleted, or Window objects that have been called to RemoveView but have not been deleted.
ViewRootImpl.setView
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout();
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
mTempInsets);
setFrame(mTmpFrame);
}
/ /...}}}@Override
public void requestLayout(a) {
if(! mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested =true; scheduleTraversals(); }}Copy the code
This method first calls requestLayout to make a refresh request, where scheduleTraversals() is the entry to the View
After requestLayout calls, call the mWindowSession. AddToDisplay method, to complete the final Window add process.
In the code above, mWindowSession is of type IWindowSession, which is a Binder object. The actual implementation is Session, i.e. the addition of Window is an IPC call.
Inside the Session, Windows are added using WindoweManagerServer, as shown below:
@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
This leaves the process of adding Windows to the Windows ManagerServer. WMS assigns surfaces to them, determines the order in which Windows are displayed, and eventually draws those surfaces onto the screen via SurfaceFlinger.
Comb through the process
-
The first call is WindowManagerImpl. The addView ()
Will implement in addView entrusted to the WindowManagerGlobal. AddView ()
-
WindowManagerGlobal.addView()
The ViewRootImpl is created in addView and assigned to root. View, Params, and root are all stored in their respective lists.
And then we call viewrootimpl.setView ()
-
ViewRootImpl.setView()
The refresh request is done by calling requestLayout in the setView, followed by the final Window addition through IWindowSession, which is a Binder object, The real implementation class is Session, which means that the Window add-on procedure tries an IPC call once.
Windows are added to the Session using Windows Manager Server.
Windows update process
Start directly with Windows ManagerGlobal:
WindowManagerGlobal.updateViewLayout
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if(! (paramsinstanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
// Set the updated parameters to the view
view.setLayoutParams(wparams);
synchronized (mLock) {
// Get the view index in the list
int index = findViewLocked(view, true);
// Get the corresponding view
ViewRootImpl root = mRoots.get(index);
// Remove the old parameter from the parameter list
mParams.remove(index);
// Add the new argument to the specified location
mParams.add(index, wparams);
/ / call ViewRootImpl. SetLayoutPrams to update the parameters
root.setLayoutParams(wparams, false); }}Copy the code
ViewRootImpl.setLayoutPrams
In the setLayoutPrams method, the scheduleTraversals method is finally called to re-policy, arrange, and redraw the View.
In addition to redrawing the View itself, viewrotimPL updates the Window View via WindowSession, a process implemented by relayoutWindow of Windows ManagerServer, This is also an IPC process.
The Window deletion process
Windows are deleted and added in the same way as Windows ManagerImpl and Then Windows ManagerGlobal:
WindowManagerGlobal.removeView
@UnsupportedAppUsage
public void removeView(View view, boolean immediate) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
synchronized (mLock) {
int index = findViewLocked(view, true);
View curView = mRoots.get(index).getView();
removeViewLocked(index, immediate);
if (curView == view) {
return;
}
throw new IllegalStateException("Calling with view " + view
+ " but the ViewAncestor is attached to "+ curView); }}Copy the code
In the above code, we find the index in the Views list, and then call removeViewLocked to remove it further
private void removeViewLocked(int index, boolean immediate) {
ViewRootImpl root = mRoots.get(index);
View view = root.getView();
if(view ! =null) {
InputMethodManager imm = view.getContext().getSystemService(InputMethodManager.class);
if(imm ! =null) { imm.windowDismissed(mViews.get(index).getWindowToken()); }}boolean deferred = root.die(immediate);
if(view ! =null) {
view.assignParent(null);
if(deferred) { mDyingViews.add(view); }}}Copy the code
RemoveViewLocked is removed using ViewRootImpl. The removeView and removeViewImmedialte are both asynchronous and synchronous delete interfaces provided in Windows Manager.
The removeViewImmedialte is not normally used to delete Windows in case of unexpected errors.
So the asynchronous deletion case is used here, using the die method. The die method simply sends a delete request and immediately returns, but the View has not finished deleting it, so it will eventually be added to the mDyingViews list.
The die is as follows:
boolean die(boolean immediate) {
// Make sure we do execute immediately if we are in the middle of a traversal or the damage
// done by dispatchDetachedFromWindow will cause havoc on return.
if(immediate && ! mIsInTraversal) { doDie();return false;
}
if(! mIsDrawing) { destroyHardwareRenderer(); }else {
Log.e(mTag, "Attempting to destroy the window while drawing! \n" +
" window=" + this + ", title=" + mWindowAttributes.getTitle());
}
mHandler.sendEmptyMessage(MSG_DIE);
return true;
}
Copy the code
The handler in ViewRootImpl receives this message and calls the doDie method. This is the difference between the two deletion methods.
void doDie(a) {
checkThread();
if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface);
synchronized (this) {
if (mRemoved) {
return;
}
mRemoved = true;
if (mAdded) {
// The actual deletion logic is in this method
dispatchDetachedFromWindow();
}
//....
mAdded = false;
}
WindowManagerGlobal.getInstance().doRemoveView(this);
}
Copy the code
Window creation process
From the above analysis, it can be concluded that a View cannot exist alone and must be attached to a Window, so there is a Window wherever there is a View. These views include: Activity, Dialog, Toast, PopupWindow, and so on.
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
/ /...
if(activity ! =null) {
appContext.setOuterContext(activity);
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);
//....
}
return activity;
}
Copy the code
In the attach method of the Activity, the system creates the Window to which the Activity belongs and does not set its Callback interface. Since the Activity implements the Window Callback interface, Therefore, when the Window receives an external state change, it calls back to the method in the Activity.
Callback methods in has a lot of, but some of us are very familiar with, for example dispatchTouchEvent, onAttachedToWdindow and so on.
Window creation for the Activity
The Activity creation process is a bit more complicated, and is eventually launched using performLaunchActivity() in the ActivityThread. This method creates an instance object of the Activity using the class loader. And call its attach method to associate the desired environment variables with it.
##Activity.attach
/ / create PhoneWindow
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
// Set the window callback
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if(info.softInputMode ! = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) { mWindow.setSoftInputMode(info.softInputMode); }Copy the code
In the attach method, the Window is created and the callback is set.
Since the Activity view is provided by the setContentView method, we can simply look at the setContentView:
# #Activity
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
Copy the code
# #PhoneWindow
public void setContentView(int layoutResID) {
if (mContentParent == null) {
// 1, create a DecorView
installDecor();
} else if(! hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); }if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
//2 Add the activity layout file
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if(cb ! =null && !isDestroyed()) {
/ / 3
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
Copy the code
In the above code, you create a DecorView if there is no DecorView, and it generally contains a title bar and an interior bar, but this changes with the theme. But anyway, the content bar must exist, and the content bar has a fixed ID content, the full ID is Android.r.i d.c. tent.
Note 1: The DecorView was created with generateDecor. GenerateLayout is then called to load a specific layout file into the DecorView, depending on the system version and theme defined. Once loaded, the View of the content area is returned, which is called mContentParent
Note 2: Add the layout the activity needs to display to the McOntentParent.
Note 3: Since the activity implements the Window’s callback interface, this indicates that the activity layout file has been added to the mParentView of the decorView, notifies onContentChanged.
After the above three steps, the DecorView is initialized and the Activity layout file is loaded into the DecorView’s mParentView. But at this point the DecorView has not been officially added to the Window by WindowManager.
In the ActivityThread’s handlerResumeActivity, the activity’s onResume method is called, and the DecorView is added to the Window
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
/ /...
// Call the activity's onResume method
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
final Activity a = r.activity;
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
ViewRootImpl impl = decor.getViewRootImpl();
if(impl ! =null) { impl.notifyChildRebuilt(); }}if (a.mVisibleFromClient) {
if(! a.mWindowAdded) { a.mWindowAdded =true;
// The DecorView completes the adding and displaying process
wm.addView(decor, l);
} else{ a.onWindowAttributesChanged(l); }}}else if(! willBeVisible) {if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
r.hideForNow = true;
}
/ /...
}
Copy the code
Dialog Window creation process
-
Create a Window
Creating a Window in a Dialog is done in its constructor as follows:
Dialog(@UiContext @NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) { / /... / / get WindowManager mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); / / create the Window final Window w = new PhoneWindow(mContext); mWindow = w; / / set the Callback w.setCallback(this); w.setOnWindowDismissedCallback(this); w.setOnWindowSwipeDismissedCallback(() -> { if(mCancelable) { cancel(); }}); w.setWindowManager(mWindowManager,null.null); w.setGravity(Gravity.CENTER); mListenersHandler = new ListenersHandler(this); } Copy the code
-
Initialize the DecorView to add the dialog view to the DecorView
public void setContentView(@LayoutRes int layoutResID) { mWindow.setContentView(layoutResID); } Copy the code
This is similar to an activity in that the Window is used to add the specified layout file
-
Add the DecorView to the Window display
public void show(a) { / /... mDecor = mWindow.getDecorView(); mWindowManager.addView(mDecor, l); // Send a callback message sendShowMessage(); } Copy the code
As you can see from the above three steps, the Window creation of a Dialog is similar to the Window creation of an Activity, with little difference between the two.
When the dialog is closed, it will be through the WindowManager to remove DecorView, mWindowManager. RemoveViewImmediate (mDecor).
A normal Dialog must use the Activity Context. If you use the Application Context, you will get an error:
Caused by: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
Copy the code
The error message is clear: there is no Token, and tokens are usually owned only by activities, so you just need to use the Activity as the Context.
In addition, system Window is special, so it does not need Token. We can change the Window Type of Dialog to system Type, as shown below:
val dialog = Dialog(application)
dialog.setContentView(textView)
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { dialog.window? .setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY) }else{ dialog.window? .setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT) } dialog.show()Copy the code
It should be noted that you need to apply for suspension window permission
Toast Window creation process
Toast is also based on Windows, but its working process is somewhat complicated. In the interior of the Toast there are two kinds of process of IPC, the first is Toast access NotificationManagerService process. The second category is NotificationManagerServer callback interface of TN in the Toast. The following will NotificationManagerService NMS for short.
Toast belongs to the system Window. There are two ways to define the internal View, one is the system default, and the other is to specify a View through the setView method (the setView method has been abandoned after Android 11, and the custom View is no longer displayed). They correspond to mNextView, an internal member of Toast.
Toast.show()
Toast provides show and cancel to show and hide toasts, respectively. Inside them is an IPC process, which is implemented as follows:
public void show(a) {
INotificationManager service = getService();
String pkg = mContext.getOpPackageName();
TN tn = mTN;
tn.mNextView = mNextView;
final int displayId = mContext.getDisplayId();
try {
if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
if(mNextView ! =null) {
service.enqueueToast(pkg, mToken, tn, mDuration, displayId);
} else {
// ...}}}//....
}
public void cancel(a) {
try {
getService().cancelToast(mContext.getOpPackageName(), mToken);
} catch (RemoteException e) {
// Empty
}
//....
}
static private INotificationManager getService(a) {
if(sService ! =null) {
return sService;
}
sService = INotificationManager.Stub.asInterface(
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
return sService;
}
Copy the code
As can be seen from the above code, both display and shadowing need to be implemented through the NMS. Since the NMS runs in the system process, the Toast can only be displayed and hidden through cross-process calls.
It calls the enqueueToast method of the NMS. The INotificationManager is just an AIDL interface that is used to communicate with the NMS. For those of you who are not familiar with the IPC communication process, read this article.
NotificationManagerService.enqueueToast
Let’s look at the enqueueToast method of the NMS, which already belongs to another process. The first parameter indicates the current application package name, the second token, the third TN indicates the remote callback, which is also an IPC process, the fourth duration, and the fifth id displayed
public void enqueueToast(String pkg, IBinder token, ITransientNotification callback,
int duration, int displayId) {
enqueueToast(pkg, token, null, callback, duration, displayId, null);
}
private void enqueueToast(String pkg, IBinder token, @Nullable CharSequence text, @Nullable ITransientNotification callback, int duration, int displayId,
@Nullable ITransientNotificationCallback textCallback) {
synchronized (mToastQueue) {
int callingPid = Binder.getCallingPid();
final long callingId = Binder.clearCallingIdentity();
try {
ToastRecord record;
int index = indexOfToastLocked(pkg, token);
// If there is one in the queue, update it instead of reordering it at the end
if (index >= 0) {
record = mToastQueue.get(index);
record.update(duration);
} else {
int count = 0;
final int N = mToastQueue.size();
for (int i = 0; i < N; i++) {
final ToastRecord r = mToastQueue.get(i);
// For the same application, taoST cannot exceed 50
if (r.pkg.equals(pkg)) {
count++;
if (count >= MAX_PACKAGE_TOASTS) {
Slog.e(TAG, "Package has already queued " + count
+ " toasts. Not showing more. Package=" + pkg);
return; }}}// Create the corresponding ToastRecord
record = getToastRecord(callingUid, callingPid, pkg, isSystemToast, token,text, callback, duration, windowToken, displayId, textCallback);
mToastQueue.add(record);
index = mToastQueue.size() - 1;
keepProcessAliveForToastIfNeededLocked(callingPid);
}
// ==0 indicates that there is only one toast left
if (index == 0) {
showNextToastLocked(false); }}finally{ Binder.restoreCallingIdentity(callingId); }}}private ToastRecord getToastRecord(int uid, int pid, String packageName, boolean isSystemToast,
IBinder token, @Nullable CharSequence text, @Nullable ITransientNotification callback,
int duration, Binder windowToken, int displayId,
@Nullable ITransientNotificationCallback textCallback) {
if (callback == null) {
return new TextToastRecord(this, mStatusBar, uid, pid, packageName,
isSystemToast, token, text, duration, windowToken, displayId, textCallback);
} else {
return new CustomToastRecord(this, uid, pid, packageName, isSystemToast, token, callback, duration, windowToken, displayId); }}Copy the code
In the above code, the number of toasts for a given application is judged and if it exceeds 50, it will exit directly. This is to prevent DOS. If one application keeps popping taost repeatedly, other applications will not be able to pop up, which is obviously unreasonable.
After the judgment is completed, ToastRecord will be created, which is divided into two kinds, one is TextToastRecord, and another is CustomToastRecord. Since Tn is passed in when enqueueToast is called, getToastRecord returns a CustomToastRecord object.
To determine that there is only one toast, showNextToastLocked is called, otherwise several Taost are actually displayed.
And then let’s take a look at showNextToastLocked
void showNextToastLocked(boolean lastToastWasTextRecord) {
ToastRecord record = mToastQueue.get(0);
while(record ! =null) {
/ /...
if (tryShowToast(
record, rateLimitingEnabled, isWithinQuota, isPackageInForeground)) {
scheduleDurationReachedLocked(record, lastToastWasTextRecord);
mIsCurrentToastShown = true;
if(rateLimitingEnabled && ! isPackageInForeground) { mToastRateLimiter.noteEvent(userId, record.pkg, TOAST_QUOTA_TAG); }return;
}
int index = mToastQueue.indexOf(record);
if (index >= 0) {
mToastQueue.remove(index);
}
// Is there any remaining TAOst to display
record = (mToastQueue.size() > 0)? mToastQueue.get(0) : null; }}private boolean tryShowToast(ToastRecord record, boolean rateLimitingEnabled,
boolean isWithinQuota, boolean isPackageInForeground) {
/ /...
return record.show();
}
Copy the code
The last call in the above code is Record.show (), which is also called CustomToastRecord.
Let’s take a look at his show method:
@Override
public boolean show(a) {
if (DBG) {
Slog.d(TAG, "Show pkg=" + pkg + " callback=" + callback);
}
try {
callback.show(windowToken);
return true;
} catch (RemoteException e) {
Slog.w(TAG, "Object died trying to show custom toast " + token + " in package "+ pkg);
mNotificationManager.keepProcessAliveForToastIfNeeded(pid);
return false; }}Copy the code
As you can see, the show method for callback is called, which is the Tn passed in when the CustomToastRecord was created. This is called back to Tn’s show method.
Toast# Tn.show
Let’s move on:
TN(Context context, String packageName, Binder token, List<Callback> callbacks,
@Nullable Looper looper) {
mPresenter = new ToastPresenter(context, accessibilityManager, getService(),packageName);
mHandler = new Handler(looper, null) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case SHOW: {
IBinder token = (IBinder) msg.obj;
handleShow(token);
break;
}
/ /...}}}; }public void show(IBinder windowToken) {
if (localLOGV) Log.v(TAG, "SHOW: " + this);
mHandler.obtainMessage(SHOW, windowToken).sendToTarget();
}
public void handleShow(IBinder windowToken) {
if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
+ " mNextView=" + mNextView);
/ /...
if(mView ! = mNextView) {// remove the old view if necessary
handleHide();
mView = mNextView;
mPresenter.show(mView, mToken, windowToken, mDuration, mGravity, mX, mY,
mHorizontalMargin, mVerticalMargin,
newCallbackBinder(getCallbacks(), mHandler)); }}Copy the code
Since the show methods are called by the NMS as a kua process, they run in Binder thread pools, where handlers are used in order to switch to the thread where the Toast request is made. From the above code, we can see that the ToastPresenter is ultimately handled by ToastPresenter
ToastPerenter.show
public class ToastPresenter {
//....
@VisibleForTesting
public static final int TEXT_TOAST_LAYOUT = R.layout.transient_notification;
/**
* Returns the default text toast view for message {@code text}.
*/
public static View getTextToastView(Context context, CharSequence text) {
View view = LayoutInflater.from(context).inflate(TEXT_TOAST_LAYOUT, null);
TextView textView = view.findViewById(com.android.internal.R.id.message);
textView.setText(text);
return view;
}
//....
public ToastPresenter(Context context, IAccessibilityManager accessibilityManager, INotificationManager notificationManager, String packageName) {
mContext = context;
mResources = context.getResources();
/ / get WindowManager
mWindowManager = context.getSystemService(WindowManager.class);
mNotificationManager = notificationManager;
mPackageName = packageName;
mAccessibilityManager = accessibilityManager;
// Create a parameter
mParams = createLayoutParams();
}
private WindowManager.LayoutParams createLayoutParams(a) {
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.format = PixelFormat.TRANSLUCENT;
params.windowAnimations = R.style.Animation_Toast;
params.type = WindowManager.LayoutParams.TYPE_TOAST; / / TYPE_TOAST: 2005
params.setFitInsetsIgnoringVisibility(true);
params.setTitle(WINDOW_TITLE);
params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
setShowForAllUsersIfApplicable(params, mPackageName);
return params;
}
public void show(View view, IBinder token, IBinder windowToken, int duration, int gravity, int xOffset, int yOffset, float horizontalMargin, float verticalMargin, @Nullable ITransientNotificationCallback callback) {
show(view, token, windowToken, duration, gravity, xOffset, yOffset, horizontalMargin,
verticalMargin, callback, false /* removeWindowAnimations */);
}
public void show(View view, IBinder token, IBinder windowToken, int duration, int gravity,int xOffset, int yOffset, float horizontalMargin, float verticalMargin,@Nullable ITransientNotificationCallback callback, boolean removeWindowAnimations) {
/ /...
addToastView();
trySendAccessibilityEvent(mView, mPackageName);
if(callback ! =null) {
try {
/ / callback
callback.onToastShown();
} catch (RemoteException e) {
Log.w(TAG, "Error calling back " + mPackageName + " to notify onToastShow()", e); }}}private void addToastView(a) {
if(mView.getParent() ! =null) {
mWindowManager.removeView(mView);
}
try {
// Add the Toast view to the WindowmWindowManager.addView(mView, mParams); }}}Copy the code
To summarize
After the above research, it is suddenly found that playing Toast is quite troublesome. The main thing is that the internal IPC is quite round.
As for the reason of CONDUCTING IPC, it is mainly for unified management of the disappearance and display of all Toast in the system. The actual display and disappearance operation is still completed in the App.
The window type of Toast is TYPE_TOAST, which belongs to the system type. Toast has its own token and is not controlled by the Activity.
Unlike an Activity like a Dialog, Toast adds a view directly to the Window via WindowManager and does not create a PhoneWindow or a DecorView.
Finally, the call flow chart shown in Toast is summarized for your reference:
So just to conclude
Each Window corresponds to a View and a ViewRootImpl. A Window is an abstract concept. It doesn’t actually exist. It exists in the form of a View.
WindowManager is our entry point to Windows, whose implementation is in WindowManagerService. WindowManager and WindowManagerService interaction is an IPC process, and the final IPC is done in RootViewImpl.
The resources
Android development art exploration
Recommended reading
- IPC AIDL cross-process communication
- AIDL principle of IPC
If this article is helpful to you, please give it a thumbs up. Thank you!