我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Android的Toast问题有哪些

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Android的Toast问题有哪些

这篇文章主要讲解了“Android的Toast问题有哪些”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Android的Toast问题有哪些”吧!

1. 异常和偶尔不显示的问题

当你在程序中调用了 ToastAPI,你可能会在后台看到类似这样的 Toast 执行异常:

android.view.WindowManager$BadTokenException    Unable to add window -- token android.os.BinderProxy@7f652b2 is not valid; is your activity running?    android.view.ViewRootImpl.setView(ViewRootImpl.java:826)    android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:369)    android.view.WindowManagerImpl.addView(WindowManagerImpl.java:94)    android.widget.Toast$TN.handleShow(Toast.java:459)

另外,在某些系统上,你没有看到什么异常,却会出现 Toast 无法正常展示的问题。为了解释上面这些问题产生的原因,我们需要先读一遍 Toast 的源码。

2. Toast 的显示和隐藏

首先,所有 Android 进程的视图显示都需要依赖于一个窗口。而这个窗口对象,被记录在了我们的 WindowManagerService(后面简称 WMS) 核心服务中。WMS 是专门用来管理应用窗口的核心服务。当 Android 进程需要构建一个窗口的时候,必须指定这个窗口的类型。 Toast 的显示也同样要依赖于一个窗口, 而它被指定的类型是:

public static final int TYPE_TOAST = FIRST_SYSTEM_WINDOW+5;//系统窗口

可以看出, Toast 是一个系统窗口,这就保证了 Toast 可以在 Activity 所在的窗口之上显示,并可以在其他的应用上层显示。那么,这就有一个疑问:

“如果是系统窗口,那么,普通的应用进程为什么会有权限去生成这么一个窗口呢?”

实际上,Android 系统在这里使了一次 “偷天换日” 小计谋。我们先来看下 Toast 从显示到隐藏的整个流程:

// code Toast.javapublic void show() {        if (mNextView == null) {            throw new RuntimeException("setView must have been called");        }        INotificationManager service = getService();//调用系统的notification服务        String pkg = mContext.getOpPackageName();        TN tn = mTN;//本地binder        tn.mNextView = mNextView;        try {            service.enqueueToast(pkg, tn, mDuration);        } catch (RemoteException e) {            // Empty        }    }

我们通过代码可以看出,当 Toastshow 的时候,将这个请求放在 NotificationManager 所管理的队列中,并且为了保证 NotificationManager 能跟进程交互, 会传递一个 TN 类型的 Binder 对象给 NotificationManager 系统服务。而在 NotificationManager 系统服务中:

//code NotificationManagerServicepublic void enqueueToast(...) {    ....    synchronized (mToastQueue) {                    ...                    {                        // Limit the number of toasts that any given package except the android                        // package can enqueue.  Prevents DOS attacks and deals with leaks.                        if (!isSystemToast) {                            int count = 0;                            final int N = mToastQueue.size();                            for (int i=0; i<N; i++) {                                 final ToastRecord r = mToastQueue.get(i);                                 if (r.pkg.equals(pkg)) {                                     count++;                                     if (count >= MAX_PACKAGE_NOTIFICATIONS) {                                         //上限判断                                         return;                                     }                                 }                            }                        }                        Binder token = new Binder();                        mWindowManagerInternal.addWindowToken(token,                                WindowManager.LayoutParams.TYPE_TOAST);//生成一个Toast窗口                        record = new ToastRecord(callingPid, pkg, callback, duration, token);                        mToastQueue.add(record);                        index = mToastQueue.size() - 1;                        keepProcessAliveIfNeededLocked(callingPid);                    }                    ....                     if (index == 0) {                        showNextToastLocked();//如果当前没有toast,显示当前toast                    }                } finally {                    Binder.restoreCallingIdentity(callingId);                }            }}

(不去深究其他代码的细节,有兴趣可以自行研究,挑出我们所关心的Toast显示相关的部分)

我们会得到以下的流程(在 NotificationManager系统服务所在的进程中):

  • 判断当前的进程所弹出的 Toast 数量是否已经超过上限 MAX_PACKAGE_NOTIFICATIONS ,如果超过,直接返回

  • 生成一个 TOAST 类型的系统窗口,并且添加到 WMS 管理

  • 将该 Toast 请求记录成为一个 ToastRecord 对象

