Toast source code
Android version: Based on API source code 29, Android version 10.0.
Toast is a weak prompt floating window, essentially a view that provides a short message to the user. When the Toast view is presented to the user, it is presented as a floating view on top of the application. It never gets noticed. The user might be typing something else. The idea is to be as unobtrusive as possible, while still showing users the information you want them to see. Such as volume controls, and prompts to set up a short message that has been saved.
Toast has the following characteristics:
- system
Window
. - Does not get focus. The view
View
Failed to receive user input event. - Show and hide by
NotificationManagerService
Controls.
Toast is simple to use:
Toast.makeText(getApplicationContext(), "Toast", Toast.LENGTH_SHORT).show();
Copy the code
Toast
Source code analysis:
Toast objects can be created using the makeText method or directly with the new keyword:
//Toast.java
@hide
public static Toast makeText(Context context,Looper looper,CharSequence text,int duration) {
// Create a Toast object.
Toast result = new Toast(context, looper);
// Load the system layout.
LayoutInflater inflate = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
// Get the text View.
TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
// Set the text.
tv.setText(text);
/ / assignment.
result.mNextView = v;
result.mDuration = duration;
return result;
}
Copy the code
The makeToast method in Toast has three overloaded methods. The method with the Looper parameter cannot be called directly by the outside world and is annotated with the @hiden annotation. In the method, we get the built-in LayoutInflater object to load the system layout:
//transient_notification
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingHorizontal="@dimen/screen_percentage_05"
android:orientation="vertical">
<TextView
android:id="@android:id/message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:minHeight="48dp"
android:paddingHorizontal="16dp"
android:paddingVertical="8dp"
android:gravity="center"
android:textAppearance="@style/TextAppearance.Toast"
android:background="? android:attr/toastFrameBackground"
/>
</LinearLayout>
Copy the code
The text displayed by Toast is displayed by the TextView with id message in the layout. By default, the background color of Toast is the theme color of the current app. However, the value of background color varies in different Android versions, so we won’t go into further analysis here. Of course, in addition to using system layouts, Toast also supports custom layouts, but use new to create Toast objects directly. Toast constructor = Toast constructor = Toast constructor
//Toast.java
public Toast(@NonNull Context context, @Nullable Looper looper) {
mContext = context;
// Create a TN object.
mTN = new TN(context.getPackageName(), looper);
//Y offset.
mTN.mY = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.toast_y_offset);
// Display the location.
mTN.mGravity = context.getResources().getInteger(
com.android.internal.R.integer.config_toastDefaultGravity);
}
Copy the code
The point is this TN object:
private static class TN extends ITransientNotification.Stub {}Copy the code
It is Toast a private, static inner class inherits from ITransientNotification. The Stub class. ITransientNotification is a system-defined AIDL file that must be used for cross-process communication.
/frameworks/java/android/android/app/ITransientNotification.aidl
package android.app;
/ * *@hide* /
oneway interface ITransientNotification {
void show(IBinder windowToken);
void hide(a);
}
Copy the code
ITransientNotification. Only in the aidl definition method of two key, there will be a corresponding implementation in TN object. Know about the source of a Toast, can understand the display of Toast with hidden, related to system service NotificationManagerService NMS for short. And the service runs in the system process, and the interaction with the NMS must involve IPC communication. Combined with NMS source code analysis, TN is the bridge between NMS service and Toast communication. As defined in the ViewRootImpl class, class W is the bridge between Windows ManagerService and ViewRootImpl. This also implies that the display and hiding of Toast is not managed by itself, but by the system service.
Since the TN class is responsible for interacting with the NMS, Toast should simply hand over window-related tasks to TN. In the constructor of TN will initialize the WindowManager. LayoutParams basic attributes, including two important properties of window type and flag:
//Toast$TN.java
TN(String packageName, @Nullable Looper looper) {
//mParams is a pre-created object.
final WindowManager.LayoutParams params = mParams;
// Set width and height.
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.format = PixelFormat.TRANSLUCENT;
/ / Window animation.
params.windowAnimations = com.android.internal.R.style.Animation_Toast;
// Set the window type.
params.type = WindowManager.LayoutParams.TYPE_TOAST;
params.setTitle("Toast");
// Set the window tag.
params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
mPackageName = packageName;
// Get the Looper of the main process, an exception will occur if Toast is displayed in the child thread.
if (looper == null) {
looper = Looper.myLooper();
if (looper == null) {
throw new RuntimeException(
"Can't toast on a thread that has not called Looper.prepare()"); }}// Create the main thread Handler.
mHandler = new Handler(looper, null) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case SHOW: {
IBinder token = (IBinder) msg.obj;
handleShow(token);
break;
}
case HIDE: {
handleHide();
mNextView = null;
break;
}
case CANCEL: {
handleHide();
mNextView = null;
try {
getService().cancelToast(mPackageName, TN.this);
} catch (RemoteException e) {
}
break; }}}}; }Copy the code
The TN constructor initializes some basic properties of the Window. The Window type of Toast is TYPE_TOAST, which belongs to the system Window. When it is added to the WMS, it is not necessary to check whether the application has hover Window permission, but to verify the Window Token. FLAG_KEEP_SCREEN_ON, which means that as long as the window is visible to the user, keep the screen on the device open and bright. The second is FLAG_NOT_FOCUSABLE, which means that the window never gets key input focus, so the user can’t send key or other button events to it, which will be forwarded to the window behind it. The third is FLAG_NOT_TOUCHABLE, which means the window does not receive Touch events. To summarize, Toast is a system Window, but cannot get input focus and respond to Touch events. The View in Toast will not receive any input events at all, so don’t expect to add click events to the View in Toast. Toast serves only as a floating window of weak hints, and the system is designed to prevent Toast from attracting users’ attention and operations too much. If you want Toast to respond to click events, you have to change the Window’s flag property dynamically through reflection.
In addition, the constructor creates a main thread Handler that switches from the Binder thread to the main thread and then performs the Toast related operations.
Show () and hide() :
//Toast.java
public void show(a) {
//Toast root layout.
if (mNextView == null) {
throw new RuntimeException("setView must have been called");
}
// Obtain system services
INotificationManager service = getService();
String pkg = mContext.getOpPackageName();
TN tn = mTN;
tn.mNextView = mNextView;
final int displayId = mContext.getDisplayId();
// Submit the Toast to the system service.
try {
service.enqueueToast(pkg, tn, mDuration, displayId);
} catch (RemoteException e) {
// Empty}}Copy the code
The show method is defined in the Toast class. The INotificationManager object is obtained by getService, and the enqueueToast method is executed. Let’s look at the getService method:
//Toast.java
static private INotificationManager getService(a) {
if(sService ! =null) {
return sService;
}
sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification"));
return sService;
}
Copy the code
The ServiceManager class is marked by the @hide annotation and is not visible to the application layer, so it can only be viewed by downloading the Android source code. Familiarity with Binder mechanisms should be familiar with ServiceManager. All servers in the Binder mechanism need to be registered with the Servicemanager service, but not with the actual IBinder objects created by the server, but with the proxy IBinder objects created by the Binder driver. The name of the server is then sent to the Servicemanager along with the broker IBinder object. The Servicemanager receives the data and stores it in the Map collection. Save the server name as key and the proxy IBinder object as value. The getService static method is provided to get the Binder proxy object of the server by the name of the server, such as notification.
Back to the source code, the ServiceManager. GetService (” notification “) method, is to obtain the registered name in ServiceManager is IBinder notification server proxy objects, Get the proxy object of the server through the asInterface() method. In system service NotificationManagerService class, create a property called the mService INotificationManager. The Stub object. After the service is started, the onStart method is executed, which registers the local proxy object represented by mService with the ServiceManager using the SystemService publishBinderService method. The distinction is that the proxy object obtained by SM is not the same object as the object represented by mService. In the process of registration for Binder server, Binder drive to save the real INotificationManager. The Stub object, and then create a proxy object, the proxy objects with name registration services to ServiceManager.
So Toast# getService () method is finally obtained NotificationManagerService class created in INotificationManager. The Stub object, enqueueToast method in the real implementation is also located in the proxy objects:
//NotificationManagerService.java
final IBinder mService = new INotificationManager.Stub() {
@Override
public void enqueueToast(String pkg, ITransientNotification callback, int duration,
int displayId){
// Determine whether the process displaying Toast is a system process.
final boolean isSystemToast = isCallerSystemOrPhone()
|| PackageManagerService.PLATFORM_PACKAGE_NAME.equals(pkg);
// omit the code
try {
// Decide if you need to show Toast.
if(ENABLE_BLOCKED_TOASTS && ! isSystemToast && ((notificationsDisabledForPackage && ! appIsForeground) || isPackageSuspended)) {return; }}synchronized (mToastQueue) {
int callingPid = Binder.getCallingPid();
long callingId = Binder.clearCallingIdentity();
try {
ToastRecord record;
int index = indexOfToastLocked(pkg, callback);
// Update the display time of Toast if the current toasts are in the queue.
if (index >= 0) {
record = mToastQueue.get(index);
record.update(duration);
} else {
// Check if it is system Toast. Generally, applications create toasts that are not system Toasts.
if(! isSystemToast) {int count = 0;
final int N = mToastQueue.size();
for (int i=0; i<N; i++) {
final ToastRecord r = mToastQueue.get(i);
// Determine the number of toasts displayed in the same application
if (r.pkg.equals(pkg)) {
count++;
if (count >= MAX_PACKAGE_NOTIFICATIONS) {
return; }}}}// Create a Binder object.
Binder token = new Binder();
// Add Token to WindowManager.
mWindowManagerInternal.addWindowToken(token, TYPE_TOAST, displayId);
// Create a ToastRecord object that contains information about the Toast.
record = new ToastRecord(callingPid, pkg, callback, duration, token,displayId);
// Add to queue.
mToastQueue.add(record);
// Gets the subscript of the currently added Toast.
index = mToastQueue.size() - 1;
keepProcessAliveIfNeededLocked(callingPid);
}
// If the currently added Toast is the first Toast, that Toast is displayed.
if (index == 0) { showNextToastLocked(); }}finally{ Binder.restoreCallingIdentity(callingId); }}}int indexOfToastLocked(String pkg, ITransientNotification callback){
// Get IBinder, the proxy object of 'Toast' in NMS service.
IBinder cbak = callback.asBinder();
ArrayList<ToastRecord> list = mToastQueue;
int len = list.size();
for (int i=0; i<len; i++) {
ToastRecord r = list.get(i);
// Compare package names with proxy objects.
if (r.pkg.equals(pkg) && r.callback.asBinder() == cbak) {
returni; }}return -1;
}
Copy the code
The enqueueToast() method determines whether the currently created Toast needs to be displayed and displayed immediately. In the if statement at the beginning of the try statement, we determine when the Toast window is not displayed, such as when the process that created the Toast is suspended. After the Toast meets the conditions that need to be displayed, it is then determined whether the current Toast is in the wait queue. This is often the case when the show() method on the same Toast object is frequently invoked. In addition, the system determines the condition that the two Toasts are the same, that is, the package name creating the Toast is equal to the TN object. If the package names are equal, it means that they are displayed by the same application. If the TN objects are equal, it means that they are the same Toast object, because the Toast objects are one-to-one corresponding to the TN objects. When the same Toast is found in the mToastQueue, its index is greater than or equal to 0, indicating that a Toast window is displayed on the screen, and then the show method is executed using the same Toast object. Although Toast allows this operation, the logic for handling this situation will be different in different Android versions, which will affect Toast’s presentation. For example, if a singleton Toast calls its show method several times within the time of presentation, it will look completely different under Android 10.0 and Android 9.0:
-
Android version 10.0 and versions other than 9.0 (same effect, source code may be slightly different) :
/ / enqueueToast method. if (index >= 0) { record = mToastQueue.get(index); record.update(duration); } Copy the code
Only the display time of the toasts to be displayed is updated when the toasts currently being displayed exist in the queue. The effect is that if the Toast#show() method is called multiple times in the LENGTH_LONG or LENGTH_SHORT time range, the Toast is displayed only once in the specified time range, and clicking on the Toast#show method will not respond. You need to wait until the Toast Window is removed, and then click on it to have the effect. The source code will be analyzed later.
-
Android Version 9.0:
/ / enqueueToast method. if (index >= 0) { record = mToastQueue.get(index); record.update(duration); try { record.callback.hide(); } catch (RemoteException e) {} record.update(callback); } Copy the code
If the current Toast is present in the queue, the display time is updated, and then the hide() method is executed to hide the Toast, and the callback is updated. The effect is that if ** calls Toast#show() more than once in the LENGTH_LONG or LENGTH_SHORT time range, the Toast already displayed will immediately disappear, but the new Toast will not be displayed again, and clicking on it will do nothing. ** Only after the Window of the Toast is removed can you click it again.
This also suggests that developers should choose whether or not to adopt the singleton pattern when encapsulating the Toast utility class, depending on the scenario in which Toast is used. Avoid, as much as possible, scenarios where toasts are shown several times, and the problem is that they are not shown.
Go back to the source code and analyze the logic after else. Before showing the Toast, if the current Toast is not the one displayed by the system process, the application will not create the system Toast. Therefore, the system determines whether the number of toasts with the same packet name cached in the current queue has exceeded the upper limit set by the system. The default limit in Android 10.0 is 25. If the limit is exceeded, the Toast presentation request at that time will not be processed. NotificationManagerService is the sectors of the service system, all the applications in mobile phone on display when Toast, whether need it to manage the display and disappear. If there are no restrictions on the app, then the Toast on the phone screen will be displayed randomly, and it is possible that the content you are currently seeing is not from the app you are currently interacting with. This is one reason why the service adds Toast to the queue.
Then, when the above conditions are met, the data of the current Toast needs to be saved and the Window token is prepared. Toast belongs to system Window, and when it is added to WMS, WMS checks the validity of its Window token. By verifying that the token has been registered in the WMS, only Windows that have registered the token can display the View. Therefore, a Binder object is created before the Toast presentation as the window token of the current Toast. The addWindowToken() method is then executed to register the current Binder object with WMS. Binder objects created after registration, along with the current Toast information, are stored in ToastRecord objects. ToastRecord represents a Toast in the NMS service, just as ActivityRecord represents an Activity in the system service. The ToastRecord object stores basic information about the Toast, including the ITransientNotification, the PROXY object of the Toast on the server, which is the TN proxy object created in the Toast. After all, the running environment of the code is still in the system process, and the NMS needs to communicate with the process that created the Toast through the TN object in the ToastRecord object. The showNextToastLocked() method is then executed to notify the client application to display the Toast View:
//NotificationManagerService.java
void showNextToastLocked(a) {
ToastRecord record = mToastQueue.get(0);
while(record ! =null) {
// Execute the show(Binder windowToken) method on the TN object.
try {
record.callback.show(record.token);
scheduleDurationReachedLocked(record);
return;
} catch (RemoteException e) {
// If IPC communication breaks, remove the current Toast.
int index = mToastQueue.indexOf(record);
if (index >= 0) {
mToastQueue.remove(index);
}
keepProcessAliveIfNeededLocked(record.pid);
// Continue to look for the next Toast display.
if (mToastQueue.size() > 0) {
record = mToastQueue.get(0);
} else {
record = null; }}}}Copy the code
Get the first Toast to be displayed from the mToastQueue queue and call callback.show(). The ITransientNotification object represented by callback, as previously explained, is the TN proxy object in the NMS created by Toast, and its show method is then called to notify the client that created the Toast, showing the Toast. This belongs to IPC communication. If RemoteException is caught by the code, it indicates that the cross-process communication fails. Then, the display of the Toast is abandoned and the next ToastRecord is removed from the queue to continue to display. The show(Binder) method is simple enough to use with Toast#TB:
//Toast$TN.java
public void show(IBinder windowToken) {
mHandler.obtainMessage(SHOW, windowToken).sendToTarget();
}
Copy the code
Binder thread pools are used to execute the code logic for IPC communication between servers and clients. So the current TN#show() code is executed in the Binder thread, while ViewRootImpl draws the View in the UI thread, which is checked in its requestLayout() method. Therefore, we need to use the main thread Handler to switch the code execution environment to the main thread, and then execute the handleShow logic:
//Toast$TN.java
public void handleShow(IBinder windowToken) {
if (mHandler.hasMessages(CANCEL) || mHandler.hasMessages(HIDE)) {
return; } f (mView ! = mNextView) {// If Toast changes the 'View', the previous View should be hidden first.
handleHide();
/ / assignment. MView starts out null.
mView = mNextView;
// Get ApplicationCOntext.
Context context = mView.getContext().getApplicationContext();
// Get the application package name.
String packageName = mView.getContext().getOpPackageName();
if (context == null) {
context = mView.getContext();
}
// Get system WindowmanagerImpl. Note to get preset in the system.
mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
// omit code...
// Set the package name.
mParams.packageName = packageName;
//Toast disappears.
mParams.hideTimeoutMilliseconds = mDuration ==
Toast.LENGTH_LONG ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT;
/ / token assignment.
mParams.token = windowToken;
// If the current View is already associated with the View wrootimpl, remove it first.
if(mView.getParent() ! =null) {
mWM.removeView(mView);
}
// Since the notification manager service cancelled the token immediately after telling us to cancel toast, there is an inherent competition and we can try to add a window after the token is invalid. So let's hedge that.
try {
// Add Window.
mWM.addView(mView, mParams);
trySendAccessibilityEvent();
} catch (WindowManager.BadTokenException e) {
/* ignore */}}}Copy the code
The main logic of the handleShow method is to add the window window. However, there is a small detail in the source code. The WindowManager object is obtained using the ApplicationContext instead of using the mContext object passed in when creating the Toast object. The purpose of this is to ensure that the WindowManager object obtained through the Context object is preset by the system. We know that the Context object passed in when creating a Toast will normally use an Activity object, so using Content.getSystemService directly will result in a WindowManager object being obtained, Is a WindowManager object with a Token created by the Activity itself. As a system window, Toast does not need to apply tokens at all. Therefore, in order to avoid unnecessary problems, the ApplicationContext is used uniformly.
Notice that the argument to the handleShow method is an IBinder object that was created in the NMS#enqueueToast() method. As mentioned earlier, before Toast is displayed, the NMS creates a Binder object that acts as a token for the Toast window in the WMS. After calling the mwm.addView () method, WMS checks the validity of the Window Token, but only in Android 8.0 and later. Prior to Android 8.0, toasts were allowed to be displayed without tokens, and tokens were not checked when Windows were added. After version 8.0, Toast is required to have a token, so the application cannot add Toast directly because the token is added by the NMS. This is why the Toast presentation is left to the NMS.
After the mwm.addView () method is executed, the Toast View is associated with the ViewRootImpl, the View drawing process is executed, and the Toast is displayed on the screen. The logic of the presentation is over. How does Toast disappear automatically? The answer goes back to the NMS#showNextToastLocked() method:
//NotificationManagerService.java
void showNextToastLocked(a) {
ToastRecord record = mToastQueue.get(0);
while(record ! =null) {
try {
// Show us Toast.
record.callback.show(record.token);
// Start the countdown.
scheduleDurationReachedLocked(record);
return;
} catch (RemoteException e) {
// omit source code...}}}private void scheduleDurationReachedLocked(ToastRecord r){
mHandler.removeCallbacksAndMessages(r);
Message m = Message.obtain(mHandler, MESSAGE_DURATION_REACHED, r);
int delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
// Omit the logic for enabling "accessibility" on the phone.
mHandler.sendMessageDelayed(m, delay);
}
Copy the code
After executing the show logic, a delayed message is sent to ms# mHandler. This delay is determined by Toast Duration and has only two values: LENGTH_LONG = 3.5s LENGTH_SHORT = 2s. For Toast, the display time is controlled by the system, and generally there is no need to customize the display time, otherwise reflection can only be used for dynamic modification. When the delay time is up, the NMS#cancelToastLocked() method is finally executed:
//NotificationManagerService.java
void cancelToastLocked(int index) {
// Get the ToastRecord object according to index.
ToastRecord record = mToastQueue.get(index);
try {
// Call the hidden method.
record.callback.hide();
} catch (RemoteException e) {
}
ToastRecord lastToast = mToastQueue.remove(index);
mWindowManagerInternal.removeWindowToken(lastToast.token, false,
lastToast.displayId);
// We pass "removeWindows" to "false" so that the client has time to stop rendering (since the hide above is a one-way message), otherwise we may cause the client to crash because the client is sitting on the surface where the token is generated. However, we need to schedule a timeout to ensure that the token is eventually killed in some way.
scheduleKillTokenTimeout(lastToast);
keepProcessAliveIfNeededLocked(record.pid);
if (mToastQueue.size() > 0) {
// Open the next Toast presentation.showNextToastLocked(); }}Copy the code
The ToastRecord object in the NMS represents the Toast object, which is passed through the method in the NMS and controls the display and hiding of the Toast. The parameter index in the method is obtained from the PKG and callback of ToastRecord in the handleDurationReached() method. The hide() method is then executed, and this part of the logic is analyzed later. After the Toast is hidden, the system needs to reclaim the display area of the current Toast. Note that the second parameter of the removeWindowToken() method is false, which means that the Window token will not be removed first, and a delay message will be sent to ensure that the token will definitely be killed eventually. Proceed to the showNextToastLocked() method to get the next Toast display from the queue.
Let’s focus on the hide() method. This method call is also an IPC call that ends up in the Toast#handleHide() method:
//Toast.java
public void handleHide(a) {
if(mView ! =null) {
// Note: Check parent() just to make sure the view has been added... I've seen cases where views haven't been added yet, so let's try not to crash.
if(mView.getParent() ! =null) {
mWM.removeViewImmediate(mView);
}
// Now that we have removed the view, the server can safely release resources.
try {
getService().finishToken(mPackageName, this);
} catch (RemoteException e) {
}
mView = null; }}Copy the code
Upon receiving the hidden message, the NMS is notified to remove the Window immediately. The NMS removes the ToastRecord corresponding to the Toast from the queue, and then executes the removeWindowToken() method again. The second parameter in the removeWindowToken() method passes true, which removes the token along with the display area. Finally, set the mView variable to NULL so that the same Toast object can be displayed multiple times.
The end:
If you are interested, you can join qq group 684891631 and then join wechat group
My Github my Nuggets my Jane book my CSDN