Toast Source Code Analysis

Keywords: Android Windows

Reflection

There are two problems before analyzing the source code

  1. 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?

  2. 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.

  1. 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.

  2. 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.

Posted by stallingjohn on Fri, 17 May 2019 00:28:19 -0700