Android9.0针对Toast的特殊处理

2024-05-15 06:58

本文主要是介绍Android9.0针对Toast的特殊处理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

我们都清楚,Toast显示时长有两个选择,长显示是3.5秒,端显示是2秒。那如果想要做到长时间显示,该怎么做呢?有个历史遗留的app通过开一个线程,不断调用show方法进行实现,这些年也没出过问题,直到系统版本更新到了Android9.0。实现方式大概如下:

mToast = new Toast(context);
mToast.setDuration(Toast.LENGTH_LONG);
mToast.setView(layout);
...
mToast.show();  //在线程里不断调用show方法,达到长时间显示的目的

在Android9.0上,Toast闪现了一下就不见了,并没有如预期那样,长时间显示。为什么呢?

概述

这里我们先来大概了解下Toast的显示流程。

Toast使用

一般使用Toast的时候,比较简单的就是如下方式:

Toast.makeText(mContext, "hello world", duration).show();

这样就可以显示一个toast。还有一种是自定义view的:

mToast = new Toast(context);
mToast.setDuration(Toast.LENGTH_LONG);
mToast.setView(layout);
mToast.show(); 

原理都一样,先new 一个Toast,然后设置显示时长,设置toast中要显示的view(text也是view),然后就可以show出来。

Toast原理

Toast实现

先看看Toast的实现:

//frameworks/base/core/java/android/widget/Toast.java
public Toast(@NonNull Context context, @Nullable Looper looper) {mContext = context;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);
}

Toast的构造函数很简单,主要就是mTN这个成员,后续对Toast的操作都在这里进行。紧接着就是设置Toast显示时长和显示内容:

public void setView(View view) {mNextView = view;
}public void setDuration(@Duration int duration) {mDuration = duration;mTN.mDuration = duration;
}

在这里插入图片描述

Toast显示

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;try {service.enqueueToast(pkg, tn, mDuration);} catch (RemoteException e) {// Empty}
}

在这里插入图片描述

show方法简单,最终是调用了通知服务的enqueueToast方法:

frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.javapublic void enqueueToast(String pkg, ITransientNotification callback, int duration){...final boolean isSystemToast = isCallerSystemOrPhone() || ("android".equals(pkg));...synchronized (mToastQueue) {int callingPid = Binder.getCallingPid();long callingId = Binder.clearCallingIdentity();try {ToastRecord record;int index;// All packages aside from the android package can enqueue one toast at a timeif (!isSystemToast) {index = indexOfToastPackageLocked(pkg);} else {index = indexOfToastLocked(pkg, callback);}// If the package already has a toast, we update its toast// in the queue, we don't move it to the end of the queue.if (index >= 0) {record = mToastQueue.get(index);record.update(duration);try {record.callback.hide();} catch (RemoteException e) {}record.update(callback);} else {Binder token = new Binder();mWindowManagerInternal.addWindowToken(token, TYPE_TOAST, DEFAULT_DISPLAY);record = new ToastRecord(callingPid, pkg, callback, duration, token);mToastQueue.add(record);index = mToastQueue.size() - 1;}keepProcessAliveIfNeededLocked(callingPid);// If it's at index 0, it's the current toast.  It doesn't matter if it's// new or just been updated.  Call back and tell it to show itself.// If the callback fails, this will remove it from the list, so don't// assume that it's valid after this.if (index == 0) {showNextToastLocked();}} finally {Binder.restoreCallingIdentity(callingId);}}}

Toast的管理是通过ToastRecord类型列表集中管理的,NotificationManagerService会将每一个Toast封装为ToastRecord对象,并添加到mToastQueue中,mToastQueue的类型是ArrayList。在enqueueToast中,首先会判断应用是否为系统应用,如果是系统应用,则通过indexOfToastLocked来寻找是否有满足条件的Toast存在:

int indexOfToastLocked(String pkg, ITransientNotification callback)
{IBinder cbak = callback.asBinder();ArrayList<ToastRecord> list = mToastQueue;int len = list.size();for (int i=0; i<len; i++) {ToastRecord r = list.get(i);if (r.pkg.equals(pkg) && r.callback.asBinder().equals(cbak)) {return i;}}return -1;
}

判断的依据是包名和callback,这里的callback其实就是上文说到的TN类,这是一个Binder类型,继承自ITransientNotification.Stub。如果条件符合,则返回对应索引,否则返回-1。首次show Toast的时候,肯定返回-1,则此时会new一个ToastRecord对象,并且加入到mToastQueue中,此时的index则为0:

record = new ToastRecord(callingPid, pkg, callback, duration, token);
mToastQueue.add(record);
index = mToastQueue.size() - 1;

那么就会走到如下分支了:

if (index == 0) {showNextToastLocked(); //显示Toast
}void showNextToastLocked() {ToastRecord record = mToastQueue.get(0);while (record != null) {if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);try {record.callback.show(record.token); //调用TN类的show方法scheduleDurationReachedLocked(record); //时间到就隐藏Toastreturn;} catch (RemoteException e) {...}}
}

该方法也简单,就是回调TN类的show方法,上文提过,TN类对外提供show,hide, cancel等方法,在这些方法中,再通过内部handler进行处理:

//frameworks/base/core/java/android/widget/Toast.java
public void show(IBinder windowToken) {if (localLOGV) Log.v(TAG, "SHOW: " + this);mHandler.obtainMessage(SHOW, windowToken).sendToTarget();
}//贴出部分handleMessage方法
case SHOW: {IBinder token = (IBinder) msg.obj;handleShow(token);break;
}public void handleShow(IBinder windowToken) {...if (mView != mNextView) {// remove the old view if necessaryhandleHide();mView = mNextView;...mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);...try {mWM.addView(mView, mParams); //交给WMS进行下一步的操作,最终显示出我们的viewtrySendAccessibilityEvent();} catch (WindowManager.BadTokenException e) {/* ignore */}}}

调用show方法,最终会调用到handleshow方法,在该方法中使用WMS服务将view显示出来。

Toast隐藏

显示说完了,什么时候隐藏消失?在scheduleDurationReachedLocked方法中:

//frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java
private void scheduleDurationReachedLocked(ToastRecord r)
{mHandler.removeCallbacksAndMessages(r);Message m = Message.obtain(mHandler, MESSAGE_DURATION_REACHED, r);long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;mHandler.sendMessageDelayed(m, delay);
}

这里也是使用了一个handler来进行处理,delay的时长取决于我们之前设置的Toast显示时长。长时间为3.5秒,短时间为2秒。

MESSAGE_DURATION_REACHED消息处理如下:

case MESSAGE_DURATION_REACHED:handleDurationReached((ToastRecord)msg.obj);break;private void handleDurationReached(ToastRecord record)
{if (DBG) Slog.d(TAG, "Timeout pkg=" + record.pkg + " callback=" + record.callback);synchronized (mToastQueue) {int index = indexOfToastLocked(record.pkg, record.callback);if (index >= 0) {cancelToastLocked(index);}}
}void cancelToastLocked(int index) {ToastRecord record = mToastQueue.get(index);try {record.callback.hide(); //隐藏掉该Toast} catch (RemoteException e) {...}ToastRecord lastToast = mToastQueue.remove(index);  //已经显示完毕的Toast,从列表中移除掉...if (mToastQueue.size() > 0) { //如果还有待显示Toast// Show the next one. If the callback fails, this will remove// it from the list, so don't assume that the list hasn't changed// after this point.showNextToastLocked();}
}

该方法调用TN的hide方法隐藏掉Toast,然后再将Toast从列表中移除。看看隐藏的过程:

case HIDE: {handleHide();// Don't do this in handleHide() because it is also invoked by// handleShow()mNextView = null; //这里会把view清掉break;
}public void handleHide() {if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);if (mView != null) {...mWM.removeViewImmediate(mView);...mView = null;}
}

隐藏的过程,其实也简单,将view从窗口中移除,然后将mNextView和mView置Null。

到此Toast的显示和隐藏已经讲完。下面说说多次show为什么会导致Toast消失。

Toast的消失

想象一个场景,如果一个全局Toast(此次出问题的app中就是一个全局Toast),我们不断的去调用Toast的show方法,那么就意味着上文说的mToastQueue列表不为空,存在Toast,就会走到如下分支:

if (!isSystemToast) {index = indexOfToastPackageLocked(pkg);} else {index = indexOfToastLocked(pkg, callback);}// If the package already has a toast, we update its toast// in the queue, we don't move it to the end of the queue.if (index >= 0) {record = mToastQueue.get(index);record.update(duration);try {record.callback.hide(); //如果存在已经显示的Toast,这里会先进行hide} catch (RemoteException e) {}record.update(callback);}
}

hide的流程我们已经清楚,会将资源释放,将mNextView和mView置为Null。执行到这里会导致第一个Toast消失,之后调用showNextToastLocked()方法显示第二个Toast,最终调用到TN的handleShow方法:

public void handleShow(IBinder windowToken) {// ...if (mView != mNextView) {// ...mView = mNextView;// ...mWM = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);// ...mWM.addView(mView, mParams);// ...}
}

由于所有的Toast都对应一个TN对象,因此此时mView和mNextView均为null,不会执行mWM.addView(),Toast也就不会显示。

解决方法

在Android9.0中如果想要一直显示某个Toast,怎么做?使用局部Toast,不要使用全局Toast。

但有一点比较奇怪的是,查看了Android10.0代码,发现Android10.0将这个机制回滚了。即Android10.0上又可以一直显示Toast:

//这里就不执行hide的操作了
if (index >= 0) {record = mToastQueue.get(index);record.update(duration);
}

结语

Android多个系统版本中,唯独Android9.0做了这个特殊处理,无非就是禁用应用长时间显示Toast。但10.0版本又取消了这个处理,难道是发现这样处理并不合适?

微信公众号

我在微信公众号也有写文章,更新比较及时,有兴趣者可以扫描如下二维码,或者微信搜索【Android系统实战开发】,关注有惊喜哦!
在这里插入图片描述

这篇关于Android9.0针对Toast的特殊处理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


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

相关文章

Golang 日志处理和正则处理的操作方法

《Golang日志处理和正则处理的操作方法》:本文主要介绍Golang日志处理和正则处理的操作方法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考... 目录1、logx日志处理1.1、logx简介1.2、日志初始化与配置1.3、常用方法1.4、配合defer

springboot加载不到nacos配置中心的配置问题处理

《springboot加载不到nacos配置中心的配置问题处理》:本文主要介绍springboot加载不到nacos配置中心的配置问题处理,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑... 目录springboot加载不到nacos配置中心的配置两种可能Spring Boot 版本Nacos

python web 开发之Flask中间件与请求处理钩子的最佳实践

《pythonweb开发之Flask中间件与请求处理钩子的最佳实践》Flask作为轻量级Web框架,提供了灵活的请求处理机制,中间件和请求钩子允许开发者在请求处理的不同阶段插入自定义逻辑,实现诸如... 目录Flask中间件与请求处理钩子完全指南1. 引言2. 请求处理生命周期概述3. 请求钩子详解3.1

Python处理大量Excel文件的十个技巧分享

《Python处理大量Excel文件的十个技巧分享》每天被大量Excel文件折磨的你看过来!这是一份Python程序员整理的实用技巧,不说废话,直接上干货,文章通过代码示例讲解的非常详细,需要的朋友可... 目录一、批量读取多个Excel文件二、选择性读取工作表和列三、自动调整格式和样式四、智能数据清洗五、

SpringBoot如何对密码等敏感信息进行脱敏处理

《SpringBoot如何对密码等敏感信息进行脱敏处理》这篇文章主要为大家详细介绍了SpringBoot对密码等敏感信息进行脱敏处理的几个常用方法,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下... 目录​1. 配置文件敏感信息脱敏​​2. 日志脱敏​​3. API响应脱敏​​4. 其他注意事项​​总结

Python使用python-docx实现自动化处理Word文档

《Python使用python-docx实现自动化处理Word文档》这篇文章主要为大家展示了Python如何通过代码实现段落样式复制,HTML表格转Word表格以及动态生成可定制化模板的功能,感兴趣的... 目录一、引言二、核心功能模块解析1. 段落样式与图片复制2. html表格转Word表格3. 模板生

Python Pandas高效处理Excel数据完整指南

《PythonPandas高效处理Excel数据完整指南》在数据驱动的时代,Excel仍是大量企业存储核心数据的工具,Python的Pandas库凭借其向量化计算、内存优化和丰富的数据处理接口,成为... 目录一、环境搭建与数据读取1.1 基础环境配置1.2 数据高效载入技巧二、数据清洗核心战术2.1 缺失

SpringBoot项目中Redis存储Session对象序列化处理

《SpringBoot项目中Redis存储Session对象序列化处理》在SpringBoot项目中使用Redis存储Session时,对象的序列化和反序列化是关键步骤,下面我们就来讲讲如何在Spri... 目录一、为什么需要序列化处理二、Spring Boot 集成 Redis 存储 Session2.1

Python处理超大规模数据的4大方法详解

《Python处理超大规模数据的4大方法详解》在数据的奇妙世界里,数据量就像滚雪球一样,越变越大,从最初的GB级别的小数据堆,逐渐演变成TB级别的数据大山,所以本文我们就来看看Python处理... 目录1. Mars:数据处理界的 “变形金刚”2. Dask:分布式计算的 “指挥家”3. CuPy:GPU

Python中CSV文件处理全攻略

《Python中CSV文件处理全攻略》在数据处理和存储领域,CSV格式凭借其简单高效的特性,成为了电子表格和数据库中常用的文件格式,Python的csv模块为操作CSV文件提供了强大的支持,本文将深入... 目录一、CSV 格式简介二、csv模块核心内容(一)模块函数(二)模块类(三)模块常量(四)模块异常