Reflection
There are two problems before analyzing the source code
When a sub-thread uses Toast directly, it throws an exception "Can't create handler in thread that has not called Looper. prepare ()", and searches for "Looper" in the Toast class and cannot find it. Where was the exception thrown?
Use Toast:
Looper.prepare();
Toast.makeText(getApplicationContext(), "test toast", Toast.LENGTH_SHORT).show();
Looper.loop();
Does a call like the one above show Toast in the main thread?
Toast Source Code Analysis
First look at Toast's makeText and show methods:
public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
// Create TN(Toast's built-in object) to show the main classes of Toast
Toast result = new Toast(context);
// Load Toast's presentation
LayoutInflater inflate = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
tv.setText(text);
// Assigning some attributes
result.mNextView = v;
result.mDuration = duration;
return result;
}
public void show() {
// Check display content
if (mNextView == null) {
throw new RuntimeException("setView must have been called");
}
INotificationManager service = getService(); // The member variable mService of NotificationManagerService is the implementation class of INotificationManager.Stub.
String pkg = mContext.getOpPackageName();
TN tn = mTN;
// Display the attributes assigned to tn by content attributes
tn.mNextView = mNextView;
try {
// Call the enqueueToast method of mService in Notification Manager Service
service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {
// Empty
}
}
Let's look at the enqueueToast method of mService in Notification Manager Service:
// callback is an example of TN
@Override
public void enqueueToast(String pkg, ITransientNotification callback, int duration)
{
// ...
synchronized (mToastQueue) {
int callingPid = Binder.getCallingPid();
long callingId = Binder.clearCallingIdentity();
try {
ToastRecord record;
int index = indexOfToastLocked(pkg, callback);
// 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 {
// ...
Binder token = new Binder();
mWindowManagerInternal.addWindowToken(token,
WindowManager.LayoutParams.TYPE_TOAST);
// Create ToastRecord objects
record = new ToastRecord(callingPid, pkg, callback, duration, token);
// mToastQueue adds records
mToastQueue.add(record);
index = mToastQueue.size() - 1;
keepProcessAliveIfNeededLocked(callingPid);
}
if (index == 0) {
showNextToastLocked();
}
} finally {
Binder.restoreCallingIdentity(callingId);
}
}
}
The above code can see that record is added to mToastQueue. When will it be executed? A global search for "mToastQueue.get(" sees that the 0th element of mToastQueue is taken out of the showNextToastLocked method and the record. callback. show (record. token) is called. Record. callback is the TN object, that is, the show method of TN is called here. Now let's look at what TN's show method does:
public void show(IBinder windowToken) {
if (localLOGV) Log.v(TAG, "SHOW: " + this);
// Hand it over to mHandler
mHandler.obtainMessage(0, windowToken).sendToTarget();
}
// TN's mHandler
final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// So the show method of TN deals with this.
IBinder token = (IBinder) msg.obj;
handleShow(token);
}
};
Let's take a look at the core method of showing Toast, which is TN's handleShow() method.
public void handleShow(IBinder windowToken) {
if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
+ " mNextView=" + mNextView);
if (mView != mNextView) {
handleHide();
mView = mNextView;
// ...
// The display uses Windows Manager
mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
// The following configuration is the location parameter
final Configuration config = mView.getContext().getResources().getConfiguration();
final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection());
mParams.gravity = gravity;
if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
mParams.horizontalWeight = 1.0f;
}
if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
mParams.verticalWeight = 1.0f;
}
mParams.x = mX;
mParams.y = mY;
mParams.verticalMargin = mVerticalMargin;
mParams.horizontalMargin = mHorizontalMargin;
mParams.packageName = packageName;
mParams.hideTimeoutMilliseconds = mDuration ==
Toast.LENGTH_LONG ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT;
mParams.token = windowToken;
if (mView.getParent() != null) {
if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
mWM.removeView(mView);
}
if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);
// Show Toast, and you can see that Toast is added to Window s
mWM.addView(mView, mParams);
trySendAccessibilityEvent();
}
}
summary
Toast calls makeText to create the view to be displayed, notifies Notification Manager Service internally across processes when the show method is called, then notifies TN to execute the show method, and finally adds it to Window s through handler.
Look at the initial thinking.
makeText creates the TN object in the new Toast constructor. TN has a member variable mHandler. When creating the Handler, it needs the thread to have Looper object. The main() method of ActivityThread of the main thread creates Looper object, but the sub-thread does not have Looper object, so it throws an exception.
If Looper.prepare() is called in the sub-thread, will it be Toast displayed in the main thread? It is obvious that the above code is not. Because Toast and TN are created in the sub-thread, mHandler is also created in the sub-thread. Looper is also created in the sub-thread when processing messages, so the display is also in the sub-thread.