Android source code learning-TOAST implementation principle explanation (2024)

Foreword:

Some of the previous logs sent a friend to send a Toast collapse log. How can TOAST collapse such a simple usage? So by the way, I learned the implementation of Toast in the source code. It is not complicated, but the content is quite a lot. Let's share it here to facilitate readers.

1. Basic usage method

There are two main ways to implement:

1. The most basic way of use:

The method of use is very simple. The direct ditch is transmitted into the Context through the static method. The content of the display content and the display time is three parameters, constructing the Toast object, and then displayed by show.

 Toast toast = Toast.maketext (getBaseContext (), "Display Content", toast.length_long); toast.show();

2. Customize the implementation of View

This method of use is also very simple. First of all, it constructs a view, and then introduces this custom view through the setView method. In the end, it is also displayed by the show method.

View selfToastView = View.inflate(getBaseContext(), R.layout.self_toast, null); Toast toast = Toast.maketext (getBaseContext (), "Display Content", toast.length_long);toast.setView(selfToastView);toast.show();

3. Summary of use

Both methods are simple. The difference is just that the second way is more than one custom view. But why do you want to separate? Because although it is only one step away, its implementation principle is completely different. One is displayed by notificationManageRservice, while the other is processed by the app itself. Next, let's talk about the implementation principles of the two methods in turn.

2. TOAST's creation display process principle explanation

1.Toast.makeText

The implementation method of this is relatively simple. The final generation method is introduced into 4 parameters, which are

Context: Binded contestants

Looper: Looper that bind threads can be empty. When it is empty, use the current thread's Looper. PS: Each thread can only be bound to the only looper. If you want to know this one, you can read another article of my article:Android source code learning-Handler mechanism and six core points

Text: Display content

duration: duration. There are two parameters:
Toast.Length_long: The display time is longer, 3.5s. The value of its 3500ms is defined in the notificationManagerservice.long_Dlay.

Toast.Length_short: Short display time,It is 2S. The value of its 2000ms is defined inNotificationManagerService.SHORT_DELAY。

But the real time is not 3.5 and 2s. The actual display time will be longer than these two times. This will be said later.

In the end, a toast object is generated. It should be noted here that the only difference between native Toast and custom view toast is that the MnextView object in the native Toast object is NULL.

