Toast源码深度分析

目录介绍

  • 1.最简单的创建方法

    • 1.1 Toast构造方法
    • 1.2 最简单的创建
    • 1.3 简单改造避免重复创建
    • 1.4 为何会出现内存泄漏
    • 1.5 吐司是系统级别的
  • 2.源码分析

    • 2.1 Toast(Context context)构造方法源码分析
    • 2.2 show()方法源码分析
    • 2.3 mParams.token = windowToken是干什么用的
    • 2.4 scheduleTimeoutLocked吐司如何自动销毁的
    • 2.5 TN类中的消息机制
    • 2.6 普通应用的Toast显示数量是有限制的
    • 2.7 为何Activity销毁后Toast仍会显示
  • 3.经典总结

    • 3.1 判断应用程序获取通知权限是否开启
    • 3.2 使用Toast注意事项
    • 3.3 Toast的显示和隐藏重点逻辑
    • 3.4 Snackbar和Toast比较
  • 4.Toast封装库介绍

    • 4.1 能够满足的需求
    • 4.2 具有的优势
  • 5.Toast遇到的问题

    • 5.1 Toast偶尔报错Unable to add window
    • 5.2 Toast运行在子线程问题
    • 5.3 Toast如何添加系统窗口的权限
    • 5.4 token null is not valid

好消息

  • 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计47篇[近20万字],转载请注明出处,谢谢!
  • 链接地址:https://github.com/yangchong211/YCBlogs
  • 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!
  • Toast封装库项目地址:https://github.com/yangchong211/YCDialog
  • 02.Toast源码深度分析

    • 最简单的创建,简单改造避免重复创建,show()方法源码分析,scheduleTimeoutLocked吐司如何自动销毁的,TN类中的消息机制是如何执行的,普通应用的Toast显示数量是有限制的,用代码解释为何Activity销毁后Toast仍会显示,Toast偶尔报错Unable to add window是如何产生的,Toast运行在子线程问题,Toast如何添加系统窗口的权限等等
  • 03.DialogFragment源码分析

    • 最简单的使用方法,onCreate(@Nullable Bundle savedInstanceState)源码分析,重点分析弹窗展示和销毁源码,使用中show()方法遇到的IllegalStateException分析
  • 05.PopupWindow源码分析

    • 显示PopupWindow,注意问题宽和高属性,showAsDropDown()源码,dismiss()源码分析,PopupWindow和Dialog有什么区别?为何弹窗点击一下就dismiss呢?
  • 06.Snackbar源码分析

    • 最简单的创建,Snackbar的make方法源码分析,Snackbar的show显示与点击消失源码分析,显示和隐藏中动画源码分析,Snackbar的设计思路,为什么Snackbar总是显示在最下面
  • 07.弹窗常见问题

    • DialogFragment使用中show()方法遇到的IllegalStateException,什么常见产生的?Toast偶尔报错Unable to add window,Toast运行在子线程导致崩溃如何解决?

1.最简单的创建方法

1.1 Toast构造方法

  • Toast只会弹出一段信息,告诉用户某某事情已经发生了,过一段时间后就会自动消失。它不会阻挡用户的任何操作。
  • Toast是没有焦点,而且Toast显示的时间有限,过一定的时间就会自动消失。

    • 通过new Toast(context)直接创建,除了将mContext = context,还有一步重要的操作,创建TN,下面会说到……
    public Toast(Context context) {mContext = context;mTN = new TN();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);
    }

1.2 最简单的创建

  • 一行代码调用,十分方便,但是这样存在一种弊端。

    • 使用中遇到的问题:例如,当点击有些按钮,需要吐司进行提示时;快速连续点击了多次按钮,Toast就触发了多次。系统会将这些Toast信息提示框放到队列中,等前一个Toast信息提示框关闭后才会显示下一个Toast信息提示框。可能导致Toast就长时间关闭不掉了。又或者我们其实已在进行其他操作了,应该弹出新的Toast提示,而上一个Toast却还没显示结束
    Toast.makeText(this,"吐司",Toast.LENGTH_SHORT).show();

1.3 简单改造避免重复创建

  • 为了解决1.2中的重复创建问题,则可以这样解决

    • 如下所示,简易型代码,需要注意问题,这里传递的上下文context需要是activity.getApplicationContext()全局上下文,避免静态toast对象内存泄漏
    /*** 吐司工具类    避免点击多次导致吐司多次,最后导致Toast就长时间关闭不掉了* 注意:这里如果传入context会报内存泄漏;传递activity..getApplicationContext()* @param content       吐司内容*/
    private static Toast toast;
    @SuppressLint("ShowToast")
    public static void showToast(String content) {checkContext();if (toast == null) {toast = Toast.makeText(mApp, content, Toast.LENGTH_SHORT);} else {toast.setText(content);}toast.show();
    }
  • 这样用的原理

    • 先判断Toast对象是否为空,如果是空的情况下才会调用makeText()方法来去生成一个Toast对象,否则就直接调用setText()方法来设置显示的内容,最后再调用show()方法将Toast显示出来。由于不会每次调用的时候都生成新的Toast对象,因此刚才我们遇到的问题在这里就不会出现

