11

When I do some action frequently(my assumption, is that due to Toast message) on my android app,getting below error.Im not getting exact location of this Issue.Can I get help from someone to resolve the same?

 --------- beginning of crash
10-04 16:13:49.250 6541-6541/com.test.myapp E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.test.myapp, PID: 6541
    android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.BinderProxy@e2815e is not valid; is your activity running?
        at android.view.ViewRootImpl.setView(ViewRootImpl.java:679)
        at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:342)
        at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:93)
        at android.widget.Toast$TN.handleShow(Toast.java:459)
        at android.widget.Toast$TN$2.handleMessage(Toast.java:342)
        at android.os.Handler.dispatchMessage(Handler.java:102)
        at android.os.Looper.loop(Looper.java:154)
        at android.app.ActivityThread.main(ActivityThread.java:6119)
        at java.lang.reflect.Method.invoke(Native Method) 

Notes: This crash is not coming always.

kavie
  • 2,154
  • 4
  • 28
  • 53
  • Possible duplicate of ["android.view.WindowManager$BadTokenException: Unable to add window" on buider.show()](https://stackoverflow.com/questions/18662239/android-view-windowmanagerbadtokenexception-unable-to-add-window-on-buider-s) – ADM Oct 04 '18 at 14:44
  • That means your `Context` which you pass in creating toast is `DEAD`. Check activity state before using `Context`. – ADM Oct 04 '18 at 14:45
  • Getting the same exception even though all my Toast calls are wrapped by a method that null-checks context and also check if finishing/destroyed state (for activities) or detached state (for fragments) but it looks like it's not enough – fillobotto Oct 10 '18 at 08:32
  • Did you solve the problem? – fillobotto Mar 28 '19 at 11:39

6 Answers6

14

Before passing a Context to the Toast, you should always check the validity of the context you are going to use. In my applications, I use a context-checker method I made:

public static boolean isContextValid(Context context, Fragment fragment) {
    if (context instanceof Activity) {
        Activity activity = (Activity) context;
        if (activity.isFinishing() || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && activity.isDestroyed())) {
            return false;
        }
    }

    return context != null && (fragment == null || (fragment.isAdded() && !fragment.isRemoving());
}

You can pass only a context, or also a Fragment if your current context is a fragment. This method checks if the context is an Activity, in this case we check if the activity is finishing/destroyed.

If you want to display the toast following fragment lifecycle, also pass to the method you current fragment, so we can tell whether the fragment is still visible and attached to the activity.

BONUS ANDROID 7.1

On API 25, this is not enough and sometimes the device still crash with the stacktrace you provided.

This repository might be the solution since it wraps the faulty call in a try/catch clause. Of course it's not the best thing to do, but at least solves this annoying crash for 7.1 devices.

fillobotto
  • 3,698
  • 5
  • 34
  • 58
5

This is an issue of Toast officially fixed after Android 8.0, and can also be fixed by hooking WindowManagerWrapper.addView(view, params) with a third party lib PureWriter/ToastCompat.

Checking activity isFinishing cannot fix the crash due to Toast.show() is an asynchronous progress:

Toast.makeText().show()
-> Toast.getService().enqueueToast()
-> Toast.TN.handleShow() // crash here, and unable to be caught from outside

After Android 8.0, the crash is caught in handleShow (See the last few lines):

        public void handleShow(IBinder windowToken) {
            if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
                    + " mNextView=" + mNextView);
            // If a cancel/hide is pending - no need to show - at this point
            // the window token is already invalid and no need to do any work.
            if (mHandler.hasMessages(CANCEL) || mHandler.hasMessages(HIDE)) {
                return;
            }
            if (mView != mNextView) {
                // remove the old view if necessary
                handleHide();
                mView = mNextView;
                Context context = mView.getContext().getApplicationContext();
                String packageName = mView.getContext().getOpPackageName();
                if (context == null) {
                    context = mView.getContext();
                }
                mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
                // We can resolve the Gravity here by using the Locale for getting
                // the layout direction
                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);
                // 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.
                try {
                    mWM.addView(mView, mParams);
                    trySendAccessibilityEvent();
                } catch (WindowManager.BadTokenException e) {
                    /* ignore */
                }
            }
        }
thundertrick
  • 1,634
  • 1
  • 17
  • 22
3

android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.BinderProxy@e2815e is not valid; is your activity running?

In this line, it gives you a hint that your activity might not be running at the time you are trying to show a toast.

If you are showing a Toast in an activity like this,

Toast toast = Toast.makeText(this, R.string.message, Toast.LENGTH_LONG).show();

