Toast的使用
Toast.makeText(MainActivity.this,"-----",Toast.LENGTH_LONG).show();
public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
return makeText(context, null, text, duration);
}
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;
}
}
在makeText就是创建一个Toast实例。
下面我们来看一下show方法。
public void show() {
if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
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");
}
}
INotificationManager service = getService();//1
String pkg = mContext.getOpPackageName();
TN tn = mTN;//2
tn.mNextView = mNextView;
final int displayId = mContext.getDisplayId();
try {
if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
if (mNextView != null) {
// It's a custom toast
service.enqueueToast(pkg, mToken, tn, mDuration, displayId);
} else {
// It's a text toast
ITransientNotificationCallback callback =
new CallbackBinder(mCallbacks, mHandler);
service.enqueueTextToast(pkg, mToken, mText, mDuration, displayId, callback);
}
} else {
service.enqueueToast(pkg, mToken, tn, mDuration, displayId);//3
}
} catch (RemoteException e) {
// Empty
}
}
注释1通过AIDL(Binder)通信拿到NotificationManagerService的服务访问接口,
注释2TN是Toast内部的一个私有静态类,继承自ITransientNotification.Stub,ITransientNotification.Stub是出现在服务端实现的Service中,就是一个Binder对象,也就是对一个aidl文件的实现而已。该内部类主要的作用是控制Toast的show和hide。
private static class TN extends ITransientNotification.Stub{
TN(Context context, String packageName, Binder token, List<Callback> callbacks,
@Nullable Looper looper) {
IAccessibilityManager accessibilityManager = IAccessibilityManager.Stub.asInterface(
ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
mPresenter = new ToastPresenter(context, accessibilityManager, getService(),
packageName);
mParams = mPresenter.getLayoutParams();
mPackageName = packageName;
mToken = token;
mCallbacks = callbacks;
mHandler = new Handler(looper, null) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case SHOW: {
IBinder token = (IBinder) msg.obj;
handleShow(token);
break;
}
case HIDE: {
handleHide();
// Don't do this in handleHide() because it is also invoked by
// handleShow()
mNextView = null;
break;
}
case CANCEL: {
handleHide();
// Don't do this in handleHide() because it is also invoked by
// handleShow()
mNextView = null;
try {
getService().cancelToast(mPackageName, mToken);
} catch (RemoteException e) {
}
break;
}
}
}
};
}
}
注释3把TN对象和一些参数传递到远程NotificationManagerService中去,调用enqueueToast().
public void enqueueToast(String pkg, ITransientNotification callback, int duration)
{
final boolean isSystemToast = isCallerSystemOrPhone() || ("android".equals(pkg));//4
final boolean isPackageSuspended =
isPackageSuspendedForUser(pkg, Binder.getCallingUid());
synchronized (mToastQueue) {
int callingPid = Binder.getCallingPid();
long callingId = Binder.clearCallingIdentity();
try {
ToastRecord record;
int index;
if (!isSystemToast) {
index = indexOfToastPackageLocked(pkg);//5
} else {
index = indexOfToastLocked(pkg, callback);//6
}
//index>0,表明队列中已经存在,则调用hide(),然后进行更新
if (index >= 0) {
record = mToastQueue.get(index);
record.update(duration);
try {
record.callback.hide();
} catch (RemoteException e) {
}
record.update(callback);
} else {
//表示队列中没有,则创建一个WindowToken和ToastRecord,然后加入到队列中。
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);//7
if (index == 0) {
showNextToastLocked();//8
}
} finally {
Binder.restoreCallingIdentity(callingId);
}
}
}
注释4通过isSystemToast判断是否为系统Toast。如果当前Toast所属的进程的包名为“android”,则为系统Toast。如果是系统Toast一定可以进入到系统Toast队列中,不会被黑名单阻止。
注释5和6则是根据是否为系统Toast分别去判断队列中根据报名判断队列中是否存在。
注释7则是将当前的Toast所在的进程设置为前台进程,这也是为什么activity销毁后Toast仍会显示。
注释8 index=0 表示Toast就是当前的Toast,
@GuardedBy("mToastQueue")
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);//9
scheduleDurationReachedLocked(record);//10
return;
} catch (RemoteException e) {
}
}
}
注释9是record.callback中的callback是ITransientNotification类型,这说明就是传经来的TN实例。也就是调用tn重点额show()。
public void show(IBinder windowToken) {
mHandler.obtainMessage(SHOW, windowToken).sendToTarget();
}
mHandler = new Handler(looper, null) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case SHOW: {
IBinder token = (IBinder) msg.obj;
handleShow(token);
break;
}
};
}
public void handleShow(IBinder windowToken) {
if (mHandler.hasMessages(CANCEL) || mHandler.hasMessages(HIDE)) {
return;
}
if (mView != mNextView) {
// remove the old view if necessary
handleHide();
mView = mNextView;
mPresenter.show(mView, mToken, windowToken, mDuration, mGravity, mX, mY,
mHorizontalMargin, mVerticalMargin,
new CallbackBinder(getCallbacks(), mHandler));
}
}
经过一系列的调用,最后调用mPresenter的show()方法。
public void show(View view, IBinder token, IBinder windowToken, int duration, int gravity,
int xOffset, int yOffset, float horizontalMargin, float verticalMargin,
@Nullable ITransientNotificationCallback callback) {
mView = view;
mToken = token;
adjustLayoutParams(mParams, windowToken, duration, gravity, xOffset, yOffset,
horizontalMargin, verticalMargin);//11
if (mView.getParent() != null) {
mWindowManager.removeView(mView);
}
try {
mWindowManager.addView(mView, mParams);//12
} catch (WindowManager.BadTokenException e) {
}
}
注释11是调用adjustLayoutParams(),主要作用是将将参数赋值到WindowManager.LayoutParams。
private void adjustLayoutParams(WindowManager.LayoutParams params, IBinder windowToken,
int duration, int gravity, int xOffset, int yOffset, float horizontalMargin,
float verticalMargin) {
Configuration config = mResources.getConfiguration();
int absGravity = Gravity.getAbsoluteGravity(gravity, config.getLayoutDirection());
params.gravity = absGravity;
if ((absGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
params.horizontalWeight = 1.0f;
}
if ((absGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
params.verticalWeight = 1.0f;
}
params.x = xOffset;
params.y = yOffset;
params.horizontalMargin = horizontalMargin;
params.verticalMargin = verticalMargin;
params.packageName = mContext.getPackageName();
params.hideTimeoutMilliseconds =
(duration == Toast.LENGTH_LONG) ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT;
params.token = windowToken;
}
注释12则将view添加到window上。
这样Toast就显示出来了,再回到注释10.
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;//13
mHandler.sendMessageDelayed(m, delay);
}
在makeText中有一个默认的时间段,
@Duration
public int getDuration() {
return mDuration;
}
@IntDef(prefix = { "LENGTH_" }, value = {
LENGTH_SHORT,
LENGTH_LONG
})
@Retention(RetentionPolicy.SOURCE)
public @interface Duration {}
public static final int LENGTH_SHORT = 0;
public static final int LENGTH_LONG = 1;
从这里可知,LENGTH_SHORT = 0,表明会立马消失,而事实是湖等一下消失,实际的操作就是在注释10这里。LONG_DELAY为3.5秒,SHORT_DELAY为2秒。
然后会发一个延时消息,延时的时间就是消失的时间。
这个mHandler是WorkerHandler的实例。
protected class WorkerHandler extends Handler
{
public WorkerHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg)
{
switch (msg.what)
{
case MESSAGE_DURATION_REACHED:
handleDurationReached((ToastRecord)msg.obj);
break;
case MESSAGE_FINISH_TOKEN_TIMEOUT:
handleKillTokenTimeout((IBinder)msg.obj);
break;
case MESSAGE_SAVE_POLICY_FILE:
handleSavePolicyFile();
break;
case MESSAGE_SEND_RANKING_UPDATE:
handleSendRankingUpdate();
break;
case MESSAGE_LISTENER_HINTS_CHANGED:
handleListenerHintsChanged(msg.arg1);
break;
case MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED:
handleListenerInterruptionFilterChanged(msg.arg1);
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();
} catch (RemoteException e) {
Slog.w(TAG, "Object died trying to hide notification " + record.callback
+ " in package " + record.pkg);
// don't worry about this, we're about to remove it from
// the list anyway
}
ToastRecord lastToast = mToastQueue.remove(index);
mWindowManagerInternal.removeWindowToken(lastToast.token, false /* removeWindows */,
DEFAULT_DISPLAY);
// We passed 'false' for 'removeWindows' so that the client has time to stop
// rendering (as hide above is a one-way message), otherwise we could crash
// a client which was actively using a surface made from the token. However
// we need to schedule a timeout to make sure the token is eventually killed
// one way or another.
scheduleKillTokenTimeout(lastToast.token);
keepProcessAliveIfNeededLocked(record.pid);
if (mToastQueue.size() > 0) {
// 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();
}
}
最终会调用cancelToastLocked,进而调用record.callback.hide()。然后将toast从队列中移除,token从WMS中移除。
总结
1、Toast 调用 show 方法的时候 ,实际上是将自己纳入到 NotificationManager 的 Toast 管理中去,期间传递了一个本地的 TN 类型或者是 ITransientNotification.Stub 的 Binder 对象
2、NotificationManager 收到 Toast 的显示请求后,将生成一个 Binder 对象,将它作为一个窗口的 token 添加到 WMS 对象,并且类型是 TOAST
3、NotificationManager 将这个窗口 token 通过 ITransientNotification 的 show 方法传递给远程的 TN 对象,并且抛出一个超时监听消息 scheduleTimeoutLocked
4、TN 对象收到消息以后将往 Handler 对象中 post 显示消息,然后调用显示处理函数将 Toast 中的 View 添加到了 WMS 管理中, Toast 窗口显示
5、NotificationManager 的 WorkerHandler 收到 MESSAGE_TIMEOUT 消息, NotificationManager 远程调用进程隐藏 Toast 窗口,然后将窗口 token 从 WMS 中删除
1610696436(1).png
网友评论