1.4 为何会出现内存泄漏

  • 原因在于:如果在 Toast 消失之前,Toast 持有了当前 Activity,而此时,用户点击了返回键,导致 Activity 无法被 GC 销毁, 这个 Activity 就引起了内存泄露。

1.5 吐司是系统级别的

  • 经常看到的一个场景就是你在你的应用出调用了多次 Toast.show函数,然后退回到桌面,结果发现桌面也会弹出 Toast,就是因为系统的 Toast 使用了系统窗口,具有高的层级

2.源码分析

2.1 Toast(Context context)构造方法源码分析

  • 在构造方法中,创建了NT对象,那么有人便会问,NT是什么东西呢?于是带着好奇心便去看看NT的源码,可以发现NT实现了ITransientNotification.Stub,提到这个感觉是不是很熟悉,没错,在aidl中就会用到这个。

    • 针对aidl,如果有人不明白,可以参考我的这边文章Aidl进程间通信详细介绍主要是Aidl相关属性介绍,实际开发中案例操作,部分源码解析,客户端绑定服务端service原理
    public Toast(Context context) {mContext = context;mTN = new TN();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);
    }
    • image
  • 在TN类中,可以看到,实现了AIDL的show与hide方法

    • TN是Toast内部的一个私有静态类,继承自ITransientNotification.Stub,ITransientNotification.Stub是出现在服务端实现的Service中,就是一个Binder对象,也就是对一个aidl文件的实现而已
    /*** schedule handleShow into the right thread*/
    @Override
    public void show(IBinder windowToken) {if (localLOGV) Log.v(TAG, "SHOW: " + this);mHandler.obtainMessage(0, windowToken).sendToTarget();
    }/*** schedule handleHide into the right thread*/
    @Override
    public void hide() {if (localLOGV) Log.v(TAG, "HIDE: " + this);mHandler.post(mHide);
    }
  • 接着看下这个ITransientNotification.aidl文件

    /** @hide */
    oneway interface ITransientNotification {void show();void hide();
    }