代码到这里,我们已经看出 Toast 是如何偷天换日的。实际上,这个所需要的这个系统窗口 token ,是由我们的 NotificationManager 系统服务所生成,由于系统服务具有高权限,当然不会有权限问题。不过,我们又会有第二个问题:

既然已经生成了这个窗口的 Token 对象,又是如何传递给 Android进程并通知进程显示界面的呢?

我们知道, Toast 不仅有窗口,也有时序。有了时序,我们就可以让 Toast 按照我们调用的次序显示出来。而这个时序的控制,自然而然也是落在我们的 NotificationManager 服务身上。我们通过上面的代码可以看出,当系统并没有 Toast 的时候,将通过调用 showNextToastLocked(); 函数来显示下一个 Toast

void showNextToastLocked() {        ToastRecord record = mToastQueue.get(0);        while (record != null) {            ...            try {                record.callback.show(record.token);//通知进程显示                scheduleTimeoutLocked(record);//超时监听消息                return;            } catch (RemoteException e) {                ...            }        }    }

这里,showNextToastLocked 函数将调用 ToastRecordcallback 成员的 show 方法通知进程显示,那么 callback 是什么呢?

final ITransientNotification callback;//TN的Binder代理对象

我们看到 callback 的声明,可以知道它是一个 ITransientNotification 类型的对象,而这个对象实际上就是我们刚才所说的 TN 类型对象的代理对象:

private static class TN extends ITransientNotification.Stub {     ... }

那么 callback对象的show方法中需要传递的参数 record.token呢?实际上就是我们刚才所说的NotificationManager服务所生成的窗口的 token
相信大家已经对 AndroidBinder 机制已经熟门熟路了,当我们调用 TN 代理对象的 show 方法的时候,相当于 RPC 调用了 TNshow 方法。来看下 TN 的代码:

// code TN.javafinal Handler mHandler = new Handler() {            @Override            public void handleMessage(Message msg) {                IBinder token = (IBinder) msg.obj;                handleShow(token);//处理界面显示            }        };@Override        public void show(IBinder windowToken) {            if (localLOGV) Log.v(TAG, "SHOW: " + this);            mHandler.obtainMessage(0, windowToken).sendToTarget();        }

这时候 TN 收到了 show 方法通知,将通过 mHandler 对象去 post 出一条命令为 0 的消息。实际上,就是一条显示窗口的消息。最终,将会调用 handleShow(Binder) 方法:

public void handleShow(IBinder windowToken) {            if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView                    + " mNextView=" + mNextView);            if (mView != mNextView) {                ...                mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);                ....                mParams.token = windowToken;                ...                mWM.addView(mView, mParams);                ...            }        }

而这个显示窗口的方法非常简单,就是将所传递过来的窗口 token 赋值给窗口属性对象 mParams, 然后通过调用 WindowManager.addView 方法,将 Toast 中的 mView 对象纳入 WMS 的管理。

上面我们解释了 NotificationManager 服务是如何将窗口 token 传递给 Android 进程,并且 Android 进程是如何显示的。我们刚才也说到, NotificationManager 不仅掌管着 Toast 的生成,也管理着 Toast 的时序控制。因此,我们需要穿梭一下时空,回到 NotificationManagershowNextToastLocked() 方法。大家可以看到:在调用 callback.show 方法之后又调用了个 scheduleTimeoutLocked 方法:

record.callback.show(record.token);//通知进程显示 scheduleTimeoutLocked(record);//超时监听消息

而这个方法就是用于管理 Toast 时序:

private void scheduleTimeoutLocked(ToastRecord r)    {        mHandler.removeCallbacksAndMessages(r);        Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);        long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;        mHandler.sendMessageDelayed(m, delay);    }

scheduleTimeoutLocked 内部通过调用 HandlersendMessageDelayed 函数来实现定时调用,而这个 mHandler 对象的实现类,是一个叫做 WorkerHandler 的内部类:

