意图分析的问题
- Toast的显示是异步的还是同步。
- Toast是否可以在子线程中实例化并调用
Toast.show
方法。 - Toast对象复用的可行性。
源码执行流程分析
Toast的本地创建和show()方法
实例化Toast对象
在实际使用中我们常使用使用如下代码实例化一个Toast对象。
Toast.makeText(Context, msg, Toast.LENGTH_SHOW);
下面看看makeText方法的源码:
/**
* Make a standard toast that just contains a text view.
*
* @param context The context to use. Usually your {@link android.app.Application}
* or {@link android.app.Activity} object.
* @param text The text to show. Can be formatted text.
* @param duration How long to display the message. Either {@link #LENGTH_SHORT} or
* {@link #LENGTH_LONG}
*
*/
public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
//实例化一个新的Toast对象
Toast result = new Toast(context);
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);
//注意此处把加载的view的引用给mNextView,该对象其实就是Toast实际显示的View
//这里实际也告诉我们,可以给mNextView对象设置不同的View(通过setView(view)方法),
//从而自定义Toast的显示样式和内容。
result.mNextView = v;
result.mDuration = duration;
return result;
}
很简单的一个过程,实例化一个对象,加载要显示的View,设置View到Toast对象,返回新建的对象。注意上面的方法中,每次调用时都会新建一个Toast
对象,看下Toast的构造器源码:
/**
* Construct an empty Toast object. You must call {@link #setView} before you
* can call {@link #show}.
*
* @param context The context to use. Usually your {@link android.app.Application}
* or {@link android.app.Activity} object.
*/
public Toast(Context context) {
mContext = context;
mTN = new TN();
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);
}
从上面的代码中发现,其构造器实际就是在新创建一个TN对象并初始化它的属性,至于TN对象是什么,后面会讲到。
Toast.show()方法调用过程
show()
方法是最终执行显示Toast消息的地方吗?下面我们来看源码。
/**
* Show the view for the specified duration.
*/
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()方法很简短,没有明显显示Toast的代码,看
INotificationManager service = getService();
这行代码中,INotificationManager是个用于Binder机制IPC通信的接口,看getService()
的源码。
static private INotificationManager getService() {
if (sService != null) {
return sService;
}
sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification"));
return sService;
}
从代码中,可以发现是返回了一个以"notification"
为标记的远程服务对象的代理。这个远程服务对象就是NotificationManagerService,以下简称NMS,所以在show()方法中的
service.enqueueToast(pkg, tn, mDuration);
实际调用的就是是NMS中的enqueueToast
方法,先来看下传递给该方法的三个参数:
- pkg 这个不用多说,就是表示应用的包名的字符串。
- mDuration 也很简单,就是Toast显示的时长。
- tn 这个参数是实现显示逻辑的核心部分,参数的类型为TN,是Toast类中的一个内部类,看到类定义的继承体系:
private static class TN extends ITransientNotification.Stub {
我们就知道了,TN实际上也是一个使用Binder机制IPC通信的远程调用的ITransientNotification接口的实现类,该类的具体实现后面再看。到目前为止,可以知道Toast.show()方法中并没有直接显示Toast消息,只是调用了NMS的enqueueToast
方法,那这个方法中会不会直接通过某种方式显示Toast呢?先来看下NMS这个类的继承结构:
public class NotificationManagerService extends INotificationManager.Stub {
......
}
显然NMS就是INotificationManager.Stub这个抽象类的具体实现类,注意在API23中NMS已经改为继承SystemServer类,其内部新增一个mService 对象实现该抽象类:
//API = 23
private final IBinder mService = new INotificationManager.Stub() {
NMS中Toast的执行流程
保存到Toast队列过程
上面说到show方法中调用NMS中的enqueueToast
方法,下面是其关键部分的源码:
public void enqueueToast(String pkg, ITransientNotification callback, int duration)
{
...... //省略部分代码
synchronized (mToastQueue) {
int callingPid = Binder.getCallingPid();
long callingId = Binder.clearCallingIdentity();
try {
ToastRecord record;
int index = indexOfToastLocked(pkg, callback);
// If it's already in the queue, we update it in place, we don't
// move it to the end of the queue.
if (index >= 0) {
record = mToastQueue.get(index);
record.update(duration);
} else {
if (!isSystemToast) {
int count = 0;
final int N = mToastQueue.size();
for (int i=0; i<N; i++) {
final ToastRecord r = mToastQueue.get(i);
if (r.pkg.equals(pkg)) {
count++;
if (count >= MAX_PACKAGE_NO