2.2 show()方法源码分析

  • 通过AIDL(Binder)通信拿到NotificationManagerService的服务访问接口,然后把TN对象和一些参数传递到远程NotificationManagerService中去

    • 当 Toast在show的时候,然后把这个请求放在 NotificationManager 所管理的队列中,并且为了保证 NotificationManager 能跟进程交互,会传递一个TN类型的 Binder对象给NotificationManager系统服务,接着看下面getService方法做了什么?
    public void show() {if (mNextView == null) {throw new RuntimeException("setView must have been called");}//通过AIDL(Binder)通信拿到NotificationManagerService的服务访问接口,当前Toast类相当于上面例子的客户端!!!相当重要!!!INotificationManager service = getService();String pkg = mContext.getOpPackageName();TN tn = mTN;tn.mNextView = mNextView;try {//把TN对象和一些参数传递到远程NotificationManagerService中去service.enqueueToast(pkg, tn, mDuration);} catch (RemoteException e) {// Empty}
    }
  • 接着看看getService方法

    • 通过单利模式获取sService对象。
    //远程NotificationManagerService的服务访问接口
    private static INotificationManager sService;
    static private INotificationManager getService() {//单例模式if (sService != null) {return sService;}//通过AIDL(Binder)通信拿到NotificationManagerService的服务访问接口sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification"));return sService;
    }
  • 接下来看看service.enqueueToast(pkg, tn, mDuration)这段代码,相信有的小伙伴会质疑,这段代码报红色,如何查看呢?

    • image
    • 于是,我直接在studio中全局搜索NotificationManagerService,终于给找到了,如下所示:
    • image
    • 下面就到重点呢……注意:record是将Toast封装成ToastRecord对象,放入mToastQueue中。通过下面代码可以得知:通过isSystemToast判断是否为系统Toast。如果当前Toast所属的进程的包名为“android”,则为系统Toast。如果是系统Toast一定可以进入到系统Toast队列中,不会被黑名单阻止。
    synchronized (mToastQueue) {int callingPid = Binder.getCallingPid();long callingId = Binder.clearCallingIdentity();try {ToastRecord record;int index;//判断是否是系统级别的吐司if (!isSystemToast) {index = indexOfToastPackageLocked(pkg);} else {index = indexOfToastLocked(pkg, callback);}if (index >= 0) {record = mToastQueue.get(index);record.update(duration);record.update(callback);} else {//创建一个Binder类型的token对象Binder token = new Binder();//生成一个Toast窗口,并且传递token等参数mWindowManagerInternal.addWindowToken(token, TYPE_TOAST, DEFAULT_DISPLAY);record = new ToastRecord(callingPid, pkg, callback, duration, token);//添加到吐司队列之中mToastQueue.add(record);//对当前索引重新进行赋值index = mToastQueue.size() - 1;}//将当前Toast所在的进程设置为前台进程keepProcessAliveIfNeededLocked(callingPid);if (index == 0) {//如果index为0,说明当前入队的Toast在队头,需要调用showNextToastLocked方法直接显示showNextToastLocked();}} finally {Binder.restoreCallingIdentity(callingId);}
    }
  • 接下来看一下showNextToastLocked()方法中的源代码,看看这个方法中做了什么……

    • 首先获取吐司消息队列中第一个ToastRecord对象,然后判断该对象如果不为null的话,就开始通过callback进行show,且传递了token参数,注意这个show是通知进程显示。然后再调用scheduleTimeoutLocked(record)方法执行超时后自动取消的逻辑【下面详细分析】。同时需要注意的时,如果出现了异常,则会从吐司消息队列中移除该record……
    • 那么callback是干嘛的呢,一般印象中callback是处理回调的?从ITransientNotification callback得知,这个callback哥们竟然是是一个 ITransientNotification 类型的对象,也就是前面说到的TN的Binder代理对象,那么他传递的这个token参数是干什么用的呢?这里我们程序员小伙伴可以接着往下看哈!
    • image

2.3 mParams.token = windowToken是干什么用的

  • 如果你仔细一点,你可以看到在handleShow(IBinder windowToken)这个方法中,将windowToken赋值给mParams.token,那么就会思考这个token是干什么用的呢?它是哪里传递过来的呢?

    • 这个所需要的这个系统窗口 token ,是由我们的 NotificationManager 系统服务所生成,由于系统服务具有高权限,果真是厉害呀。
    • 上文2.3中我已经分析了showNextToastLocked()方法部分源码record.callback.show(record.token),可以知道callback对象的show方法中需要传递的参数 record.token实际上就是上面所说的NotificationManager服务所生成的窗口的 token。
    • image
    • image
  • 这个显示窗口的方法比较简单,就是将所传递过来的窗口 token 赋值给窗口属性对象 mParams, 然后通过调用 WindowManager.addView 方法,将 Toast中的mView对象纳入WindowManager中,而WindowManager看源码可知是一个接口,具体是放在WindowManagerService中处理。

2.4 scheduleTimeoutLocked吐司如何自动销毁的

  • 接下来再来看看scheduleTimeoutLocked(record)这部分代码,这个主要是超时监听消息逻辑

    • 通过看这段代码知道,handler延迟delay时间后发送消息,并且这个delay时间只有原生自带的两种时间类型,无法开发者自己定义。
    • image
  • 既然发送了消息,那肯定有地方接收消息并且处理消息呀。接着看下面代码,重点看cancelToastLocked源码

    • 可以看到当接收到消息时,先判断是否吐司,如果是有的话,也就是索引index>=0,那么就去cancel,在cancelToastLocked(int index)这段源码里面,我们终于可以看到record.callback.hide()这个方法了,前面我们知道callback是前面提到TN的binder代理对象,所以这个方法是调用了TN类中的hide()方法,下面2.5中将详细讲解TN中的消息机制。
    • 同时结束吐司之后,移除消息队列中对象,同时判断吐司消息队列中是否还有剩下的消息,如果是有的话,则会接着调用showNextToastLocked()继续弹吐司,关于showNextToastLocked()可以看2.3中的源码分析。
    • image
    • image
    • image
    • image
  • cancelToastLocked源码逻辑主要是

    • 调用 ITransientNotification.hide 方法,通知客户端隐藏窗口,并且移除队列中对象
    • 将给Toast 生成的窗口Token从WMS 服务中删除
    • 判断吐司消息队列中是否存在消息,如果存在消息,则继续开始show吐司……

2.5 TN类中的消息机制

  • 看源码可知,TN中的消息机制也是通过handler消息机制实现的。如果对handler 消息机制还不太熟悉,可以查看我的这篇博客:Handler消息机制
  • 当创建TN对象的时候,就创建了handler和runnable对象。

    • 然后看看show与hide方法,在show方法中发送消息,当mHandler接受到消息之后,就调用handleShow(token)处理逻辑,通过WindowManager将view添加进来,同时在该方法中也设置了大量的布局属性。
    • 在把Toast的View添加之前发现Toast的View已经被添加过(有partent)则删掉;把Toast的View添加到窗口,其中mParams.type在构造函数中赋值为TYPE_TOAST!
    • image
    • image
  • 同时,当toast执行show之后,过了一会儿会自动销毁,那么这又是为啥呢?那么是哪里调用了hide方法呢?

    • 回调了Toast的TN的show,当timeout可能就是hide呢。从上面我分析NotificationManagerService源码中的showNextToastLocked()的scheduleTimeoutLocked(record)源码,可以知道在NotificationManagerService通过handler延迟delay时间发送消息,然后通过callback调用hide,由于callback是TN中Binder的代理对象, 所以便可以调用到TN中的hide方法达到销毁吐司的目的。handleHide()源码如下所示
    public void handleHide() {if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);if (mView != null) {// note: checking parent() just to make sure the view has// been added...  i have seen cases where we get here when// the view isn't yet added, so let's try not to crash.if (mView.getParent() != null) {if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);mWM.removeViewImmediate(mView);}mView = null;}
    }

2.6 普通应用的Toast显示数量是有限制的

  • 如何判断是否是系统吐司呢?如果当前Toast所属的进程的包名为“android”,则为系统Toast,或者调用isCallerSystem()方法

    final boolean isSystemToast = isCallerSystem() || ("android".equals(pkg));
  • 接着看看isCallerSystem()方法源码,isCallerSystem的源码也比较简单,就是判断当前Toast所属进程的uid是否为SYSTEM_UID、0、PHONE_UID中的一个,如果是,则为系统Toast;如果不是,则不为系统Toast。

    private static boolean isUidSystem(int uid) {final int appid = UserHandle.getAppId(uid);return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID || uid == 0);
    }private static boolean isCallerSystem() {return isUidSystem(Binder.getCallingUid());
    }
  • 为什么要这样判断是否是系统吐司呢?从源码可知:首先系统Toast一定可以进入到系统Toast队列中,不会被黑名单阻止。然后系统Toast在系统Toast队列中没有数量限制,而普通pkg所发送的Toast在系统Toast队列中有数量限制。

    • 那么关于数量限制这个结果从何而来,大概是多少呢?查看将要入队的Toast是否已经在系统Toast队列中。这是通过比对pkg和callback来实现的。通过下面源码分析可知:只要Toast的pkg名称和tn对象是一致的,则系统把这些Toast认为是同一个Toast。
    • 然后再看看下面这个源码截图,可知,非系统Toast,每个pkg在当前mToastQueue中Toast有总数限制,不能超过MAX_PACKAGE_NOTIFICATIONS,也就是50
    • image
    • image