private final class WorkerHandler extends Handler    {        @Override        public void handleMessage(Message msg)        {            switch (msg.what)            {                case MESSAGE_TIMEOUT:                    handleTimeout((ToastRecord)msg.obj);                    break;                ....            }    }     private void handleTimeout(ToastRecord record)    {        synchronized (mToastQueue) {            int index = indexOfToastLocked(record.pkg, record.callback);            if (index >= 0) {                cancelToastLocked(index);            }        }    }

WorkerHandler 处理 MESSAGE_TIMEOUT 消息会调用 handleTimeout(ToastRecord) 函数,而 handleTimeout(ToastRecord) 函数经过搜索后,将调用 cancelToastLocked 函数取消掉 Toast 的显示:

void cancelToastLocked(int index) {        ToastRecord record = mToastQueue.get(index);            ....            record.callback.hide();//远程调用hide,通知客户端隐藏窗口            ....        ToastRecord lastToast = mToastQueue.remove(index);        mWindowManagerInternal.removeWindowToken(lastToast.token, true);        //将给 Toast 生成的窗口 Token 从 WMS 服务中删除        ...

cancelToastLocked 函数将做以下两件事:

  1. 远程调用 ITransientNotification.hide 方法,通知客户端隐藏窗口

  2. 将给 Toast 生成的窗口 TokenWMS 服务中删除

上面我们就从源码的角度分析了一个Toast的显示和隐藏,我们不妨再来捋一下思路,Toast 的显示和隐藏大致分成以下核心步骤:

  1. Toast 调用 show 方法的时候 ,实际上是将自己纳入到 NotificationManagerToast 管理中去,期间传递了一个本地的 TN 类型或者是 ITransientNotification.StubBinder 对象

  2. NotificationManager 收到 Toast 的显示请求后,将生成一个  Binder 对象,将它作为一个窗口的 token 添加到 WMS 对象,并且类型是 TOAST

  3. NotificationManager 将这个窗口 token 通过 ITransientNotificationshow 方法传递给远程的 TN 对象,并且抛出一个超时监听消息 scheduleTimeoutLocked

  4. TN 对象收到消息以后将往 Handler 对象中 post 显示消息,然后调用显示处理函数将 Toast 中的 View 添加到了 WMS 管理中, Toast 窗口显示

  5. NotificationManagerWorkerHandler 收到 MESSAGE_TIMEOUT 消息, NotificationManager 远程调用进程隐藏  Toast 窗口,然后将窗口 tokenWMS 中删除

3. 异常产生的原因

上面我们分析了 Toast 的显示和隐藏的源码流程,那么为什么会出现显示异常呢?我们先来看下这个异常是什么呢?

Unable to add window -- token android.os.BinderProxy@7f652b2 is not valid; is your activity running?    android.view.ViewRootImpl.setView(ViewRootImpl.java:826)    android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:369)

首先,这个异常发生在 Toast 显示的时候,原因是因为 token 失效。那么 token 为什么会失效呢?

通常情况下,按照正常的流程,是不会出现这种异常。但是由于在某些情况下, Android 进程某个 UI 线程的某个消息阻塞。导致 TNshow 方法 post 出来 0 (显示) 消息位于该消息之后,迟迟没有执行。这时候,NotificationManager 的超时检测结束,删除了 WMS 服务中的 token 记录。也就是如图所示,删除 token 发生在 Android 进程 show 方法之前。这就导致了我们上面的异常。我们来写一段代码测试一下:

public void click(View view) {        Toast.makeText(this,"test",Toast.LENGTH_SHORT).show();        try {            Thread.sleep(10000);        } catch (InterruptedException e) {            e.printStackTrace();        }}

我们先调用 Toast.show 方法,然后在该 ui 线程消息中 sleep 10秒。当进程异常退出后我们截取他们的日志可以得到:

12-28 11:10:30.086 24599 24599 E AndroidRuntime: android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.BinderProxy@2e5da2c is not valid; is your activity running?12-28 11:10:30.086 24599 24599 E AndroidRuntime:     at android.view.ViewRootImpl.setView(ViewRootImpl.java:679)12-28 11:10:30.086 24599 24599 E AndroidRuntime:     at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:342)12-28 11:10:30.086 24599 24599 E AndroidRuntime:     at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:93)12-28 11:10:30.086 24599 24599 E AndroidRuntime:     at android.widget.Toast$TN.handleShow(Toast.java:434)12-28 11:10:30.086 24599 24599 E AndroidRuntime:     at android.widget.Toast$TN$2.handleMessage(Toast.java:345)

果然如我们所料,我们复现了这个问题的堆栈。那么或许你会有下面几个疑问:

Toast.show 方法外增加 try-catch 有用么?

当然没用,按照我们的源码分析,异常是发生在我们的下一个 UI 线程消息中,因此我们在上一个 ui 线程消息中加入 try-catch 是没有意义的

为什么有些系统中没有这个异常,但是有时候 toast不显示?

我们上面分析的是7.0的代码,而在8.0的代码中,Toast 中的 handleShow发生了变化:

//code handleShow() android 8.0                try {                    mWM.addView(mView, mParams);                    trySendAccessibilityEvent();                } catch (WindowManager.BadTokenException e) {                                    }

8.0 的代码中,对 mWM.addView 进行了 try-catch 包装,因此并不会抛出异常,但由于执行失败,因此不会显示 Toast

有哪些原因引起的这个问题?

  1. 引起这个问题的也不一定是卡顿,当你的 TN 抛出消息的时候,前面有大量的 UI 线程消息等待执行,而每个 UI 线程消息虽然并不卡顿,但是总和如果超过了 NotificationManager 的超时时间,还是会出现问题

  2. UI 线程执行了一条非常耗时的操作,比如加载图片,大量浮点运算等等,比如我们上面用 sleep 模拟的就是这种情况

  3. 在某些情况下,进程退后台或者息屏了,系统为了减少电量或者某种原因,分配给进程的 cpu 时间减少,导致进程内的指令并不能被及时执行,这样一样会导致进程看起来”卡顿”的现象

感谢各位的阅读,以上就是“Android的Toast问题有哪些”的内容了,经过本文的学习后,相信大家对Android的Toast问题有哪些这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Android的Toast问题有哪些

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Android的Toast问题有哪些

这篇文章主要讲解了“Android的Toast问题有哪些”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Android的Toast问题有哪些”吧!1. 异常和偶尔不显示的问题当你在程序中调用了
2023-06-04

android toast的用法有哪些

Android中Toast的用法有以下几种:1.显示短时间的提示信息:使用`Toast.makeText(context, text, Toast.LENGTH_SHORT).show();`方法来显示一个短时间的提示信息。示例代码:```
2023-08-15

android中toast的用法有哪些

在Android中,Toast用于显示短暂的消息提示。下面是一些Toast的用法:1. 显示默认Toast:使用makeText方法创建Toast实例,并调用show方法显示。示例代码如下:```javaToast.makeText(con
2023-08-12

Android的Toast问题怎么解决

这篇“Android的Toast问题怎么解决”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Android的Toast问题怎么
2023-06-04

Android开发问题有哪些

这篇“Android开发问题有哪些”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Android开发问题有哪些”文章吧。1.如
2023-07-04

android studio的常见问题有哪些

Android Studio的常见问题包括但不限于以下内容:1. 安装问题:如安装过程中出现错误、无法打开Android Studio等。2. 编译问题:如编译错误、无法构建项目等。3. 运行问题:如应用程序无法在模拟器或设备上运行、应用程
2023-08-31

android开发遇到的常见问题有哪些

1. 兼容性问题:由于Android设备的碎片化,不同设备的硬件和软件规格不同,开发者需要对不同设备进行适配和测试,以确保应用在各种设备上正常运行。2. 性能问题:Android设备资源有限,应用的性能优化是一个重要问题。开发者需要确保应用
2023-08-15

更新Android Studio 3.0碰到的问题有哪些

这篇文章主要介绍更新Android Studio 3.0碰到的问题有哪些,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!更新完后试下运行正在维护的旧项目,出现各种错误,因为后来发现问题不在这,所以没记完整,大概如下:A
2023-05-30

android连接设备常见问题有哪些

1. 设备无法被识别:可能是由于USB驱动未安装或者设备未启用USB调试模式。2. USB连接断开:可能是由于USB线松动或者设备电量不足。3. 设备无法连接互联网:可能是由于网络设置错误或者无线网络信号强度不稳定。4. 设备无法进行文件传
2023-09-04

JavaScript的问答题有哪些

这篇文章主要介绍“JavaScript的问答题有哪些”,在日常操作中,相信很多人在JavaScript的问答题有哪些问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”JavaScript的问答题有哪些”的疑惑有所
2023-06-27

android fragment生命周期常见问题有哪些

在Android开发中,使用Fragment时常见的生命周期问题包括:1. Fragment的创建和销毁:如何正确地创建和销毁Fragment实例,以及在销毁时如何处理数据和资源的释放。2. Fragment的显示和隐藏:如何在Activi
2023-10-20

Android的面试题都有哪些

今天就跟大家聊聊有关Android的面试题都有哪些,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。Android基础View的绘制流程;自定义View如何考虑机型适配;自定义View的
2023-05-30

Android小程序开发中遇到的问题有哪些

本篇内容介绍了“Android小程序开发中遇到的问题有哪些”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!渲染列表时用 block 包裹
2023-06-29

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录