public static Toast makeText(@NonNull Context context, @Nullable Looper looper, @NonNull CharSequence text, @Duration int duration) { // The default configuration here is TRUE, and the judgment logic above if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) { Toast result = new Toast(context, looper); result.mText = text; result.mDuration = duration; return result; } else { Toast result = new Toast(context, looper); View v = ToastPresenter.getTextToastView(context, text); result.mNextView = v; result.mDuration = duration; return result; } }

Then the structure method of Toast is as follows, mainly to build a few objects that need to be used in the back door:

public Toast(@NonNull Context context, @Nullable Looper looper) { mContext = context; mToken = new Binder(); looper = getLooper(looper); mHandler = new Handler(looper); mCallbacks = new ArrayList<>(); mTN = new TN(context, context.getPackageName(), mToken, mCallbacks, looper); mTN.mY = context.getResources().getDimensionPixelSize( com.android.internal.R.dimen.toast_y_offset); mTN.mGravity = context.getResources().getInteger( com.android.internal.R.integer.config_toastDefaultGravity); }

mcontext: context object

MTOKEN: Constructing the Binder object, and the communication with notificationManageRservice later is through this Binder.

Looper: The currently binding thread Looper, the current thread is defaulted when it is passed into NULL.

MTN: Binder.stub type object, as the client side of Binder. It is processed in a separate Binder thread when accepting the information passed by cross -process.

MTN.MY: The vertical margin volume is simply controlling whether Toast shows whether the position of the display on the screen is a little bit or the next.

mtn.mgravity: Control the display position of Toast. Generally, there are two types in the bureau.

2.toast.show () method

The show method is mainly to execute the three -stage logic,

First assign the TOAST MNExtView to tn.mnextView. If the MnextView of Toast is NULL, then TN.MnextView is naturally null;

Then get the Binder's binder quoting service;

Next, take a judgment logic,

1. If MnextView == NULL, take the service.enqueuuetoast logic and communicate through the Binder cross -process communication, and will be called to the EnqueueToast method in NotificationManageRSERVICE. We will explain in Chapter 3.

2. If MnextView! = NULL, by calling the service.enqueuuetextToast method, through the Binder cross -process communication, it will be called to the EnqueuetextToast method in the NOTIFICATIONMANAGERVICE. We will explain in Chapter 4.

public void show() { ... INotificationManager service = getService(); String pkg = mContext.getOpPackageName(); TN tn = mTN; tn.mNextView = mNextView; final int displayId = mContext.getDisplayId(); ... if (mNextView != null) { // It's a custom toast // Customized Method Chapter 4 Explanation service.enqueueToast(pkg, mToken, tn, mDuration, displayId); } else { // It's a text toast // Default Method Chapter 3 Explanation ITransientNotificationCallback callback = new CallbackBinder(mCallbacks, mHandler); service.enqueueTextToast(pkg, mToken, mText, mDuration, displayId, callback); } } ... }

3. The complete process oftoast display

3.1 Receive in service

As mentioned above, it is transmitted through Binder. At this time, the ENQueuetextToast () method in the MSERVICE object in the notificationManagerservice will receive the notification. The specific parameters are explained as follows: as follows:

 /** * * @param pkg package name * @param token app, Binder * @param text display content * @param duration duration * @param Displayid Mark the only display area ID, the corresponding physical class is DisplayContent * @param callback cross -process Callback object, customize the toast of view is valuable. The default Toast method is null */ @Override public void enqueueTextToast(String pkg, IBinder token, CharSequence text, int duration, int displayId, @Nullable ITransientNotificationCallback callback) { enqueueToast(pkg, token, text, null, duration, displayId, callback); }

This method will be passed to the EnqueueToast method (a little expansion here, in fact, the custom view toast will also come to this method).

3.2 Processing queue logic in the enqueuetoast method

We all know that the toast display is sometimes sequentially. The toast that is called first will definitely be displayed first, so this requires a collection to maintain this sequential relationship, and this collection is MTOASTQUEUE.

final ArrayList<ToastRecord> mToastQueue = new ArrayList<>();

After the process of the previous section entered the Enqueuetoast method, it was actually divided into two logic. The core code is as follows:

private void enqueueToast(String pkg, IBinder token, @Nullable CharSequence text, @Nullable ITransientNotification callback, int duration, int displayId, @Nullable ITransientNotificationCallback textCallback) { ... // The content above is all doing parameter legitimacy check final int callingUid = Binder.getCallingUid(); ... // This method does permission check if (!checkCanEnqueueToast(pkg, callingUid, isAppRenderedToast, isSystemToast)) { return; } synchronized (mToastQueue) { int callingPid = Binder.getCallingPid(); final long callingId = Binder.clearCallingIdentity(); try { ToastRecord record; int index = indexOfToastLocked(pkg, token); // If it's already in the queue, we update it in place, we don't // move it to the end of the queue. if (index >= 0) { record = mToastQueue.get(index); record.update(duration); } else { // Insert logic ... } ... if (index == 0) { showNextToastLocked(false); } } ... } }

In this method, first of all, we saw the lock code: synchronized (MTOASTQUEUE), so it shows that this is a multi -threaded scene. In the Binder mechanism, as the Server side, there will be a thread pool to handle the Binder request from the client. Each request will allocate a thread to process it, so there will be multiple thread lock logic here.

The following logic is divided into the following two pieces:

1. First do parameter legitimacy check and permission check,

2. Then enter the queue logic.

In the queue logic, first determine whether it exists in the collection according to PKG and Token pass through the IndexOFTOASTLOCKED method.

int index = indexOfToastLocked(pkg, token);

If Index> = 0, it means that the Binder object corresponding to the app has already existed in the MTOASTQUEUE, and the duration of the RECORD corresponding to the corresponding record is directly updated.

IndexoftoastLocked method is as follows:

int indexOfToastLocked(String pkg, IBinder token) { ArrayList<ToastRecord> list = mToastQueue; int len = list.size(); for (int i=0; i<len; i++) { ToastRecord r = list.get(i); if (r.pkg.equals(pkg) && r.token == token) { return i; } } return -1; }

It is judged through a circular convenience method, and the efficiency is slightly low. Here is a little bit of source code. Perhaps using TreeMap will be a better choice (Key = pkg+token.hashcode). Of course, Google also considers that TOAST queuing has fewer scenes, only ArrayList is chosen.

Since each Toast corresponds to a Binder object, if Toast is reused, the show is called multiple times in a short period of time, and it will only correspond to the same RECORD object, so it will only be displayed once.

If Index <0, it means that MTOASTQUEUE does not exist in the corresponding Binder corresponding to the toast, and the logic of entering the insertion.

3.3 Insert logic

The code of inserting logic is as follows

 } else { // Limit the number of toasts that any given package can enqueue. // Prevents DOS attacks and deals with leaks. int count = 0; final int N = mToastQueue.size(); for (int i = 0; i < N; i++) { final ToastRecord r = mToastQueue.get(i); 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; } } } Binder windowToken = new Binder(); mWindowManagerInternal.addWindowToken(windowToken, TYPE_TOAST, displayId, null /* options */); record = getToastRecord(callingUid, callingPid, pkg, isSystemToast, token, text, callback, duration, windowToken, displayId, textCallback); mToastQueue.add(record); index = mToastQueue.size() - 1; keepProcessAliveForToastIfNeededLocked(callingPid); } // If it's at index 0, it's the current toast. It doesn't matter if it's // new or just been updated, show it. // If the callback fails, this will remove it from the list, so don't // assume that it's valid after this. if (index == 0) { showNextToastLocked(false); }

First judge whether there are 5 (inclusive) or more under the same package name, and the toast is not displayed. If any, it is not allowed to continue to be added.

Otherwise, generate a ToastRecord object through the gettoastRecord method to add to the end of the set, and to ensure that the process of playing Toast is not killed through KeepProcessaliveFortoastifnededLocked method. The Stlock method is displayed.

ToastRecord is actually an abstract method. It has two implementation classes, TextToastRecord and CustomtoastRecord. The GettoastRecord method will generate the corresponding generation according to whether the callback is empty. Among them, the Callback == NULL is generated by the TextToastRecord type object.

3.4 Producer consumer model

Here are the producer consumer model. Since the APP is inserted into the MTOASTQUEUE collection through the Binder method, consumers must consume. And this consumer is the ShownextToastLocked method.

Because the lock logic mentioned above, there will always be only one thread executing the ShownextToastLocked method.

Methods as below:

void showNextToastLocked(boolean lastToastWasTextRecord) { if (mIsCurrentToastShown) { return; // Don't show the same toast twice. } ToastRecord record = mToastQueue.get(0); while (record != null) { int userId = UserHandle.getUserId(record.uid); boolean rateLimitingEnabled = !mToastRateLimitingDisabledUids.contains(record.uid); boolean isWithinQuota = mToastRateLimiter.isWithinQuota(userId, record.pkg, TOAST_QUOTA_TAG) || isExemptFromRateLimiting(record.pkg, userId); boolean isPackageInForeground = isPackageInForegroundForToast(record.uid); 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); } record = (mToastQueue.size() > 0) ? mToastQueue.get(0) : null; } }

Although the code is long, there are only three core logic:

1. Follow the order of MTOASTQUEUE in order to take out the RECORD object.

2. Try to display the RECORD object through the Tryshowtoast method. If successful, execute the ScheduleDurationReachedLocked method.

3. If it fails, delete it from the collection. That is to say, if Toast is displayed, it will not try again if it fails.

Tryshowtoast's logic we will talk about the next section. Let's take a look at the implementation of scheduleDurationReacheDlocked:

 private void scheduleDurationReachedLocked(ToastRecord r, boolean lastToastWasTextRecord) { mHandler.removeCallbacksAndMessages(r); Message m = Message.obtain(mHandler, MESSAGE_DURATION_REACHED, r); int delay = r.getDuration() == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY; // Correct this delay value through the barrier -free auxiliary function. If the barrier -free auxiliary is started, the incident will be longer than the normal value delay = mAccessibilityManager.getRecommendedTimeoutMillis(delay, AccessibilityManager.FLAG_CONTENT_TEXT); // If the previous Toast is still being displayed, the last Toast's departure animation event flows out. if (lastToastWasTextRecord) { delay += 250; // delay to account for previous toast's "out" animation } // If it is TextToastRecord type, the animation time is flowing. if (r instanceof TextToastRecord) { delay += 333; // delay to account for this toast's "in" animation } mHandler.sendMessageDelayed(m, delay); }

First delete the MESSAGE with the current TaskRecord object in the MQueue in Looper.

Then re -generate a MESSAGE with the TaskRecord object and add it to the delay task. The delay time is exactly the 2S or 3.5s set in Duration.

In addition, the animation time of entering and playing, so the final delay time is longer than 2S or 3.5s. The logic of this piece is actually deleting Toast. Therefore, the delay time here becomes longer, which will cause the final actual time to be longer than the setting value.

Handler will execute the MESSAGE_Duration_Reached type event after the time has arrived, call the handledurationReached method, and call the CancelToastLocked method in this method:

void cancelToastLocked(int index) { // 1. Notification of the hide method of the APP layer TN TN; ToastRecord record = mToastQueue.get(index); record.hide(); if (index == 0) { mIsCurrentToastShown = false; } // 2. Delete the ToastReCord Object to the queue ToastRecord lastToast = mToastQueue.remove(index); // 3. Delete WindowToken registered in WMS mWindowManagerInternal.removeWindowToken(lastToast.windowToken, false /* removeWindows */, lastToast.displayId); // 4. Send another delay signal to ensure that token deletes complete scheduleKillTokenTimeout(lastToast); // 5. Make sure that the process will not be killed during the Toast display process keepProcessAliveForToastIfNeededLocked(record.pid); // 6. If there is news in the collection, continue to execute if (mToastQueue.size() > 0) { // Show the next one. If the callback fails, this will remove // it from the list, so don't assume that the list hasn't changed // after this point. showNextToastLocked(lastToast instanceof TextToastRecord); } }

Mainly execute the following logic:

1. Reverse the Hide method of the APP layer TN to notify; (Subsequent logic 4.3 will be mentioned)

2. Delete the ToastRecorder object in the queue

3. Delete WindowToken registered in WMS

4. Send another delay signal to ensure that token deletes complete

5. Make sure that the process will not be killed during the Toast display process

6. If there are news in the collection, continue to execute

3.5 Tryshowtoast method Try to display

This method is also very simple, whether the relevant logical judgment can be displayed, if the record.show method can be directly called.

 private boolean tryShowToast(ToastRecord record, boolean rateLimitingEnabled, boolean isWithinQuota, boolean isPackageInForeground) { if (rateLimitingEnabled && !isWithinQuota && !isPackageInForeground) { reportCompatRateLimitingToastsChange(record.uid); Slog.w(TAG, "Package " + record.pkg + " is above allowed toast quota, the " + "following toast was blocked and discarded: " + record); return false; } if (blockToast(record.uid, record.isSystemToast, record.isAppRendered(), isPackageInForeground)) { Slog.w(TAG, "Blocking custom toast from package " + record.pkg + " due to package not in the foreground at the time of showing the toast"); return false; } return record.show(); }

At this time, we will look at the Show method of ToastRecord. I said before that there are two types of implementation, namely TextToastRcord and CustomtoastRcord. The type in CustomToastRecord is the custom of custom view. Let's talk about the next chapter. Here we only talk about the type of TextToastRecord.

Here we look at the implementation of the TextToastRecord type:

@Override public boolean show() { ... mStatusBar.showToast(uid, pkg, token, text, windowToken, getDuration(), mCallback); return true; }

The MSTATUSBAR here is another registered service. The realization class is in StatusBarManagerService.java:

 private final StatusBarManagerInternal mInternalService = new StatusBarManagerInternal() {}

Let's look at its Showtoast method directly:

 public void showToast(int uid, String packageName, IBinder token, CharSequence text, IBinder windowToken, int duration, @Nullable ITransientNotificationCallback callback) { if (mBar != null) { try { mBar.showToast(uid, packageName, token, text, windowToken, duration, callback); } catch (RemoteException ex) { } } }

The MBAR here is actually a reference to Binder. Its Server's implementation in the SystemUI process, the implementation class is CommandQueue. Therefore, it will eventually be transferred to the showtoast method of CommandQueue for processing:

 @Override public void showToast(int uid, String packageName, IBinder token, CharSequence text, IBinder windowToken, int duration, @Nullable ITransientNotificationCallback callback) { synchronized (mLock) { SomeArgs args = SomeArgs.obtain(); args.arg1 = packageName; args.arg2 = token; args.arg3 = text; args.arg4 = windowToken; args.arg5 = callback; args.argi1 = uid; args.argi2 = duration; mHandler.obtainMessage(MSG_SHOW_TOAST, args).sendToTarget(); } }

Repost with handler to the main thread, the code is as follows:

 case MSG_SHOW_TOAST: { args = (SomeArgs) msg.obj; String packageName = (String) args.arg1; IBinder token = (IBinder) args.arg2; CharSequence text = (CharSequence) args.arg3; IBinder windowToken = (IBinder) args.arg4; ITransientNotificationCallback callback = (ITransientNotificationCallback) args.arg5; int uid = args.argi1; int duration = args.argi2; for (Callbacks callbacks : mCallbacks) { callbacks.showToast(uid, packageName, token, text, windowToken, duration, callback); } break; }

The main thread is displayed through McallBacks callback, the realbacks here are realized in com.android.SystemUI.Toast.toastuijava.

3.6 Toastui.showtoast completes the display process

In the showtoast method, the class is entrusted to ToastPresenter to display logical display, the classic MVP architecture.

public void showToast(int uid, String packageName, IBinder token, CharSequence text, IBinder windowToken, int duration, @Nullable ITransientNotificationCallback callback) { Runnable showToastRunnable = () -> { UserHandle userHandle = UserHandle.getUserHandleForUid(uid); Context context = mContext.createContextAsUser(userHandle, 0); mToast = mToastFactory.createToast(mContext /* sysuiContext */, text, packageName, userHandle.getIdentifier(), mOrientation); if (mToast.getInAnimation() != null) { mToast.getInAnimation().start(); } mCallback = callback; mPresenter = new ToastPresenter(context, mIAccessibilityManager, mNotificationManager, packageName); // Set as trusted overlay so touches can pass through toasts mPresenter.getLayoutParams().setTrustedOverlay(); mToastLogger.logOnShowToast(uid, packageName, text.toString(), token.toString()); mPresenter.show(mToast.getView(), token, windowToken, duration, mToast.getGravity(), mToast.getXOffset(), mToast.getYOffset(), mToast.getHorizontalMargin(), mToast.getVerticalMargin(), mCallback, mToast.hasCustomAnimation()); }; if (mToastOutAnimatorListener != null) { // if we're currently animating out a toast, show new toast after prev toast is hidden mToastOutAnimatorListener.setShowNextToastRunnable(showToastRunnable); } else if (mPresenter != null) { // if there's a toast already showing that we haven't tried hiding yet, hide it and // then show the next toast after its hidden animation is done hideCurrentToast(showToastRunnable); } else { // else, show this next toast immediately showToastRunnable.run(); } }

3.7 toastpresenter.show () Method to complete the final display display

So next we will look at the show method in ToastPressenter, and it is also finally completed the ordinary Toast display in this method. Methods as below:

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) { checkState(mView == null, "Only one toast at a time is allowed, call hide() first."); mView = view; mToken = token; adjustLayoutParams(mParams, windowToken, duration, gravity, xOffset, yOffset, horizontalMargin, verticalMargin, removeWindowAnimations); addToastView(); trySendAccessibilityEvent(mView, mPackageName); if (callback != null) { try { callback.onToastShown(); } catch (RemoteException e) { Log.w(TAG, "Error calling back " + mPackageName + " to notify onToastShow()", e); } } }

In this method, there are two main things:

1. Set the attribute value in mparams.

2. Add to WindowManager to complete the final display.

Let's talk about it next.

3.8 Adjustlayoutparams method Configure MPARAMS parameters

First, adjust the attribute values ​​in MPARAMS based on the entry parameters. The attribute value determines the location displayed by Toast, and the display time, etc. The method is as follows:

private void adjustLayoutParams(WindowManager.LayoutParams params, IBinder windowToken, int duration, int gravity, int xOffset, int yOffset, float horizontalMargin, float verticalMargin, boolean removeWindowAnimations) { Configuration config = mResources.getConfiguration(); int absGravity = Gravity.getAbsoluteGravity(gravity, config.getLayoutDirection()); params.gravity = absGravity; if ((absGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) { params.horizontalWeight = 1.0f; } if ((absGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) { params.verticalWeight = 1.0f; } params.x = xOffset; params.y = yOffset; params.horizontalMargin = horizontalMargin; params.verticalMargin = verticalMargin; params.packageName = mContext.getPackageName(); params.hideTimeoutMilliseconds = (duration == Toast.LENGTH_LONG) ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT; params.token = windowToken; if (removeWindowAnimations && params.windowAnimations == R.style.Animation_Toast) { params.windowAnimations = 0; } }

Here we take a look at the HidetimeoutmilliseConds parameters, which is this to control the final display time. Of course, this time is the longest time to display. In actual situation, there is a delay time in the 3.4 section. Toast, so HidetimeoutmilliseConds will not take effect in a major scene.

The settings of the set_duration_timeout and long_duration_timeout set are set in the code as follows:

 private static final long SHORT_DURATION_TIMEOUT = 4000; private static final long LONG_DURATION_TIMEOUT = 7000;

It is necessary to explain it here. The parameters of the Params configuration are also part of the constructed method:

private WindowManager.LayoutParams createLayoutParams() { 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; 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; }

Let's focus here on the line below

params.type=WindowManager.LayoutParams.TYPE_TOAST;

In Android, Type represents the preferred level of Window. The larger the numbers, the higher the priority, the higher the priority, and it will be displayed on it. Type_toast = 2005, and the Window priority corresponding to the Activity is the lowest, the corresponding Type = 1, so Toast will be displayed on the Activity.

The specific code refers to the following:

public static final int TYPE_BASE_APPLICATION = 1;public static final int FIRST_SYSTEM_WINDOW = 2000;public static final int TYPE_TOAST = FIRST_SYSTEM_WINDOW+5; so TYPE_TOAST = 2005TYPE_BASE_APPLICATION = 1 // The code of the Type parameter set in Activity, the code is in the handleresumeActivity method of ActivityThread l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;

addtoastView method is added to WindowManager

The method is as follows, which is relatively simple here, and directly add to WindowManager.

private void addToastView() { if (mView.getParent() != null) { mWindowManager.removeView(mView); } try { mWindowManager.addView(mView, mParams); } catch (WindowManager.BadTokenException e) { // Since the notification manager service cancels the token right after it notifies us // to cancel the toast there is an inherent race and we may attempt to add a window // after the token has been invalidated. Let us hedge against that. Log.w(TAG, "Error while attempting to show toast from " + mPackageName, e); return; } }

It should be noted that the third is from the Binder receiving in the Service. The code is the SystemServer process that is executed in the SystemServer of the NoticationManageRSERVICE, and the SystemUI process where the ToastPresenter is located is not the APP process, so if the Toast is displayed, kill the App process. Toast will still be displayed normally.

Toast timeout hidden process

Toast shows that there is a process of WindowManager.AddView, so when the duration comes, there is a hidden toast process.

Now that this is the case, then I have to talk about the process after addView. The main processes are as follows:

Android source code learning-TOAST implementation principle explanation (1)

So in the end of the Addwindow method of WindowManagerService.

The entire method process is too long, so we only look at this part related to Toast. A delay message will be registered in the code, and the delay time is exactly the HIDETIMEOUTMILLLISECONDS that was previously set in MPARAMS, which is the 4S we mentioned above. Or 7s.

public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility, int displayId, int requestUserId, InsetsVisibilities requestedVisibilities, InputChannel outInputChannel, InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) { ... if (type == TYPE_TOAST) { if (!displayContent.canAddToastWindowForUid(callingUid)) { ProtoLog.w(WM_ERROR, "Adding more than one toast window for UID at a time."); return WindowManagerGlobal.ADD_DUPLICATE_ADD; } // Make sure this happens before we moved focus as one can make the // toast focusable to force it not being hidden after the timeout. // Focusable toasts are always timed out to prevent a focused app to // show a focusable toasts while it has focus which will be kept on // the screen after the activity goes away. if (addToastWindowRequiresToken || (attrs.flags & FLAG_NOT_FOCUSABLE) == 0 || displayContent.mCurrentFocus == null || displayContent.mCurrentFocus.mOwnerUid != callingUid) { mH.sendMessageDelayed( mH.obtainMessage(H.WINDOW_HIDE_TIMEOUT, win), win.mAttrs.hideTimeoutMilliseconds); } } ... return res; }

So next we have to handle the code of h.window_hide_timeout event:

case WINDOW_HIDE_TIMEOUT: { final WindowState window = (WindowState) msg.obj; synchronized (mGlobalLock) { ... window.mAttrs.flags &= ~FLAG_KEEP_SCREEN_ON; window.hidePermanentlyLw(); window.setDisplayLayoutNeeded(); mWindowPlacerLocked.performSurfacePlacement(); } break; }

Then WindowState.java's HideperManentlyLW method is as follows. The hidden method is used to achieve hidden. Therefore, the hidden process does not require the client or notificationManagerservice, but WMS is maintained by itself.

 public void hidePermanentlyLw() { if (!mPermanentlyHidden) { mPermanentlyHidden = true; hide(true /* doAnimation */, true /* requestAnim */); } }

As for Hide hidden, or the process displayed by Show, we will not start here. This one is actually the content in the complete display process of view. There will be another article. Here we just need to know that after registering Window into WMS, it is not immediately displayed, but the rendering process executed when the next Vsync signal comes is coming and finally displayed on the screen.

4. Customized view of the TOAST process explanation

4.1 Repost to the app layer to execute logic

As mentioned above, the type of realized View's implementation is CustomToastRcord. The show () method is as follows:

 public boolean show() { ... callback.show(windowToken); ... }

It is simply completing Callback's SHOW callback. And this callback is a Binder object, which is realized as the TN object in the APP side toast. So let's look at the Show method in TN:

 public void show(IBinder windowToken) { mHandler.obtainMessage(SHOW, windowToken).sendToTarget(); }

The thread that forwarded from the Binder thread from the Binder thread to the thread bound to the toast (usually the main thread, but it is not absolutely). Handler will execute the handleshow method, the code is as follows:

 public void handleShow(IBinder windowToken) { ... // If the cancellation and hidden signal has been passed in, then there is no need to continue to display if (mHandler.hasMessages(CANCEL) || mHandler.hasMessages(HIDE)) { return; } // MnextView is a custom view, and MVIEW is the content of the last time (if Toast reuses) if (mView != mNextView) { // remove the old view if necessary handleHide(); mView = mNextView; mPresenter.show(mView, mToken, windowToken, mDuration, mGravity, mX, mY, mHorizontalMargin, mVerticalMargin, new CallbackBinder(getCallbacks(), mHandler)); } }

If the Toast object is displayed for the first time, MVIEW == NULL. The follow -up is mainly divided into two logic:

1. First call Handlehide to hide the current MVIEW, and its final implementation is also achieved by ToastPresenter.hide. Specific implementation logic, let's talk about it in 4.3

2. Display the process through the toastpresenter.show method.

4.2 Toastpresenter.show Show Toast

Before executing Toastpresenter.Show, first set MnextView to the MVIEW to be displayed.

 mView = mNextView;

We have already talked about the 3.7 chapters above the show () method, so we will not repeat it. The only difference is that the code at this time is performed in the APP process, and 3.7 is in the SystemServer process.

So the custom view is finally displayed by WindowManager.addView.

4.3 The hidden process of Toast

At the 3.4 section above, it is also mentioned that when the display time is over, the HIDE method in Toast.tn will be notified through the Binder mechanism.

The HIDE method forwarded from the Binder thread to the thread where Looper was located through the Handler thread.

Then handed it to the handlehide method in the handler for processing. In addition, before 4.1 display a custom view toast, the logic of handlehide is also called. The code of handlehide is as follows, which is mainly handed over to ToastPresenter.hide for processing

 public void handleHide() { if (mView != null) { ... mPresenter.hide(new CallbackBinder(getCallbacks(), mHandler)); mView = null; } }

The hide method in ToastPresenter is as follows:

public void hide(@Nullable ITransientNotificationCallback callback) { checkState(mView != null, "No toast to hide."); if (mView.getParent() != null) { mWindowManager.removeViewImmediate(mView); } try { mNotificationManager.finishToken(mPackageName, mToken); } catch (RemoteException e) { Log.w(TAG, "Error finishing toast window token from package " + mPackageName, e); } if (callback != null) { try { callback.onToastHidden(); } catch (RemoteException e) { Log.w(TAG, "Error calling back " + mPackageName + " to notify onToastHide()", e); } } mView = null; mToken = null; }

The specific code is as follows, the following process is mainly implemented:

1. If there is a paint in MVIEW, first delete it from WindowManager, where the MVIEW must be the top View.

2. Notify NotificationManageRservice

3. Execute the destroying ontoasthidden for notification

4. Clear MView and MTOKEN, because the previous process is executed.

4.4 Small section

So to sum up, the customized TOAST display and hiding are actually similar to that of the APP side to add a custom view to WindowManager, and then remove the custom View from WindowManager.

5. Summary

Let's summarize that in fact, toast is mainly divided into two types, Text types and Custom types.

If the Text type is finally displayed to the SystemServer process, it will eventually be handed over to Toastui to be responsible for the final display work. When it registered Window to WMS, it will be attached to the end, and the WMS will be responsible for hiding Window after the time has arrived.

The CUSTOM type finally returned to the APP process responsible for displaying, and eventually it was displayed by adding Window to WMS. At this time, the notificationManagerService is responsible for recording the time, and the time is notified to be notified to hide the work.

The main flowchart of the Toast display process can be summarized as follows:

Android source code learning-TOAST implementation principle explanation (2)

6. Expansion of several related issues

1. Can Toast be used by sub -thread?

Answer: This question is similar to whether the UI can be updated in the sub -thread. It's just that the checkpoint and process are slightly different.

First of all, when the toast object is generated, there will be an inspection toast.Getlooper () method::

private Looper getLooper(@Nullable Looper looper) { if (looper != null) { return looper; } return checkNotNull(Looper.myLooper(), "Can't toast on a thread that has not called Looper.prepare()"); }

If the sub -thread defaults to the current thread, the Looper will not be bound, so an error will be reported. So what if the Looper is initialized in our sub -thread? That's it, but there are still some differences in the end. TextToastRecord will eventually be added to WindowManager in the SystemServer process, and the CustomToastRecord type will eventually be displayed in the APP side sub -thread. In addition, the prepare must be used with the LOOP method to use it, as follows:

 new Thread(new Runnable() { @Override public void run() { Looper.prepare(); Toast toast = Toast.maketext (getBaseContext (), "Display Content", toast.length_long); toast.show(); Looper.loop(); } }).start();

2. Why do you sometimes show that toast prompts to open the notification authority?

The above 3.2 section is mentioned that it shows that the authority checks before TOAST. The code is as follows:

private boolean checkCanEnqueueToast(String pkg, int callingUid, boolean isAppRenderedToast, boolean isSystemToast) { // Whether the current app is hung up final boolean isPackageSuspended = isPackagePaused(pkg); // Whether the current app has notification permissions android.manifest.Permission.interact_across_usersers final boolean notificationsDisabledForPackage = !areNotificationsEnabledForPackage(pkg, callingUid); // Whether the app is in the background final boolean appIsForeground; final long callingIdentity = Binder.clearCallingIdentity(); try { appIsForeground = mActivityManager.getUidImportance(callingUid) == IMPORTANCE_FOREGROUND; } finally { Binder.restoreCallingIdentity(callingIdentity); } // First of all, in the case of non -system Toast, the app process is in the background without Internet_across_users permiss if (!isSystemToast && ((notificationsDisabledForPackage && !appIsForeground) || isPackageSuspended)) { Slog.e(TAG, "Suppressing toast from package " + pkg + (isPackageSuspended ? " due to package suspended." : " by user request.")); return false; } // The previous custom toast stuck if (blockToast(callingUid, isSystemToast, isAppRenderedToast, isPackageInForegroundForToast(callingUid))) { Slog.w(TAG, "Blocking custom toast from package " + pkg + " due to package not in the foreground at time the toast was posted"); return false; } return true; }

in conclusion:

First of all, in the case of non -system Toast, the APP process is in the background and does not have Interact_across_users permissions, or the APP process is hung, and the Toast will not be displayed.

In other words, if there is an authority of the Internet_across_users permissions, toast can be displayed in the background.

3. Will you first call the toast of Show first?

In fact, this question is a bit meaningless. Generally speaking, the earlier the TOAST calls the show method, the earlier it will be displayed.

However, there are some special circ*mstances, such as the two processes or two threads, A first executes Toast, but it is stuck and did not execute the display process immediately. At this time, only the show method was performed in the B thread. After 100 milliseconds, A executed again.

The general process is as follows

In thread: toast.show () In thread B: Toast.show () // After 100 seconds In thread: toast.show ()

In this case, it is said that A can also be in front of B, saying that A is behind B, and eventually A is still performed first. A's second show calls will only update the parameters in the TOASTRECORD object corresponding to NMS before the final display.

4. Why does Toast display on Activity without being covered by Activity?

This involves the concept of Window's priority. 3.8 In the section, there are details.

5. Show the toast and kill the process, will the Toast disappear immediately?

No, there are two types of scenes:

If it is the default toast, the operations of Addwindow and Removewindow are finally in the SystemServer process, and naturally killing the APP process has no effect on the display of Toast. (Real scene verification)

If it is a custom toast, it shows that it will be in the APP process. 3.3 In the section, it is said that when TOAST is displayed, KeepProcessaliveFortoastiFnededLocked method is called to ensure that the process of displaying toast is not killed, so the customization of toast should still be displayed normally. (Inference from the code, there is no verification, and enthusiastic friends can help verify it)

6. The display time oftoast must be 4S or 7S?

Since the time calculation is on the system side, it can only be 4S or 7S, of course, it is not necessarily the absolute value.

3.4 In the section, after obtaining the delay delay time, if the barrier -free auxiliary function is turned on, this delay time will be corrected through barrier -free. Secondly, toast's entry and appearance animation also calculate the time alone.

So the final display time will be slightly greater than 4s or 7s.

Below is a mistake that is often easy to encounter in two Toast.

7.Show Reminder not attache to window manager

The specific error is as follows:

java.lang.IllegalArgumentException: View=androidx.recyclerview.widget.RecyclerView{adb693 VFED..... ........ 0,0-1080,1548 #7f0800d1 app:id/recycler} not attached to window manager at android.view.WindowManagerGlobal.findViewLocked(WindowManagerGlobal.java:534) at android.view.WindowManagerGlobal.removeView(WindowManagerGlobal.java:438) at android.view.WindowManagerImpl.removeView(WindowManagerImpl.java:157) at android.widget.ToastPresenter.addToastView(ToastPresenter.java:303) at android.widget.ToastPresenter.show(ToastPresenter.java:231) at android.widget.ToastPresenter.show(ToastPresenter.java:214) at android.widget.Toast$TN.handleShow(Toast.java:699) at android.widget.Toast$TN$1.handleMessage(Toast.java:631) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loopOnce(Looper.java:201) at android.os.Looper.loop(Looper.java:288) 

First of all, let's look at the addtoastView method:

if (mView.getParent() != null) { mWindowManager.removeView(mView);}

If MView.getParent is not empty, go to RemoveView the view in WindowManager.

Then look at the FindViewLocked method in WindowManagerGlobal:

private int findViewLocked(View view, boolean required) { final int index = mViews.indexOf(view); if (required && index < 0) { throw new IllegalArgumentException("View=" + view + " not attached to window manager"); } return index; }

Will go to MVIEWS to find the view. If you can't find it, you will throw the error in the above.

MViews keeps the RootView registered in all registered Window in the app. The View is not in MVIEWS, but there is ParentView, indicating that the View has binded ParentView.

According to the following code analysis, we can draw conclusions. The view introduced in SetView is problematic. It has been bound to ParentView. This View cannot naturally be used as rootView.

View=androidx.recyclerview.widget.RecyclerView{adb693 VFED..... ........ 0,0-1080,1548 #7f0800d1 app:id/recycler}

8. When hide, it is prompted to attache to window manager error solution

The specific error is as follows:

java.lang.IllegalArgumentException: View=android.widget.LinearLayout{12b02f3 V.E...... ......ID 0,52-629,368} not attached to window managerat android.view.WindowManagerGlobal.findViewLocked(WindowManagerGlobal.java:572)at android.view.WindowManagerGlobal.removeView(WindowManagerGlobal.java:476)at android.view.WindowManagerImpl.removeViewImmediate(WindowManagerImpl.java:144)at android.widget.ToastPresenter.hide(ToastPresenter.java:230)at android.widget.Toast$TN.handleHide(Toast.java:826)at android.widget.Toast$TN$1.handleMessage(Toast.java:746)at android.os.Handler.dispatchMessage(Handler.java:106)at android.os.Looper.loop(Looper.java:236)at com.xxx.NeverCrash$1.run(NeverCrash.java:39)at android.os.Handler.handleCallback(Handler.java:938)at android.os.Handler.dispatchMessage(Handler.java:99)at android.os.Looper.loop(Looper.java:236)at android.app.ActivityThread.main(ActivityThread.java:8060)at java.lang.reflect.Method.invoke(Native Method)at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:656)at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:967)

We can see the part of the red circle in the flowchart:

Android source code learning-TOAST implementation principle explanation (3)

When Tryshowtoast returns True, it will definitely go to the hide process of Toastpresenter. But when Tryshowtoast returns True, will it definitely show success?

In the above flowchart, we can know that the return value of Tryshowtoast is that there is a “) method of CustomtoastRcord.

 @Override public boolean show() { ... try { callback.show(windowToken); return true; } catch (RemoteException e) { ... mNotificationManager.keepProcessAliveForToastIfNeeded(pid); return false; } }

Only when Binder's communication fails, False will be returned, and the rest are returned to True. In the end, can the app layer be successful? The answer is naturally negative.

We are watching the final display of ToastpreSnter.addtoastView method:

private void addToastView() { if (mView.getParent() != null) { mWindowManager.removeView(mView); } try { mWindowManager.addView(mView, mParams); } catch (WindowManager.BadTokenException e) { return; } }

In other words, since the additional failure is added, there is no treatment.

So that is, if you display the custom toast, if the final adDView fails for some reason, then the time is over, which will cause the collapse above. This can be understood as a problem in the source code. It is protected during the show, but it does not protect any protection during HIDE.

So how to solve this problem? Since it is a systematic problem, it is more troublesome to solve.

My idea is this: Since it is Try Catch when it is show, can I also have try catch when he hide?

We can get the Handler object in Toast.tn when reflected, and then achieve the effect we want first by proxy it.

First, get the Handler object Mhandler in Toast.tn, and then create a Handler object with Looper, replace the original Mhandler by reflection.

The custom Handler implements the pseudo code as follows:

Handler oldhandler = null; // The original handler obtained by reflection Handler mHandler = new Handler(oldHandler.getLooper(), null) { @Override public void handleMessage(Message msg) { switch (msg.what) { case 0: { oldHandler.obtainMessage(0, msg.obj).sendToTarget(); break; } case 1: { try{ // Reflex calls handlehide (); Method }catch (Exception e){ e.printStackTrace(); } // Reflect the mnextView = null; break; } case 2: { // The process of 1 is similar to } } } };

Generally speaking, because of too much reflection, the efficiency is not high, but it can indeed solve the toast problem in theory.

Android source code learning-TOAST implementation principle explanation (2024)
Top Articles
Latest Posts
Article information

Author: Lidia Grady

Last Updated:

Views: 5769

Rating: 4.4 / 5 (65 voted)

Reviews: 80% of readers found this page helpful

Author information

Name: Lidia Grady

Birthday: 1992-01-22

Address: Suite 493 356 Dale Fall, New Wanda, RI 52485

Phone: +29914464387516

Job: Customer Engineer

Hobby: Cryptography, Writing, Dowsing, Stand-up comedy, Calligraphy, Web surfing, Ghost hunting

Introduction: My name is Lidia Grady, I am a thankful, fine, glamorous, lucky, lively, pleasant, shiny person who loves writing and wants to share my knowledge and understanding with you.