Search code examples
androidandroid-toast

Why Toast keeps showing even when the app called onDestroy()?


Let's say I have this code in onCreate()

   for (int i = 0; i < 20; i++) {
        Toast.makeText(MainActivity.this, "Toast "+i, Toast.LENGTH_SHORT).show();
    }

when I launch the app, Toasts start to pop up.

now, when I press the back button(let's say after Toast 5). The onDestroy() is called, app is closed.

But I can still see the Toast popping up until it reaches to 20 or I clear app from memory.

Question:

Why my code is running out of the app?

I had given the context of my activity, then shouldn't it stop as soon as the activity is destroyed?

Doesn't the context matter here?

It would be helpful if you link any documentation.


Solution

  • In Toast class, Toast.makeText() is a static method. When you call this method a new Toast object is created and your passed Context is saved in it and system's default layout is used to create a view which is attached to your Toast object and gravity is also set that manages where on screen your toast will be displayed.

    Your toast is displayed by system service. This service maintains a queue of toast messages to be displayed and displays them using its own Thread. When you call show() on your toast object then it enqueues your toast into the message queue of system service. So when your activity is destroyed after creating 20 toast, then system service has already swung into action and it has messages in its message queue to display. By back press on your activity (on destroy) system cannot conclude that you may not intend to display remaining toast messages. Only when you clear your app from memory, system can confidently infer that it no longer needs to display toast message from your app.

    For more information you may look at the source code of Toast class. I am including relevant methods for you. BTW good question 👍🏻

    Implementation of Toast.makeText

     /**
     * Make a standard toast to display using the specified looper.
     * If looper is null, Looper.myLooper() is used.
     * @hide
     */
    public static Toast makeText(@NonNull Context context, @Nullable Looper looper,
            @NonNull CharSequence text, @Duration int duration) {
        Toast result = new Toast(context, looper);
    
        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);
    
        result.mNextView = v;
        result.mDuration = duration;
    
        return result;
    }
    

    Creation of new Toast :

    /**
     * Constructs an empty Toast object.  If looper is null, Looper.myLooper() is used.
     * @hide
     */
    public Toast(@NonNull Context context, @Nullable Looper looper) {
        mContext = context;  // your passed `context` is saved.
        mTN = new TN(context.getPackageName(), 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);
    }
    

    Implementation of show()

    /**
     * Show the view for the specified duration.
     */
    public void show() {
        if (mNextView == null) {
            throw new RuntimeException("setView must have been called");
        }
    
        INotificationManager service = getService(); 
        String pkg = mContext.getOpPackageName();
        TN tn = mTN;
        tn.mNextView = mNextView;
        final int displayId = mContext.getDisplayId();
    
        try {
            service.enqueueToast(pkg, tn, mDuration, displayId);
        } catch (RemoteException e) {
            // Empty
        }
    }