2.7 为何Activity销毁后Toast仍会显示

  • 记得以前昊哥问我,为何toast在activity销毁后仍然会弹出呢,我毫不思索地说,因为toast是系统级别的呀。那么是如何实现的呢,我就无言以对呢……今天终于可以回答呢!

    • 还是回到NotificationManagerService类中的enqueueToast方法中,直接查看keepProcessAliveIfNeededLocked(callingPid)方法。这段代码的意思是将当前Toast所在进程设置为前台进程,这里的mAm = ActivityManager.getService(),调用了setProcessImportant方法将当前pid的进程置为前台进程,保证不会系统杀死。这也就解释了为什么当我们finish当前Activity时,Toast还可以显示,因为当前进程还在执行。
    • image

3.经典总结

3.1 判断应用程序获取通知权限是否开启

  • 一行代码调用即可:DialogUtils.requestMsgPermission(this);
  • 大部分手机通知权限是开启的。如果关闭了,则吐司是无法显示的,但是仍有部分手机,比如某型号小米手机,锤子手机等就权限需要手动开启。
  • Toast的展示是由NMS服务控制的,NMS服务会做一些权限、token等的校验,当通知权限一旦关闭,Toast将不再弹出。
  • 具体可以参考我的弹窗封装库:https://github.com/yangchong211/YCDialog

    • 自定义对话框,其中包括:自定义Toast,采用builder模式,支持设置吐司多个属性;自定义dialog控件,仿IOS底部弹窗;自定义DialogFragment弹窗,支持自定义布局,也支持填充recyclerView布局;自定义PopupWindow弹窗,轻量级,还有自定义Snackbar等等;还有自定义loading加载窗,简单便用。
    //判断是否有权限
    NotificationManagerCompat.from(context).areNotificationsEnabled()//如果没有通知权限,则直接跳转设置中心设置
    @SuppressLint("ObsoleteSdkInt")
    private static void toSetting(Context context) {Intent localIntent = new Intent();localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);if (Build.VERSION.SDK_INT >= 9) {localIntent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");localIntent.setData(Uri.fromParts("package", context.getPackageName(), null));} else if (Build.VERSION.SDK_INT <= 8) {localIntent.setAction(Intent.ACTION_VIEW);localIntent.setClassName("com.android.settings","com.android.setting.InstalledAppDetails");localIntent.putExtra("com.android.settings.ApplicationPkgName", context.getPackageName());}context.startActivity(localIntent);
    }