you should be aware that you are trying to show it in the Activity's context by setting the first argument to 'this' and by this way, if you finish(); your activity before this line, you get that exception. So I suggest checking if this is the case, or maybe you can use Application's context instead:

Toast toast = Toast.makeText(getApplicationContext(), R.string.message, Toast.LENGTH_LONG).show();

Hope this is helpful!

Onur D.
  • 515
  • 3
  • 11
  • I'm passing Context reference as a first parameter.before getting Toast Crash,I'm getting Duplicate finish request for ActivityRecord{6ed6c1a u0 com.test.myapp /.activities.MyActivity t328 f}.But In this activity i'm not showing any Toast.. – kavie Oct 04 '18 at 15:02
  • If you are using any background threads (AsyncTasks, Api calls), make sure your activity is not finished or in finishing process before doing anything in its context (like manipulating the UI, showing toasts or dialogs, etc.) – Onur D. Oct 04 '18 at 15:18
0

Checking Activity.isFinishing was not sufficient like others mentioned. Zebra Android devices that are stuck at 7.1 kept crashing.

Thankfully, the ToastCompat library did prevent the crashes. However, we were working with Xamarin.Android.

We ported the PureWriter/ToastCompat library to Xamarin/C#, below is how to use.

Example of use:

using Droid.ToastCompat;// Copy Droid.ToastCompat from the code example below

ToastCompat.MakeText(context: this, message, duration, onBadTokenCaught: toast =>
    {
        // A bad token often means the underlining race condition
        // in Android 7.1 was hit but the activity is typically still running
        // We will resend the toast to give it another shot
        ToastCompat.MakeText(context: this, message, duration, onBadTokenCaught: toast =>
            {
                // Ok we did all we could, user will not be getting a toast but will also not be crashing
            })
            .Show();
    })
    .Show();

PureWriter/ToastCompat in Xamarin in a single file:

using System;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Widget;

namespace Droid
{
    /// <summary>
    /// This is a Xamarin.Android port of https://github.com/PureWriter/ToastCompat
    ///
    /// It addresses a race condition in Android 7.1 which can sometimes result
    /// in a <see cref="WindowManagerBadTokenException"/> being thrown when
    /// displaying a toast. Because the exception is in async Android OS code we
    /// can not directly wrap with a try/catch. We need to wrap the context the
    /// toast uses and supply a wrapped <see cref="IWindowManager"/> which allows
    /// us a then try/catch where the exception is thrown.
    ///
    /// Copyright 2017 drakeet.
    /// 
    /// Licensed under the Apache License, Version 2.0 (the "License");
    /// you may not use this file except in compliance with the License.
    /// You may obtain a copy of the License at
    /// 
    /// http://www.apache.org/licenses/LICENSE-2.0
    /// 
    /// Unless required by applicable law or agreed to in writing, software
    /// distributed under the License is distributed on an "AS IS" BASIS,
    /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    /// See the License for the specific language governing permissions and
    /// limitations under the License.
    /// </summary>
    public class ToastCompat : Toast
    {
        /// <summary>
        /// Called when a debug statement can be logged. By default it uses the
        /// default Android logger, but can be set to invoke a custom logger.
        /// </summary>
        public static Action<(string tag, string statement)> LogDebug { get; set; } = (info) => { Android.Util.Log.Debug(info.tag, info.statement); };

        /// <summary>
        /// Called when an exception can be logged. By default it uses the
        /// default Android logger, but can be set to invoke a custom logger.
        /// </summary>
        public static Action<(string tag, string statement, Exception exception)> LogException { get; set; } = (info) => { Android.Util.Log.Error(info.tag, info.statement, info.exception); };

        private readonly static string TAG = nameof(ToastCompat);

        private readonly Toast _toast;

        /// <summary>
        /// The toast message, useful for tracking when exception occurs based
        /// on what toast message is failing to display.
        /// </summary>
        private readonly string _toastMessage;

        /// <summary>
        /// Called when a <see cref="WindowManagerBadTokenException"/> which only happens in Android 7.1
        /// </summary>
        private readonly Action<Toast> _onBadTokenCaught;

        /// <summary>
        /// Construct an empty Toast object.
        /// You must call <see cref="SetContextCompat(View, Context)"/>
        /// before you can call <see cref="Show"/>.
        /// </summary>
        /// <param name="context">The context to use. Usually Application or Activity.</param>
        /// <param name="toast">The base toast</param>
        /// <param name="_onBadTokenCaught">
        /// The callback when a <see cref="WindowManagerBadTokenException"/> is thrown
        /// when trying to display a toast.
        /// </param>
        private ToastCompat(Context context, Toast toast, string toastMessage, Action<Toast> onBadTokenCaught) : base(context)
        {
            _toast = toast;
            _toastMessage = toastMessage;
            _onBadTokenCaught = onBadTokenCaught;
        }

