我们使用Toast的时候,通常都是这样
Toast.makeText(this, "Hello world", Toast.LENGTH_SHORT).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) {
//构建Toast实例
Toast result = new Toast(context, looper);
LayoutInflater inflate = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
//获取默认的布局文件,就是要展示的View
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);
result.mNextView = v;
result.mDuration = duration;
//返回创建的Toast实例
return result;
}
Toast的构造函数
public Toast(@NonNull Context context, @Nullable Looper looper) {
mContext = context;
//1. mTN是一个本地对象,用于进程间通信
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);
}
注意: mTN是一个本地对象,用于进程间通信
private static class TN extends ITransientNotification.Stub {
//...
TN(String packageName, @Nullable Looper looper) {
//1. 初始化looper 对象
if (looper == null) {
// Use Looper.myLooper() if looper is not specified.
looper = Looper.myLooper();
if (looper == null) {
throw new RuntimeException(
"Can't toast on a thread that has not called
Looper.prepare()");
}
}
}
}
注意:在TN的构造函数中,我们发现有初始化looper
对象的一段逻辑。如果我们要在子线程弹出Toast的时候必须先调用Looper.prepare();
来初始化一个Looper对象或者传入一个不为null的Looper对象。
Toast的show方法
public void show() {
//mNextView不能为null
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;
try {
//将show toast的请求加入NotificationManagerService的toast队列
service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {
// Empty
}
}
@Override
public void enqueueToast(String pkg, ITransientNotification callback, int duration)
{
//...
synchronized (mToastQueue) {
int callingPid = Binder.getCallingPid();
long callingId = Binder.clearCallingIdentity();
try {
ToastRecord record;
int index;
//除了android 包以外,其他应用同一时刻只能把一个toast加入队列
if (!isSystemToast) {//不是系统级别的toast
index = indexOfToastPackageLocked(pkg);
} else {
index = indexOfToastLocked(pkg, callback);
}
// 如果当前的应用已经有一个ToastRecord对象了,我们就更新这个对象
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);
//创建一个新的ToastRecord对象,并加入列表中
record = new ToastRecord(callingPid, pkg, callback, duration, token);
mToastQueue.add(record);
//获取列表中最后一个ToastRecord对象的下标
index = mToastQueue.size() - 1;
}
keepProcessAliveIfNeededLocked(callingPid);
// 如果index是0,就显示这个toast
if (index == 0) {
showNextToastLocked();
}
} finally {
Binder.restoreCallingIdentity(callingId);
}
}
}
void showNextToastLocked() {
ToastRecord record = mToastQueue.get(0);
while (record != null) {
//...
//1. Toast是如何显示的
record.callback.show(record.token);
//2. Toast是如何消失的
scheduleDurationReachedLocked(record);
return;
//...
}
}
Toast是如何显示的
record.callback.show(record.token);
这里record.callback
是一个 ITransientNotification 类型的对象,而这个对象实际上就是我们刚才所说的 TN 类型对象的代理对象。当我们调用 TN 代理对象的 show 方法的时候,相当于 RPC 调用了 TN 的 show 方法。
TN的show方法
@Override
public void show(IBinder windowToken) {
if (localLOGV) Log.v(TAG, "SHOW: " + this);
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 (mView != mNextView) {
// 如果必要,先移除老的View
handleHide();
//...
mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
//...
mParams.token = windowToken;
//...
mWM.addView(mView, mParams);
}
}
显示窗口的方法,就是将所传递过来的窗口 token 赋值给窗口属性对象 mParams, 然后通过调用 WindowManager.addView 方法,将 Toast 中的 mView 对象纳入 WMS 的管理。
Toast 是如何消失的
scheduleDurationReachedLocked(record);
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);
}
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;
//...
}
}
//...
}
NotificationManagerService的handleDurationReached方法
private void handleDurationReached(ToastRecord record) {
synchronized (mToastQueue) {
int index = indexOfToastLocked(record.pkg, record.callback);
if (index >= 0) {
cancelToastLocked(index);
}
}
}
void cancelToastLocked(int index) {
ToastRecord record = mToastQueue.get(index);
try {
//隐藏toast
record.callback.hide();
} catch (RemoteException e) {
// ...
}
ToastRecord lastToast = mToastQueue.remove(index);
mWindowManagerInternal.removeWindowToken(lastToast.token, false /* removeWindows */,
DEFAULT_DISPLAY);
//...
//如果队列中还有toast,就展示下一个
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();
}
}
//隐藏toast
record.callback.hide();
这里record.callback是一个 ITransientNotification 类型的对象,而这个对象实际上就是我们刚才所说的 TN 类型对象的代理对象。当我们调用 TN 代理对象的 hide方法的时候,相当于 RPC 调用了 TN 的 hide方法。
TN 的 hide方法
@Override
public void hide() {
if (localLOGV) Log.v(TAG, "HIDE: " + this);
mHandler.obtainMessage(HIDE).sendToTarget();
}
mHandler关于HIDE情况的处理
case HIDE: {
handleHide();
mNextView = null;
break;
}
public void handleHide() {
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) {
//WindowManager移除Toast的View
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;
}
}
自定义Toast
如果想自定义Toast,可以使用Toast的setView方法设置要展示的View。如下所示:
private fun showCustomToast(text: String) {
val view = LayoutInflater.from(this).inflate(R.layout.custom_toast, null)
val tvMessage = view.findViewById<TextView>(R.id.toast_tv)
tvMessage.text = text
//注意这里没有使用Toast.makeText方法
val toast = Toast(this)
toast.view = view
toast.show()
}
布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="@color/colorPrimary">
<TextView
android:id="@+id/toast_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginTop="10dp"
android:layout_marginRight="20dp"
android:layout_marginBottom="10dp"
android:gravity="center"
android:textColor="#fff"
android:textSize="18sp"
tools:text="一段很长的测试文字" />
</LinearLayout>
<ImageView
android:id="@+id/toast_iv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_star" />
</RelativeLayout>
具体的例子可以参考 Android自定义Toast。
参考链接:
网友评论