3.2 使用Toast注意事项

  • 通过分析TN类的handler可以发现,如果想在非UI线程使用Toast需要自行声明Looper,否则运行会抛出Looper相关的异常;UI线程不需要,因为系统已经帮忙声明。
  • 在使用Toast时context参数尽量使用getApplicationContext(),可以有效的防止静态引用导致的内存泄漏。
  • 有时候我们会发现Toast弹出过多就会延迟显示,因为上面源码分析可以看见Toast.makeText是一个静态工厂方法,每次调用这个方法都会产生一个新的Toast对象,当我们在这个新new的对象上调用show方法就会使这个对象加入到NotificationManagerService管理的mToastQueue消息显示队列里排队等候显示;所以如果我们不每次都产生一个新的Toast对象(使用单例来处理)就不需要排队,也就能及时更新呢。

3.3 Toast的显示和隐藏重点逻辑

  • Toast调用show方法 ,其实就是是将自己纳入到NotificationManager的Toast管理中去,期间传递了一个本地的TN类型或者是 ITransientNotification.Stub的Binder对象
  • NotificationManager 收到 Toast 的显示请求后,将生成一个 Binder 对象,将它作为一个窗口的 token 添加到 WMS 对象,并且类型是 TOAST
  • NotificationManager 将这个窗口token通过ITransientNotification的show方法传递给远程的TN对象,并且抛出一个超时监听消息 scheduleTimeoutLocked
  • TN 对象收到消息以后将往 Handler 对象中 post 显示消息,然后调用显示处理函数将 Toast 中的 View 添加到了 WMS 管理中,Toast窗口显示
  • NotificationManager的WorkerHandler收到MESSAGE_TIMEOUT消息, NotificationManager远程调用hide方法进程隐藏Toast 窗口,然后将窗口token从WMS中删除,并且判断吐司消息队列中是否还有消息,如果有,则继续吐司!

3.4 Snackbar和Toast比较

  • 可以使用snackBar替代Toast,即使用户禁掉了通知权限,也可以显示出来。SnackBar,其实就是使用View系统去模拟一个窗口行为,而且还能更加快速的实现动画效果,是不是很棒。
  • Snackbar是Android自5.0系统推出MaterialDesign后官方推荐的控件,在交互友好性方面比Toast要好

4.Toast封装库介绍

4.1 能够满足的需求

  • 可以设置吐司的位置,偏移,吐司文字颜色,吐司背景颜色等等。简单的代码就可以实现你需要的多种场景。也可以设置定义布局的吐司。项目地址:https://github.com/yangchong211/YCDialog

4.2 具有的优势

  • 采用builder构造者模式,链式编程,一行代码调用即可设置吐司Toast。
  • 为了避免静态toast对象内存泄漏,固可以使用应用级别的上下文context。所以这里我就直接采用了应用级别Application上下文,需要在application进行初始化一下。即可调用……

    //初始化
    ToastUtils.init(this);//可以自由设置吐司的背景颜色,默认是纯黑色
    ToastUtils.setToastBackColor(this.getResources().getColor(R.color.color_7f000000));//直接设置最简单吐司,只有吐司内容
    ToastUtils.showRoundRectToast("自定义吐司");//设置吐司标题和内容
    ToastUtils.showRoundRectToast("吐司一下","他发的撒经济法的解放军");//第三种直接设置自定义布局的吐司
    ToastUtils.showRoundRectToast(R.layout.view_layout_toast_delete);//或者直接采用bulider模式创建
    ToastUtils.Builder builder = new ToastUtils.Builder(this.getApplication());
    builder.setDuration(Toast.LENGTH_SHORT).setFill(false).setGravity(Gravity.CENTER).setOffset(0).setDesc("内容内容").setTitle("标题").setTextColor(Color.WHITE).setBackgroundColor(this.getResources().getColor(R.color.blackText)).build().show();
  • 因为看到网上有许多toast的封装,需要传递上下文,后来感觉是不是不需要传递这个参数,直接统一初始化一下就好呢。所以才有了这个toast的改良版。

    • 如果没有调用ToastUtils.init(this)初始化,则会提示报错ToastUtils context is not null,please first init",具体看下面代码。
    /*** 检查上下文不能为空,必须先进性初始化操作*/
    private static void checkContext(){if(mApp==null){throw new NullPointerException("ToastUtils context is not null,please first init");}
    }

