Toast调用例子
Toast.makeText(this, "Toast源码解读", Toast.LENGTH_LONG).show();
调用步骤
一、Toast中makeText()方法
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) {
// 标注1️⃣
Toast result = new Toast(context, looper);
LayoutInflater inflate = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
tv.setText(text);
// 标注2️⃣
result.= v;
result.mDuration = duration;
return result;
}mNextView
标注1️⃣: Toast 构造方法有一个Looper参数,传到TN类处理
标注2️⃣: 把当前的布局mNextView 与 显示的时长duration存全局
二、Toast中构造方法
public Toast(@NonNull Context context, @Nullable Looper looper) {
mContext = context;
// 标注1️⃣
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);
}
标注1️⃣: looper往TN类传递
三、TN类源码
private static class TN extends ITransientNotification.Stub {
TN(String packageName, @Nullable Looper looper) {
...
if (looper == null) {
// 标注1️⃣
looper = Looper.myLooper();
if (looper == null) {
throw new RuntimeException(
"Can't toast on a thread that has not called Looper.prepare()");
}
}
// 标注2️⃣
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();
...
break;
}
case CANCEL: {
handleHide();
...
break;
}
}
}
};
}
@Override
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public void show(IBinder windowToken) {
mHandler.obtainMessage(SHOW, windowToken).sendToTarget();
}
@Override
public void hide() {
mHandler.obtainMessage(HIDE).sendToTarget();
}
public void cancel() {
mHandler.obtainMessage(CANCEL).sendToTarget();
}
// 标注3️⃣
public void handleShow(IBinder windowToken) {
...
mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
...
mWM.addView(mView, mParams);
...
}
}
TN是常见典型的AIDL生成的IBinder服务端Stub类,之后在NMS会调用到。
标注1️⃣: TN类的构造方法中looper 为null时默认looper = Looper.myLooper()
标注2️⃣: looper最终会传到Handler。
由此可知:如果想要在子线程使用Toast,先调用Looper.prepare(),然后调用Toast的show()显示,接着再调用Looper.loop(),最后得要调用Looper.myLooper().quit(); 避免出现内存泄漏等问题。
四、Toast中show()方法
static private INotificationManager getService() {
if (sService != null) {
return sService;
}
// 标注1️⃣
sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification"));
return sService;
}
public void show() {
...
INotificationManager service = getService();
String pkg = mContext.getOpPackageName();
TN tn = mTN;
tn.mNextView = mNextView;
final int displayId = mContext.getDisplayId();
try {
// 标注2️⃣
service.enqueueToast(pkg, tn, mDuration, displayId);
} catch (RemoteException e) {
...
}
}
标注1️⃣: 通过SystemServer来获取NMS的远程代理对象
标注2️⃣:调用NotificationManagerService的enqueueToas()方法
五、NotificationManagerService中enqueueToas()方法
public class NotificationManagerService extends SystemService {
...
@VisibleForTesting
final IBinder mService = new INotificationManager.Stub() {
@Override
public void enqueueToast(String pkg, ITransientNotification callback, int duration,
int displayId){
...
// 标注1️⃣
final boolean isSystemToast = isCallerSystemOrPhone()
|| PackageManagerService.PLATFORM_PACKAGE_NAME.equals(pkg);
...
synchronized (mToastQueue) {
int callingPid = Binder.getCallingPid();
long callingId = Binder.clearCallingIdentity();
try {
ToastRecord record;
// 标注2️⃣
int index = indexOfToastLocked(pkg, callback);
if (index >= 0) {
// 标注3️⃣
record = mToastQueue.get(index);
record.update(duration);
} else {
...
//新建一个窗口令牌,Toast拿到这个令牌之后才能创建系统级的Window
Binder token = new Binder();
mWindowManagerInternal.addWindowToken(token,
WindowManager.LayoutParams.TYPE_TOAST)
// 标注4️⃣
record = new ToastRecord(callingPid, pkg, callback, duration, token);
mToastQueue.add(record);
index = mToastQueue.size() - 1;
keepProcessAliveIfNeededLocked(callingPid);
}
if (index == 0) {
// 标注5️⃣
showNextToastLocked();
}
} finally {
Binder.restoreCallingIdentity(callingId);
}
}
}
}
}
标注1️⃣:判断是否是系统的Toast
标注2️⃣:检查该Toast是否存在,返回下标-1则代表没有Toast
标注3️⃣:通过下标在队列中找到Toast,然后更新时间
标注4️⃣:Toast队列为空时,则新建一个ToastRecord,添加到Toast队列中
标注5️⃣:展示下一个Toast
六、NotificationManagerService 中内部类ToastRecord
public class NotificationManagerService extends SystemService {
private static final class ToastRecord{
// 进程PID
final int pid;
// 包名
final String pkg;
// TN类
final ITransientNotification callback;
// 显示时间
int duration;
// 显示ID
int displayId;
// 窗口令牌
Binder token;
ToastRecord(int pid, String pkg, ITransientNotification callback, int duration,
Binder token, int displayId) {
this.pid = pid;
this.pkg = pkg;
this.callback = callback;
this.duration = duration;
this.token = token;
this.displayId = displayId;
}
void update(int duration) {
this.duration = duration;
}
void dump(PrintWriter pw, String prefix, DumpFilter filter) {
if (filter != null && !filter.matches(pkg)) return;
pw.println(prefix + this);
}
}
}
七、NotificationManagerService 中showNextToastLocked()方法
public class NotificationManagerService extends SystemService {
@GuardedBy("mToastQueue")
void showNextToastLocked() {
ToastRecord record = mToastQueue.get(0);
while (record != null) {
...
// 标注1️⃣
record.callback.show(record.token);
scheduleDurationReachedLocked(record);
return;
}
}
// 标注2️⃣
@GuardedBy("mToastQueue")
private void scheduleDurationReachedLocked(ToastRecord r)
{
mHandler.removeCallbacksAndMessages(r);
Message m = Message.obtain(mHandler, MESSAGE_DURATION_REACHED, r);
int delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
delay = mAccessibilityManager.getRecommendedTimeoutMillis(delay,
AccessibilityManager.FLAG_CONTENT_TEXT);
mHandler.sendMessageDelayed(m, delay);
}
}
标注1️⃣:record的callback为ITransientNotification,通过Binder IPC访问到其服务端TN,调用TN类的show()方法。
标注2️⃣:Toast展示时间到了之后,发送取消Toast的指令,程序往下走会出现record.callback.hide()这句代码,最终也是通过IPC的方式调用到TN类的hide()方法。
TN类的代码可以看该文档第三节点,便可知道show()、hide()最终会分别调用handleShow()、handleHide()。
7、TN类中handleShow()与handleHide()方法
private static class TN extends ITransientNotification.Stub {
public void handleShow(IBinder windowToken) {
if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
+ " mNextView=" + mNextView);
// If a cancel/hide is pending - no need to show - at this point
// the window token is already invalid and no need to do any work.
if (mHandler.hasMessages(CANCEL) || mHandler.hasMessages(HIDE)) {
return;
}
if (mView != mNextView) {
// remove the old view if necessary
handleHide();
mView = mNextView;
Context context = mView.getContext().getApplicationContext();
String packageName = mView.getContext().getOpPackageName();
if (context == null) {
context = mView.getContext();
}
mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
// We can resolve the Gravity here by using the Locale for getting
// the layout direction
final Configuration config = mView.getContext().getResources().getConfiguration();
final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection());
mParams.gravity = gravity;
if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
mParams.horizontalWeight = 1.0f;
}
if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
mParams.verticalWeight = 1.0f;
}
mParams.x = mX;
mParams.y = mY;
mParams.verticalMargin = mVerticalMargin;
mParams.horizontalMargin = mHorizontalMargin;
mParams.packageName = packageName;
mParams.hideTimeoutMilliseconds = mDuration ==
Toast.LENGTH_LONG ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT;
mParams.token = windowToken;
if (mView.getParent() != null) {
if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
mWM.removeView(mView);
}
if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);
// Since the notification manager service cancels the token right
// after it notifies us to cancel the toast there is an inherent
// race and we may attempt to add a window after the token has been
// invalidated. Let us hedge against that.
try {
// 标记1️⃣
mWM.addView(mView, mParams);
trySendAccessibilityEvent();
} catch (WindowManager.BadTokenException e) {
/* ignore */
}
}
}
@UnsupportedAppUsage
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);
// 标记2️⃣
mWM.removeViewImmediate(mView);
}
// Now that we've removed the view it's safe for the server to release
// the resources.
try {
getService().finishToken(mPackageName, this);
} catch (RemoteException e) {
}
mView = null;
}
}
}
标记1️⃣: 在handleShow()方法中可以看到最终会调用到WindowManager的addView()方法添加View
标记2️⃣: 在handleHide()方法中可以看到最终会调用到WindowManager的removeViewImmediate()方法移除View
总结Toast的调用流程
- step1: 调用Toast 的makeText()方法初始化Toast与布局View,Toast的构造方法中会初始化TN,TN是IBinder服务端Stub类。
- step2: TN构造方法中会初始化Handler,其中looper会从Toast开始传递到TN,再传递到TN构造方法中的Handler,如果looper为空,则会以Looper.myLooper()作为默认looper。
- step3: 调用Toast 的show()方法, 获取到NotificationManagerService的代理类INotificationManager,调用NMS的enqueueToast()方法。
- step4:接着调用showNextToastLocked()方法,record.callback获取到TN的代理类ITransientNotification代理类,通过Binder IPC方式调用TN的show()方法,接着通过Handler方式调用到对应的handleShow(),把Viwe添加到WindowManager的addView()方法中。此时Toast显示
- step5:Toast显示之后,接着调用NMS的scheduleDurationReachedLocked()方法,同样record.callback获取到TN的代理类ITransientNotification代理类,通过Binder IPC方式调用TN的hide()方法,接着通过Handler方式调用到对应的handleHide(),把Viwe从WindowManager的removeViewImmediate()方法中移除。此时Toas消息