        /// <summary>
        /// Make a standard toast that just contains a text view.
        /// </summary>
        /// <param name="context">The context to use. Usually Application or Activity.</param>
        /// <param name="text">The text to display</param>
        /// <param name="duration">How long to display the message.  </param>
        /// <param name="onBadTokenCaught">
        /// The callback when a toast fails to display with the toast as a argument for the callback.
        ///
        /// Typically this would be a good place to try displaying the toast one
        /// more time because the Activity may very well be still running and we
        /// just ran into the Android OS race condition.
        /// </param>
        public static ToastCompat MakeText(Context context, string text, ToastLength duration, Action<Toast> onBadTokenCaught)
        {
            // We cannot pass the SafeToastContext to Toast.makeText() because
            // the View will unwrap the base context and we are in vain.
            var toast = Toast.MakeText(context, text, duration)!;

            #pragma warning disable CS0618 // Type or member is obsolete
            if (toast?.View is View view)
            #pragma warning restore CS0618 // Type or member is obsolete
            {
                SetContextCompat(view, new SafeToastContext(context, text, onBadTokenCaught: () => onBadTokenCaught(toast)));
            }
            else
            {
                LogDebug((TAG, "Toast failed to apply ToastCompat fix because it's view is null"));
            }

            return new ToastCompat(context, toast!, text, onBadTokenCaught);
        }

        public override ToastLength Duration
        {
            get => _toast.Duration;
            set => _toast.Duration = value;
        }

        public override void Show() => _toast.Show();
        public override void SetGravity([GeneratedEnum] GravityFlags gravity, int xOffset, int yOffset) => _toast.SetGravity(gravity, xOffset, yOffset);
        public override void SetMargin(float horizontalMargin, float verticalMargin) => _toast.SetMargin(horizontalMargin, verticalMargin);
        public override void SetText(int resId) => _toast.SetText(resId);
        public override void SetText(Java.Lang.ICharSequence? s) => _toast.SetText(s);
        public new void SetText(string s) => _toast.SetText(s);
        public override float HorizontalMargin => _toast.HorizontalMargin;
        public override float VerticalMargin => _toast.VerticalMargin;
        public override GravityFlags Gravity => _toast.Gravity;
        public override int XOffset => _toast.XOffset;
        public override int YOffset => _toast.YOffset;

        [Obsolete]
        public override View? View
        {
            get => _toast.View;
            set
            {
                _toast.View = value;

                if (value is not null)
                {
                    if (value.Context is Context context)
                    {
                        SetContextCompat(value, new SafeToastContext(value.Context, _toastMessage, onBadTokenCaught: () => _onBadTokenCaught(_toast)));
                    }
                    else
                    {
                        LogDebug((TAG, "Toast failed to apply ToastCompat fix because the new view's context is null"));
                    }
                }
            }
        }

        private static void SetContextCompat(View view, Context context)
        {
            if (Android.OS.Build.VERSION.SdkInt == Android.OS.BuildVersionCodes.NMr1)
            {
                try
                {
                    var field = Java.Lang.Class.FromType(typeof(View)).GetDeclaredField("mContext");

                    field.Accessible = true;
                    field.Set(view, context);
                }
                catch (Exception e)
                {
                    LogException((TAG, $"Failed to {nameof(SetContextCompat)}", e));
                }
            }
        }

        private class SafeToastContext : ContextWrapper
        {
            private readonly string _toastMessage;
            private readonly Action _onBadTokenCaught;

            public SafeToastContext(Context context, string toastMessage, Action onBadTokenCaught) : base(context)
            {
                _toastMessage = toastMessage;
                _onBadTokenCaught = onBadTokenCaught;
            }

            public override Context? ApplicationContext => new ApplicationContextWrapper(BaseContext!.ApplicationContext!, _toastMessage, _onBadTokenCaught);

            private sealed class ApplicationContextWrapper : ContextWrapper
            {
                private readonly string _toastMessage;
                private readonly Action _onBadTokenCaught;

                public ApplicationContextWrapper(Context context, string toastMessage, Action onBadTokenCaught) : base(context)
                {
                    _toastMessage = toastMessage;
                    _onBadTokenCaught = onBadTokenCaught;
                }