5.Toast遇到的异常问题

5.1 Toast偶尔报错Unable to add window

  • 报错日志,是不是有点眼熟呀?更多可以看我的开源项目:https://github.com/yangchong211

    android.view.WindowManager$BadTokenExceptionUnable to add window -- token android.os.BinderProxy@7f652b2 is not valid; is your activity running?
  • 查询报错日志是从哪里来的

    • image
  • 发生该异常的原因

    • 这个异常发生在Toast显示的时候,原因是因为token失效。通常情况下,一般是不会出现这种异常。但是由于在某些情况下, Android进程某个UI线程的某个消息阻塞。导致 TN 的 show 方法 post 出来 0 (显示) 消息位于该消息之后,迟迟没有执行。这时候,NotificationManager 的超时检测结束,删除了 WMS 服务中的 token 记录。删除 token 发生在 Android 进程 show 方法之前。这就导致了上面的异常。
    • 测试代码。模拟一下异常的发生场景,其实很容易,只需要这样做就可以出现上面这个问题
     Toast.makeText(this,"潇湘剑雨-yc",Toast.LENGTH_SHORT).show();try {Thread.sleep(20000);} catch (InterruptedException e) {e.printStackTrace();}
  • 解决办法,目前见过好几种,思考一下那种比较好……

    • 第一种,既然是报is your activity running,那可以不可以在吐司之前先判断一下activity是否running呢?
    • 第二种,抛出异常增加try-catch,代码如下所示,最后仍然无法解决问题

      • 按照源码分析,异常是发生在下一个UI线程消息中,因此在上一个ui线程消息中加入try-catch是没有意义的。而且用到吐司地方这么多,这样做也不方便啦!
    • 第三种,那就是自定义类似吐司Toast的view控件。个人建议除非要求非常高,不然不要这样做。毕竟发生这种异常还是比较少见的
  • 哪些情况会发生该问题?

    • UI 线程执行了一条非常耗时的操作,比如加载图片等等,就类似上面用 sleep 模拟情况
    • 进程退后台或者息屏了,系统为了减少电量或者某种原因,分配给进程的cpu时间减少,导致进程内的指令并不能被及时执行,这样一样会导致进程看起来”卡顿”的现象
    • 当TN抛出消息的时候,前面有大量的 UI 线程消息等待执行,而每个 UI 线程消息虽然并不卡顿,但是总和如果超过了 NotificationManager 的超时时间,还是会出现问题

5.2 Toast运行在子线程问题

  • 先来看看问题代码,会出现什么问题呢?

    new Thread(new Runnable() {@Overridepublic void run() {ToastUtils.showRoundRectToast("潇湘剑雨-杨充");}
    }).start();
    • 报错日志如下所示:
    • image
  • 然后找找报错日志从哪里来的

    • ![image]()
  • 子线程中吐司的正确做法,代码如下所示

    new Thread(new Runnable() {@Overridepublic void run() {Looper.prepare();ToastUtils.showRoundRectToast("潇湘剑雨-杨充");Looper.loop();}
    }).start();
  • 得出的结论

    • Toast也可以在子线程执行,不过需要手动提供Looper环境的。
    • Toast在调用show方法显示的时候,内部实现是通过Handler执行的,因此自然是不阻塞Binder线程,另外,如果addView的线程不是Loop线程,执行完就结束了,当然就没机会执行后续的请求,这个是由Hanlder的构造函数保证的。可以看看handler的构造函数,如果Looper==null就会报错,而Toast对象在实例化的时候,也会为自己实例化一个Hanlder,这就是为什么说“一定要在主线程”,其实准确的说应该是 “一定要在Looper非空的线程”。
    • Handler的构造函数如下所示:
    • image
    • image

5.3 Toast如何添加系统窗口的权限

  • 作为程序员,都知道任何视图的显示都要依赖于一个视图窗口Window,同样Toast的显示也需要一个窗口,而且它还是一个系统窗口,这个窗口最终会被WindowManagerService(WMS)标记管理。当显示一个Toast时,调用show方法后,会通过TN 类中的handleShow方法处理展示的逻辑,同时WMS会生成一个token,而我们知道WMS本身就是一个系统级的服务,所以由它生成的token必然拥有权限添加系统窗口,最后WMS调用addView方法将view和mParams参数带进来,这样就可以展示吐司呢。
  • 需要注意:WindowManager检查当前窗口的token是否有效,如果有效,则添加窗口展示Toast;如果无效,则抛出异常,会发生5.1这种类型的异常。

    • 在那个地方检查token呢?在mWM.addView(mView, mParams)这里检查token,点击去可以发现ViewManager是个接口,这时候可以去看WindowManagerImpl类,继承ViewManager。
    • image
    • image

