Toast类用于在屏幕中显示一个消息提示框,该消息提示没哟任何控制按钮,并且不会获得焦点经过一段时间后会自动消失。通常用于显示一些快速提示信息,应用范围非常广泛;
Toast 应用
Toast创建
Toast的创建有两种方式:
- 使用构造方式进行创建;
ini
Toast toast = new Toast(context);
toast.setDuration(Toast.LENGTH_SHORT);//设置持续时间
toast.setGravity(Gravity.CENTER,0, 0);//设置对齐方式
LinearLayout layout = new LinearLayout(context);//创建一个线性布局管理器
TextView tv = new TextView(context);
tv.setText("我是通过构造方法创建的消息提示框");
layout.addView(tv);
toast.setView(layout);
toast.show();
这种方式使用了自定义View,通过setView方法传入这个自定义View,最终也是通过show方法显示;
- 调用Toast提供的makeText()方法创建;
ini
Toast toast = Toast.makeText(context, text, Toast.LENGTH_SHORT).show();
一般根据使用场景的不同来确定使用哪种方式,例如:
- 简单的信息提示,一般使用makeText的方式;
- 复杂场景的信息提示,如需要进行图片的显示,则可以使用构造方法的方式提示;
Toast 构造方法
ini
public Toast(@NonNull Context context, @Nullable Looper looper) {
mContext = context;
mToken = new Binder();
looper = getLooper(looper);
mHandler = new Handler(looper);
mCallbacks = new ArrayList<>();
mTN = new TN(context, context.getPackageName(), mToken,
mCallbacks, 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);
}
- mContext:Context实例;
- mToken:构造binder对象,后面和NotificationManagerService通信使用;
- looper:当前Toast绑定的线程looper;
- mTN:Binder.Stub类型对象,作为binder的client端,其接受跨进程传递过来的信息时是在单独的binder线程中处理;
- mTN.mY:纵坐标偏移量,简单来说就是控制Toast在屏幕中显示位置是靠上一点还是靠下一点;
- mTN.mGravity:控制Toast的显示位置,一般是居中、靠下两种;
Toast 原理
基于两种使用方式的不同,其工作原理也是不同的;
- 使用构造方法,创建自定义View:是由APP自身处理的;
- makeText方法:通过NotificationManagerService显示;
makeText
less
public static Toast makeText(@NonNull Context context, @Nullable Looper looper,
@NonNull CharSequence text, @Duration int duration) {}
-
context:绑定的上下文对象;
-
looper:绑定线程的Looper,可以为空,为空时则默认使用当前线程的looper;
-
text:显示内容;
-
duration:持续时间
- Toast.LENGTH_LONG:显示时间较长,为3.5s,其3500ms的值定义在NotificationManagerService.LONG_DELAY;
- Toast.LENGTH_SHORT:显示时间较短,为2s,其2000ms的值定义在NotificationManagerService.SHORT_DELAY;
但是真实显示的时间,却不是3.5s和2s,实际显示时间会比这两个时间更长一些;
less
public static Toast makeText(@NonNull Context context, @Nullable Looper looper,
@NonNull CharSequence text, @Duration int duration) {
if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
Toast result = new Toast(context, looper);
result.mText = text;
result.mDuration = duration;
return result;
} else {
Toast result = new Toast(context, looper);
View v = ToastPresenter.getTextToastView(context, text);
result.mNextView = v;
result.mDuration = duration;
return result;
}
}
-
CHANGE_TEXT_TOASTS_IN_THE_SYSTEM:判断该文本消息框是否由SystemUI呈现;
CHANGE_TEXT_TOASTS_IN_THE_SYSTEM对于Android R(API级别30)或更高版本为目标平台的应用处于启用状态;
默认为true,即支持使用SystemUI定义的ToastUI组件进行显示;
原生Toast和自定义View的Toast的唯一区别就是原生Toast对象中的mNextView对象为null;
show
java
public void show() {
if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
// 校验Toast 配置是否合理
checkState(mNextView != null || mText != null, "You must either set a text or a view");
} else {
if (mNextView == null) {
throw new RuntimeException("setView must have been called");
}
}
// 获取NotificationManagerService
INotificationManager service = getService();
String pkg = mContext.getOpPackageName();
TN tn = mTN;
tn.mNextView = mNextView;
// 获取Display Id
final int displayId = mContext.getDisplayId();
try {
if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
if (mNextView != null) {
// It's a custom toast -- 定制化Toast
service.enqueueToast(pkg, mToken, tn, mDuration, displayId);
} else {
// It's a text toast -- SystemUi Toast
ITransientNotificationCallback callback =
new CallbackBinder(mCallbacks, mHandler);
service.enqueueTextToast(pkg, mToken, mText, mDuration, displayId, callback);
}
} else {
service.enqueueToast(pkg, mToken, tn, mDuration, displayId);
}
} catch (RemoteException e) {
// Empty
}
}
至此,该Text就传入到了NotificationManagerService中了;
NotificationManagerService
less
@Override
public void enqueueTextToast(String pkg, IBinder token, CharSequence text, int duration,
int displayId, @Nullable ITransientNotificationCallback callback) {
enqueueToast(pkg, token, text, null, duration, displayId, callback);
}
- pkg:包名;
- token:APP端binder;
- text:需要提示的信息内容;
- duration:持续时间;
- displayId:标记唯一显示区域的ID,对应的实体类是DisplayContent;
- callback:跨进程的callback对象,自定义View的Toast有值,默认的Toast中的callback = null;
生产者
每个Toast都对应一个binder对象,所以如果toast是复用的,则短时间内多次调用show放,也只会对应同一个Record对象,所以也只会显示一次;
如果index<0,则说明mToastQueue不存在该toast所对应的binder,则进入插入的逻辑;
通过getToastRecord方法生成一个ToastRecord对象加入到集合最尾端,并且通过keepProcessAliveForToastIfNeededLocked方法保证弹Toast的进程不被杀死,如果当前只有一条记录的话,则直接调用showNextToastLocke方法进行显示;
ToastRecord其实是一个抽象方法,它有两个实现类,TextToastRecord和CustomToastRecord。getToastRecord方法中会根据callback是否为空来进行对应的生成,其中callback==null时生成的是TextToastRecord类型对象;
消费者
这里涉及到生产者消费者模式了,既然APP端通过binder方法向mToastQueue集合中插入数据,那么就一定有消费者来消费。而这个消费者就是showNextToastLocked方法,所以永远只会有一个线程在执行showNextToastLocked方法;
- 按照先后顺序便利mToastQueue集合,取出record对象。
- 通过showToast方法尝试显示record对象。如果成功,则执行scheduleDurationReachedLocked方法。
- 如果失败,则从集合中删除。就是说如果Toast显示时如果失败了也不会再次尝试;
我们这边分析TextToastRecord的show逻辑;
CommandQueue
mStatusBar是一个注册的service:
arduino
private final StatusBarManagerInternal mStatusBar;
其实现类在StatusBarManagerService.java中:
java
private final StatusBarManagerInternal mInternalService = new StatusBarManagerInternal() {}
mBar其实是一个binder的引用,为IStatusBar,其server的实现在SystemUI进程中,实现类是CommandQueue;
arduino
private volatile IStatusBar mBar;
scala
public class CommandQueue extends IStatusBar.Stub implements CallbackController<Callbacks>,
DisplayManager.DisplayListener {}
而在SystemUI模块中,很多SystemUI组件都和CommandQueue有关联,因为在CommandQueue中定义了一个Callback接口,这些接口用于对外进行交互;
scala
public class ToastUI extends SystemUI implements CommandQueue.Callbacks {}
public class SizeCompatModeActivityController extends SystemUI implements CommandQueue.Callbacks {}
public class AuthController extends SystemUI implements CommandQueue.Callbacks,
AuthDialogCallback {}
public class PipUI extends SystemUI implements CommandQueue.Callbacks {}
public class PowerUI extends SystemUI implements CommandQueue.Callbacks {}
public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Callbacks,
StatusBarStateController.StateListener {}
........................
在SystemUI中,在StatusBar的初始化过程中,将CommandQueue实例注册到了IStatusBarService中:
java
protected final CommandQueue mCommandQueue;
protected IStatusBarService mBarService;
@Override
public void start() {
........................
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
........................
// Connect in to the status bar manager service
mCommandQueue.addCallback(this);
RegisterStatusBarResult result = null;
try {
result = mBarService.registerStatusBar(mCommandQueue);
} catch (RemoteException ex) {
ex.rethrowFromSystemServer();
}
createAndAddWindows(result);
........................
}
在StatusBar中,将创建好的CommandQueue对象注册到了IStatusBarService中,而IStatusBarService aidl的实现为:
scala
public class StatusBarManagerService extends IStatusBarService.Stub implements DisplayListener {}
StatusBarManagerService是状态栏导航栏向外界提供服务的前端接口,运行于system_server进程中;
CustomToastRecord
CustomToastRecord区别于TextToastRecord在于show的逻辑;
-
TextToastRecord:
- Android P中,Text和Custom类型的Toast都是在Toast的TN类中进行show,没有明确的通过代码区分TextToastRecord和CustomToastRecord;
- Android R中,对Text和Custom进行了代码区分,定义了CustomToastRecord和TextToastRecord,其中Text类型的Toast的show移动到了SystemUI模块中进行显示,SystemUI组件中定义了一个ToastUI组件;
-
CustomToastRecord:
CustomToastRecord的显示流程和Android P基线的流程相同;
CustomToastRecord是直接使用了Toast类的内容类TN进行show,而TN的show的本质还是调用了ToastPresenter实现的;