                public override Java.Lang.Object? GetSystemService(string? name)
                {
                    var service = base.GetSystemService(name);

                    // Override the window manager so we can capture the bad token exception
                    if (Context.WindowService == name)
                    {
                        if (service.JavaCast<IWindowManager>() is IWindowManager windowManager)
                        {
                            return new WindowManagerWrapper(windowManager!, _toastMessage, _onBadTokenCaught);
                        }

                        LogDebug((TAG, $"Failed to cast window service to {nameof(IWindowManager)}. ToastCompat fix not applied."));
                    }

                    return service;
                }
            }

            /// <summary>
            /// Wraps <see cref="IWindowManager"/> in order to override <see cref="IViewManager.AddView(View?, ViewGroup.LayoutParams?)"/>
            /// which allows us to catch and handle the thrown <see cref="WindowManagerBadTokenException"/>.
            /// </summary>
            public sealed class WindowManagerWrapper : Java.Lang.Object, IWindowManager
            {
                private readonly static string TAG = "WindowManagerWrapper";
                private readonly IWindowManager _windowManager;
                private readonly string _toastMessage;
                private readonly Action _onBadTokenCaught;

                public WindowManagerWrapper(IWindowManager windowManager, string toastMessage, Action onBadTokenCaught)
                {
                    _windowManager = windowManager;
                    _toastMessage = toastMessage;
                    _onBadTokenCaught = onBadTokenCaught;
                }

                void IViewManager.AddView(View? view, ViewGroup.LayoutParams? @params)
                {
                    try
                    {
                        LogDebug((TAG, "WindowManager's addView(view, params) has been hooked."));
                        // NOTE: Set a breakpoint here and you will always throw a WindowManagerBadTokenException exception since it will cause the underlining race condition to be true
                        _windowManager.AddView(view, @params);
                    }
                    catch (WindowManagerBadTokenException e)
                    {
                        LogException((TAG, $"{nameof(WindowManagerBadTokenException)} caught when `{nameof(IViewManager.AddView)}` was called within `{nameof(WindowManagerWrapper)}` for toast message `{_toastMessage}`", e));

                        _onBadTokenCaught();
                    }
                    catch (Exception e)
                    {
                        LogException((TAG, "Unexpected Exception", e));
                    }
                }

                Display? IWindowManager.DefaultDisplay => _windowManager.DefaultDisplay;
                void IViewManager.RemoveView(View? view) => _windowManager.RemoveView(view);
                void IWindowManager.RemoveViewImmediate(View? view) => _windowManager.RemoveViewImmediate(view);
                void IViewManager.UpdateViewLayout(View? view, ViewGroup.LayoutParams? @params) => _windowManager.UpdateViewLayout(view, @params);
            }
        }
    }
}
ryanholden8
  • 536
  • 4
  • 13
0

as prev answer, it's only happened in android 7.x . So just hook Toast like this

private static void hookToast(Toast toast) {
    // only work on 7.x
    if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M
            && Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
        SafelyHandlerWrapper.hook(toast);
    }
}

@SuppressLint("SoonBlockedPrivateApi")
private static class SafelyHandlerWrapper extends Handler {

    private final Handler impl;

    private static Field sField_TN;
    private static Field sField_TN_Handler;

    static {
        try {
            sField_TN = Toast.class.getDeclaredField("mTN");
            sField_TN.setAccessible(true);
            sField_TN_Handler = sField_TN.getType().getDeclaredField("mHandler");
            sField_TN_Handler.setAccessible(true);
        } catch (Exception ignore) {
        }
    }

    static void hook(Toast toast) {
        try {
            Object tn = sField_TN.get(toast);
            Handler preHandler = (Handler) sField_TN_Handler.get(tn);
            sField_TN_Handler.set(tn, new SafelyHandlerWrapper(preHandler));
        } catch (Exception ignore) {
        }
    }

    public SafelyHandlerWrapper(Handler impl) {
        this.impl = impl;
    }

    @Override
    public void dispatchMessage(Message msg) {
        try {
            super.dispatchMessage(msg);
        } catch (Exception ignore) {
        }
    }

    @Override
    public void handleMessage(Message msg) {
        impl.handleMessage(msg);
    }
}
kevin liu
  • 23
  • 3
-1

Try to use application context ,instead of Activity context.


Update: Sorry,This exception occurs regardless of whether the Context you passed to Toast is an Activity or ApplicationContext or Service. And you can not try-catch it.

There is no way to resolve it.It's a Android SDK bug on API 25. Try to write a new Toast class.

Leon
  • 29
  • 1
  • 5