5.4 token null is not valid

  • 看了美团的技术文档分享得知,这个异常其实并非是Toast的异常,而是Google对WindowManage的一些限制导致的。Android从7.1.1版本开始,对WindowManager做了一些限制和修改,特别是TYPE_TOAST类型的窗口,必须要传递一个token用于权限校验才允许添加。在stackoverflow上搜索,也较少得到这方面的解答,这块有点难以解决这个问题。

关于其他内容介绍

01.关于博客汇总链接

  • 1.技术博客汇总
  • 2.开源项目汇总
  • 3.生活博客汇总
  • 4.喜马拉雅音频汇总
  • 5.其他汇总

02.关于我的博客

  • 我的个人站点:www.yczbj.org,www.ycbjie.cn
  • github:https://github.com/yangchong211
  • 知乎:https://www.zhihu.com/people/yang-chong-69-24/pins/posts
  • 简书:http://www.jianshu.com/u/b7b2c6ed9284
  • csdn:http://my.csdn.net/m0_37700275
  • 喜马拉雅听书:http://www.ximalaya.com/zhubo/71989305/
  • 开源中国:https://my.oschina.net/zbj1618/blog
  • 泡在网上的日子:http://www.jcodecraeer.com/member/content_list.php?channelid=1
  • 邮箱:yangchong211@163.com
  • 阿里云博客:https://yq.aliyun.com/users/article?spm=5176.100- 239.headeruserinfo.3.dT4bcV
  • segmentfault头条:https://segmentfault.com/u/xiangjianyu/articles

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/389504.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

运行keras出现 FutureWarning: Passing (type, 1) or ‘1type‘ as a synonym of type is deprecated解决办法

运行keras出现 FutureWarning: Passing (type, 1) or ‘1type’ as a synonym of type is deprecated; in a future version of numpy, 原则来说&#xff0c;没啥影响&#xff0c;还是能运行&#xff0c;但是看着难受 解决办法&#xff1a; 点击蓝色的链接&#xff1a; 进入 …

mongdb 群集_群集文档的文本摘要

mongdb 群集This is a part 2 of the series analyzing healthcare chart notes using Natural Language Processing (NLP)这是使用自然语言处理(NLP)分析医疗保健图表笔记的系列文章的第2部分。 In the first part, we talked about cleaning the text and extracting sectio…

keras框架实现手写数字识别

详细细节可学习从零开始神经网络&#xff1a;keras框架实现数字图像识别详解&#xff01; 代码实现&#xff1a; [1]将训练数据和检测数据加载到内存中(第一次运行需要下载数据&#xff0c;会比较慢): &#xff08;mnist是手写数据集&#xff09; train_images是用于训练系统…

gdal进行遥感影像读写_如何使用遥感影像进行矿物勘探

gdal进行遥感影像读写Meet Jose Manuel Lattus, a geologist from Chile. In the latest Soar Cast, he discusses his work in mineral exploration and environmental studies, and explains how he makes a living by creating valuable information products based on diff…

从零开始神经网络:keras框架实现数字图像识别详解!

接口实现可参考&#xff1a;keras框架实现手写数字识别 思路&#xff1a; 我们的代码要导出三个接口&#xff0c;分别完成以下功能&#xff1a; 初始化initialisation&#xff0c;设置输入层&#xff0c;中间层&#xff0c;和输出层的节点数。训练train:根据训练数据不断的更…

推荐算法的先验算法的连接_数据挖掘专注于先验算法

推荐算法的先验算法的连接So here we are diving into the world of data mining this time, let’s begin with a small but informative definition;因此&#xff0c;这一次我们将进入数据挖掘的世界&#xff0c;让我们从一个小的但内容丰富的定义开始&#xff1b; 什么是数…

Tensorflow入门神经网络代码框架

Tensorflow—基本用法 使用图 (graph) 来表示计算任务.在被称之为 会话 (Session) 的上下文 (context) 中执行图.使用 tensor 表示数据.通过 变量 (Variable) 维护状态.使用 feed 和 fetch 可以为任意的操作(arbitrary operation)赋值或者从其中获取数据。 • TensorFlow 是一…

手把手教你把代码丢入github 中

手把手教你把代码丢入github 中 作为一个小运维一步步教你们怎么把代码放入到github 中 首先呢我们下载一个git的客户端 https://git-scm.com/downloads/ 下载一个最新版的2.16.2 下载后那就安装吧。如果看不懂英文就选择默认安装的方式吧。但是你得记住你的软件安装的位置 小…

时间序列模式识别_空气质量传感器数据的时间序列模式识别

时间序列模式识别 1. Introduction 2. Exploratory Data Analysis ∘ 2.1 Pattern Changes ∘ 2.2 Correlation Between Features 3. Anomaly Detection and Pattern Recognition ∘ 3.1 Point Anomaly Detection (System Fault) ∘ 3.2 Collective Anomaly Detection (Externa…

oracle 性能优化 07_诊断事件

2019独角兽企业重金招聘Python工程师标准>>> 一、诊断事件 诊断事件无官方技术文档支持&#xff0c;使用存在风险&#xff0c;慎用。使用诊断事件可以获取问题更多的信息&#xff0c;调整系统运行 特性&#xff0c;启用某些内部功能。用于系统故障的诊断。跟踪应…

Tensorflow框架:卷积神经网络实战--Cifar训练集

Cifar-10数据集包含10类共60000张32*32的彩色图片&#xff0c;每类6000张图。包括50000张训练图片和 10000张测试图片 代码分为数据处理部分和卷积网络训练部分&#xff1a; 数据处理部分&#xff1a; #该文件负责读取Cifar-10数据并对其进行数据增强预处理 import os impo…

linux内存初始化初期内存分配器——memblock

2019独角兽企业重金招聘Python工程师标准>>> 1.1.1 memblock 系统初始化的时候buddy系统&#xff0c;slab分配器等并没有被初始化好,当需要执行一些内存管理、内存分配的任务&#xff0c;就引入了一种内存管理器bootmem分配器。 当buddy系统和slab分配器初始化好后&…

Keras框架:Alexnet网络代码实现

网络思想&#xff1a; 1、一张原始图片被resize到(224,224,3)&#xff1b; 2、使用步长为4x4&#xff0c;大小为11的卷积核对图像进行卷积&#xff0c;输出的特征层为96层&#xff0c; 输出的shape为(55,55,96)&#xff1b; 3、使用步长为2的最大池化层进行池化&#xff0c;此时…

PHP对象传递方式

<?phpheader(content-type:text/html;charsetutf-8);class Person{public $name;public $age;}$p1 new Person;$p1->name 金角大王;$p1->age 400;//这个地方&#xff0c;到底怎样?$p2 $p1;$p2->name 银角大王;echo <pre>;echo p1 name . $p1->n…

微软Azure CDN现已普遍可用

微软宣布Azure CDN一般可用&#xff08;GA&#xff09;&#xff0c;客户现在可以从微软的全球CDN网络提供内容。最新版本是对去年五月份发布的公众预览版的跟进。\\今年5月&#xff0c;微软与Verizon和Akamai一起推出了原生CDN产品。现在推出了GA版本&#xff0c;根据发布博文所…

数据科学生命周期_数据科学项目生命周期第1部分

数据科学生命周期This is series of how to developed data science project.这是如何开发数据科学项目的系列。 This is part 1.这是第1部分。 All the Life-cycle In A Data Science Projects-1. Data Analysis and visualization.2. Feature Engineering.3. Feature Selec…

Keras框架:VGG网络代码实现

VGG概念&#xff1a; VGG之所以经典&#xff0c;在于它首次将深度学习做得非常“深”&#xff0c;达 到了16-19层&#xff0c;同时&#xff0c;它用了非常“小”的卷积核&#xff08;3X3&#xff09;。 网络框架&#xff1a; VGG的结构&#xff1a; 1、一张原始图片被resize…

Keras框架:resent50代码实现

Residual net概念 概念&#xff1a; Residual net(残差网络)&#xff1a;将靠前若干层的某一层数据输出直接跳过多层引入到后面数据层的输入 部分。 残差神经单元&#xff1a;假定某段神经网络的输入是x&#xff0c;期望输出是H(x)&#xff0c;如果我们直接将输入x传到输出作…

Tensorflow框架:InceptionV3网络概念及实现

卷积神经网络迁移学习-Inception • 有论文依据表明可以保留训练好的inception模型中所有卷积层的参数&#xff0c;只替换最后一层全连接层。在最后 这一层全连接层之前的网络称为瓶颈层。 • 原理&#xff1a;在训练好的inception模型中&#xff0c;因为将瓶颈层的输出再通过…

成为一名真正的数据科学家有多困难

Data Science and Machine Learning are hard sports to play. It’s difficult enough to motivate yourself to sit down and learn some maths, let alone to becoming an expert on the matter.数据科学和机器学习是一项艰巨的运动。 激励自己坐下来学习一些数学知识是非常…